Files
FruitServer/DEPLOY-CHECKLIST.md
T
jianghaifeng 7dd4b15804 docs(deploy): 按 specs 约束补全部署文档(新增 DEPLOY-CHECKLIST.md)
为什么:用户要求"写好文档,一定要根据 specs 文档约束好"。把 specs 里与部署相关的
硬约束逐条对照现状后发现 9 条里有 6 条未覆盖或只写了一半,需要一次补齐并如实标注工程缺口。

- 新增 DEPLOY-CHECKLIST.md:环境映射(办公室主机 = test)、域名/HTTPS/反代(含 nginx 与证书建议)、
  网络分区、发布与回滚规范(语义化 tag / release→main / 回滚演练)、备份与恢复(specs 目标 vs 现状差距)、
  监控与告警现状、密钥托管与轮换、**上线检查清单对照表(docs/12 逐条 ✅⚠️❌ + 责任方)**、
  风险/待确认(R1–R10)、上线前必补清单(P0–P2)
- DEPLOY.md 增加"本机属于 test 环境"与交叉引用,并单列"上线前必须补的四条"提示
- 对照表里的 ✅ 都带证据(如分区生效先实测 playback_ticket_202610 落分区、_default = 0)
- 只写文档,未改任何实现;mvn test 83 条全过、两入口 package 通过
2026-10-16 13:20:31 +08:00

23 KiB
Raw Permalink Blame History

Author: fruit.com.cn

DEPLOY-CHECKLIST —— 部署合规视角(specs 约束逐条结论 + 上线检查清单对照 + 风险/待确认)

这份文档回答两个问题:

1) docs/04-部署架构、docs/08-备份恢复方案、docs/10-安全方案、docs/11-发布与回滚方案、

docs/12-上线检查清单、docs/07-监控告警方案、GIT_WORKFLOW.md、CODING_STANDARDS.md

这些 specs 里跟部署有关的要求,我们现在满足到哪一步;

2) 上线前必须补什么、谁来补。

操作层面的"怎么部署/怎么回滚命令"在 DEPLOY.md(本文件只做结论与对照,避免两份口径分叉)。

结论用三态:✅ 已满足 | ⚠️ 部分满足(能用但有差距)| ❌ 未满足(上线前必补)

一、环境映射(对应 04-部署架构§一)

04-部署架构§一把环境分成四级:

环境 用途 数据 访问
dev 本地开发 本地 PostgreSQL 内网
test 测试/联调 独立测试库 内网/VPN
pre 预发(生产镜像演练) 接近生产 受限
prod 生产 生产库 公网

当前这台办公室主机 = test 环境,依据三条:

  1. 用途是"给前端联调 + 内部验收",不是对外生产(04-部署架构§一 test = 测试/联调);
  2. 访问方式计划是内网/办公网(admin-api 只绑 127.0.0.1),而不是公网;
  3. 用的是独立的开发库(fruitshorts,含种子数据),不是生产库。

规范要求与我们的对应:

  • docker-compose.yml 用 Docker Compose 单机编排 → 对应 §五「第一阶段(dev/test):Docker Compose 单机编排」,✅。
  • 一台机器上合并 app/api/DB/Redis + worker 独立容器 → 对应 §三「精简方案:test 环境可将 app/api/DB/Redis 合并到 1 台 8C32G,转码 worker 独立 1 台」,✅(worker 是独立容器,但与在线服务同机:资源隔离靠容器限额,真正的分机留到生产)。
  • 差距:pre 环境(生产镜像演练)尚未建立 —— 上线前建议在目标机用同一份 compose 跑一次"预发演练",即用生产镜像 + 接近生产的数据走一遍发布流程。状态:❌(流程未建立,不阻塞 test 联调)。

二、域名 / HTTPS / 反向代理(对应 04-部署架构§四 + 10-安全方案§二)

specs 原文:04-部署架构§四「域名/SSL:管理端、客户端 API、媒资分别域名;HTTPS 强制」; 10-安全方案§二「传输安全:HTTPS 强制;密钥环境变量注入」。

当前状态:❌ 未满足。现在的形态是 IP:端口 + HTTP:

需要的域名 现状 上线前必须做
管理端(后台 Web + /admin/**) 无域名;admin-api 绑 127.0.0.1:8080,靠内网/SSH 隧道访问 申请域名(如 admin.<domain>)+ 证书;nginx 反代 → 127.0.0.1:8080;并保持只允许内网/VPN 来源(见 §三)
客户端 API(/app/**) 无域名;app-api 监听 8081(HTTP) 申请域名(如 api.<domain>)+ 证书;nginx 反代 → 8081,只暴露 /app/**;强制 HTTP→HTTPS 跳转
媒资(/files/**) 无域名;与 app-api 同一端口 申请域名(如 media.<domain>)或直接走 CDN 域名;播放地址已带签名参数,CDN 需按签名做缓存策略(04-部署架构§四)

测试环境(本机)可以先用内网 HTTP 联调(04-部署架构§一 test 是内网/VPN 访问), 但上线(pre/prod)前必须满足上述三条 HTTPS + 独立域名,否则不满足 12-上线检查清单§三「传输安全」。

nginx + 证书的建议做法(上线前照做)

# /etc/nginx/conf.d/fruitshorts.conf(示意;域名与证书路径按实际替换)
server {                      # 客户端 API
    listen 443 ssl http2;
    server_name api.example.com;
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    client_max_body_size 20m;     # 分片上传,与 admin-api 的 multipart 上限一致
    location / { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host;
                 proxy_set_header X-Forwarded-For $remote_addr;
                 proxy_set_header X-Forwarded-Proto $scheme; }
}
server {                      # 管理端:只允许内网/VPN 网段
    listen 443 ssl http2;
    server_name admin.example.com;
    allow 10.0.0.0/8; allow 192.168.0.0/16; deny all;    # 按实际内网段调整
    location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host;
                 proxy_set_header X-Forwarded-For $remote_addr;
                 proxy_set_header X-Forwarded-Proto $scheme; }
}
server {                      # HTTP 强制跳 HTTPS
    listen 80;
    server_name api.example.com admin.example.com;
    return 301 https://$host$request_uri;
}

证书建议用 Let's Encrypt(certbot) 自动续期(certbot --nginx -d api.example.com -d admin.example.com), 或公司自有证书;无论哪种,到期前必须能自动续,否则接口会整体不可用。 反代配置文件需与本仓库一起纳入版本管理(放目标机的 /etc/nginx/conf.d/,另存一份到运维仓库)。

三、网络分区(对应 04-部署架构§四)

specs 原文:「网络分区:管理后台仅内网/VPN 可访问;客户端 API 公网。」

要求 现状 上线前必须做
管理后台仅内网/VPN ✅ 已满足:docker-compose.yml 里 admin-api 端口映射为 127.0.0.1:8080:8080(只绑本机,公网访问不到) 上公网后仍要保证:只经 nginx + allow/deny 内网段暴露(见 §二示例)
客户端 API 公网 ⚠️ 部分满足:app-api 监听 0.0.0.0:8081,同网段可访问;但还没有反代/域名,也没限制只暴露 /app/** 反代只放行 /app/**(/files/** 走媒资域名/CDN);不要把 /v3/api-docs、/healthz 直接暴露到公网(/healthz 可留给内网监控)

注意:/doc.html 与 /v3/api-docs 由 SWAGGER_ENABLED 控制(见 DEPLOY.md)。 联调环境建议 true(前端要看),公网生产建议 false(不暴露接口清单)。

四、发布与回滚规范(对应 11-发布与回滚方案§一/§四 + GIT_WORKFLOW.md§四)

specs 原文:

  • 11-发布与回滚方案§一:「语义化版本 v<major>.<minor>.<patch>;分支 main(生产)← release/* ← develop ← feature/*;每次 release/hotfix 合并 main 必须打 tag,tag 不可移动/删除。」
  • GIT_WORKFLOW.md§四:「major 破坏性变更、minor 新功能、patch 修复;发布后 tag 不可移动、不可删除(错误版本发新 tag 修正,不覆盖旧 tag)。」
  • 11-发布与回滚方案§四:「回滚演练:上线前演练回滚,确保可执行。」「数据库迁移脚本必须可回滚(向前兼容,避免破坏性变更)。」
要求 现状 上线前必须做
语义化 tag v<x.y.z> ⚠️ 部分满足:DEPLOY.md 里只是示例式地提到"每次发版打 tag",没有命名规范 按 §四 规则打:首个上线版本 v1.0.0;之后功能 v1.1.0、修复 v1.0.1;错误版本发新 tag,绝不移动/删除旧 tag
release/hotfix 合并 main ⚠️ 流程未走过:目前工作都在 develop,main 未动 上线时:develop → 建 release/v1.0.0 → 在 test 环境验证 → 合并 main + 打 tag v1.0.0(由用户操作,Codex 不碰 main)
回滚可执行 ✅ 技术路径已写全(DEPLOY.md§七:切回上一个镜像 tag / git 回上一个 release tag + up -d --build + 重跑冒烟) 上线前演练一次(在 test 环境故意回滚到上一版本并跑 tools/smoke.sh),把演练记录留档
数据库迁移可回滚 ⚠️ 部分满足:sql/*.sql 全是幂等增量(ADD COLUMN IF NOT EXISTS、CREATE TABLE IF NOT EXISTS),没有破坏性变更;upsert 语义的种子脚本也在 上线前确认:本次发布不包含 DROP/ALTER TYPE 类破坏性语句(当前没有);如将来有,必须先写"回滚脚本 + 备份"再发
灰度/放量 ❌ 未满足(单机 Compose 无灰度能力) 上线首日按"先内测 → 再小流量(如有网关可切)";正式灰度需要 K8s/网关,属后续阶段(04-部署架构§五:生产上 K8s)

五、备份与恢复(对应 08-备份恢复方案§二/§三)

specs 目标:全量每日 × 保持 30 天;WAL 归档(实时);可选增量;备份加密 + 异地存储; RPO ≤ 5 分钟、RTO ≤ 30 分钟;每季度至少一次恢复演练。

项 specs 要求 现状 差距 / 上线前必须补
全量备份 每日 1 次,保留 30 天 ⚠️ DEPLOY.md§七 给了手动 pg_dump 命令;没有定时任务、没有保留策略 目标机加 cron(或 compose 侧容器)每日 pg_dump,按日期命名保留 30 天;脚本可放 tools/backup.sh(本轮未写,属运维脚本)
WAL 归档 实时,用于 PITR ❌ 未配置(PG 默认 archive_mode=off) postgresql.conf:wal_level=replica、archive_mode=on、archive_command 推到异地;这是达成 RPO≤5min 的唯一途径
增量 可选 ❌ 未做 有了 WAL 归档即可满足 RPO,增量备份可后补
加密 备份加密 ❌ 未做 pg_dump 管道给 age/gpg,或落到加密卷;密钥单独保管(与备份分开)
异地 与生产不同可用区/账号 ❌ 未做(本机 dump 还在同一台机器上) 推到对象存储(另一账号/另一区);"备份和数据库在同一台机器上"等于没备份
恢复演练 每季度 ≥ 1 次,记录归档 ❌ 从未做过 首次上线前做一次"恢复到新库 + 起服务 + 冒烟",记录耗时(验 RTO)与丢失窗口(验 RPO)

结论:备份恢复这一项与 specs 差距最大,是上线前必须补的硬缺口(❌)。 当前只有"能手动 dump",距离「RPO≤5min / RTO≤30min / 加密 / 异地 / 季度演练」还差整套机制。 建议排期:上线前至少做到「每日全量 + 异机保存 + 一次恢复演练」;上线后两周内补 WAL 归档与加密。

六、监控与告警现状(对应 07-监控告警方案§七)

specs 原文:07-监控告警§七「Prometheus/Loki/Grafana 尚未部署,第一阶段先用日志 + SlowRequestLogger 兜底」; §六「告警渠道:飞书/钉钉/邮件 + 值班轮转」;§六「值班表与 Runbook 见 docs/plans/(上线前补)」。

项 现状 上线前必须补
指标(Prometheus) ❌ 未部署;项目也没引 actuator(/actuator/health 一直是未映射,见 DEPLOY.md§五说明) 按 §七 的既定路线部署;要暴露指标需先给两个入口加 spring-boot-starter-actuator + micrometer-registry-prometheus(属代码改动,需立项)
日志聚合(Loki) ❌ 未部署;现在日志在容器 stdout(docker compose logs) 部署 Loki + Promtail 采集容器日志
大盘(Grafana) ❌ 未部署 按 §五 大盘定义建面板
第一阶段兜底 ✅ 已具备:应用结构化日志(转码耗时/失败原因、业务异常、SlowRequestLogger 慢请求告警都在代码里)+ 容器级 healthcheck(/healthz) 至少把"容器 unhealthy"接到人的手机上(可用 docker compose events + 简单脚本/飞书机器人)
告警渠道 + 值班 + Runbook ❌ 未定 定渠道(飞书/钉钉/邮件)、排值班表、把 DEPLOY.md§九「故障速查」扩成 Runbook

结论:监控告警 ❌ 未满足,但按 specs §七 的既定节奏,第一阶段允许先用日志兜底 —— 前提是"告警能到达人"。上线前最低要求:容器 unhealthy / 冒烟失败能被发现(哪怕先是人工每天跑一次 tools/smoke.sh)。

七、密钥托管与轮换(对应 10-安全方案§五)

specs 原文:§五「密钥托管:生产密钥集中托管,最小权限」;12-上线检查清单§三「JWT/签名密钥走环境变量,未硬编码/入库」「生产密钥集中托管」。

要求 现状 上线前必须补
密钥走环境变量、未硬编码/入库 ✅ 已满足:三个可运行件的密钥全部 ${ENV:...} 注入;.env 已 gitignore;.env.example 只有占位;镜像里不含密钥 继续保持;代码审查时禁止把密钥写回 yml
缺失即启动失败 ✅ 已满足:ProdSecretGuard 在 prod profile 下校验三个密钥 + 签名开关 + DB_URL + CORS,缺失/仍是 dev 默认值/不足 32 位 → 退出码 1 —
集中托管 ⚠️ 部分满足:现在是"目标机的 .env 文件"(文件权限 600),没有密钥管理系统 短期:.env 权限 600、只有运维账号可读、不进任何 git 仓库、单独备份(加密);中期:迁到 K8s Secret / 云 KMS / Vault(04-部署架构§五 生产路线也是 K8s Secret)
最小权限 ⚠️ 部分满足:DB 用单一 fruitshorts 账号(读写全库) 拆账号:应用用 fruitshorts_app(DML 权限、无 DDL);备份用只读账号;DDL 只留给运维
轮换 ❌ 未做(也没有流程) 定义轮换周期(建议 90 天或按事件):先加新密钥 → 双密钥并行验证期 → 再下线旧密钥;JWT 轮换会导致存量 token 失效,需选低峰并公告(10-安全方案§一 token 有效期 120min,影响窗口可控)

八、上线检查清单对照表(逐条映射 docs/12-上线检查清单.md)

三态:✅ 已满足 | ⚠️ 部分满足 / 有条件满足 | ❌ 未满足(上线前必补) 「责任方」列区分:Codex(我能做的)/ 用户或运维(需要账号、域名、外部资源)。

一、代码与门禁

检查项 状态 依据 / 缺口 责任方
三端门禁全绿 ⚠️ 后端 ✅:mvn test 83 条全过;前端(Flutter/Web)不在本工程范围,由另一同事的 AI 负责,需其提供门禁结果 用户/前端
接口契约 api-phase1.md 已同步 ✅ 本轮 B1-1/B1-2 的 verify / reconcile 改动都已同步进契约 Codex
作者标识齐全 ✅ 远端已做全量补齐(35be37c),本轮新增文件也都带 // Author: fruit.com.cn Codex
代码规模红线、无关键路径 TODO ⚠️ 未做静态检查工具(后端无 checkstyle/pmd),存量未逐文件核对;无 TODO 扫描证据 Codex(需立项)

二、功能与测试

检查项 状态 依据 / 缺口 责任方
核心用例通过、P0 清零 ⚠️ 后端 83 条(含真实 ffmpeg smoke)全过;前端与端到端用例不在本工程 Codex/用户
支付闭环端到端 ⚠️ 票据校验/幂等/退款回收/对账已实现并有单测;真实 Google Play 未联调(缺 packageName + service account JSON,属付费凭据) 用户(凭据)+ Codex(联调)
播放长稳 + 弱网测试 ❌ 未做(需真机 + 网络模拟) 前端/用户
多端(iOS/Android)回归 ❌ 未做(Android 无可用模拟器,iOS 有真机) 前端/用户

三、安全

检查项 状态 依据 / 缺口 责任方
播放签名 signature-required=true(生产必开) ✅ application-prod.yml 写死 true,ProdSecretGuard 再校验一次,缺了直接拒启 Codex
JWT/签名密钥走环境变量,未硬编码/入库 ✅ 三件全 ${ENV:...};.env gitignore;镜像不含密钥 Codex
越权/注入/敏感数据专项测试 ⚠️ 越权:创作者中心有专项用例(B 批);注入/SQL 注入:全部走 MyBatis-Plus 参数绑定,无字符串拼接 SQL(人工核对),无自动化用例;敏感数据脱敏有实现(AppIdentifier.mask)但无专项测试 Codex(需立项)
生产密钥集中托管 ⚠️ 见 §七:现为 .env 文件权限隔离,未接 KMS/Vault 用户/运维

四、基础设施

检查项 状态 依据 / 缺口 责任方
数据库分区已生效 ✅ watch_history/playback_ticket/order_event 均为声明式月分区,partition-maintenance 启动时+每日补齐。实测证据:select tableoid::regclass, count(*) from playback_ticket group by 1 → playback_ticket_202610 | 1(落月分区,不是 _default);select count(*) from watch_history_default → 0 Codex
Redis/缓存(如接入)配置正确 ⚠️ compose 已备 Redis 容器,代码尚未接入(C2 待做);当前不依赖 Redis Codex(C2)
媒资已迁 OSS/S3 + CDN(生产) ❌ 仍是本地磁盘 ./data/uploads,与在线服务同机;MediaStorageService 已把存储收敛到一处,切换 OSS 只需改它 + 转码产物上传路径 用户(账号)+ Codex
备份恢复已配置并演练 ❌ 见 §五,差距最大 用户/运维 + Codex(脚本)
监控告警已接入、告警可达 ❌ 见 §六 用户/运维

五、商店与合规

检查项 状态 依据 / 缺口 责任方
应用名称/截图/文案/隐私标签一致 ❌ 未办(客户端上架材料) 用户
App Privacy / Data Safety / SDK 清单 ❌ 未办 用户
隐私政策、用户协议已上线 ❌ 未办(后端有 app_user_consent 落库能力,文案与页面需法务/前端) 用户
版权/素材/商标授权台账 ❌ 未办 用户
商店账号/证书/包名有效 ❌ 未办;并且包名仍不一致(Android com.stormg.lumadrama / iOS com.stormg.lumadramv,见 C 端回执) 用户(建议上架前统一)

六、发布与回滚

检查项 状态 依据 / 缺口 责任方
版本号 + tag 冻结(v1.0.0) ❌ 尚未打 tag(规范见 §四) 用户(合并 main 时打)
回滚方案已演练 ❌ 方案已写(DEPLOY.md§七),未演练 用户/运维
值班表 + 告警 Runbook 就绪 ⚠️ DEPLOY.md§九「故障速查」可作为 Runbook 初稿;值班表未定 用户
灰度/放量计划明确 ❌ 单机 Compose 无灰度能力;生产上 K8s 后再做 用户

七、观察窗口(上线后)

检查项 状态 依据 / 缺口 责任方
核心链路监控 ❌ 监控未接;上线首日建议人工每 30 分钟跑一次 tools/smoke.sh + 看 docker compose logs 作为过渡 用户/运维
支付/订单对账数据回看 ⚠️ 对账能力已在(/admin/reconcile/run,storeAmountSum 无数据源时返回 null + notes),真实商店数据要等凭据 Codex/用户
崩溃率/错误率观察 ❌ 未接监控;容器日志可人工查 用户/运维
问题复盘归档 ⚠️ 本工程有 PROGRESS/回执留痕习惯,可沿用 Codex/用户

九、风险 / 待确认(CODING_STANDARDS§7 要求:技术决策必须留痕)

# 风险 / 待确认 影响 建议与责任方
R1 媒资仍在本机磁盘(./data/uploads),未迁 OSS/CDN 单机磁盘满/机器损坏即媒资全丢;播放无法借 CDN 扛量 12-上线检查清单§四把"迁 OSS/CDN"列为上线项,属上线前必补;MediaStorageService 已是唯一替换点,切换工作量可控(Codex 可做,需 OSS 账号)
R2 单点无高可用:一台办公室主机跑全栈 机器/网络故障 = 服务全停;无灰度能力 test 阶段可接受;生产必须按 04-部署架构§三 生产规格(应用 2+、DB 主备)+ §五 上 K8s
R3 备份未演练、无 WAL 归档、备份与库同机 真出事时可能既恢复不了也丢得多(RPO 远大于 5 分钟) 上线前至少"每日全量 + 异机保存 + 一次恢复演练";两周内补 WAL 归档与加密(见 §五)
R4 监控与告警未接 故障靠人发现;上线首日风险最高的窗口无自动告警 短期用"人工冒烟 + 容器 healthcheck";随后按 07-监控告警方案部署(需先给服务加 actuator,属代码改动)
R5 商店合规未办(隐私政策/Data Safety/SDK 清单/版权台账/包名) 无法上架;印尼还涉及 Kominfo PSE 注册(未注册可能被整体封锁,见 docs/08/PRD 合规节) 全部为用户侧工作;其中"包名统一"建议在上架前完成(一旦上架不可改)
R6 真实 Google Play 联调未做(缺 packageName + service account JSON) 支付闭环在真机/商店侧未验证;STORE_VERIFIER=disabled 时对外明确返回"尚未配置",不会产生假订单 用户提供凭据后,Codex 用沙盒测试购买联调(STORE_VERIFIER=google-play)
R7 HTTPS/域名未就绪,当前 IP+HTTP 不满足 04-部署架构§四与 10-安全方案§二;公网明文传输不可接受 上线前按 §二 配域名 + 证书 + nginx(用户/运维)
R8 DB 账号未做最小权限(单账号读写全库) 应用被攻破即拥有 DDL 能力 拆 fruitshorts_app(仅 DML)与备份只读账号(见 §七)
R9 /swagger-ui/** 静态资源在 SWAGGER_ENABLED=false 时仍可访问 不泄露接口信息(spec 与 config 都已 404),但严格合规环境下应彻底关闭 可选加固:spring.web.resources 排除 webjar;需你点头再改(Codex)
R10 转码 worker 与在线服务同机(虽为独立容器) 大批量转码会抢 CPU,影响播放稳定性 04-部署架构§三 的完整方案是 worker 独立 1 台;上线前如有第二台机器就拆开,否则给 worker 容器加 CPU 限额(Codex 可改 compose)

十、上线前必补清单(按优先级)

优先级 事项 依赖 责任方
P0 HTTPS + 独立域名 + nginx 反代(§二) 域名、证书 用户/运维
P0 每日全量备份 + 异机保存 + 一次恢复演练(§五) 目标机 + 异地存储 用户/运维(Codex 可提供脚本)
P0 媒资迁 OSS/CDN(R1) 云账号 用户 + Codex
P1 tag 规范落地 + 回滚演练(§四) — 用户(合并 main 时)/ Codex 配合
P1 监控告警最低可用(§六:容器 unhealthy 能到人) 通知渠道 用户/运维
P1 真实 Google Play 联调(R6) 付费凭据 用户 → Codex
P2 DB 最小权限账号(R8)、密钥集中托管(§七) 运维 用户/运维
P2 K8s 生产化(R2)、灰度能力、WAL 归档与加密 生产资源 后续阶段