项目部署上线完整流程(Spring Boot + Vue + SSE 实战)

AI 爆款文章创作器部署上线,走的是「后端 jar 直跑 + 前端 Nginx 托管 + SSE 接口单独反代」这条路,完整流程共八步:环境准备、后端打包、生产配置、启动服务、前端构建、Nginx 接入、SSE 验证、监控与回滚。这套流程同时跑通了「企业级 AI 网关」项目,两者共用同一套部署骨架,区别只在网关多了一层多模型路由配置。下面按实际踩坑顺序把每一步的操作和原因写清楚。

一、部署前先想清楚三件事

动手部署前,先定三件事:部署形态、网络规划、配置管理。这三点没定,后面每一步都会返工。

1.1 部署形态选型

部署方式 环境一致性 发布速度 回滚难度 适用阶段
jar + nohup 直跑 依赖手动装齐 快,替换 jar 即可 低,留旧包即回滚 测试机、小流量上线
systemd 托管 同左,多了开机自启 中 低 单机长期运行
Docker Compose 镜像即环境 中,镜像构建占时间 中,需版本化镜像 生产首选

首版上线我选了 systemd 托管后端,原因是 SSE 长连接场景下进程管理必须稳,nohup 的进程在服务器重启后不会自动拉起,而 systemd 的 Restart=always 能兜住偶发崩溃。后续切到 Docker Compose 是另一个故事。

1.2 端口与网络规划

对外只暴露 80/443,后端 8080 端口只监听内网。Nginx 负责把 /api 转到后端,把 /sse 按流式规则单独转,/ 直接服务前端 dist 静态文件。MySQL 只绑 127.0.0.1,Redis 设密码并禁止外网访问。这样安全组只需要开 80、443、22 三个端口。

二、后端部署四步走

2.1 打包

用 Maven 打可执行 jar,跳过测试减少构建时间:

mvn clean package -DskipTests -Dfile.encoding=UTF-8
ls -lh target/ai-writer-backend-*.jar

多阶段生成文章涉及异步任务和 SSE 推送,jar 里必须确认引入 spring-boot-starter-webflux 或 spring-boot-starter-web 的异步支持,否则 SseEmitter 启动即报错。

2.2 生产配置外置

把 application-prod.yml 单独维护,数据库密码、Redis 密码、大模型 API Key 全部从环境变量注入,不写进文件。多阶段生成里每一阶段调哪个模型、温度参数、超时秒数,也都放配置:

server:
  port: 8080
  compression:
    enabled: true
    mime-types: application/json,text/html
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ai_writer?useSSL=false&serverTimezone=Asia/Shanghai
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  data:
    redis:
      host: 127.0.0.1
      password: ${REDIS_PASSWORD}
ai:
  writer:
    llm:
      api-key: ${LLM_API_KEY}
      base-url: ${LLM_BASE_URL}
    stages:
      topic-timeout-ms: 30000
      draft-timeout-ms: 60000

2.3 上传并启动

scp target/ai-writer-backend-*.jar deploy@服务器:/opt/ai-writer/backend/app.jar

systemd 服务文件 /etc/systemd/system/ai-writer.service:

[Unit]
Description=AI Writer Backend
After=network.target mysqld.service redis.service

[Service]
User=deploy
WorkingDirectory=/opt/ai-writer/backend
Environment=SPRING_PROFILES_ACTIVE=prod
Environment=DB_USER=writer
Environment=DB_PASSWORD=xxx
Environment=LLM_API_KEY=sk-xxx
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar app.jar
SuccessExitStatus=143
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now ai-writer
sudo systemctl status ai-writer

SuccessExitStatus=143 不能漏,否则 systemd stop 时把正常关闭当成异常退出。

2.4 接口自检

curl -s http://127.0.0.1:8080/actuator/health | jq .

三、前端部署:构建到托管

前端是 Vue 3 + Vite。生产构建关闭 sourcemap,减小体积:

npm ci
npm run build
ls dist/

dist 整个目录上传到 /var/www/ai-writer/dist。前端接口地址通过 Vite 的 VITE_API_BASE 环境变量在构建时注入,指向同域 /api,由 Nginx 反代,规避跨域。

四、Nginx 接入:静态、API、SSE 三块分开配

SSE 是这篇文章创作器交互的核心——前端选主题、点生成,后端按「选题→大纲→初稿→润色」多阶段流式返回。Nginx 若不特殊处理,SSE 会被缓冲成块状输出,体验直接崩掉。

server {
    listen 80;
    server_name writer.example.com;

    root /var/www/ai-writer/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /sse/ {
        proxy_pass http://127.0.0.1:8080/sse/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        add_header X-Accel-Buffering no;
        gzip off;
        chunked_transfer_encoding on;
    }
}

两个关键点:/sse/ 的 proxy_pass 结尾带斜杠,路径前缀会被剥掉,后端收到 /sse/... 去掉 /sse 后的真实路径;proxy_buffering off 只作用在 SSE 的 location 里,普通 API 保持默认缓冲,性能不受影响。

4.1 前端 EventSource 与部署形态的配合

前端用 EventSource 订阅生成流,部署时有一个容易忽略的点:EventSource 的地址必须走 /sse/ 这个路径,不能直连后端 8080,否则 Nginx 的负载、SSL、日志全被绕过。前端只开一个连接,后端按多阶段生成流程往同一条流里推 event: stage 事件,前端按阶段渲染状态。上线后最常出现的问题是浏览器对 SSE 长连接的心跳——Nginx 的 proxy_read_timeout 3600s 已经兜底,但后端最好每 15 秒推一条 event: heartbeat,防止中间任何一层空闲超时把流掐断。

const es = new EventSource('/sse/generate?topic=' + encodeURIComponent(topic));
es.addEventListener('stage', (e) => renderStage(JSON.parse(e.data)));
es.onerror = () => { /* 断线自动重连,EventSource 默认行为 */ };

五、部署踩坑记录

这几条坑在测试环境一次都没踩到,全部是在真机部署时才暴露的。

  1. 连接串里漏了时区参数:MySQL 报 The server time zone value 'CST' is unrecognized,application-prod.yml 加 serverTimezone=Asia/Shanghai 解决;
  2. jar 权限不足:deploy 用户启动时读取不到 API Key 环境变量,因为 .env 文件权限是 644 且属主是 root,改成 600 并 chown;
  3. SSE 被 CDN 缓存:如果前面还套了 CDN,必须在源站响应里带 Cache-Control: no-cache,否则中间层把流式响应当普通响应缓存,用户拿到的是截断内容;
  4. 多阶段生成接口超时:网关层默认 connect_timeout 是 60 秒,而”初稿生成”阶段在高峰期可能跑 90 秒,Nginx 的 proxy_connect_timeout 300s 与后端 WebClient 的超时参数要统一。

六、上线验证清单

按下面的顺序跑一遍,任何一步异常都停:

  1. nginx -t && nginx -s reload,确认配置语法通过;
  2. curl -s http://127.0.0.1:8080/actuator/health,后端健康检查返回 UP;
  3. 浏览器打开首页,确认 SPA 路由刷新不 404;
  4. 发起一次真实生成,curl -N http://127.0.0.1/sse/generate?topic=xxx,确认内容按字符流式吐出;
  5. 停掉后端进程,确认 systemd 5 秒内自动拉起,SSE 连接重连成功。

验证通过后,把旧 jar 备份到 /opt/ai-writer/backup/app-$(date +%s).jar。一旦新版本出问题,替换回去重启即可,这一步让回滚成本压到两分钟内。

七、同一套骨架复用给企业级 AI 网关

这套部署流程后来直接复用到「企业级 AI 网关」项目上。网关用 Spring Boot + Spring AI 接入多模型,对外以 /v1/chat/completions 形式提供 SSE 对话流。部署时只多改了两处:网关的 location 换成 /v1/,同样开 proxy_buffering off;多模型的路由 key 从环境变量注入,每接一个模型加一组变量,不碰代码。前面验证清单里的第 4 步在网关侧验证的就是对话流按 token 逐字吐出。两套系统共用 systemd 服务模板和回滚脚本,新项目上线只需要复制目录、改服务名。

八、上线后的日常运维

进程层面交给 systemd,日志用 journalctl -u ai-writer -f 盯;应用日志按天滚动,保留 7 天,多阶段生成的每个阶段耗时打点,出现生成超时能在日志里定位到具体是哪一阶段调模型卡住。Redis 的过期键数量、MySQL 慢查询两张表每周看一次,这两项是文章创作这类写多读少场景最容易出问题的位置。

常见问题(FAQ)

Q1:刷新页面 404 是什么原因?

前端 location 少了 try_files $uri $uri/ /index.html,Vue Router 的 history 路由找不到物理文件。

Q2:SSE 内容一卡一卡的是为什么?

Nginx 默认缓冲了流式响应。给 SSE 的 location 加 proxy_buffering off 和 X-Accel-Buffering no 即可。

Q3:后端一直重启是哪里没配好?

先看 systemd 日志 journalctl -u ai-writer -f,多半是数据库连接串或环境变量没注入导致启动失败。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部