Nuxt.js 项目部署函数计算优化(省钱)小记

Serverless说来并不是一个新鲜的东西,早在当年App Engine就提供了一个基于特定代码运行环境配合云服务的代码托管思路,但因为其各种环境的限制与迁移成本使其并没有很广泛的推广开了。而如今容器化的大行其道,云生态的丰富与完善,则为云原生应用提供了更为丰富的可能性,云厂商也不予余力的包装出新的概念、产品。

Serverless应用场景很多,但绕不开的一个问题就是Web项目可以跑吗?用Serverless跑Web项目真的能节约成本吗?于是我就开始尝试使用阿里云的函数计算来看一看。

在函数计算里运行Nuxt.js项目

为什么是Nuxt.js项目?
Nuxt.js 是基于 Vue.js 的 SSR,SSR 满足作为内容性质的Web应用的SEO需求,同时首次返回页面之后,在页面上的所有操作就都在浏览器上完成了,可以很大程度上减少网络交互。
(其实是手头目前最适合测试的项目用了Nuxt.js)

Nuxt.js项目如何跑在函数计算上?
官方文档中会告诉你,只需要安装并设置好命令行工具,并在项目目录运行就好了。
究其本质是利用了官方提供的一个custom的机制,在容器中预置好了环境,不管你是什么项目,只要给出你的项目的启动命令,并且启动后通过HTTP 9000端口进行交互,就可以通过函数计算访问到你的项目。所以不管你是Nuxt还是Express还是Wordpress,只要你能监听9000端口提供Web服务,一切就都就绪了。

官方是这么说的,对于一般个人Web项目来说也够了,(毕竟对云厂商来说,个人就是凑人头的),但本着能省则省的原则,如何进行优化呢?

函数计算的费用是怎么算的?

(截止本文撰写的时间)阿里云的函数计算主要包括了以下几个部分的费用:
1. 调用次数费用 2. 执行时长费用 3. 流量费用 4. 其他费用

调用次数费用

调用次数费用,指的是每次成功的通过HTTP请求函数计算,都会被计算请求次数,并进行计费,如果我们通过函数计算提供的是API类的服务,那就是API接口被请求的次数,但如果我们提供的是Web服务呢,在前面我们提到了,我们将整个Web项目通过9000接口监听的方式提供HTTP服务,那么相当于并不仅限于我们的Web页面,CSS、JS以及图片等等的静态资源也都是在通过这种方式在提供服务,是的,用户打开一次页面,并不会只有一次请求,还会有连带有各种静态资源的请求。

执行时长费用

执行时长既包括了使用的计算资源也包括实际运行的时长,当然 Nuxt.js 本身的资源占用以及执行渲染的时长并不会太长。

流量费用

从函数计算中流出的费用,这个与前面提到的调用次数类似,不作任何调整的情况下,所有的静态资源也都会直接从函数计算中流出,并计算费用。

优化方案

UI库的按需加载

如果在使用诸如 ant-design-vue 或者 element-ui 之内的库的话,如果直接将整个库引用到项目中的话,会很大程度上增加编译后的文件大小。
使用 ant-design-vue 的情况可以参考:Nuxt.js 中按需加载 ant-design-vue

开启 extractCSS

Nuxt.js 默认未开启 extractCSS,在引入UI组件时,进行额外编译的情况,会导致页面中大量的 style 片段,增加页面大小,且不利用复用,通过 extractCSS 将样式都输出到独立文件中,这样就可以和其他静态资源一块儿优化了。

静态资源优化

前文中提到,默认情况下,所有的静态资源(CSS/JS/图片),通过Nuxt.js内的express,直接通过监听端口的方式提供访问,这些资源从函数计算流出,带来额外的请求数和流量,这部分如果和动态流量来计算显然是不划算的。因此可以考虑静态资源使用CDN。

使用CDN可以有两种方式:
1. 全站使用:通过CDN加速整个函数计算项目,webpack编译出来的前端资源默认都在 /_nuxt/ 路径,在CDN中配置对 /_nuxt/ 的缓存策略,减少回源,这种方式实行起来相对简单,但是不够彻底。
2. 使用对象存储:通过将webpack编译后的 .nuxt/dist/client 目录下的文件,上传到对象存储,并配置CDN。然后更改 nuxt.config.js 中的 publicPath 项目,将其改为CDN地址的方式,从而实现将全部静态资源直接通过CDN进行访问。通过这种方式,在部署函数计算时,因为前端静态文件已经全部转到对象存储上了,因此代码包可以通过不包括 .nuxt/dist/client 目录,以减少代码包大小,从而提高冷启动速度

优化 node_modules

除了前端静态文件外,对于node项目来说 node_modules 犹如文件地狱,当引入的依赖不断增加时,node_modules 也会日趋增加,打包时带上 node_modules 会大幅增大代码包大小,影响冷启动时间。因此在部署前,需要对 node_modules 进行优化(使用yarn会在更新依赖后自动进行优化)
此外,如果 node_modules 确实过大,则建议引入 NAS,将 node_modules 放入 NAS中,容器启动挂载NAS提供服务,以减少冷启动时间。

服务端请求接口优化

Nuxt.js 中使用 axios 发起请求,获取接口数据进行页面渲染,对于服务端渲染的请求来说,是由服务端向上游接口进行请求,前面也提到,关于执行时长和资源使用来说,服务端渲染其实占用的时间并不多,但不可忽视的就是请求上游接口。
在 nuxt.config.js 的 axios 设置中,baseURL 和 browserBaseURL 分别指定了服务端和浏览器请求上游接口时使用的地址。在可能的情况下 baseURL 指定为接口内网地址,而 browserBaseURL 使用接口外网服务的地址,也能在一定程度上减少延迟,缩短执行时长。

成本如何

相较于传统的Web托管,静态资源部分的CDN使用成本与使用函数计算相差无异,同时在优化过程中,也已经对静态资源进行了优化。那么使用函数计算的成本如何呢?

当然,托Nuxt的福,请求方面用户在不刷新页面的情况下,仅第一次加载时的请求落在函数计算服务进行 SSR,此后的请求都是在前端请求接口,同时,我们也将静态资源全都转移到了对象存储上,因此对于函数计算的请求量其实是远小于整个Web项目,相当于用函数计算做了一个 SSR 服务。

实际开销来说,目前迁移到函数计算上的一个项目大致情况如下:

函数实例内存规格:192M(平均内存使用月130M)
WEB统计PV:18w(含前端路由跳转的PV)
函数计算调用次数:16w
函数计算资源使用量:1.2w CU·s
以本文截稿时的价格,若无免费额度,成本约为1.5元。(实际并未超过当前的免费额度)

可以说,对于流量不大的个人Web项目来说,开支基本上就是流量费用,计算成本相对来说可以忽略不计,不过国内的云服务,流量费用依旧不便宜,要省钱还是一门学问啊。

Nuxt.js 中按需加载 ant-design-vue

使用Nuxt.js构建网站时,使用UI库,能够更快的完成页面的构建,当盲目的引用,则会带入大量项目中不需要的组件,极大的增加构建后的项目大小,导致用户加载页面时间过长,文件过大,影响用户体验。因此按需加载就非常必要。

Ant Design 提供了多种按需加载的方案,对于 Nuxt.js 项目来说,因为SSR是在服务端进行,因此组件是在服务端和客户端都需要引入的,因此如果在单文件组件中引入指定的组件,通过loader加载时,会加载组件的css,但是服务端编译的时候是不认识css的,会导致报错,因此还是基于插件文件的方式,将插件文件中原本的:

import Antd from 'ant-design-vue/lib'

更改为

import {
  Form, Button // ... 你所用到的组件
} from 'ant-design-vue';

并在 nuxt.config.js 中的 css 里面去掉引用 antd 的 css 文件,改为通过build里面的babel插件引入

{
  // ...
  css: [
     // 'ant-design-vue/dist/antd.less',
  ],
  // ...
  build: {
    babel: {
      plugins: [
        [
          'import',
          {
            libraryName: 'ant-design-vue',
            libraryDirectory: 'es',
            style: true,
          }
        ]
      ]
    },
    transpile: [/ant-design-vue/],
  }
}

如果有在使用 Icons ,ant-design 的 Icons 会将全部用到没用到的图标全部引入,文件相当大,可以考虑新增一个 ant-icons.js 插件文件,指定项目中需要用到的 Icons 图标

export { 
  // 需要使用到的 Icons
  InfoCircleFill, DownOutline, UpOutline
} from '@ant-design/icons'

更改 nuxt.config.js 中 webpack 的设置:

{
  // ...
  build: {
    extend(config) {
      config.resolve.alias['@ant-design/icons/lib/dist$'] = path.resolve(__dirname, './plugins/antd-icons.js') // 引入需要的
    }
  }
}

Lumen + Swoole 探坑小记

注意:本文以 Lumen 为对象进行写作,部分内容适用于 Laravel 但考虑到 Lumen 和 Laravel 在部分实现上有所差异,所以不保证本文提到的内容都也适用于 Laravel。本文写作时间点 Lumen 的版本是 v5.7.4 ,laravel-swoole 的版本是 v2.5.0。
考虑到随着未来版本的更新,部分特性会发生变化,所述内容仅供参考。

Swoole 是一个已经被广泛验证过的生产级别的PHP高性能异步网络框架,其近期逐渐完善的协程特性,更是极大提高了编码的体验。

目前查到这方面的文章大多数 Swoole 非常好 , Swoole 的运行机制能够大幅改善 Laravel、Lumen 的性能,让我们一起装一下 Swoole ,引入一个库,Webbench 对比看一下吧。

但实际上手完成应用,而不仅仅是一个 echo server ,就不是这么美好的事情,将 Swoole 集成到先有的应用系统中,也不仅仅是单靠引入一个库就能完成的。因此作为探坑学习,相对来说轻量级,并不包含过多组件的 Lumen 就是一个不错的选择。

要玩 Swoole 首先还是需要为 PHP 安装 Swoole 扩展,Linux环境下,可以直接使用PECL进行安装。当然,考虑到环境的复杂性,最省心的做法还是使用 Docker。

目前为 Lumen 增加 Swoole 支持的库有 laravel-s 和 laravel-swoole 两个,由于laravel-s 方案默认关闭并且不推荐开启协程,因此这次探坑选择了 laravel-swoole 方案。

在开始探坑前,首先还是需要了解一下 Swoole 的运行机制,以及 laravel-swoole 这个库到底做了什么。这边借用 laravel-swoole 的 Wiki 上的图。

Swoole 服务 通过自行PHP脚本启动之后,Swoole 会创建主要的反射器和管理器,进行管理调度,其中 主反射器 的工作模式和 Nginx 类似。而管理器则用于调度 Woker 和 Task Worker。而实际请求处理逻辑则是在 Worker 里面完成的。

对于 Lumen 来说,它的 Application 会在 Worker 启动时被创建,并且常驻内存,而不会每次接收请求都重新创建,这会有很好的性能,但问题也显而易见,原先我们并不管新的 全局属性、类的静态属性、单例 都会成为问题。

对于 Lumen 来说,其核心的特性,则在于 Service Container,Container 中的单例不会重新创建,每次请求是复用之前的,那之前的请求过程势必会引入很多污染,这些污染如果不清理的话,被带入到之后的请求,就可能引起后面请求的结果和预期不一致。但作为最大单例的 Application 如果清理掉,重新创建的话,那就失去了其加载常驻内存的意义了,因此 laravel-swoole 库采用的方式是引入了 沙盒 Sandbox。

Sandbox 机制 与 静态访问安全

在前面提到 常驻内存 需要格外注意的有 类的静态属性 ,那是不是我们自己写的类注意了就好呢,并不全是,因为在 Lumen 实现本身,也再使用静态属性。最典型的地方便是 Container 和 Facade 。Lumen 借助 Container 类的 静态属性 $instance 实现单例。而 Facade 则使用 静态属性 $app 保存 Application 实例,使用 $resolvedInstance 保存已经通过 Container 解析好的 Facade 实例。

那么 laravel-swoole 的 Sandbox 机制是怎么运作的呢,首先 Swoole 实际请求执行都是在 Worker 中执行的,laravel-swoole 在 Worker 被创建时,创建了 Sandbox 并且对 Lumen 的 Application 进行了初始化,同时预解析了一些常用的,同时不会因为不同请求处理之间会产生污染的 单例,这个预解析部分在其配置文件中是可以配置的。

接下来对于每一次请求来说,Sandbox 都会克隆一个已经创建好的 Application 的副本,并且通过预设的 重置器(Resetter) 对确保克隆的 Application 内已经创建的单例如果有已经绑定 Application 的,重新都绑定到当前克隆后的 Application 上,接着由这个克隆后的副本来处理本次请求的内容。这样看着很完美,但还有什么需要注意的呢,那便是前面说的 Lumen 的静态属性。Container 和 Facede。静态属性在同一个 Worker 中是公共的,不会因为不同请求有 独立的 Application ,静态属性就能被隔离。这个问题无解的话,laravel-swoole 目前的做法是在每一次创建时把当前的 Application 设置到 Container 的 $instance 上,同时清空 Facade 的 $resolvedInstance 。这对于同一时间一个Worker只处理一个请求来说是没问题的,但是 Swoole 的协程特性,使得在协程切换时,Worker 是可以接受并处理下一个分配的请求的。这就使得但凡涉及到 访问 Container 单例 和 Facade 进行服务解析都需要被格外注意。而最典型的就是 服务扩展,以及最为敏感的每次服务的鉴权问题。

对于 Lumen来说,Lumen 提供 app 帮助函数进行的服务解析依赖于 Container 单例,而不管使用哪个 Facade 都依赖于 Facade 的静态属性,因此这两种使用方式是需要被注意的。

比较多见的一种情况是在 Service Provider 中对 某个 Application 的 AuthManager 扩展了 guard 或者 guard driver。但如果使用时,通过Container得到的 AuthManager是新创建的,没有被 Service Provider扩展,则可能会遇到 guard 或者 drive 找不到的错误。

Auth guard [{$name}] is not defined.
Auth driver [{$config[‘driver’]}] for guard [{$name}] is not defined.

laravel-swoole 的文档的 Debug Guideline 会建议,如果遇到授权问题时,需要检查每一次请求的 AuthManager 是不是会被重新解析。但是不是重新解析的关键并不是解析本身,而是何时使用何种方式触发解析,怎么样才是安全的。

前面提到 Sandbox 对于 每一个请求 都会创建一个对应的 Application 的副本,Lumen 的 Application 也充当 Service Container 的功能,因此对于每一个请求都需要单独解析的服务类来说,使用 请求本身的 Application 副本 而不是 公共的 Container 单例、或者 Facade ,是一个相对来说安全的做法。

对于 Service Provider 来说,如果需要获取请求本身的 Application 副本,做法是将 Service Provider 添加到 laravel-swoole 配置文件中的 providers 中,从而laravel-swoole在每次重置 Application 的时候,会重新把新的Application关联到 Service Provider 中,并重新执行 Service Provider 。而在 Service Provider 中,则需要使用 $this->app 来访问被重新关联上的当前请求的  Application 副本。

对于其他类来说,如果需要获取请求本身的 Application 副本,那么最常见的做法就是在构造函数接受参数中接受 Application,并且使用 Application 副本的 Service Container 进行解析,通过依赖注入的方式进行获取。

当然,对于全局公共的,并不涉及到限定于本次请求的类来说,保持原来的方式,不管是用 Sandbox 一开始初始化的 Application 中获取,还是从 某一次请求的 Application 中获取,都是没有问题的。

MySQL 中 ONLY_FULL_GROUP_BY 模式

ONLY_FULL_GROUP_BY 对 GROUP BY 查询的限制

在 SQL 中 利用 GROUP BY 聚合 可以起到将字段相同的记录合并的目的。但也常常被滥用,以及使用不当出现结果与预期不一致的情况。

在 sql_mode 中提供了 ONLY_FULL_GROUP_BY 用以限制在 select 字段中 使用 group by 之外的字段 但没有使用聚合函数的情况。

例如:

select type, price from products group by type 

结果根据 type 聚合,但 price 未使用聚合函数,在未开启 ONLY_FULL_GROUP_BY 的情况下 mysql 会依照聚合顺序 返回默认的结果,而开启之后,则会返回错误:

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column ‘db.tbs.price’ which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

当前所使用的 sql_mode 可以使用 show variables 查看

show variables like 'sql_mode';

MySQL 5.7 新增 ANY_VALUE()

从 MySQL 5.7 版本开始 默认将 ONLY_FULL_GROUP_BY 设置为启用状态。因此 如果依旧按照原先的方式进行查询 将会出现错误,在开启 ONLY_FULL_GROUP_BY 时 如果对非 GROUP BY 字段需要获取同原先一样的结果,5.7 版本开始则提供了一个 ANY_VALUE() 函数:

select type, ANY_VALUE(price) from products group by type 

Laravel 的 strictMode

对于 Laravel 框架 在 config/database.php 文件启用严格模式(strict 为 true )时 ONLY_FULL_GROUP_BY 设为启用状态

strict 为 true
strictMode 下启用 ONLY_FULL_GROUP_BY

如果使用的 MySQL 版本高于 5.7 则 优先考虑使用 ANY_VALUE() 函数
如果还在使用低版本 MySQL 又不方便升级,可以考虑将 strict 设为 false 关闭 strictMode

基于Docker 项目PHP环境运行 Composer 并使用国内镜像源

使用Docker可以快速在不同机器上运行构建好一致的运行环境,越来越多的被用在项目生产中。而 Composer 基本是PHP开发必不可少的包管理工具。

但在实际PHP开发部署过程中,Composer对于环境与依赖的判断源自于运行Composer的PHP环境,不同的运行方式,对于Composer的执行情况产生运行。

因为Composer限制root权限运行,因此Docker容器并不能直接shell运行Composer,如果直接在本机安装 Composer ,那么则依赖于机器本身的 PHP 环境,而使用Composer官方的镜像,则基于构建源的PHP版本环境。而在项目开发中,为了保证可靠,还是应用使用与项目一致的 PHP环境来运行 Composer。

首先 为项目 PHP 的 Dockerfile 增加 Composer 可以直接使用 Docker 的 multi-stage 功能直接从官方源复制

FROM php:7.1-fpm # 或者指定你自己build好的镜像
#
# .... 自身项目PHP环境配置 ... 建议直接基于已经 build 好的镜像
#

# 增加 TINI (这里直接使用二进制 也可以使用镜像对应的包管理安装)
ENV TINI_VERSION v0.17.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /sbin/tini
RUN chmod +x /sbin/tini

# COPY composer
ENV COMPOSER_ALLOW_SUPERUSER 1
COPY --from=composer:1.6 /usr/bin/composer /usr/bin/composer # 在镜像中增加 composer
COPY --from=composer:1.6 /docker-entrypoint.sh /composer-entrypoint.sh
WORKDIR /app
ENTRYPOINT ["/composer-entrypoint.sh"]
CMD ["composer"]

build 镜像 并标记 tag

docker build -t my-composer ./

在本地创建持续存储 composer 全局数据目录

mkdir ~/.composer

配置本地 .bash_composer 环境增加 composer 命令运行

composer () {
tty=
tty -s && tty=--tty
docker run \
$tty \
--interactive \
--rm \
--user $(id -u):$(id -g) \
--volume $(pwd):/app \
--volume $HOME/.composer:/tmp/composer \
-e "COMPOSER_HOME=/tmp/composer" \
my-composer "$@"
}

在 .bash_rc 或者引用 .bash_composer 文件,使用 source 命令引入

. "~/.bash_composer"

开始使用,并且配置中国镜像

composer config -g repo.packagist composer https://packagist.laravel-china.org

有顶天家族 舞台巡礼 Part.A

第一次来京都是在三年以前,走马观花转了很多热门景点,但对于这个传统和现代结合得很好的城市,依旧充满着诸多向往,但并不知道什么时候还能有机会再来。而看完「有顶天家族」之后,更是对于森见老师刻画下的古都更加的喜爱,「一定要再去看看」就算这么说,出远门总会想着要有一个主题,那么这次的主题大概就是「おもしろき古都は、良きことなり 」

拖了不知道多久的巡礼 report 毕竟是故事的舞台 处处都是实景 也不愧是京都旅游指南的(X

努力整理好了一部分 就先把 part A 发出来了

京都站

抵达京都之后首先当然是 JR京都站啦

出柳町车站附近

有顶天家族的巡礼 首先当然是要拜访 下鸭神社啦 乘坐电车前往下鸭神社 出柳町应该是最近的车站了

母亲大人常去的台球厅也就在这附近

出柳町商店街
司津屋

沿着商店街前行就能看到 笨蛋2兄弟伪装成的面馆 竹林亭的原型

继续阅读有顶天家族 舞台巡礼 Part.A

贪玩的 2016 的小结

比起前些年多多少少能有着些许成绩,2016年倒是相对平淡的,平淡到了作年终总结的时候,大概是要好好检讨检讨,这过去的一年到底都在干些什么,恩,也许就像标题所述主要就是贪玩吧 XD

游戏

如果非要说做了什么的话,那大概还是要把和同伴一块儿从2015年下半年开始到2016年中完成的游戏「Outbreak: Legacy」翻出来了吧,游戏的具体情况之前也简单写过一些介绍。以此为契机,接着IndiePlay的提名,暑假的时候去了趟魔都,感受了一下国内独立游戏的氛围,也体验了不少很有意思的独立游戏,顺带也见了见在魔都的朋友,虽然确实游戏还有很多做得不够的地方,也没有拿上奖,不过有这么一段经历,还是觉得十分开心。

电影

虽说电影已经是很多人的日常,但是平时基本都是SOLO状态,再加上国内上映的也没有什么特别喜欢的片子,所以过往电影看的是真不多

但想想2016年还真是看了不少片子呢,不管说是争议很大的tri,动作片的柯南,还是话题作的君名。也满心激动的感受了零点场以及初日舞台。

不管怎么说在大荧幕上看喜欢的作品,思来念去很多年,2016年真是很满足,希望未来在国内也能看到越来越多喜欢的片子呢,恩,如果还有人可以一块儿就更好了(想多了

旅行

想想这一两年,基本也都会有些出门的机会,所以这次出行在考虑行程的时候就想想除开常规路线之外还有什么特别的地方。于是决定去大山里探险,去了惦记了许久的狐狸村,和狐狸来了次近距离接触。

不管是躺着的“尸体”还是懒散的散步,亦或者打起架来,都好可爱 XD 以后有机会的话,还想换个季节再去转转呢

 

 

继续阅读贪玩的 2016 的小结

Lumen 5.4 修改 PDO Fetch Mode

项目需要采用了 Lumen 来开发 RESTful API 但又有需要修改 PDO 的 Fetch Mode 的需求

由于 Lumen 文档并没有关于此的特别说明 便参照 Laravel 过往的做法试图配置 config/database.php 并不起作用

在 翻阅 Laravel 文档中关于 升级变更的说明时才提到 在几周前发布的 5.4 版本 中不再支持 在配置文件中自定义 Fetch Mode (其实本身 Laravel 也一直不推荐修改)

如果需要这么做的话 目前采用的方案是 监听 Illuminate\Database\Events\StatementPrepared 事件 进行配置

对应到 Lumen 中也是一样的 可以在 EventServiceProvider 的 boot 方法中 设置

use Illuminate\Database\Events\StatementPrepared;
public function boot()
{
// 修改 PDO Fetch Mode
Event::listen(StatementPrepared::class, function($event) {
$event->statement->setFetchMode( ... );
});

// 其他自定义监听事件
}

当然 如果之前没有启用 EventServiceProvider 的话 还需要在 bootstrap/app.php 启用

// 去除前面的注释
$app->register(App\Providers\EventServiceProvider::class);

先导者卡牌资料站

恩 又开了一个新坑 这次是和 THE一灭寂 合作完成的先导者卡牌资料站

#卡片战斗先导者# 在线卡牌资料非官方中文综合站·《先导者卡牌资料站》今日开始公开测试!
地址:http://vgcard.yimieji.com
设计&资料:THE一灭寂
开发:@smdcn
PC与手机浏览器两者均可使用!具备卡片搜索、卡片详情查看、规则释疑(此项暂未填写完毕)等功能!
目前为开放测试期间,希望大家积极向我们反馈使用后的意见或建议。我们在完善暂未完成的项目的同时,也会根据大家的反馈尽可能进行优化!谢谢大家的支持!

继续阅读先导者卡牌资料站

数码兽大冒险 tri 第一章 再会 圣地巡礼

不知道为什么就是非常执着于跑实景 大概每次去东京 台场都会是必修课了吧

跑归跑 拖延症拖了不知道多久一直没有好好整理 于是先对着 Tri 第一章 把舞台实景探访整理了出来

虽然跑了很多地方 但还是没太做好功课 还是缺了一些场景 以往以后还能有机会再去了

0-のぞみ橋
0
首先是第一章BD的封面 这个在 のぞみ橋 桥上拍的

接下来的内容也基本顺延着 第一章 的故事推进整理的

Part 1. 太一从台场家上学

1-のぞみ橋
1
太一从台场出发上学沿途 位置是 のぞみ橋台场侧
2-晴海大桥
2
太一从台场出发上学沿途 位置是 在 晴海大桥
3-%e6%99%b4%e6%b5%b7%e5%a4%a7%e6%a1%a5
3
太一从台场出发上学沿途 位置是 在 晴海大桥
4-%e6%99%b4%e6%b5%b7%e5%a4%a7%e6%a1%a5
4
太一从台场出发上学沿途 位置是 在 晴海大桥
5-%e5%ad%a6%e6%a0%a1
5
太一上学学校外景 位置在 月島

Part 2. 岳、光从学校回家

继续阅读数码兽大冒险 tri 第一章 再会 圣地巡礼