Files
FruitServer/README.md
T
jianghaifeng 5609b42dcd docs: 对齐契约 §3 与 README 文档索引的进度口径
按实测修正 4 处状态描述,消除"已完成/未完成"口径不一致:

- api-phase1.md §3「契约与实现状态对照」:
  04 多码率为「转码 worker(C1)已产出」;06 分享埋点标「待 C3」;
  09 由"下一批"改为「Google Play 票据校验闭环已实现,Apple IAP 只留端口」;
  10 由"压测与埋点 下一批"改为「C1 已实现,C2/C3/压测 未做」;
  并补最后更新时间与「未完成清单见 README」的指向
- README.md 文档索引:api-phase1 的描述由"标注已实现 / 下一批"
  改为"含「契约与实现状态对照」"

为什么改:契约里的状态表过期会直接误导排期与联调判断,属最容易踩的坑。
2026-10-16 15:32:05 +08:00

29 KiB
Raw Permalink Blame History

FruitShorts 短剧服务(后端)

短剧服务的服务端,Spring Boot + PostgreSQL,对外两个入口: 管理后台 /admin/**(8080)与客户端 /app/**(8081)。

文档 内容
docs/prd-phase1.md 第一阶段 PRD:范围、验收口径、里程碑
docs/backend-design-phase1.md 后端设计:1 万并发观看的容量推导、成本、部署
docs/api-phase1.md 接口契约:后台管理 + 客户端;文末含「契约与实现状态对照」(逐模块标注已实现 / 未完成)
docs/architecture-design.md 早期架构设计文档
项 版本
运行时 JDK 21(LTS),编译目标 Java 17
框架 Spring Boot 3.2.5、Spring Security 6、MyBatis-Plus 3.5.7
数据库 PostgreSQL 18
构建 Maven 3.10 多模块

目录结构

FruitServer/
├── fruitshorts-common/          统一响应、分页、错误码、业务异常
├── fruitshorts-framework/       JWT 鉴权(管理端/客户端双密钥)、权限校验、全局异常、分区维护、MyBatis-Plus、CORS
├── fruitshorts-module-system/   管理员、角色、权限、菜单、角色分类、配置、操作日志
├── fruitshorts-module-content/  短剧、剧集、题材、轮播、审核发布
├── fruitshorts-module-media/    分片上传、媒资、播放地址签名、转码任务与多码率产物
├── fruitshorts-module-user/     C 端账号:验证码、注册登录、会话、资料、注销、风控
├── fruitshorts-module-playback/ 播放票据、观看进度与流水、收藏、下载授权
├── fruitshorts-admin-api/       启动模块与 /admin/** 控制器(8080)
├── fruitshorts-app-api/         启动模块与 /app/** 控制器(8081)
├── sql/                       建表、初始化数据、分区维护(见下)
└── docs/                      PRD、后端设计、接口清单

开发环境

本机环境由 Homebrew 统一管理(版本以本机实际安装为准):

组件 版本 说明
Homebrew 7.0.9 远端指向清华镜像,见文末常见问题
JDK OpenJDK 21.0.12.1 brew install openjdk@21,JAVA_HOME 已写入 ~/.zshrc
Maven 3.10.0 brew install maven,命令为 /opt/homebrew/bin/mvn
PostgreSQL 18.6 brew install postgresql@18,brew services start postgresql@18 启动
ffmpeg 9.0.2(Homebrew 当前稳定版) brew install ffmpeg;只有转码 worker 需要,两个在线入口(8080/8081)不依赖它

postgresql@18 是 keg-only,命令行工具在 /opt/homebrew/opt/postgresql@18/bin, 用之前把它加到 PATH:export PATH="/opt/homebrew/opt/postgresql@18/bin:$PATH"。

java -version   # openjdk 21.0.12.1
mvn -v          # Maven 3.10.0 / Java 21.0.12.1
ffmpeg -version # 转码 worker 用;没装也能跑在线服务,只是转码任务会一直排队

ffmpeg 只被 fruitshorts-transcode-worker 使用:backend-design-phase1.md §8.5 要求转码与在线服务隔离, 所以 app-api / admin-api 里 fruitshorts.media.transcode.worker-enabled 默认 false,转码要单独起 worker 进程 (java -jar fruitshorts-transcode-worker/target/fruitshorts-transcode-worker-1.0.0-SNAPSHOT.jar)。

说明:

  • 工程编译目标为 Java 17(maven.compiler.release=17),在 JDK 21 上构建与运行。
  • 父 POM 显式声明了注解处理器(<proc>full</proc> + annotationProcessorPaths): JDK 23 起不再默认扫描 classpath 上的处理器,缺这段配置 Lombok 会失效。
  • 旧的 JDK 18 仍留在 /Library/Java/JavaVirtualMachines/jdk-18.0.2.jdk,删除需要 sudo, 不影响使用(java_home 与 JAVA_HOME 都已指向 21)。

快速开始

服务有两个入口,可以只启动其中一个:

服务 模块 端口 面向
管理端 fruitshorts-admin-api 8080 Vue 后台
客户端 fruitshorts-app-api 8081 Flutter 客户端

1. 初始化数据库

export PATH="/opt/homebrew/opt/postgresql@18/bin:$PATH"
cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema.sql
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data.sql
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema-phase1.sql
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/partition-maintenance.sql
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data-phase1.sql
脚本 作用
schema.sql / data.sql 系统域与内容域的基础表与初始化数据
schema-phase1.sql 第一阶段增量表:用户、播放记录、互动、交易、创作者、转码、运营
partition-maintenance.sql 分区函数 + 把高写入表转成分区 + 预建未来 3 个月分区
data-phase1.sql 新增权限点、超管授权、占位商品

四个脚本都幂等可重复执行,顺序不要颠倒:分区转换要求目标表是空的。

数据库名 fruitshorts,角色 fruitshorts(密码 fruitshorts)。 首次搭建环境时先建角色与库:

psql -d postgres -c "CREATE ROLE fruitshorts LOGIN PASSWORD 'fruitshorts';"
psql -d postgres -c "CREATE DATABASE fruitshorts OWNER fruitshorts ENCODING 'UTF8';"

连接信息在 fruitshorts-admin-api/src/main/resources/application.yml,可用环境变量 DB_USER / DB_PASSWORD / JWT_ADMIN_SECRET 覆盖。

关于 PostgreSQL 的两点说明

  • 自增列用 GENERATED BY DEFAULT AS IDENTITY;sql/data.sql 里用了显式 ID, 脚本末尾会把序列 setval 到当前最大值,新增数据不会主键冲突。
  • PostgreSQL 没有 ON UPDATE CURRENT_TIMESTAMP,updated_at 由 set_updated_at() 触发器统一维护(见 sql/schema.sql 末尾),实体侧仍不参与更新该字段。
  • 轮播图的展示位置列名叫 slot(position 在 PostgreSQL 里是关键字), 实体用 @TableField("slot") 映射,对外接口字段仍是 position,前端无需改动。

大表分区与保留策略

为什么要一开始就分区:watch_history(观看流水)、playback_ticket(播放票据)、 order_event(订单流水)都是「每次行为写一行」的表。按 1 万并发观看估算,观看流水约 300 万行/天、播放票据约 360 万行/天,一年就是十亿级。这类表后补分区的代价很高 (要在维护窗口里整表重建),所以第一阶段就按 watched_at / created_at 做按月声明式分区。

光建表没用:声明式分区不会自动长分区,只建 default 分区的话数据会全堆进 default, 分区等于白做。所以有两层保障:

  1. sql/partition-maintenance.sql 里的 ensure_monthly_partitions(table, from, months) 负责按月建分区,执行脚本时会建好当前月 + 未来 3 个月;
  2. fruitshorts-framework 的 PartitionMaintainer 在启动时和每天 03:17 各调一次, 跨月时即使服务停机一周也不会漏建。

验证分区是否真的生效(应落在 watch_history_2026xx 而不是 _default):

psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -c \
  "select tableoid::regclass as 分区, count(*) from watch_history group by 1;"

保留策略默认只建不删。删数据不可逆,第一阶段先解决"写不爆",等保留期与合规确认后 再把 fruitshorts.partition.retention-enabled 打开;各表保留月数写在 PartitionMaintainer 的 TABLES 常量里(观看流水 24 个月、播放票据 6 个月、订单流水 36 个月)。手工清理:

SELECT drop_partitions_before('watch_history', CURRENT_DATE - INTERVAL '24 months');

分区表有两条 PostgreSQL 限制要注意:唯一索引必须包含分区键,所以 playback_ticket 的唯一索引是 (ticket_no, created_at)、主键是 (id, created_at)。 coin_ledger 故意不分区 —— 它靠 idempotent_key 全局唯一保证不重复发币, 这个唯一性比分区更重要。

2. 构建

cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
mvn -DskipTests package

3. 启动

cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
java -jar fruitshorts-admin-api/target/fruitshorts-admin-api-1.0.0-SNAPSHOT.jar
java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar

启动后:服务地址 http://localhost:8080,接口文档 http://localhost:8080/doc.html。

关键配置

配置项 默认值 说明
fruitshorts.jwt.admin-secret 开发默认值 管理端签名密钥,生产必须用 JWT_ADMIN_SECRET 注入
fruitshorts.jwt.app-secret 开发默认值 客户端签名密钥,与管理端物理隔离,两边 token 不能互换
fruitshorts.jwt.app-access-ttl-minutes 120 客户端 access token 有效期
fruitshorts.jwt.app-refresh-ttl-days 30 refresh token 有效期,每次刷新轮换
fruitshorts.media.signature-secret 开发默认值 播放地址签名密钥,生产必须注入
fruitshorts.media.signature-ttl-minutes 15 播放地址有效期
fruitshorts.media.signature-required false 生产必须打开;打开后 /files/episode/** 无有效签名返回 403
fruitshorts.otp.sender log 验证码通道,开发打日志;接入短信/邮件时实现 OtpSender 并改这里
fruitshorts.partition.enabled true 是否自动维护分区
fruitshorts.partition.months-ahead 3 预建未来几个月分区
fruitshorts.partition.retention-enabled false 是否自动清理过期分区(默认只建不删)

开发阶段签名校验默认关闭,是为了不打断正在联调的客户端。上线前必须打开, 否则片源地址可以被任意复用与盗链。

第一阶段实现进度

最后更新:2026-10-16(后端 develop 分支 4418a84)。未完成清单见文末「尚未完成」。

模块 状态 说明
01 基础与交付准备 已实现 多模块骨架、接口契约、双密钥鉴权、分区维护;雪花主键(S0)、Swagger 在线文档 /doc.html(S1)
02 用户账号与登录 已实现 手机号/邮箱注册登录、验证码反刷、令牌轮换、资料、注销、封禁即时生效
03 内容发现与详情 已实现 首页/发现/分类/详情;内容分级、可播区域、版权窗口、标签字段
04 播放器与播放控制 已实现 播放票据 + 签名地址 + 进度;多码率 HLS 由转码 worker 产出(见 10)
05 记录、收藏与下载 已实现 进度、观看流水、收藏、离线下载授权与任务
06 评论、点赞与分享 已实现 评论(游标翻页/审核/删除)、点赞、举报、敏感词;分享埋点待 C3
07 创作者中心 已实现 在后台管理(Web 端 ReelShortWeb):工作台、内容编辑、分片上传与断点续传、提交审核与上下架、分集收费与试看、数据看板;不含提现/结算。App 客户端不提供创作者中心(口径 2026-10-16)
08 平台运营后台 已实现 用户管理、内容治理、订单与权益、创作者与内容审核、数据看板 + 四个后台页面
09 分集收费与支付 部分 Google Play 票据校验闭环已实现(校验→建单→发权益、幂等与防重放、恢复购买、退款拒付回收、对账);Apple IAP 只留端口未实现(印尼 iOS 占比低,文档口径为"预留")
10 质量、数据与上线 部分 转码 worker(C1)已实现;Redis 缓存与限流(C2)、埋点(C3)、压测未做

已完成批次(按提交顺序)

批次 内容 提交
S0 主键改雪花(58 表 / 52 实体 / 存量库幂等迁移脚本) 37c9a14 84aa0e1
S1 接口文档在线化(39 个 Controller / 172 个接口中文注解 + /doc.html) c6fcfcc
契约补遗 补全接口契约遗漏(系统管理 / 内容 / watch-history / creators 账号 / media 详情) efe8af6
M2 后端契约测试补齐(0 → 37 条) fafadfc
B1-1 Google Play 票据校验闭环(校验→建单→发权益) db897d5
B1-2 恢复购买 / 退款拒付回收 / 对账真实比对 + Apple 端口预留 1dc97b4
C1 转码 worker(独立进程消费 transcode_task,四档 HLS + 封面) 9f76302
D1 部署就绪(配置外置 + prod fail-fast + 自包含 Compose + 冒烟/回滚) b567365
D1-fix 冒烟脚本与 prod Swagger 开关解耦 cbb9961
D2 部署文档按 specs 约束补全(新增 DEPLOY-CHECKLIST.md) 7dd4b15
部署修复 PG18 数据卷挂载路径 + worker 的 prod 密钥/CORS 映射(PR #1) 8b46e53 4c4f643
E1 补 5 个 C 端接口(/app/creator/apply、/app/config、/app/notices) 14b5e41
契约修正 移除 /app/creator/works、/app/creator/stats 的 C 端定义 bb21920
E1-rollback 撤除上述两条接口对应代码(创作者中心归 Web 端) 4418a84

门禁现状:mvn test 91 条全过(app-api 49 / commerce 27 / worker 12 / admin-api 3)。

客户端仓库 ReelShort(Flutter)另有已完成批次:网络层换 dio(1853e18)、客户端改名 nontonki → FruitShorts + 印尼语 id-ID 文案(61454e3)。

默认账号

密码均为 admin123。

账号 角色 权限范围
admin 超级管理员 全部 14 项权限
content.editor01 内容运营 短剧、剧集的增删改与封面上传
content.reviewer01 审核专员 查看内容、审核发布、数据报表
data.analyst01 数据分析师 查看短剧列表与数据报表

接口一览

认证 /admin/auth

方法 路径 说明
POST /login 登录,返回 token 与权限码
GET /menus 当前用户菜单路由树(前端动态路由)
GET /profile 当前登录人信息
POST /logout 登出(JWT 无状态,前端清除 token 即可)
POST /password 修改自己的密码

后台管理

模块 接口
管理员 GET /admin/admin-users/page、POST /admin/admin-users、PUT|DELETE /admin/admin-users/{id}、PUT /{id}/status、PUT /{id}/password
角色 GET /admin/roles/page、GET /admin/roles/list、POST /admin/roles、PUT|DELETE /admin/roles/{id}、GET|PUT /admin/roles/{id}/permissions
角色分类 GET|POST /admin/role-categories、PUT|DELETE /admin/role-categories/{id}
权限点 GET /admin/permissions(按分组返回,供权限配置页使用)
菜单 GET /admin/menus/tree、POST /admin/menus、PUT|DELETE /admin/menus/{id}
系统配置 GET|PUT /admin/configs
操作日志 GET /admin/operation-logs/page(按操作人、模块、动作、结果、时间范围筛选)、GET /admin/operation-logs/{id}
字典 GET /admin/dicts/drama-category、GET /admin/dicts/content-status

内容管理

模块 接口
短剧 GET /admin/dramas/page、GET /admin/dramas/{id}、POST /admin/dramas、PUT|DELETE /admin/dramas/{id}
审核发布 POST /admin/dramas/{id}/submit、/approve、/reject、/publish、/offline
审核记录 GET /admin/dramas/{id}/review-records
剧集 GET /admin/episodes?dramaId=、POST /admin/episodes、PUT|DELETE /admin/episodes/{id}、PUT /admin/episodes/sort
轮播图 GET /admin/banners/page、POST /admin/banners、PUT|DELETE /admin/banners/{id}、PUT /admin/banners/{id}/status、PUT /admin/banners/sort
短剧类型 GET|POST /admin/drama-categories、PUT|DELETE /admin/drama-categories/{id}(权限沿用 drama:*)

媒资 /admin/media

方法 路径 说明
POST /upload/init 建立分片会话,返回 uploadId、chunkSize、totalChunks
POST /upload/chunk 上传单个分片(multipart:uploadId、index、file)
POST /upload/complete 合并分片,返回媒资(含可访问的 url)
GET /{id} 查询媒资

媒资存储(本地)

当前没有对象存储,文件直接落在本机磁盘:

项 默认值 说明
存储目录 ./data/uploads 配置项 fruitshorts.media.root-dir,按 业务类型/年月/uuid.ext 分目录
访问前缀 /files/** 配置项 fruitshorts.media.public-prefix,映射为静态资源
分片大小 5MB fruitshorts.media.chunk-size,单文件上限 2GB
请求上限 20MB spring.servlet.multipart,只需容纳一个分片

要点:

  • /files/** 在 SecurityConfig 里是公开路径,因为 <img> / <video> 无法携带请求头。
  • 上传权限:封面用 cover:upload,视频用 episode:upload。
  • data/ 是运行时产物,不需要提交到版本库。
  • 换成 OSS/S3 时只需替换 MediaStorageService(分片落盘、合并、生成访问地址三处), MediaService 与前端接口都不用动。

统一约定

  • 响应体 { code, message, data },成功 code=0。
  • 分页请求 pageIndex(从 1 开始)、pageSize,响应 { total, list }。
  • 鉴权头 Authorization: Bearer <token>;未登录 code=401,无权限 code=403。
  • 错误码见架构文档附录 A;业务错误如 100000 账号密码错误、200003 状态不允许该操作。

客户端接口 /app/**

面向 Flutter 客户端,只暴露已上线内容,不含审核状态、操作日志等管理端字段。 完整接口清单(含请求/响应示例与错误码)见 docs/api-phase1.md。

方法 路径 说明
POST /app/auth/otp 发送验证码(REGISTER/LOGIN/RESET_PWD/BIND)
POST /app/auth/register 手机号/邮箱注册,返回令牌
POST /app/auth/login 验证码或密码登录
POST /app/auth/refresh 刷新令牌(refresh token 每次轮换)
POST /app/auth/logout 登出,allDevices=true 吊销全部会话
GET/PUT /app/me 我的资料
GET /app/me/devices 登录设备
GET /app/me/export 数据导出(隐私合规)
POST /app/me/deactivate /cancel 申请/撤销注销(15 天冷静期)
GET /app/home/feed 首页聚合:轮播(HOME_TOP)+ 分区(热门 / 新剧 / 各分类)
GET /app/discover/feed 发现页聚合:轮播(DISCOVER)+ 分类分区
GET /app/categories 分类列表
GET /app/dramas/{id} 详情,含剧集列表,一次请求即可渲染详情页
GET /app/episodes/{id}/play 播放票据:服务端判定权益后签发短时效签名地址
POST /app/watch/progress 上报观看进度(建议每 10s 一次)
GET /app/watch/progress?dramaId= 某剧续看位置
GET /app/watch/recent 最近观看
DELETE /app/watch/progress[/{dramaId}] 删除 / 清空记录
POST /app/favorites/toggle 收藏切换
GET /app/favorites 收藏列表(卡片结构与首页一致)

约定:

  • 只返回 status = published 的短剧与剧集,未上线按不存在处理。
  • 图片与视频返回相对路径(/files/...),客户端拼上服务地址即可; 播放地址会带 ?e=&s= 签名参数,客户端原样使用、不要自己拼接。
  • 播放票据四种结果:FREE、ENTITLED(已购权益)、COIN_REQUIRED(返回 unlockCoin)、 NO_MEDIA(已上线但还没传视频或未转码完成)。
  • 鉴权边界:只有浏览类接口(/app/auth/**、/app/home|discover|categories|dramas、 /app/episodes/*/play)允许匿名,其余 /app/** 一律需要客户端 token; 管理端 token 打到客户端接口会被拒(两边密钥不同)。
  • 需要登录的接口建议带 X-Device-Id,用于下载授权、并发播放限制与风控。

性能(本机实测,预热后 30 次采样,目标 50ms):

接口 平均 P95 最大 响应体
/app/home/feed 13.6ms 20.3ms 23.4ms 3.9KB
/app/discover/feed 7.3ms 8.1ms 8.8ms 1.7KB
/app/categories 2.0ms 2.1ms 3.4ms 0.2KB
/app/dramas/{id} 2.8ms 3.1ms 3.5ms 0.7KB
/app/episodes/{id}/play 2.2ms 2.6ms 3.3ms 0.2KB

SlowRequestLogger 会对超过 50ms 的 /app/** 请求打 WARN 日志, 内容量增长或索引失效时能第一时间发现;首页聚合是查询最多的一档(分类 + 热门 + 新剧 + 各分类列表), 如果将来分类变多,优先给它加缓存。

权限模型

  • 权限码随 JWT 下发(如 drama:create、banner:list),控制器用 @PreAuthorize("@ss.hasPermission('drama:create')") 校验。
  • 菜单树由权限码派生:角色拥有哪些权限,就可见哪些菜单。
  • 内容状态机:draft → reviewing → approved → published → offline。 草稿不能直接发布,已上线的短剧需先下线才能删除;每次流转写入审核记录与操作日志。
  • 鉴权实时读库(PermissionChecker 通过 PermissionProvider 查当前用户的权限), 后台改了角色权限或菜单,前端刷新一下即可生效,不用重新登录。 前端在每次进入应用时会重新拉取 GET /admin/auth/menus 与 GET /admin/auth/profile。

操作日志

后台写操作由切面自动记录,不需要在每个接口里手写,在控制器方法上加注解即可:

@OperationLog(module = "内容管理", action = "新增短剧类型")
public Result<Long> create(@Valid @RequestBody DramaCategorySaveReqVO req) { ... }

每次调用记录:操作人、操作时间、模块、动作、请求方式与地址、请求参数、请求体、 响应体、成功/失败、错误信息、耗时、IP。

约定:

  • 只记写操作(POST/PUT/DELETE),GET 查询不入库,避免刷屏。
  • 请求体与响应体超过 4000 字符会截断;密码、密钥、token 等字段自动替换成 ***。
  • 业务异常记 FAIL 并带错误信息;@PreAuthorize 拦下的越权访问由 AccessDeniedRecorder 单独记一条(模块=安全,动作=越权访问被拒绝)。
  • 日志写入失败不影响业务(切面内兜底 try/catch)。
  • 查看入口:后台管理 → 操作日志,权限码 log:list,默认只给超级管理员。

前端对接

管理后台 ../ReelShortWeb 已完成全部对接,本地演示数据已删除:

页面 使用接口
登录 / 左侧菜单 POST /admin/auth/login、GET /admin/auth/menus
工作台 GET /admin/dramas/page(统计与最近更新)
短剧管理 /admin/dramas/**、/admin/dicts/drama-category、审核发布动作
剧集管理 /admin/episodes/**、PUT /admin/episodes/sort
角色权限配置 /admin/roles/**、GET /admin/permissions、GET /admin/role-categories
管理员用户列表 /admin/admin-users/**
菜单管理 GET /admin/menus/tree + 增删改
角色分类 /admin/role-categories/**
系统设置 GET|PUT /admin/configs

前端侧改动:

  1. .env.base 关闭 mock(VITE_USE_MOCK=false);
  2. vite.config.mts 增加 /admin 代理到 http://127.0.0.1:8080;
  3. src/api/admin/*.ts 封装全部管理端接口;
  4. 请求头自动携带 Authorization: Bearer <token>;
  5. src/stores/rbac.js 的 can(code) 改用登录返回的权限码, 原 src/data/、src/permissions/ 下的演示数据已删除;
  6. 表格状态列改用接口返回的 status / statusText, 大数值用 src/utils/format.js 的 formatCount 格式化。

尚未完成

最后更新:2026-10-16。完成后请同步本表与 docs/api-phase1.md。

一、后端研发(剩余)

# 项 内容 依据
1 C2 Redis 缓存与限流 首页/发现/分类等热点接口缓存(Cache-Aside)+ 验证码/注册/登录限流;生产 Redis 规格 4C16G 主从 2~3 哨兵 任务清单 C2、17-风险评估 R-10、10-安全方案 §三
2 C3 埋点 播放/支付/转化事件 + 数据字典对齐(含 06 的分享埋点) 任务清单 C3、api-phase1 §3
3 Apple IAP 票据校验 目前只留端口与枚举,未做真实校验(印尼 iOS 占比低,文档口径为"预留") api-phase1 §1.8
4 压测 目标 P95 < 50ms(预热后);容量口径文档里有两套("1 万并发"与"2 万并发 + 3000 QPS"),需定稿 09-测试计划 §五、13/14-容量
5 对账数据源 结算金额/手续费/税只能从 Play Console 财务报告取(需报表权限);当前 storeAmountSum 恒为 null + notes 说明 B1-2 待确认
6 退款口径完善 部分退款按比例回收、退款实时性(当前由每日对账 + 手动驱动,未接 RTDN 推送) B1-2 待确认

二、上线前必补(P0 / Go-No-Go:非研发,但阻塞上线)

# 项 当前状态 目标
1 生产密钥集中托管 + 轮换 仅 .env(权限 600),无托管、无轮换流程 密钥集中托管、最小权限、可轮换(10-安全方案 §五)
2 安全专项测试 未做 越权/注入/敏感数据专项,高危上线前清零(12-上线检查清单 §三)
3 备份与恢复演练 仅手动 pg_dump,且与数据库同机 RPO ≤ 5min / RTO ≤ 30min;全量 + WAL 归档 + 加密 + 异地 + 季度演练(08-备份恢复)
4 监控告警接入 未部署,仅日志 + SlowRequestLogger 兜底 Prometheus / Grafana / Loki + P0/P1 告警规则(07-监控告警;需先加 actuator 指标端点)
5 HTTPS + 域名 + 反代 无 管理端 / 客户端 API / 媒资分别域名,HTTPS 强制(04-部署架构 §四、10-安全方案 §二)
6 媒资迁 OSS/S3 + CDN 本地磁盘 ./data/uploads 生产必须换 OSS/S3 + CDN(08-备份恢复 §六、12-上线检查清单 §四)

三、基础设施

  • CI/CD 流水线:当前手动跑门禁,未接流水线(06-CI-CD方案)
  • 多实例 / 高可用:当前单点部署;文档要求应用 8C16G × 3~4、转码 worker 8C16G × 2~3,生产用 K8s(14-生产资源规划)
  • 短信 / 邮件真实通道:开发走 log,生产待接通道(05-中间件清单)
  • 分区保留策略:partition.retention-enabled=false,待合规确认后打开(03-数据架构)
  • watch_history 异步落库:Kafka 异步通道为 Phase 2 预留(13-容量与扩展规划 §四)
  • 备份存储位置与成本待定(08-备份恢复 §六)

四、不在后端范围(另有分工)

  • 客户端 Flutter(ReelShort):A2~A6 与后端联调、B2 Google Play Billing 购买流程、秒开基线(需低端安卓设备);网络层 dio、改名与印尼语文案已完成
  • Web 后台(ReelShortWeb):由另一位同事侧负责

五、文档待同步(供文档维护人)

docs/api-phase1.md 与 specs docs/ 下仍有 20+ 处过期陈述(例:api-phase1 §3 仍把 09 支付标"下一批";specs 17-风险评估 的 R-01/R-03/R-04/R-09 仍标"未开始")。 遇到冲突以本表与本仓库 api-phase1.md 正文为准。

已作废的方案:曾提议新建 fruitshorts-module-analytics 收敛"两份创作者看板"。 因 2026-10-16 口径变更(创作者中心由 Web 端承载)撤除了 App 侧 /app/creator/works、/app/creator/stats, 两份看板问题已从根上消失,该方案不再需要。

常见问题

编译报「找不到符号 getXxx / 无法推断类型参数」 Lombok 注解处理器没跑起来。确认是在 JDK 21 下构建(mvn -v 应显示 Java 21), 父 POM 已声明 annotationProcessorPaths,若仍报错先 mvn clean。

8080 端口被占用

lsof -tiTCP:8080 -sTCP:LISTEN | xargs kill

brew 连不上 GitHub 本机访问 github.com 不通,brew 仓库已改用清华镜像:

git -C /opt/homebrew remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

Bottle 走 USTC 镜像(环境变量 HOMEBREW_BOTTLE_DOMAIN)。 要换回官方源,把 origin 改回 https://github.com/Homebrew/brew 即可。

临时切换 JDK

export JAVA_HOME=$(/usr/libexec/java_home -v 21)

Homebrew 的 Maven 在 JAVA_HOME 未设置时会回退到它自带的 JDK(当前为 27), 父 POM 的注解处理器配置保证这种情况下也能编译通过。