零到全栈(让部署更稳、更简单:systemd、Nginx 反向代理与前后端同源)

朴素部署留下的三笔账:

前面最朴素的全栈部署已经跑通,但留下三笔待还的账:后端服务脆弱、8000 端口直接暴露在公网、前后端处于不同源

接下来要解决的正是这三件事:用 systemd 取代 nohup,实现开机自启、崩溃恢复和统一日志;让 Nginx 把 /api/ 反向代理到本机 Uvicorn,关闭公网对 8000 的访问;让前后端都经 80 端口同源协作------逐步把这套朴素部署升级得更稳、更易维护

第一笔:systemd 取代 nohup

之前为了让后端在后台运行,用了 nohup,把启动方式从:

bash 复制代码
fastapi run

换成:

bash 复制代码
nohup .venv/bin/fastapi run > backend.log 2>&1 &

这样 SSH 会话结束后进程还能继续运行;但日常管理变得麻烦:想确认服务是否存活,要先按进程名查进程号:

bash 复制代码
ps aux | grep fastapi

命令可能列出两行,要找其中包含 python 的那行;第二列的数字就是进程号 (PID),进程还在就说明服务仍在运行

要停掉服务,也没有一条直接的 "停止" 命令,只能按进程号杀掉:

bash 复制代码
kill 那个进程号

杀完再查 fastapi 进程,应当已经不存在(文字实验室的后端也会停止工作);查状态要先找进程号,停服务也要先找进程号,翻日志还得记住当初重定向到了哪个文件------这相当于没人替我们管理服务,我们得一直盯着它

更要紧的是,nohup 做不到两件事:服务器重启后不会自动拉起后端;进程自己崩溃后也不会自动恢复

所以我们需要一个管家来负责:服务器重启后自动启动程序;程序崩溃后重新拉起;方便检查运行状态;替我们收集输出和报错,不用记日志文件放在哪儿;Linux 已经提供了这样的工具:由一个程序守护另一个程序的运行,这叫进程守护;而在 Ubuntu 上负责这件事的工具就是 systemd

systemd 名字末尾的 d 是 daemon(守护进程)的意思------systemd 就是 system daemon;Linux 上不少以 d 结尾的程序都是守护进程,例如 httpd、crond

把程序交给 systemd:

systemd 并不陌生,只是之前没有专门点明:安装 Nginx 后,我们用过:

bash 复制代码
systemctl status nginx

当时看到 Active: active (running),就知道 Nginx 已经启动;前端部署时,改完 Nginx 配置又会执行:

bash 复制代码
sudo systemctl reload nginx

这两条命令都在使用 systemd,只不过命令行工具叫 systemctl;拆开看,systemctl(system control)是 systemd 的命令行工具,nginx 是交给它管理的程序,中间的 status、reload 是发给 systemd 的指令

所以,当前运行的 Nginx 已经委托给 systemd 管理

Nginx 能自己运行,为什么还要委托:

Nginx 离开 systemd 也能运行,可以用自己的命令启停:

bash 复制代码
sudo nginx              # 启动
sudo nginx -s reload    # 重载配置
sudo nginx -s stop      # 停止

知道有这些命令就够了,不要在当前服务器上直接试------Nginx 正由 systemd 管着,手动再起一个会撞端口

Nginx 自带的命令和 Uvicorn、FastAPI 类似:它们管 "怎么启动、怎么停止",不具备 systemd 那些管理能力;FastAPI 的 fastapi dev / fastapi run,背后也是调用 Uvicorn;程序自带的命令负责启停;交给 systemd 后,systemd 负责开机启动、崩溃重启、记录运行日志(底层仍然调用程序自己的命令)

Nginx 如何交给 systemd 管:

委托不是口头交代,需要写一份 service 文件,明确程序怎么运行、希望 systemd 怎么管理;Nginx 的 service 文件可以这样查看:

bash 复制代码
systemctl cat nginx

内容放不下时会进入分页器:用方向键 ↑ ↓ 逐行翻,空格向下翻一页,b 向上翻一页,看完按 q 退出;输出大致如下(版本不同,细节会有差异):

bash 复制代码
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
...

[Service]
Type=forking
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
...

[Install]
WantedBy=multi-user.target

无论 service 文件多复杂,基本由三段组成,各自回答一个问题:

段 回答的问题
Unit 这是什么服务------叫什么名字、和其他服务有什么关系
Service 怎么运行它------用哪条命令、什么身份、崩溃后怎么办;这是主角
Install 什么时候启动它------开机自启就在这里配置

不需要完全掌握 service 文件的写法;需要时可以请 AI 帮忙,现在先认两行重要配置:

  • ExecStart=------启动 Nginx 的命令;Nginx 也是由一条普通命令拉起来的,和 nohup .venv/bin/fastapi run ... 里的命令本质相同

  • WantedBy=multi-user.target------开机自启的配置;有这一行,系统启动时就会自动启动 Nginx

每个程序支持的命令不同,所以对应的 service 文件也不一样;但它们基本都围绕程序自己的命令编写,整体骨架相同

编写后端的 service 文件:

把后端交给 systemd 管理,需要新建一份 service 文件,放在 systemd 指定目录 /etc/systemd/system/ 下,文件后缀为 .service;文件名可以自定,但不能和机器上已有的服务重名

现在为后端创建 zero-backend.service:

bash 复制代码
sudo vim /etc/systemd/system/zero-backend.service

写入下面内容;如果服务器上的项目目录或登录用户名不是 ubuntu,要按实际情况调整:

bash 复制代码
[Unit]
Description=zero-to-tech backend

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/zero-to-tech/backend
ExecStart=/home/ubuntu/zero-to-tech/backend/.venv/bin/fastapi run
Restart=always

[Install]
WantedBy=multi-user.target

真正需要填写的部分只有六行:

  • Description------给人看的名称,查询状态时会显示

  • User------用哪个用户身份运行;省略时 systemd 默认用 root,但项目不需要这么高的权限;遵循最小权限原则,够用就好

  • WorkingDirectory------工作目录,不能省,而且要用绝对路径

  • ExecStart------启动服务的完整命令;使用 .venv 里的绝对路径,不需要先 activate;activate 只是让终端优先找到虚拟环境里的命令,直接写明路径更明确

  • Restart=always------进程退出后自动重新启动

  • WantedBy=multi-user.target------开机时自动启动,和 Nginx service 里的设置相同

启用 service:

service 文件写好后,将后端委托给 systemd 管理,依次执行:

bash 复制代码
sudo systemctl daemon-reload          # 新增了 service 文件,让 systemd 重新读取
sudo systemctl start zero-backend     # 启动
sudo systemctl status zero-backend    # 查看状态
sudo systemctl enable zero-backend    # 设置开机自启

状态输出大致如下:

bash 复制代码
● zero-backend.service - zero-to-tech backend
     Loaded: loaded (/etc/systemd/system/zero-backend.service; enabled; ...)
     Active: active (running) since ...
   Main PID: 12345 (fastapi)

看到绿色的 active (running),说明服务已经由 systemd 启动成功

现在做一件很有用的验证:status 里的 Main PID 就是进程号,我们亲手把它杀掉:

bash 复制代码
sudo kill 进程号          # 换成看到的 Main PID

再查状态:

bash 复制代码
sudo systemctl status zero-backend

状态仍然是 active (running),但 Main PID 换成了新号码;这说明进程确实死过,systemd 随即启动了一个新的------这就是 Restart=always 的崩溃恢复效果;以后代码里的 bug 把进程弄死,服务也会自动爬起来

用 systemctl 管理后端:

交给 systemd 后,后端的日常管理都用 systemctl:

想做什么 命令
查看运行状态 sudo systemctl status zero-backend
改完代码后重启 sudo systemctl restart zero-backend
停止服务 sudo systemctl stop zero-backend
启动服务 sudo systemctl start zero-backend
设置 / 取消开机自启 sudo systemctl enable / disable zero-backend
查看日志 journalctl -u zero-backend -f

注意,systemctl stop 和 kill 进程不同:执行 sudo systemctl stop zero-backend 后服务会真正停止,不会因 Restart=always 立刻重启;除非再执行 sudo systemctl start zero-backend;可以分别试一下:

bash 复制代码
sudo systemctl stop zero-backend
curl localhost:8000/api/profile
# curl: (7) Failed to connect ... Connection refused    ← 服务确实停了
bash 复制代码
sudo systemctl start zero-backend
curl localhost:8000/api/profile
# JSON 回来了

后端日志:

服务运行后,我们常常需要了解它在做什么------来了哪些请求、有没有报错;这就要看日志;之前见过三种日志管理方式

第一种:实时显示; 本地开发时,终端运行 fastapi dev 后会被进程占用;每来一个请求,屏幕上就多一行,代码里的 print 和出错时的 traceback 也直接输出;终端开着,日志就在眼前;代价是窗口必须一直开着,关闭后之前的日志也没了------适合开发,不适合正式上线

第二种:写入文件; 用 nohup 把服务放到后台后,终端不再显示输出,需要重定向到文件:

复制代码
nohup .venv/bin/fastapi run > backend.log 2>&1 &
#                           └──────┬──────┘
#                         把输出接进 backend.log

> 把正常输出写进文件,2>&1 让错误输出也进入同一文件;之后可以用 cat backend.log 或 tail -f backend.log 查看;这样日志能留存,但位置、文件名、清理时间都得自己记着和管理,文件还会不断变大

第三种:交给 systemd; systemd 会自动管理服务日志,这就是 "委托" 的一部分;而且日志有容量上限,旧内容会自动清理,不会像 backend.log 那样一直增长

查看 systemd 日志:

查看 systemd 管理的日志用 journalctl(journal 是 "日志",ctl 是 "控制",和 systemctl 同属一套):

bash 复制代码
journalctl -u zero-backend            # 查看这个服务的日志
journalctl -u zero-backend -n 50      # 只看最后 50 行
journalctl -u zero-backend -f         # 实时跟随新日志

-u 是必须的参数(u = unit);后面写 service 的名字 zero-backend,就表示查看它的日志

-f 的效果:

-f 是 follow(跟随);这个参数值得单独试一次,因为行为和其他命令不同:

bash 复制代码
journalctl -u zero-backend -f

回车后会显示最近几行,然后光标停在那儿、命令提示符也不回来------这不是卡住,而是在等后端下一次输出;先别关这个窗口,切到浏览器点一次 "分析",再切回来,终端会立刻多出一行------这次分析请求被实时记下来了

看完按 Ctrl + C 退出,提示符就会回来;不带 -f 的 journalctl 在日志较多时也会进入分页器,操作和查看 service 文件一样,按 q 退出

清理旧日志文件:

日志已交给 systemd 管理,backend.log 就成了一份不会再更新的旧账;留着可能误导以后排查------某天看到的是很久以前的内容,反而怀疑错方向;可以在服务器上删掉:

bash 复制代码
rm ~/zero-to-tech/backend/backend.log

第二笔:Nginx 与反向代理

现在前后端的运行方式是:前端由 Nginx 发送静态文件,Nginx 监听 80;后端由 Uvicorn 运行,监听 8000;两个进程背后都由 systemd 管理

Nginx 和 Uvicorn 都监听端口、接收 HTTP 请求、返回响应,但各自擅长的事情不同:

  • Nginx 的看家本领是从磁盘上找文件送出去;围绕 "把东西送给外面的人",它还配有访客日志、加密、压缩、前置拦截等功能

  • Uvicorn 擅长运行我们写的 Python;它的职责是把请求交给 main.py,再把计算结果带回来

一句话:Nginx 擅长 "对外",Uvicorn 擅长 "算"

它们在对外服务方面的差异如下:

能力 Nginx Uvicorn
访客日志(谁来过、从哪来、看了什么) 逐条记录来源页面、浏览器和传输字节数 没有单独的访客日志
加密(HTTPS) 配置方便,证书续期也容易管理 需要自己配置证书和续期
提速(压缩、长缓存) 有现成配置 没有
前置接待 核心职责 能做,但本职是运行 Python 应用

这里的 "日志" 要分清:systemd 收集的是运行日志------程序做了什么、是否报错、traceback 是什么;表格里说的是访客日志(正式名称是访问日志,access log)------谁访问了网站、从哪个页面来、用了什么浏览器、传了多少数据;Nginx 的访问日志在 /var/log/nginx/access.log,和 systemd 日志无关;Uvicorn 目前没有单独的访问日志,只有 systemd 收集的运行日志

Nginx 的功能更齐全,不是 Uvicorn 不好,而是两者定位不同:Nginx 二十多年来专注站在最前面、接住来自互联网的请求;而 Uvicorn 的工作是运行我们的 Python

Nginx 不能替代 Uvicorn,因为它不会运行 Python 业务代码;如果想让后端也用上 Nginx 的前置能力,可以调整分工:Nginx 负责对外,Uvicorn 负责计算------两个程序都保留,只把 "对外" 这部分从兼职的 Uvicorn 转给专职的 Nginx

Nginx 会做两件事:从磁盘上找文件送出去(前端部署时一直在用);把收到的请求原样转交给另一个程序,再把返回结果带出去(这部分之前还没用过);第二件正是关键:Nginx 自己不算,但可以在门口接请求,再派给 Uvicorn:

bash 复制代码
Nginx 当门房------守着 80 端口,静态文件自己送;遇到后端请求就转交进屋
Uvicorn 当算手------在屋里只管跑 Python,只对接 Nginx,不直接见公网

这套分工里没有谁被替代:Uvicorn 仍在服务器内部运行,只是从此所有找它的请求都要先经过 Nginx

配置反向代理:

项目里怎么做?让 Nginx 同时监听 80 和 8000,80 端口给前端、8000 给 Uvicorn?不是

先看用户访问前端时的 URL:

bash 复制代码
/
/text-lab

后端 API 的路径则是:

bash 复制代码
/api/profile
/api/analyze
/api/history

FastAPI 还有 /docs 等调试入口------这是开发时用的,不该向公网开放;按路径分流就能顺手解决:只有 /api/ 开头的请求交给 Uvicorn,其他请求继续由 Nginx 查找静态文件;这样 Nginx 只需监听 80,Uvicorn 仍监听 8000,但 8000 不直接对外

编辑 Nginx 配置:

bash 复制代码
sudo vim /etc/nginx/sites-enabled/default

在 server 块里新增一个 location,其余不动:

bash 复制代码
server {
    listen 80 default_server;
    server_name _;

    root /home/ubuntu/zero-to-tech/out;
    index index.html;

    location / {
        try_files $uri $uri.html $uri/ =404;
    }

    location /api/ {                          # ← 新增:路径以 /api/ 开头的请求
        proxy_pass http://127.0.0.1:8000;     # ← 不找文件,原样转给本机 8000

        proxy_set_header Host              $host;              # 访客敲的是哪个地址
        proxy_set_header X-Real-IP         $remote_addr;       # 访客的真实 IP
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;            # 访客走的是 http 还是 https
    }
}

新规则可以这样读:凡是路径以 /api/ 开头的请求,不要在磁盘找文件,原样转给 127.0.0.1:8000(后端),拿到响应再带回给访客;其他请求不受影响,照旧由 location / 查找静态文件------一个 server、两个角色,按路径分工

注意 proxy_pass 的值是 http://127.0.0.1:8000,末尾千万别再加斜杠,否则会导致整片 404;不带斜杠才是原样转发,这一点以后抄别处的 Nginx 配置时也要留心

顺带,之前担心的 /docs 暴露问题也解决了:只有 /api/ 开头的请求会转给后端;/docs 这类路径会落到 location /,在静态目录找不到文件就返回 404,公网访问不到

这层转发有个正式名字:反向代理(reverse proxy);代理就是替人转发请求,"反向" 指它站在服务器一侧------如果是站在使用者一侧、替人上网的那种,叫正向代理(例如有些公司要求员工上网先经过公司的代理服务器);Nginx 在服务器上转发请求,所以叫反向代理

配置完成后,检查语法并让 Nginx 重新读取:

bash 复制代码
sudo nginx -t
sudo systemctl reload nginx

服务器本机验证:

bash 复制代码
curl localhost/api/profile

JSON 回来了;注意 localhost 后面没写端口(省略时就是 80);这次请求的路径是:

curl → Nginx(80) → Uvicorn(8000) → 原路返回

再换到本地电脑,在浏览器访问 http://服务器IP/api/profile;如果不带 8000 端口也能跑通,反向代理就配置好了

让 8000 不再对外:

现在所有用户请求都能走 80 端口这个正门,8000 那扇偏门就不该继续向公网敞开;注意,8000 并没有关------Uvicorn 仍在运行,只是要限制它只能被本机访问;分两步做

第一步:让后端只监听本机

后端现在还监听 0.0.0.0,这是 fastapi run 的默认值;之前讲过,和 127.0.0.1 相比,0.0.0.0 会接受所有网卡上的连接,对外开放

但有了 Nginx 反向代理,后端只需要服务同一台机器上的 Nginx,外部访客不再直接和它通信;回到 service 文件,给 ExecStart 加一个参数:

bash 复制代码
sudo vim /etc/systemd/system/zero-backend.service
bash 复制代码
ExecStart=/home/ubuntu/zero-to-tech/backend/.venv/bin/fastapi run --host 127.0.0.1

改完 service 文件,重新加载并重启:

bash 复制代码
sudo systemctl daemon-reload      # service 文件改了,让 systemd 重新读取
sudo systemctl restart zero-backend

改完后,在服务器本机执行 curl localhost/api/profile 应该仍能返回 JSON;curl localhost:8000/api/profile 也仍然可以

但如果换成本地电脑直接访问 http://服务器IP:8000/api/profile,就会连不上;此时文字实验室也可能暂时不能正常分析,因为前端仍通过公网 8000 请求后端------这个问题留到调整前端地址时一并解决

第二步:从云平台防火墙里删除 8000 的放行规则

虽然 Uvicorn 已改为只监听 127.0.0.1,安全组仍应再挡一道 8000------安全无小事,这就是纵深防御:不要把安全押在单独一道防线上

经过这番调整,前后端同源的条件已经具备:协议相同(http)、主机相同(服务器 IP)、端口相同(80);只剩前端配置还指向 8000

第三笔:前端切生产,把地址整个删掉

现在 8000 已不再对外开放,但前端 .env.production 里仍写着旧地址:

bash 复制代码
NEXT_PUBLIC_API_BASE_URL=http://服务器IP:8000

可以把端口去掉,改成 http://服务器IP;不过更省心的写法是把值留空:

bash 复制代码
NEXT_PUBLIC_API_BASE_URL=

此时 API 变量是空字符串,代码里的 ${API}/api/profile 就变成 /api/profile------相对路径会自动使用当前页面的源;前后端已经同源,因此浏览器会把请求送到正确的后端

这样做不只是少写几个字:后面还要把 IP 换成域名、把 http 换成 https;把地址留空后,这些变化都不需要再改 API 地址

服务器上的配置变了,前端需要重新构建:

bash 复制代码
cd ~/zero-to-tech
npm run build

至此,部署改造完成

验证成果:

再次访问线上网站 http://服务器IP/:分析、拼音、历史记录都能正常工作,和之前看到的一样

重点是:用户那一侧什么都没变,底下的地基却整个换了一遍;好的重构就该这样------外面看不出来,里面焕然一新

要确认部署确实变了,可以从三处检查

第一处:同源

在浏览器 F12 的 Network 面板里查看:请求 URL 和地址栏里的页面地址协议、主机、端口相同,不再走 8000;点 "分析" 后,也没有 OPTIONS 请求,只剩一条 POST------同源请求不需要先发预检

前后端同源了,但 CORSMiddleware 别删,credentials: "include" 也别删:线上不再需要跨源许可,但本地开发仍是 3000 → 8000;后端 .env 里的 ALLOWED_ORIGINS 也保留,虽然线上已经没有跨源请求,代码仍需要这个值才能启动

第二处:8000 端口对外关闭了

之前已经从两处收紧:Uvicorn 只听 127.0.0.1,云平台安全组也删掉了 8000 放行规则;从本地电脑访问 http://服务器IP:8000/api/profile 已经连不上

第三处:服务会自己恢复

关闭 SSH 窗口,网站照常可用;在服务器上杀掉后端进程,它会很快重新启动;重启整台服务器,后端也会自动回来

这些检查都通过,就说明部署现在更稳健了

更新 README:

README 应该跟着项目一起变化------过时的说明比没有更糟;部署方式改变了,README 也要同步更新:重写 "部署到服务器" 整节,并修改 "配置说明" 里的前端地址

新的部署说明如下:

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. 后端:委托给 systemd**(首次部署才需要)
​
新建 `/etc/systemd/system/zero-backend.service`:
​
```ini
[Unit]
Description=zero-to-tech backend
​
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/zero-to-tech/backend
ExecStart=/home/ubuntu/zero-to-tech/backend/.venv/bin/fastapi run --host 127.0.0.1
Restart=always
​
[Install]
WantedBy=multi-user.target
```
​
```bash
sudo systemctl daemon-reload
sudo systemctl start zero-backend     # 启动
sudo systemctl enable zero-backend    # 开机自启
sudo systemctl status zero-backend    # 看到 active (running) 就对了
```
​
**5. Nginx:静态文件 + `/api/` 反向代理**(首次部署才需要)
​
编辑 `/etc/nginx/sites-enabled/default`:
​
```nginx
server {
    listen 80 default_server;
    server_name _;
​
    root /home/ubuntu/zero-to-tech/out;
    index index.html;
​
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
​
    location /api/ {
        proxy_pass http://127.0.0.1:8000;     # 末尾不要加斜杠
​
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```
​
```bash
sudo nginx -t && sudo systemctl reload nginx
```
​
**6. 安全组 / 防火墙**
​
只放行 **22(SSH)** 和 **80(HTTP)**。**8000 不要放行**------后端只监听 `127.0.0.1`,由 Nginx 转发进去。
​
**7. 验证**
​
```bash
curl localhost/api/profile        # 服务器本机,注意不带端口
```
​
浏览器访问 `http://服务器IP`,打开文字实验室做一次分析,再看历史记录。换一个浏览器(或无痕窗口)再试,两边的历史记录应该互相看不到。
​
## 日常发布
​
```bash
cd ~/zero-to-tech
git pull
​
npm run build                          # 前端改了才需要
sudo systemctl restart zero-backend    # 后端改了才需要
```
​
## 常用运维命令
​
```bash
sudo systemctl status zero-backend     # 看状态
sudo systemctl restart zero-backend    # 重启
sudo systemctl stop zero-backend       # 停掉
journalctl -u zero-backend -f          # 实时看日志(Ctrl+C 退出)
journalctl -u zero-backend -n 50       # 看最后 50 行
​
sudo nginx -t                          # 改完 Nginx 配置先验语法
sudo systemctl reload nginx            # 再让它重读
```
​
再改"配置说明"里前端那一行------线上值现在留空:
​
| 键 | 说明 | 本地 | 线上 |
| --- | --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | 后端接口地址 | `http://localhost:8000` | 留空------前后端同源,代码里使用相对路径 |
​
后端的 ALLOWED_ORIGINS 不用改:线上虽然已经没有跨源请求,但代码调用 os.getenv("ALLOWED_ORIGINS") 时仍需要这个值;照旧填 `http://服务器IP`
​
改完提交:
​
```bash
git add README.md
git commit -m "更新部署说明:systemd + 反向代理 + 同源"
git push
```

写完自己走一遍------最好的办法是假装自己是第一次拿到这个项目:照着敲,不凭记忆补任何一步;漏了哪句、少了哪个前提,当场就露馅了

README 是活的,跟着项目一起长:现在这一版够用了------够一个陌生人把项目在本地跑起来,也够他照着部署到自己的服务器上;等部署方式换掉之后,再回来改它

相关推荐
前端双越老师3 小时前
程序员 1v1 访谈系列第一期:转行也不会等到失业以后
程序员·agent·全栈
濮水大叔14 小时前
Cabloy 双引擎架构详解:Vona 与 Zova 是如何协作的
typescript·node.js·全栈
全栈Agent 小李1 天前
【无标题】
前端·后端·agent·ai编程·全栈·cursor·mcp
三天不学习2 天前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
濮水大叔2 天前
AI 编码实战:一句提示词,实现 CRUD、SSR 与多任务
node.js·ai编程·全栈
SL_staff3 天前
Excel如何自动升级为可搜索的知识节点:JVS文档平台的语义解析实践
开源·github·全栈
白兰地空瓶3 天前
项目要上线了,我连夜学完 Docker:从底层原理到容器化部署全记录
docker·全栈
SL_staff3 天前
3分钟零代码配置带审批流的报销单:JVS三引擎可视化串联实战
spring boot·开源·全栈
泡泡oO4 天前
“如果你还在用Superpowers,那我不要和你说话”
前端·后端·全栈