先上线:最朴素的部署方案
现在把整套全栈应用发布到互联网------配置、环境、服务器操作,用最朴素的办法一次跑通
清点行囊:
此刻的项目由三样东西组成:
-
静态前端------基于 React 和 Next.js 开发,可以构建为静态前端代码
-
FastAPI 后端------基于 Python 和 FastAPI 开发,通过 Uvicorn 提供服务
-
SQLite------对应硬盘上的一个 .db 文件
距离上一次把项目发上服务器,已经过去很长一段路:现在用浏览器访问服务器的 IP 还能看到它,但那仅仅是一个静态前端网站;后端 API、数据库、会话都已经做了,只不过全在本地开发、本地运行;开发后端的同时,前端也跟着动了------加了 fetch 去请求后端,添了历史记录列表和会话
所以全栈部署要前后端一起做:前端只需推送代码、重新构建,后端则要从零部署------这就是现在的起点
另外,上线并不是终点:光发到服务器还不够,后续的迭代、维护,以及运营、安全和用户体验都值得分开细聊;这条路的走法是------先用最朴素的办法把整套跑通上线,再换成更易维护的部署方式并通过反向代理让前后端同源,然后接上域名与 HTTPS,接着通过日志与统计数据观察用户行为,最后是调优、安全与一键发布
部署是什么:
"部署" 说到底是一句话:把自己电脑上能跑的东西,在另一台机器上(尤其是服务器)跑起来;线上部署、上线,说的都是同一件事
项目虽有前端、后端、数据库三部分,但 SQLite 基本就是后端的一部分------它的安装、创建、使用都基于 Python 的能力;所以要部署的,就是前端和后端
朴素的部署方案:
本地运行时:前端通过 npm run dev 监听 3000 端口,后端通过 fastapi dev 监听 8000 端口(背后是 Uvicorn);我们用浏览器访问 3000 拿页面,页面里的 js 脚本再通过 fetch 访问 8000 的后端程序,实现交互
服务器也只是一台电脑,可以用一种朴素的方式想部署方案:前端监听服务器的一个端口,后端监听另一个端口;用户通过浏览器获取前端页面,页面中的 js 脚本再通过 fetch 访问后端
只不过前端不需要监听 3000 了------讲前端时一直是 Nginx 监听 80 端口并转发前端资源(本地用 3000 只是为了方便开发的权宜之计);所以在这个方案里,前端仍然通过 Nginx 来代理,通过 80 端口给到用户,后端则与本地运行一样,通过 Uvicorn 的能力在 8000 端口提供服务
按这种朴素的方案,许多事情对我们来说并不是一个全新的领域------但也有一些要了解的新知识点
我们已经知道的:
前端部署流程 :把代码推到服务器、构建为静态前端代码、交给 Nginx------部署前端时已经轻车熟路,照做即可;只有一处要留心:给前端加的配置文件 .env.local 并没有被 git 追踪,等下要处理(先记着这笔账)
Python 环境:后端运行的基础;Ubuntu 自带 Python,想要其他版本也可以很轻松地安装
.venv 和依赖:fastapi、uvicorn、pypinyin、snownlp 这些依赖用 .venv 管理;requirements.txt 加 pip install 就能把依赖装回来------venv 也是 Python 官方自带的能力
代码和数据库 :项目代码完备------main.py、storage.py,代码运行时会自动创建 history.db;把代码拉到服务器和前端是同一套动作,背后是 git 和 GitHub;数据库用的是 SQLite,后端运行时会自动创建------换 MySQL 或 PostgreSQL 这样的数据库,部署时才会有些麻烦
启动后端服务 :需要 Uvicorn 监听一个端口提供 API 服务------本地一直是用 fastapi dev 调用它,默认监听 8000 端口
会撞上的五个挑战:
服务器部署与本地运行毕竟差异显著,只凭已经知道的这些去部署,还会遇到五件事:
挑战一:缺失前端配置文件 ------.env.local 不进 git,把代码 pull 到服务器之后,服务器上没有任何前端配置;而且服务器上要的也不是 .env.local 这个名字------.local 的意思就是 "本机私有",是给自己开发用的,线上构建要的是另一个名字;总之前端的配置得在服务器上手动建一份,不然构建出来的前端不知道该去哪儿找后端------这是配置文件的问题
挑战二:后端配置不匹配 ------后端 main.py 里的 CORS 中间件有这样一行:
allow_origins="http://localhost:3000",
这行代码在线上运行时必定出问题:localhost 的意思就是本地,而项目部署之后要靠用户通过互联网访问------它会 "水土不服";讲道理,这种内容就不应该写在代码里,它应该写进单独的配置文件------和上一条一样,也属于配置文件问题
挑战三:fastapi dev 只支持本地访问 ------本地用 fastapi dev 启用 Uvicorn 没问题,但它只接收本地的访问,也就是发请求的电脑和运行程序的电脑是同一台;按现在的部署逻辑,用户要在自己的电脑上通过 js 向服务器的 8000 端口发请求,这个方案行不通;好在文档有说明:面向公网提供服务,要用 fastapi run 命令------用这个命令运行时,Uvicorn 就不止能处理来自本地的请求了
挑战四:终端窗口不敢关闭------本地运行时前后端各占一个终端窗口,窗口一关服务就停;部署之后前端可以不再依赖终端(静态页面由 Nginx 提供服务),后端却很尴尬:终端要一直开着,一旦关闭,后端服务就停了
挑战五:防火墙------服务器在云平台上,前端和后端目前分别通过 80 和 8000 端口监听请求;80 已经开放(部署前端时确认过),但云平台的安全组 / 防火墙默认不开 8000,需要手动放行
把五条归拢一下,其实是四件事:
-
处理配置文件(挑战一、二)------这件得在本地办,上服务器之前就要办完
-
换成 fastapi run(挑战三)------一条命令的事
-
开通防火墙(挑战五)------去云平台控制台点一下
-
让后端在后台常驻(挑战四)------等前面都跑通了,最后办
配置文件:
先办第一件:挑战一和挑战二;两条一个在前端、一个在后端,其实是同一类问题------都是 "某个值不该写死,它得随着机器变";而且都得在本地办完再推上去,所以放在上服务器之前
什么是配置:
同一份代码,跑在自己电脑上和跑在服务器上,有些东西就是会不一样------比如前后端地址:不同服务器的 IP 不同,地址自然不同
除此之外,还有一些信息值得放进配置:前后端地址;数据库地址和连接配置(包括密码);第三方服务密钥(例如大模型 API key);服务监听哪个端口;日志写到哪儿、要不要开调试模式
这些可以写在同一个配置文件中,用类似这样的格式:
名称a=xxx
名称b=123
保存为一个文件,代码就可以从中读取到运行所需的参数------这个文件就叫配置文件
这些东西不属于代码:代码回答的是 "怎么做",配置回答的是 "这一次,对着谁做";代码和配置相互配合------同一份代码配上不同的配置,就能服务不同的环境
我们项目里有两处和地址相关的配置;一处是前端的 .env.local:
前端根目录,.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
它写了后端的访问地址并赋给常量 NEXT_PUBLIC_API_BASE_URL,前端请求后端时顺着这个常量找到地址
另一处在后端,就是前面提到的那行:
allow_origins="http://localhost:3000", # 之前加的
它本不应该写死在代码里,而应该也抽取为一个配置文件------之前就说过 "后端这类配置,留到部署时一起处理",现在就是那个时候
配置文件的命名与读取:
命名的规矩比较松散:基本上会叫 .env,但有时也可能不这么叫------取决于框架认不认它;目前项目里唯一的配置文件,是前端的 .env.local
为什么叫 .env.local 而不是 .env?其实 Next.js 两个都认,但后缀在传递不同的意思:.local 表示本机私有(即别人机器上没有,也别提交);同时它还认这几种写法:
-
.env.development.local -
.env.development -
.env.production
.production 表示生产环境构建时才用;之前那份配置本来就是本机私有的,所以叫 .env.local
有余力的话,可以了解一下 Next.js 到底是怎么找配置文件的:它不是 "只认一个",而是按固定顺序找一串,先找到的值优先;以 npm run dev 为例,顺序是:
.env.development.local → .env.local → .env.development → .env
npm run build 时则把中间的 development 换成 production;这个顺序解释了两件事:其一,.env 也是认的,只是优先级最低------适合放 "各环境通用" 的值;其二,.local 那几个排在前面,意味着本机的设置永远盖过公共设置------这正是 "本机私有" 该有的待遇
以上是前端 Next.js 的配置文件;Python 后端的要求和它不同------用 Python 代码读取配置文件时,认的就是 .env,不需要加其它后缀;配置文件放在指定位置即可:前端配置放前端项目根目录,后端配置放后端根目录(在这个项目里就是 /backend 下);另外,Python 读取配置文件需要借助一个 python-dotenv 库,等下安装
为什么配置文件名前面有个点?点开头的文件是隐藏文件------用隐藏文件写配置,也是一种心照不宣的约定
配置文件与 git:
配置文件不进 git,原因有两个:其一,git 经常用来在不同设备之间同步代码(比如本地电脑与服务器),而配置文件和具体的设备有关,不需要同步;其二更重要------配置文件中很可能包含密码、密钥这类信息,它们的保密程度显然比代码高,绝对不能随着代码推送到远程仓库,尤其是 GitHub 的公开仓库!
所以要把配置文件写进 .gitignore,明确要求 git 忽略它
README 与 .example:
让 git 忽略配置文件是出于安全考虑,但它会引起一个问题------别人拿到代码之后跑不起来:不知道需要补充哪些配置、不知道配置文件应该叫什么名字、也不知道按什么格式写,就会 "玩不转" 这个项目
这怎么办?惯例是:所有项目的根目录中都需要有一个 README.md------在 GitHub 上新建仓库时那行 "Initialize this repository with a README",就是在询问是否需要建一个 README 文件;我们判断一个开源库靠不靠谱时,也会去看它的 README;这个文件的作用,是告诉拿到代码的人这个项目是怎么回事、解决什么问题、代码结构是怎样的、如何运行它
有了 README,配置文件缺失的问题就有了着落------把配置文件的要求写在 README 里,人们拿到代码之后照着创建就行;但关于配置文件,还有一个心照不宣的约定:写一份示例配置文件,叫 example,放进项目
比如项目依赖一个 .env 配置文件,就再创建一个 .env.example(也有人叫 .env.sample、.env.template,都是一回事);它和 .env 长得一模一样,键都在,只是值全是示例------因为没有真的值,所以能进 git;运行时也不会真的生效,它的作用就是给人看配置文件应该怎么写
动手处理配置文件:
后端配置目前还写死在代码里,先处理它;要干两件事:把值搬出去------挪到一个专门放配置的文件里;让代码去读它------让程序启动时自己去那个文件里取
第一件:把值搬出去------在 backend/ 下新建一个 .env 文件:
backend/.env
ALLOWED_ORIGINS=http://localhost:3000
实际的配置就一行(顶部 # 开头的是注释,不会被代码读取);按前面说的,这个文件不能进 git------打开 .gitignore 确认有这一行:
.env*
没有就现在加上;然后 git status 看一眼------backend/.env 不该出现在待提交列表里;这一步别跳过:"配置不进 git" 不能靠 "我记得应该被挡住了",要靠亲眼看一次 git status
第二件:让代码去读它 ------改后端 main.py,顶部先引入两个库:
python
import os
from dotenv import load_dotenv
紧接着,读取配置文件:
python
load_dotenv() # ← 读同目录下的 .env
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS").split(",")
然后修改中间件的 allow_origins,不再使用写死的地址,换成从配置文件读出来的动态值:
python
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS, # ← 不再写死,从配置来
allow_credentials=True,
...
)
注意,dotenv 不是 Python 标准库的成员,需要手动安装并记进 requirements.txt:
bash
cd backend
source .venv/bin/activate
pip install python-dotenv
pip freeze > requirements.txt # 老规矩,记账
这个 dotenv 只干一件事:把 .env 里的值读进环境变量
改完之后本地验证:后端用 fastapi dev 跑起来,前端 npm run dev,文字实验室照常能用就说明后端配置改好了;想更确定,把 .env 里那行改成一个别的地址、重启后端、再点分析------这次真被拦了,再改回来即可
至此,后端配置就单独抽成了配置文件;接下来创建前后端配置文件的示例文件
创建配置示例文件:
在项目根目录 ~/zero-to-tech/ 下创建 .env.example:
NEXT_PUBLIC_API_BASE_URL=http(s)://ip:port
在后端根目录 ~/zero-to-tech/backend 下也创建 .env.example:
ALLOWED_ORIGINS=http(s)://ip:port
键要写全,值给一个能用的示例,或者写有指引性的内容------这样别人 cp .env.example .env 之后改值就行,不用再去猜键叫什么
不过这里有一个问题:配置文件本身不需要让 git 追踪,但这两份 .example 文件要随代码一起提交,不能被忽略;现在 .gitignore 的 .env* 会把它们也拒之门外------所以要在 .env* 下面添加一行:
!.env.example
感叹号的意思是把示例配置排除在忽略列表之外------它们可以随代码提交了
提交代码:
到这里,本地的活基本干完------代码改了、配置样例建好了,一起推:
bash
git add -A
git commit -m "CORS 名单改为从配置读取;补 .env.example"
git push
这次推上去的包括:main.py(读配置的代码)、requirements.txt(多了 python-dotenv)、两份 .env.example、还有改过的 .gitignore
服务器部署:
按照朴素的部署方案,接下来只需把代码同步到服务器、在服务器上准备所需的环境,然后前端构建、后端用 fastapi run 跑起来就可以了;至于终端的后台运行和防火墙,执行到对应步骤时再介绍
拉取代码:
拉代码动手就快------先 SSH 登录:
bash
ssh 用户名@你的服务器IP
cd ~/zero-to-tech
git pull
前后端代码都在这一个 git 仓库中,所以这次 pull 就把前后端代码都拉到了服务器;再 ls backend/ 看一眼,后端目录里应该有这些内容:
backend/
requirements.txt
.env.example
有代码文件、有配置示例和 requirements.txt,但没有 .venv/,没有 history.db,也没有 .env------我们自己定的规矩正在生效:代码走 Git,依赖和数据不随行
准备 Python 环境:
接下来检查服务器的 Python 安装情况:
bash
python3 --version # 有没有?版本够不够?
which python3 # 用的是哪一个?
Ubuntu 自带 Python 3,第一句多半直接给出版本号;但这两句都不能省------它们问的是两件不同的事:
-
--version回答 "有没有、够不够";fastapi、snownlp 这些库都有各自的最低 Python 版本要求,太老的装不上------在装依赖之前发现版本不够,和装到一半才报错,是完全不同的两种体验 -
which回答 "用的是哪一个";一台机器里可能住着好几个 Python,python和python3还各指各的------不是自己装的机器,这个问题会更常见
如果是 3.10 或更高的版本,一般就没问题;版本过低或者没装,就需要安装(安装方法不再展开);Node 呢?不用装------部署前端时我们就在这台服务器上装过 Node 并 build 过了,不放心的话敲一句 node -v 确认一下
安装依赖:
本地用 venv 管理 Python 环境,服务器上也用 venv:
bash
cd backend
python3 -m venv --prompt=zero-to-tech .venv
source .venv/bin/activate # 提示符出现 (zero-to-tech)
pip install -r requirements.txt
建 venv 那一步 Ubuntu 可能提示 venv 组件没装,需要先 sudo apt install python3-venv(或提示里给出的具体包名,如 python3.12-venv),照提示装完再重跑 venv 命令;pip install 会按 requirements.txt 逐个安装 fastapi、uvicorn、pypinyin、snownlp 这些依赖,可能需要一些时间
写配置文件:
依赖装完,下一步是准备配置文件;先写前端的:
bash
cd ~/zero-to-tech
cp .env.example .env.production
vim .env.production
在里面写:
NEXT_PUBLIC_API_BASE_URL=http://服务器ip地址:8000
再写后端的:
bash
cd ~/zero-to-tech/backend
cp .env.example .env # 照抄一份
vim .env # 再改值
在里面写:
ALLOWED_ORIGINS=http://服务器ip地址
注意这里没有端口号------别顺手写成 :3000,也不要写 :80
两份配置值的变化规律并不一样,值得对照着看:
| 配置 | 本地 | 线上 | 变了什么 |
|---|---|---|---|
| NEXT_PUBLIC_API_BASE_URL | http://localhost:8000 | http://服务器IP:8000 | 只换了地址 |
| ALLOWED_ORIGINS | http://localhost:3000 | http://服务器IP | 地址换了,端口也没了 |
为什么后端这份连端口都变了?因为 ALLOWED_ORIGINS 登记的是 "允许谁来找我" ------也就是前端页面所在的那个源:本地的前端跑在 3000 端口上,线上的前端是 Nginx 在 80 端口提供的------80 是 http 的默认端口,浏览器发出的 Origin 里根本不带它
所以配置值要跟着 "那台机器上的事实" 走,不能照着本地做字符串替换------这正是配置存在的意义:同一份代码,在不同机器上适配各自的实际环境
跑起来:
这一步顺手解决挑战三;前后端分别跑:前端直接构建------
bash
cd ~/zero-to-tech
npm install
npm run build
后端正式上线用 fastapi run:
bash
cd ~/zero-to-tech/backend
fastapi run
启动日志(示意,以实机为准):
bash
FastAPI Starting production server 🚀
server Server started at http://0.0.0.0:8000
INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
背后仍然是 Uvicorn,但跑的地址是 0.0.0.0,不是 127.0.0.1------这是 run 和 dev 的一个重要区别:
| fastapi dev | fastapi run | |
|---|---|---|
| 改代码自动重启 | 有 | 没有 |
| 默认监听 | 127.0.0.1------只听本机 | 0.0.0.0------听所有网卡 |
| 给谁用 | 自己电脑上开发 | 服务器上跑给全世界用 |
-
127.0.0.1------只接受本机内部的连接,外面的世界连不进来
-
0.0.0.0------听所有网卡,外面也能连进来
服务器本地验证:
服务跑起来了,先确认它在服务器本地运行正常,再从公网验证------再开一个终端窗口、再 SSH 一次
先验证后端接口:
bash
curl localhost:8000/api/profile
能看到 JSON 返回,就说明后端服务已经起来了,正在监听 8000 端口并且能在服务器本地请求
再验证数据库已经初始化:
bash
ls ~/zero-to-tech/backend/
能看到 history.db,就说明数据库初始化完成;要完整验证,还得用 curl 做一次分析、再取一次 history------但没必要,我们迫不及待想打开浏览器,从公网访问一次真正的页面了
开通防火墙:
轮到挑战五;80 端口已经开放(部署前端时确认过),但云平台的安全组 / 防火墙默认不开 8000------需要手动放行,方式和此前给 80 端口放行一样
8000 开启之后,用本地电脑访问 http://服务器IP:8000/api/profile,能获取到返回的 JSON,就说明 8000 端口通了
测试:
通过浏览器访问 http://服务器ip地址,就能打开前端页面;在文字实验室对一句文字做分析------这一步测试后端服务是否正常运行;点开历史记录,看有没有分析过的那句话------这一步测试数据库是否正常工作;更换浏览器(或开启无痕模式),换一句文字再做分析、再看历史记录------这一次应该看不到刚才那个浏览器分析过的内容,只有新浏览器自己打的那一句------这一步测试的是会话机制:换了浏览器就是换了访客,各人只看得到自己的那一份
都正常,就说明我们已经成功把整套应用部署上线了------不仅可以通过电脑访问,甚至用手机浏览器也能访问
让后端在后台常驻:
只剩最后一条:挑战四
网站能用了,很想合上笔记本去睡觉;先别急,做个实验:直接关掉 SSH 窗口,再刷新网站------页面还在(那是 Nginx 送的静态文件),但点分析按钮,转圈、报错
前端没问题,但后端程序死了!原因是 uvicorn 目前是个前台进程,它寄生在我们的 SSH 会话里------我们走了,它就没了
"会话" 这个词又出现了:我们和服务器之间那段连着的关系就是 SSH 会话------从登录那一刻开始,到退出那一刻结束
我们希望 uvicorn 的运行不依赖会话:关掉终端之后,它仍然可以持续在服务器上运行;这个需求 Linux 有现成的办法------再次 SSH 登录远程服务器,这次不直接执行 fastapi run,而是:
bash
cd backend
nohup .venv/bin/fastapi run > backend.log 2>&1 &
cd backend 我们已经很熟,关键是第二行------一句话四个零件,逐个拆:
-
末尾的
&------把这条命令丢到后台跑,终端不用干等着 -
开头的
nohup------ "no hang up",别挂断;它让这个进程不再随着 SSH 断开而被杀掉;光有&还不够------&只是不占着终端,人一走它照样陪葬 -
> backend.log------把输出重定向进一个文件;以前日志直接打在终端上,现在没终端可打了,不接住就全丢了 -
2>&1------认个脸:把错误输出也一并塞进同一个文件(1 是正常输出,2 是错误输出,这句的意思是 "2 号跟着 1 号走")------报错和正常日志混在一处,翻起来方便
敲完回车,屏幕上会蹦出一个数字------那是进程号(PID)
顺带留意这次写的是 .venv/bin/fastapi 这个完整路径,而不是先激活环境再跑 fastapi------丢到后台的进程不见得还带着我们 activate 出来的那套环境,指名道姓最保险
现在把实验再来一遍:关掉 SSH 窗口,再次访问网站------这一次一切正常
回顾并写入 README.md:
部署工作做完,就可以安心写 README 文件了;注意这个顺序:不是先写好文档再照着做,而是做完了、验证过了,再把走通的那条路写下来------前者写出来的是 "我以为该这么做",后者写出来的才是 "这么做真的可以";一份过时或者想当然的 README,比没有更糟------没有的话,人家会来问你;写错了的话,人家会照着走进坑里
打开项目根目录的 README.md,把这次的成果补进去:
bash
# 文字实验室
一个中文文本分析小工具:输入一段话,给出情感倾向评分和全文拼音,
并把每次分析的结果存下来,各人只看得到自己的那一份。
零到全栈课程的贯穿项目。
## 技术栈
- 前端:Next.js(静态导出)+ React
- 后端:FastAPI + uvicorn
- 分析:snownlp(情感)、pypinyin(注音)
- 存储:SQLite
- 线上:Nginx
## 本地跑起来
需要:Node.js 18+、Python 3.10+
**后端**
```bash
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # 按下面「配置说明」填好
fastapi dev # → http://localhost:8000
```
**前端**(另开一个终端)
```bash
npm install
cp .env.example .env.local # 按下面「配置说明」填好
npm run dev # → http://localhost:3000
```
## 部署到服务器
前提:服务器上已装好 Python 3.10+、Node.js 18+ 和 Nginx,
且 Nginx 的站点根目录已指向本项目的 `out/`、监听 80 端口。
**1. 拉取代码**
```bash
cd ~/zero-to-tech
git pull
```
**2. 前端:装依赖、写配置、构建**
```bash
npm install
cp .env.example .env.production # 按下面「配置说明」填好
npm run build # 产物进 out/,由 Nginx 提供服务
```
**3. 后端:建环境、装依赖、写配置**
```bash
cd backend
python3 -m venv --prompt=zero-to-tech .venv # 首次部署才需要
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # 按下面「配置说明」填好
```
**4. 后端:在后台跑起来**
```bash
nohup .venv/bin/fastapi run > backend.log 2>&1 &
```
`fastapi run` 是生产模式,监听 `0.0.0.0:8000`;`nohup ... &` 让它在
SSH 断开后继续运行,日志写进 `backend.log`。
查看日志、停止服务:
```bash
tail -f backend.log # 看日志
ps aux | grep fastapi # 找到进程号
kill 进程号 # 停掉
```
**5. 放行 8000 端口**
去云平台控制台的安全组 / 防火墙,放行 8000 端口(80 端口应该已经放行)。
**6. 验证**
浏览器访问 `http://服务器IP`,打开文字实验室做一次分析,再看历史记录。
换一个浏览器(或无痕窗口)再试一次,两边的历史记录应该是互相看不到的。
## 配置说明
配置文件不进 Git,请照着 `.env.example` 自己建一份。
**前端**:开发用 `.env.local`,生产构建用 `.env.production`
| 键 | 说明 | 本地 | 线上 |
| --- | --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | 后端接口地址 | `http://localhost:8000` | `http://服务器IP:8000` |
**后端**:`backend/.env`
| 键 | 说明 | 本地 | 线上 |
| --- | --- | --- | --- |
| `ALLOWED_ORIGINS` | 允许跨源访问的前端地址,多个用逗号隔开 | `http://localhost:3000` | `http://服务器IP`(不带端口) |
写完自己走一遍------最好的办法是假装自己是第一次拿到这个项目:照着敲,不许凭记忆补任何一步;漏了哪句、少了哪个前提,当场就露馅了
README 是活的,跟着项目一起长:现在这一版够用了------够一个陌生人把项目在本地跑起来,也够他照着部署到自己的服务器上;等部署方式换掉之后,再回来改它
思考:现在这套部署好不好
站点上线了,全世界能访问------但它是用最朴素的办法凑出来的,存在三个明显的缺点
第一,后端服务脆弱------nohup 确实让后端活过了 SSH 断开,但它只解决了这一件事,还有一些问题它没解决:
-
服务器重启,它不会自己起来;服务器毕竟也是一台电脑------云厂商维护、断电、操作时手滑重启,一旦发生,后端服务就悄无声息地没了
-
它崩了也不会自己起来;代码里一个 bug 把进程弄死,它就一直死着,直到我们发现为止
-
管起来全靠手工;更新了代码想停掉重启一次比较麻烦(得去找进程号),想看它现在是不是活着,也是一件麻烦事
-
想看日志也比较麻烦,得自己记着日志文件在哪儿
第二,裸奔的 8000 端口------8000 一直对公网开着,而且是个裸端口:
-
没有日志:谁来访问过 8000、干了什么,无从查起(80 端口就有,Nginx 会记录)
-
没有加密:内容明文来回跑------接下来要给网站配 HTTPS,而 HTTPS 配在 Nginx 上,走 8000 的那些请求,一个字都保护不到
-
没有任何管控:限流、转发规则,一概没有
多开一扇门,就多一分风险------这也是为什么服务器的防火墙不会默认开那么多端口
第三,前后端跨源------浏览器的 CORS 策略对前后端有太多限制:开发阶段遇到的问题已经逐个解决,但接下来要上域名、上 HTTPS 时,跨源问题还会持续困扰;我们能不能在部署环节,就让前后端变成同源?
这三个问题,就是接下来要换掉的东西:让 systemd 取代 nohup------开机自启、崩溃自愈、一句命令问状态、一句命令看日志;让 Nginx 反向代理把 /api/ 接进 80 端口------8000 端口不再对外提供服务;前后端都走 80 之后自然变成同源,配置文件也随之更简单------这套朴素的部署,之后来一样样换掉