《从零开发校园墙微信小程序》第 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 结尾的文件。这条规则决定了后面两个重要约定:
- jar 的文件名要和本地构建产物一致;
- 停用的旧 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 |
| 02 | WebFlux 与阻塞代码共存:boundedElastic;配置与密钥分层 |
| 03 | 微信登录 + Sa-Token;ThreadLocal 在响应式里的坑 |
| 04 | Taro 项目结构;HTTP 401 与业务 401;去重重登 |
| 05 | 投稿五道关卡;前端拼长图;检查值 = 使用值 |
| 06 | Trie 敏感词;首字重复绕过 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 链接)。还没写的是分享卡片生成 ,有兴趣的话留言告诉我。
谢谢读到这里。