前后端分离开发实践了很久了,前两天需要把一个项目上线,准备 SSL 证书时发现,居然需要申请 3 个证书(1 年期免费的证书只能对单个子域名申请),这 3 个域名是:
- sys.project.cn,Web 应用前端
- api.project.cn,应用后端 RESTful API
- m.project.cn,移动端
屁大点个项目,需要搞得这么复杂?用子路径不行吗?就像这样:
- //sys.project.cn/
- //sys.project.cn/api/
- //sys.project.cn/m
回答我说,不行!因为是前后端分离方式开发的,每一项都是单独的项目,单独发布出来,只能分别作为网站发布……我那感觉,就像喝了两瓶二锅头,不仅醉了,而且上头,就差发酒疯了!
且不说是不是一定要分离发布的问题,就算各自独立发布,至少有三种方案可以发布成子路径:
- 虚拟目录或纯静态的子目录
- IIS 站点上“添加应用程序”
- 使用 Nginx 反向代理
但这不是重点,重点是:
分离开发就一定得分离发布吗
自从应用前后端分离开发模式以来,分工明确,合作愉快。最近后端一般是用 NET Core 开发,前端使用的 Vue 技术栈。如果偶尔后端人力不足,有点数据库基础的前端工程师还可以拿 Node.js 帮着实现一部分后端需求。移动端的 Android 团队已经精简得只剩下一个人了,只需要维护一个框架应用,处理点硬件调用,把移动端页面往里一套就能解决问题。
各团队已经习惯了分离开发,除了讨论接口设计,其他时候团队间交流最多的可能就是:“API 测试地址是啥,我需要联调一下。”所以各团队也习惯了自己发布,公布地址给其他团队测试、联调。久而久之,居然形成了分离开发就得分离发布的印象。
对于较大型一些的项目来说,分离发布可能是必要的:纯静态的前端部分可以发布到 CDN,后端部分可以发布到多个服务器上外加一层负载均衡。但是对于使用人数不过几百人,并发最多几十人的小型应用来说,就一台应用服务器,把所有东西揉吧揉吧,放一起就能当作一体式开发的 Web 应用发布出来,真没必要去分离。
就上面的例子来说,除了应用后端需要跑程序,需要 NET Core Runtime,另外两项全是纯静态。然而,
A: 另外两项不是静态的,因为要通过构建生成!
B: 什么构建?
A: Vue 框架写的,需要通过npm run build
构建了才能发布。
B: 那么,构建结果是不是纯静态的?
A: 构建结果应该不是纯静态的吧,需要在 IIS 上建站点发布。
B: 那么,构建结果直接用浏览器可以打开吗?不用 IIS,只用静态 http-server 可以部署吗?
A: 好像可以
B: 那就是纯静态!
这是一个插曲,不过这得强调一下,“构建”这一过程的结果,不一定就非得是动态的 Web 应用。我们已经在前端工程化上实践了这么久,应该了解:前端工程化之后,构建的结果是静态的,不需要在服务器上跑程序,只需要服务器按 URL 提供静态资源。
分析下后端应用的发布内容
现在来看一下后端发布的结果(部分)
.../api_publish
|-- wwwroot/
`-- *.dll
程序中,所有 API 都是通过路由中间件解析 URL 之后转发到各 Controller 的。假如发布后绑定了域名 api.project.cn
,那么:
//api.project.cn/
打开的是项目模板提供的一个默认页面,这个页面在wwwroot
中 —— 对了,wwwroot
就是这个 Web 应用的静态资源目录//api.project.cn/api/...
这个子路径下提供全套 Web API 服务
因为应用后端目前只提供 Web API 服务,wwwroot
里只不过放了一些没用的静态资源 —— 都是创建项目时模板提供的静态资源,完全可以删得一个不剩。wwwroot
中的内容删干净之后,访问 http://api.project.cn/
会得到一个 404,但没关系,因为 API 完好!
那么,如果把 wwwroot
里放上前端构建的结果呢?
看看前端项目结构
.../project_root
|-- src/ <-- 源文件
|-- dist/ <-- 构建结构(发布目录)
| |-- index.html <-- 入口页面
| |-- assets/ <-- 资源(图片等)
| `-- *.js;*.js.map <-- 构建出来的 js 脚本等
|-- node_modules/ <-- npm 包缓存
`-- * <-- 项目配置、说明等
这个结构中,dist
目录是 npm run build
构建出来的,这是一个发布目录,只有 dist
中的内容需要部署到 Web 服务器上。
揉一下子
现在把 dist
目录放在后端发布目录 api_publish
中去,改名为 wwwroot
,替换掉原来的 wwwroot
,Api 的发布目录就变成了这样:
.../publish
|-- wwwroot/ <-- 前端构建结果:dist
| |-- index.html
| |-- assets/
| `-- *.js;*.js.map
`-- *.dll
这个目录在 IIS 里部署出来,直接访问 //api.project.cn/
(之前绑定的域名),我们会毫无悬念地看到前端页面出来了。由于前端页面中 Ajax 调用的 Base URL 都是 //api.project.cn/
,所以 API 调用也没有问题。
不过是 Web 应用的主页一般不会通过 //api.project.cn/
来访问,所以绑定 sys.project.cn
域名来访问。//sys.project.cn/
没有问题,可以打开页面。之前绑定的 api.project.cn
并未取消,所以 Ajax 调用也没有问题。
注:从//sys.project.cn/
通过 Ajax 调用//api.project.cn/
可能会存在跨域问题,不过在这个案例中,跨域问题早就处理过了,不细说。
申请 SSL 证书
接下来,开始申请 SSL 证书。如果只申请一个证书(收费证书很贵的),是该申请 api.project.cn
的,还是 sys.project.cn
的?
不管 api.project.cn
还是 sys.project.cn
都可能在接收到的请求中包含敏感信息,也可能在响应中包含敏感数据。别的不说,Ajax 调用就已经涉及到了两个部分的信息交换,任何一方不安全,整体都是不安全的。
但是只有一个证书,就得放弃一个域名,放弃哪一个比较好?
sys.project.cn
是应用入口,应该告知用户,而 api.project.cn
是在页面中隐含调用的,所以应该放弃 api.project.cn
。而放弃 api.project.cn
,就意味着需要把 Web API 部署为 sys.project.cn
的子路径中,即 //sys.project.cn/api/
。然后把前端 Ajax 调用的 Base URL 改为 //sys.project.cn/api/
即可。
这不,//sys.project.cn/
和 //sys.project.cn/api
就把前后端揉合在一起了,搞成一体式发布。
再来个移动端
对了,还有一个针对移动端的前端静态资源需要发布,它和发布应用前端原理一样,但是得发布到 .../wwwroot/m/
。问题是,需要以虚拟目录的形式发布吗?
其实这个问题不是难题,纯静态的东西,怎么揉都行。
一体式发布
如果,三端不是同时发布,而是各有各的生命周期,那最好发布成三个目录:
.../publish/api/
,部署为 IIS 站点(删除掉其中的 wwwroot 目录).../publish/sys/
,做成符号链接(Windows 下用 Junction)到.../publish/api/wwwroot
.../publish/mobile/
,可以直接在 IIS 中部署成/m
虚拟目录,也可以sys
那样做成一个符号链接
对于多数小项目来说,三端都是同时发布、联合测试的。这种情况下,就可以使用一个构建脚本将前端 dist
、移动端 dist
和 Web API 发布目录拷贝到一起,按如下结构发布:
.../publish/
|-- *.dll
`-- wwwroot/ <-- 前端构建结果:dist
|-- index.html
|-- assets/
|-- *.js;*.js.map
`-- m/ <-- 移动端构建结果:dist
|-- index.html
|-- assets/
`-- *.js;*.js.map
分离式开发
一体式发布说完了,再回过头来说说分离式开发。
因为一开始的问题出现在部署的时候,所以我们反推了一体式发布的过程。但实际上,应该反过来,按正常的顺序,从项目开始开发的时候来规划。
项目开始开发,说明它的需求已经确定下来。那么,就基本上能确定用户该怎么来使用它,它应该怎样部署。所以创建项目工作区的时候会想到这样一个目录结构:
.../project
|-- wwwroot/ <-- 前端静态资源
| `-- m/ <-- 移动端静态资源
|-- **/*.cs <-- 源代码
`-- * <-- 项目及其他各种配置文件等
然后进行分工计划:
- 前端一组写
wwwroot
,但要把wwwroot/m
这个目录保留给二组 - 前端二组写
wwwroot/m
- 后端组写
project
如果直接在整个工作区中协作复杂度会比较高,而且前端工程师看到后端代码会头痛,后端工程师看到前端代码也头痛。所以拆分项目,同时决定构建方法:
- 为
wwwroot
创建一个前端项目web
,构建输出到wwwroot
- 为
wwwroot/m
创建另一个前端项目mobile
,构建输出到wwwroot/m
project
从源文件中删除wwwroot
,不关心前端过程构建方法:按如下顺序整体构建
- 构建
project
,并整体发布到.../publish/
- 构建
web
,得到.../web/dist
,将其拷贝到.../project/wwwroot/
- 构建
mobile
,得到.../mobile/dist
,拷贝到.../project/wwwroot/m/
- 构建
然后各组领任务,项目技术负责人开始创建项目文件,编写开发规范……
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。