《从零开发校园墙微信小程序》第 8 篇(完结篇)。代码写完只是一半,能稳定上线、出问题能快速回滚才是另一半。这篇讲我现在的部署方式,以及几个真实踩过的坑。

构建

后端用 Gradle 打成一个可执行的 fat jar:

# 项目自带构建脚本,等价于 gradlew clean build --no-daemon --rerun-tasks
python run_build.py

# 或者手动:
./gradlew clean build --no-daemon --rerun-tasks

# 产物:
#   build/libs/pzsl-<版本>.jar          可执行 fat jar
#   build/libs/pzsl-<版本>-plain.jar    不含依赖,部署用不到

两点提示:

  • 构建需要 JDK 17 。机器上没配 JAVA_HOME 时,先 export JAVA_HOME=<你的 JDK 17 路径>;
  • 国内直连 Maven 中央仓可能不通,build.gradle.kts 里已把阿里云镜像 放在前面(见第 2 篇)。

小程序前端是另一条线:npm run build:weapp 得到 dist/,用微信开发者工具上传、提交审核即可,和后端发布互不依赖。

服务器上怎么跑

服务器上有一个启停脚本 start_pzsl.sh,逻辑很简单:杀掉旧进程 →**nohup java -jar**拉起最新的 jar 。
它选 jar 的规则是:按修改时间取最新的**pzsl-*.jar**,并排除 .bak 结尾的文件。这条规则决定了后面两个重要约定:

  1. jar 的文件名要和本地构建产物一致;
  2. 停用的旧 jar 一律加**.bak**后缀 ,否则它可能被脚本当成「最新的」误启动。

一键发布脚本

手动 scp + SSH 容易出错,所以写了个部署脚本,每次发布都按同一流程:

1. 备份现网 jar         →  pzsl-x.y.z.jar.bak-<时间戳>
2. 上传新 jar
3. 执行 start_pzsl.sh   →  重启服务
4. 轮询 /actuator/health,直到返回 UP(或超时)
5. 输出接口自检结果

第 4 步是关键 。重启命令「执行成功」不代表服务可用——Spring Boot 启动要十几秒到几十秒。只有健康检查通过,才算发布成功;超时则明确报失败,提醒你去回滚。

为什么只暴露 health

这和第 2 篇呼应:监控端点只开 health,并设置 show-details: never。部署脚本和负载均衡探活只需要它。env、heapdump、beans 一旦暴露,数据库口令和第三方 token 就一起泄露了。

回滚

回滚的前提是发布前一定做了备份 (脚本第 1 步)。回滚步骤:

# 1. 把当前坏版本加 .bak,避免被误选
mv pzsl-x.y.z.jar pzsl-x.y.z.jar.broken.bak

# 2. 把备份改名回去(去掉 .bak-<时间戳> 后缀,让它成为「最新的 jar」)
mv pzsl-x.y.z.jar.bak-20261111-1200 pzsl-x.y.z.jar
touch pzsl-x.y.z.jar          # 保证修改时间是最新

# 3. 重启
setsid bash start_pzsl.sh < /dev/null

回滚本质上就是「让旧 jar 重新成为按 mtime 最新的那个」。

几个真实踩过的坑

坑 1:用 SSH 远程重启,服务没起来

最典型的错误写法:

ssh server "nohup java -jar app.jar &"

SSH 命令一结束,通道关闭,子进程会被一起杀掉 ,表现为「重启命令明明执行了,服务却没起来」。
正确做法是用 setsid 让进程脱离当前会话:

ssh server "setsid bash /root/start_pzsl.sh < /dev/null"

启动脚本内部自己再 nohup,交给它就行。

坑 2:配置文件不会跟着 jar 走

第 2 篇说过,application-local.yaml(真实的数据库、Redis、MinIO 连接)被**.gitignore**排除 ,也不会打进最终的发布流程里。部署到新机器时,要单独传输这个文件 。
另外,项目有一项配置没写进 local.yaml,需要通过环境变量 注入:微博的 access_token。如果漏了,现象是:服务正常启动、健康检查通过,但发微博、评论、拉取全部返回错误 ——典型的「能启动但功能是坏的」。所以发布脚本最后的接口自检 很重要,不能只看 health。

坑 3:停用的旧 jar 被误启动

由于 start_pzsl.sh 是按修改时间取最新的 jar ,所以只要有一个没带 .bak 后缀的旧 jar 被 touch 过、或者被重新拷贝了一次,它的 mtime 就可能比真正要上线的版本更新,脚本会把它当成「最新的」拉起来——悄悄上线一个旧版本。
所以才有了前面那条约定:不用的 jar 一律**.bak**结尾 。这类「靠文件时间猜版本」的方案很省事,但必须配套严格的命名纪律;如果你的项目会有多人部署,更稳妥的做法是让启动脚本读一个明确指向当前版本的软链接 (如 current.jar -> pzsl-2.1.4.jar),而不是靠 mtime 推断。

坑 4:时区(建议)

这一点不是线上事故,而是我建议你一开始就做对:Java 和数据库连接串里都要指定时区,否则「一小时内限流」「教学周计算」「定时任务触发时间」会错乱:

jdbc:mariadb://<host>:3306/<db>?useSSL=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&serverTimezone=Asia/Shanghai

上线检查清单

这是我根据上面的流程整理的一份建议清单,可以按自己的情况增删:

  • [ ] 本地 build 通过,jar 版本号已更新
  • [ ] 数据库变更脚本(IF NOT EXISTS 的那种)已先于新 jar 执行
  • [ ] 现网 jar 已备份(.bak-<时间戳>)
  • [ ] application-local.yaml 在服务器上存在且正确
  • [ ] 微博 token 等环境变量已配置
  • [ ] 重启用 setsid,不用裸 nohup &
  • [ ] /actuator/health 返回 UP
  • [ ] 登录、投稿、读墙三个主流程手动点一遍
  • [ ] 日志里没有持续刷屏的 ERROR
  • [ ] 旧 jar 都带 .bak 后缀

系列回顾

篇你学到了
00项目全貌:墙建在微博之上,投稿镜像到自己的库
01数据库设计:openid、镜像表、软封禁、幂等 SQL
02WebFlux 与阻塞代码共存:boundedElastic;配置与密钥分层
03微信登录 + Sa-Token;ThreadLocal 在响应式里的坑
04Taro 项目结构;HTTP 401 与业务 401;去重重登
05投稿五道关卡;前端拼长图;检查值 = 使用值
06Trie 敏感词;首字重复绕过 bug
07缩放压缩;MinIO 内外网地址
08发布、健康检查、回滚与踩坑
09~11进阶:微博接入、多令牌限流退避、评论多层降级
12~14进阶:强智课表的登录、队列与解析
15~16管理后台:为什么做、怎么设防
17选型笔记与 GitHub 链接

主线到这里结束。但「培正森林」里还有两块技术含量最高的模块,我放在进阶篇 里单独讲:

  • 微博开放平台接入 (第 9~11 篇):OAuth2.0 access_token 的使用、发帖与 @ 转发的反风控节流、多个令牌按「令牌 × 读写作用域」递增冷却、评论读取的多层降级;
  • 强智课表 (第 12~14 篇):教务登录与验证码识别、Redis 登录队列与失败重试的白名单判定、课表 HTML 解析、周次位掩码、学期与教学周、校历调整。

除此之外还有管理后台 (第 15、16 篇)和一篇选型笔记 (第 17 篇,所有用到的开源项目都附了 GitHub 链接)。还没写的是分享卡片生成 ,有兴趣的话留言告诉我。
谢谢读到这里。

最后修改:2026 年 11 月 12 日
如果觉得我的文章对你有用,请随意赞赏