chore: init repository skeleton
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,ts,tsx,yaml,yml,js,jsx}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.turbo/
|
||||
coverage/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,6 @@
|
||||
# 图片直播(photo-live)
|
||||
|
||||
图片直播平台:活动现场照片即拍即传、审核后实时分发的直播相册系统。
|
||||
|
||||
- 需求文档:[docs/01-需求说明书.md](docs/01-需求说明书.md)
|
||||
- 实施总纲:[docs/plans/2026-09-17-实施总纲.md](docs/plans/2026-09-17-实施总纲.md)
|
||||
@@ -0,0 +1,335 @@
|
||||
# 图片直播项目需求说明书
|
||||
|
||||
版本:V1.3
|
||||
日期:2026-09-17
|
||||
适用阶段:第一版需求评审与开发
|
||||
关联文档:[项目计划](./02-项目计划.md) · [技术实施方案](./03-技术实施方案.md) · [优化方案](./04-优化方案.md)
|
||||
|
||||
本项目为公司自用的活动图片直播系统,面向广交会、企业活动等场景。工作人员通过 App 创建相册并协作上传,观众通过网页实时看图和互动,公司网站通过 JS 悬浮组件同步展示照片。
|
||||
|
||||
本文用“已确认”表示用户明确提出或接受的要求;“建议基线”表示供第一版实施使用的设计建议;“待确认”表示仍需决定的范围。建议基线不等同于已确认需求。
|
||||
|
||||
V1.1 纳入 R17 至 R26 优化;V1.2 新增 R27 至 R30 国内外部署要求;V1.3 将第一期改为账号密码登录,移除飞书接入,新增 R31 定期用户接口同步。未冻结的同步周期、地域及容量参数仍按建议值标识。
|
||||
|
||||
## 1 产品范围
|
||||
|
||||
### 1.1 已确认范围
|
||||
|
||||
| 编号 | 模块 | 要求 |
|
||||
|---|---|---|
|
||||
| R01 | 工作人员 App | Flutter 开发,同时支持 Android 和 iOS |
|
||||
| R02 | 工作人员身份 | 第一期仅账号密码登录,不接入飞书,不开放自行注册;App 与后台共用内部账号 |
|
||||
| R03 | 相册管理 | App 内创建和配置相册,包括活动时间、地点等信息 |
|
||||
| R04 | 多人协作 | 创建者管理相册并邀请同事,协作者管理自己的照片,管理员管理全部 |
|
||||
| R05 | 照片上传 | 手机拍照或选择照片,多人上传,上传后自动公开,无人工审核和修图 |
|
||||
| R06 | 上传任务 | 切换后台和锁屏后继续上传,提供上传记录列表 |
|
||||
| R07 | 文件存储 | 对接现有阿里云 OSS Bucket,照片默认长期保存 |
|
||||
| R08 | 观众访问 | 扫码或分享链接访问,不要求登录,不设访问密码 |
|
||||
| R09 | 观众展示 | 顶部宣传图、活动信息、照片瀑布流 |
|
||||
| R10 | 大图浏览 | 点击弹出大图,左右滑动切换,支持点赞、分享、下载 |
|
||||
| R11 | 点赞 | 每张照片可连续点赞,多次累计,无需登录,不设取消 |
|
||||
| R12 | 气泡 | 同一活动内所有在线观众能看到其他人的点赞气泡 |
|
||||
| R13 | 多语言 | App、管理后台、观众端均支持语言切换 |
|
||||
| R14 | 网站嵌入 | 引入 JS 后在网站右侧固定悬浮轮播,新照片持续更新,点击打开观众页 |
|
||||
| R15 | 管理后台 | 公司统一管理账号、相册、照片和权限 |
|
||||
| R16 | 相册浏览次数 | 记录每个相册的累计浏览次数,并在观众页活动信息区展示 |
|
||||
| R17 | 连续拍摄 | 拍摄后自动准备并入队,常驻显示目标相册和队列状态 |
|
||||
| R18 | 活动控制 | 允许上传和对外公开独立控制,支持批量隐藏及跨端同步 |
|
||||
| R19 | 相册交接 | 管理员转移负责人,停用账号不删除历史照片 |
|
||||
| R20 | 下载与水印 | 下载和原图下载分开控制,水印版本化,历史重生成明确发起 |
|
||||
| R21 | 上传准备与历史 | 区分准备失败和上传失败,本地待传任务与服务端历史分离 |
|
||||
| R22 | 原图与访问保护 | 已发布原图不可被旧上传授权覆盖,图片链接过期可恢复 |
|
||||
| R23 | 浏览去重 | 同浏览器跨标签页共享匿名标识,窗口参数待定 |
|
||||
| R24 | 气泡体验 | 自身广播不重复播放,后台暂停动画,支持减少或关闭动画 |
|
||||
| R25 | 网站展示位 | JS 绑定展示位,后台更换相册,无需修改官网代码 |
|
||||
| R26 | 用量管理 | 容量流量统计与告警,组件不可见时暂停轮询,不自动清理正式照片 |
|
||||
|
||||
|
||||
术语约定:第一版建议一个活动对应一个直播相册;跨天活动在同一相册内分类。若后续需要一个活动下多个独立相册,再扩展活动实体。本版本中的“同一活动广播”按相册隔离。
|
||||
|
||||
### 1.2 建议基线
|
||||
|
||||
- 首批语言为简体中文和英文,其他语言可扩展;具体首发语种待确认。
|
||||
- 图片处理仅包括解码、方向校正、缩略图、浏览图及可选水印,不包含美化修图。
|
||||
- 相册可配置封面、宣传图、简介、分类、Logo、水印和原图下载开关。
|
||||
- 新照片以提示方式更新瀑布流,避免直接插入导致正在浏览的位置跳动。
|
||||
- 支持“最新”和“热门”列表,热门按累计点赞次数排序。
|
||||
- 分享单张照片直接定位该照片;关闭大图回到原列表位置。
|
||||
- JS 组件支持收起、关闭、尺寸和轮播间隔配置,默认优先轮播最近照片。
|
||||
- 照片删除采用软删除;永久清理规则另行确定,不默认自动删除 OSS 原图。
|
||||
|
||||
### 1.3 第一版不包含
|
||||
|
||||
飞书登录与飞书绑定、相机直传、桌面传图客户端、AI 修图、人工修图工作流、审核流程、人脸找图、评论、留言墙、投票、抽奖、视频直播、付费下载、对外注册和多企业商业化租户系统。
|
||||
|
||||
### 1.4 国内管理与海外访问
|
||||
|
||||
已确认:管理后台与 App 业务请求访问国内服务器,图片直传现有国内 OSS;观众主要在国外,观众页及查询、浏览统计、点赞气泡由海外服务承接,图片使用 OSS 全球 CDN。第一版不建立第二套海外图片存储,不把图片文件经业务服务器跨境复制。
|
||||
|
||||
国内向海外同步公开展示所需元数据,海外向国内回传统计。采用事件触发推送与定时补漏的实施基线,同步周期和延迟指标待验证。海外直接读本地数据,不在每次浏览或点赞时请求国内。
|
||||
|
||||
| 编号 | 模块 | 要求 |
|
||||
|---|---|---|
|
||||
| R27 | 地域分工 | 国内管理和上传,海外观众服务,OSS 全球 CDN 分发图片 |
|
||||
| R28 | 内容同步 | 相册分类语言、照片资源标识、展示位及隐藏删除状态可靠同步海外 |
|
||||
| R29 | 互动回传 | 海外权威记录点赞浏览,国内展示回传统计及更新时间 |
|
||||
| R30 | 海外验收 | 验证新图冷缓存、同步中断恢复、乱序去重和下线传播 |
|
||||
| R31 | 用户同步 | 国内服务定期拉取指定用户接口,更新本地用户资料,支持日志、重试和手动补拉 |
|
||||
|
||||
公开状态增加海外同步中、海外可见、同步失败等管理提示。国内处理完成不代表海外立即可见;海外确认元数据完整与必要浏览资源可访问后展示。相册下线及照片隐藏优先同步;跨境中断期间不能承诺海外即时撤回,需明确告警和海外紧急停用通道。
|
||||
|
||||
## 2 角色与权限
|
||||
|
||||
工作人员资料通过指定接口定期同步到国内系统,登录凭据与项目权限在本系统管理,不把资料同步等同于密码同步。初始管理员通过受控初始化开通;其他手工账号是否保留待确认。相册角色与公司角色分开:同一工作人员可在相册 A 中是创建者,在相册 B 中是协作者。
|
||||
|
||||
| 操作 | 公司管理员 | 相册创建者 | 相册协作者 | 观众 |
|
||||
|---|---|---|---|---|
|
||||
| 激活停用与密码管理 | 全部 | 不允许 | 不允许 | 不允许 |
|
||||
| 创建相册 | 允许 | 工作人员允许 | 工作人员允许 | 不允许 |
|
||||
| 配置相册 | 全部 | 自己创建的相册 | 不允许 | 不允许 |
|
||||
| 邀请移除协作者 | 全部 | 自己创建的相册 | 不允许 | 不允许 |
|
||||
| 上传照片 | 全部 | 自己创建的相册 | 已加入的相册 | 不允许 |
|
||||
| 管理照片 | 全部 | 相册全部照片 | 自己上传的照片 | 不允许 |
|
||||
| 浏览点赞分享 | 允许 | 允许 | 允许 | 无需登录 |
|
||||
| 下载原图 | 管理权限内 | 管理权限内 | 自己照片 | 按相册设置 |
|
||||
|
||||
“管理自己照片”建议包括修改分类、隐藏和软删除,不包含相册配置与他人照片操作。是否允许协作者重新公开自己隐藏的照片,按此建议在原型评审时确认。
|
||||
|
||||
服务端必须逐次校验权限;前端隐藏按钮不能替代权限验证。协作者被移除或账号被停用后,不得继续提交发布操作。已签发短期上传凭证可能尚未到期,服务端应拦截其发布并回收孤立上传任务。
|
||||
|
||||
## 3 业务流程
|
||||
|
||||
### 3.1 准备活动
|
||||
|
||||
1. 国内服务同步用户资料,管理员按确定规则激活账号、设置初始凭据并分配公司角色。
|
||||
2. 工作人员登录 App,创建相册并填写活动信息。
|
||||
3. 创建者邀请已开通账号的同事参加相册,不通过邀请开放注册。
|
||||
4. 生成观众链接、二维码;需要时创建或选择有权限的网站展示位,绑定相册并生成嵌入代码。
|
||||
5. 空相册可访问并展示“照片即将上线”等本地化提示。
|
||||
|
||||
### 3.2 上传与发布
|
||||
|
||||
1. 工作人员选择相册、分类,通过拍摄或系统相册选择图片。
|
||||
2. App 将文件保存到受管理的本地任务目录,并记录上传任务。
|
||||
3. 服务端验证身份与相册权限,签发限定对象范围的临时上传授权。
|
||||
4. App 将文件直接上传 OSS,业务服务器不承担图片中转。
|
||||
5. 服务端核验对象存在、大小和必要的完整性信息,创建处理任务。
|
||||
6. 生成浏览资源后将照片设为公开,通知观众端和嵌入组件。
|
||||
7. 上传和发布结果写回任务列表;失败时显示原因及恢复方式。
|
||||
|
||||
“上传即公开”指无需人工审批,必要图片处理完成后自动公开。上传结束到公开存在处理时间,不能把传输完成直接显示为已公开。
|
||||
|
||||
### 3.3 观众浏览与互动
|
||||
|
||||
1. 观众扫描二维码或打开链接,无需登录。
|
||||
2. 查看宣传图、活动信息和瀑布流,按需切换语言、分类和排序。
|
||||
3. 点击照片打开大图,左右滑动查看当前列表中的相邻照片。
|
||||
4. 连续点赞时立即显示本地动画;确认后更新计数并广播活动气泡。
|
||||
5. 分享相册或照片链接;按设置下载允许的图片版本。
|
||||
6. 页面恢复或网络重连后重新获取计数和新增照片,不要求补播历史气泡。
|
||||
|
||||
### 3.4 网站悬浮轮播
|
||||
|
||||
1. 管理员配置网站展示位并授权,创建者或管理员将有管理权限的相册绑定展示位,生成嵌入代码。
|
||||
2. 网站维护人员在页面引入 JS,设置公开的展示位标识;以后切换活动在后台修改绑定即可。
|
||||
3. 组件在右侧悬浮,轮播已公开照片,自动发现新照片。
|
||||
4. 点击图片打开观众页面,建议在新标签页定位该图片。
|
||||
5. 隐藏或删除的照片应从后续轮播中移除;空相册不显示损坏图片。
|
||||
|
||||
## 4 功能明细
|
||||
|
||||
### 4.1 登录与账号
|
||||
|
||||
- 第一期管理后台仅账号密码登录,统一账号体系下 App 同样使用账号密码;飞书不在第一期开发和验收范围内。
|
||||
- 支持退出、修改密码和管理员重置密码,不开放自行注册。
|
||||
- 用户资料由国内定时任务调用指定接口新增或更新,不能按姓名匹配,必须使用上游稳定唯一 ID。
|
||||
- 接口负责资料同步,默认不拉取明文密码、不覆盖本地密码、项目角色、相册成员关系和语言偏好。
|
||||
- 新用户建议先处于待激活状态,由管理员完成初始密码与授权;初始密码交付、首次修改和是否允许自动激活待确认。
|
||||
- 上游明确停用时,本地阻止登录、续签和发布并撤销会话;本地管理员主动停用不能被普通同步自动解除。重新启用策略待确认。
|
||||
- 接口失败、空响应、分页未完成或增量中未出现某人,不能据此批量停用或删除用户。
|
||||
- 后台展示上次成功同步时间、状态、新增更新停用及失败数量,允许管理员手动补拉和查看失败记录。
|
||||
- 用户同步不删除历史相册和照片,不改变历史上传人;同步接口不可用时已有有效本地账号仍可按本地状态登录。
|
||||
- 同步周期、接口鉴权、字段、分页、增量水位、离职和删除语义待接口文档确认。
|
||||
|
||||
### 4.2 相册字段
|
||||
|
||||
| 字段 | 建议规则 | 状态 |
|
||||
|---|---|---|
|
||||
| 标题 | 必填,支持多语言 | 标题已确认,规则建议 |
|
||||
| 活动介绍 | 可选,支持多语言,第一版建议纯文本 | 建议基线 |
|
||||
| 活动时间 | 开始与结束,允许跨天,结束不得早于开始 | 已确认需要,规则建议 |
|
||||
| 活动时区 | 默认 Asia/Shanghai,可配置 | 建议基线 |
|
||||
| 地点 | 场馆名和地址文本,支持多语言 | 已确认需要,规则建议 |
|
||||
| 宣传图 | 观众页顶部展示,按原比例或规定裁切比例 | 已确认 |
|
||||
| 封面 | 用于工作人员列表和分享,可复用宣传图 | 建议基线 |
|
||||
| 主办承办信息 | 可选文本 | 待确认是否纳入 |
|
||||
| 分类 | 名称、语言版本和显示顺序 | 建议基线 |
|
||||
| Logo 和水印 | 可选;原图不覆盖 | 建议基线 |
|
||||
| 下载与原图下载开关 | 原图下载以允许下载为前提;关闭全部下载不阻止截图 | V1.1 纳入 |
|
||||
| 上传与公开开关 | 分开控制,活动结束不自动改变开关 | V1.1 纳入 |
|
||||
| 默认语言 | 翻译缺失时用于回退 | 建议基线 |
|
||||
|
||||
活动结束不触发删除、禁止浏览或自动停止补图。相册采用独立上传与公开开关,下线后停止公开查询、新签发图片链接和公开频道订阅。
|
||||
|
||||
### 4.3 上传与记录
|
||||
|
||||
- 支持多选、批量上传;照片数和单文件大小上限通过技术验证后配置。
|
||||
- 第一版建议支持 JPEG、PNG;iPhone HEIC 输入必须验证并完成可浏览格式转换。动态照片和 RAW 不默认支持。
|
||||
- 上传记录按相册、状态和时间筛选,显示缩略图、文件名、进度、数量、错误原因。
|
||||
- 准备阶段覆盖源文件获取、本地复制和空间检查,区分准备中与准备失败;准备完成才显示已入队。
|
||||
- App 提供连续拍摄入口,单张自动入队;切换目标仅影响新照片。
|
||||
- 服务端历史可跨设备查看,本机尚未上传的文件和队列不能在另一台手机直接恢复。
|
||||
- 支持失败重试、批量重试和取消未完成任务;手动暂停与恢复为可选增强。
|
||||
- 本地记录应跨 App 重启保留,禁止因页面关闭而丢失任务。
|
||||
- 锁屏和后台由平台原生传输能力承接,不能仅依靠 Flutter 页面内任务。
|
||||
- 强制退出、系统停止、权限变化、断网等情况下保留状态,并说明何时可恢复;不承诺无限后台执行。
|
||||
- 账号退出不应继续使用旧权限创建上传;未完成队列按账号隔离,重新登录原账号再恢复。
|
||||
- 已上传文件的本地缓存可按策略清理,不能提前清除未完成任务源文件。
|
||||
|
||||
### 4.4 观众页面
|
||||
|
||||
- 手机端双列瀑布流,照片保持合理比例;桌面端可增加列数。
|
||||
- 顶部宣传图下展示活动信息;语言入口易于查找。
|
||||
- 使用分页与按需渲染,大相册不一次加载全部照片。
|
||||
- 大图支持左右滑动、缩放、关闭、当前序号和加载失败重试。
|
||||
- 大图内点赞、分享、下载操作始终针对当前照片。
|
||||
- 采用稳定浏览顺序,点赞改变热门排名时不突然切换当前图片。
|
||||
- 相册累计浏览次数为第一版必做项,在观众页活动信息区展示,文案为“浏览次数”,不能标成“浏览人数”。
|
||||
- 微信内分享与保存按浏览器能力提供引导;系统分享不可用时提供复制链接。
|
||||
|
||||
### 4.5 相册浏览统计
|
||||
|
||||
已确认:每个相册独立累计浏览次数,观众页必须展示;计数由服务端保存,不能只写浏览器本地存储。
|
||||
|
||||
浏览器匿名标识跨同源标签页共享并持久保存;存储不可用时降级为当前页标识,不能承诺精确去重人数。
|
||||
|
||||
建议口径:一次访问在同一浏览器匿名标识、同一相册的 30 分钟窗口内只计一次,窗口内刷新、返回和重新打开不重复累计;超过窗口后的新页面访问可再计一次,停留页面不按计时器自动加量。30 分钟为待确认参数。
|
||||
|
||||
- 相册有效、公开且页面可见并成功展示活动信息后上报,避免预加载误计。
|
||||
- 单张照片分享入口归入所属相册,不单独另加相册浏览量。
|
||||
- 网站 JS 自动轮播、后台预览、API 轮询、分页请求、图片资源请求和点赞不计入。
|
||||
- 点击悬浮组件并实际打开观众页,按正常访问规则计入。
|
||||
- 计数请求重试必须幂等,不能因网络失败重复累计。
|
||||
- 这是匿名访问次数,不承诺准确去重人数;换设备、清除浏览器标识可能产生新访问。
|
||||
- 观众页初次加载与计数确认后更新显示;第一版建议不单独广播浏览量。
|
||||
- 后台建议展示相同累计数字用于核对,不包含独立 BI 分析系统。
|
||||
|
||||
### 4.6 点赞与气泡
|
||||
|
||||
- 点赞次数按照片累计,可匿名连续操作,不记录“已点赞不可再点”的状态。
|
||||
- 同一相册内列表页和大图页均接收活动级气泡。
|
||||
- 气泡建议包含当前被点赞照片的小缩略图,点击可打开对应照片。
|
||||
- 气泡不抢占主要点击区域;密集互动时限制屏幕动画数量。
|
||||
- 动画合并与计数分开:多次有效点击可合为一组动画,计数不能只加一次。
|
||||
- 防止网络重试重复累计;基础频率控制用于稳定服务,不恢复为每人只能一次。
|
||||
- 断开实时连接时允许浏览;重连后获取权威计数,不补播过期动画。
|
||||
- 本地已播放批次不因自身广播重复播放,合并事件仅过滤自身部分。
|
||||
- 页面不可见时暂停动画,恢复时只校准计数;支持减少或关闭气泡。
|
||||
- 隐藏照片同时从待广播和待展示气泡中过滤。
|
||||
|
||||
### 4.7 多语言
|
||||
|
||||
- App、后台、观众页与组件使用统一语言代码,首批建议 zh-CN、en。
|
||||
- 初次跟随设备或浏览器,手动选择优先,保存用户选择。
|
||||
- 工作人员语言偏好可随账号同步,匿名观众保存在浏览器。
|
||||
- 活动内容分别填写翻译,缺失则返回活动默认语言;图片文字不会自动翻译。
|
||||
- 不同语言可配置宣传图;未配置时回退默认图片。
|
||||
- 时间按活动时区明确展示;界面语言不改变活动实际时间。
|
||||
- 第一版不包含自动机器翻译。
|
||||
|
||||
### 4.8 网站 JS 组件
|
||||
|
||||
- 网站引入一个版本化 JS 地址,通过 slotId 指定展示位,后台配置其相册绑定。
|
||||
- 默认右侧固定悬浮,滚动页面时位置不变,支持图片轮播和自动更新。
|
||||
- 建议提供尺寸、边距、轮播间隔、语言、收起和关闭选项。
|
||||
- 不加载整个相册,只获取近期公开照片与小尺寸浏览资源。
|
||||
- 第一版通过轮询更新展示位配置与照片,不依赖点赞模块;不可见时暂停,恢复后校准。
|
||||
- 切换展示位相册后丢弃旧响应;管理员统一管理展示位,创建者只操作被授权展示位。
|
||||
- 组件样式、事件和依赖不污染宿主站点;不能阻塞网站首屏。
|
||||
- 访问异常、相册下线、无图、移动端窄屏均有可用状态。
|
||||
- 公开嵌入代码不包含工作人员凭证;具体接入方式见实施文档。
|
||||
|
||||
### 4.9 活动控制与交接
|
||||
|
||||
关闭上传后拒绝新建任务和授权续签,尚未确认的对象等待重新开放或管理员处理;已经完成核验的处理任务可以继续。关闭公开不阻止内部上传处理,但所有观众入口停止返回相册内容。重新公开不会恢复隐藏或软删除照片。
|
||||
|
||||
批量隐藏返回逐项结果,并同步通知列表、大图及组件;已签发资源链接按确认的失效窗口处理。管理员转移负责人时明确原负责人是否保留协作者身份,历史上传人与审计记录不变。
|
||||
|
||||
### 4.10 下载水印与用量
|
||||
|
||||
允许下载且不允许原图时,只提供配置允许的浏览图或水印图。水印变更默认用于新照片;创建者或管理员可明确发起历史重生成,显示进度、失败可重试,完成前保留可用旧版本,原图不覆盖。
|
||||
|
||||
后台统计项目和相册容量、流量及相关资源用量,设置阈值和告警接收人。告警不自动删除照片或中断活动;日志和临时碎片与正式图片分别制定保留策略。
|
||||
|
||||
## 5 数据与长期保存
|
||||
|
||||
原图、浏览资源存放现有 OSS,账号、相册、照片归属和点赞计数存数据库。独立对象键避免同名覆盖,数据库不保存长期有效的签名 URL。
|
||||
|
||||
照片默认长期保存,不配置原图自动到期。需检查现有 Bucket 生命周期规则对项目路径的影响。允许清理失败上传产生的碎片和临时资源,但应与正式原图区分,并经过策略确认。
|
||||
|
||||
隐藏仅影响对外展示,不删除 OSS 原图。软删除、恢复和永久删除的权限及期限待确认。已有用户下载的文件无法通过后续隐藏操作收回。
|
||||
|
||||
## 6 非功能要求与验收
|
||||
|
||||
| 编号 | 验收场景 | 通过条件 |
|
||||
|---|---|---|
|
||||
| A01 | 密码登录 | App 和后台使用本地账号密码,无飞书入口;未激活与停用账号无法进入 |
|
||||
| A02 | 协作权限 | 协作者不能修改他人照片,创建者不能越权其他相册 |
|
||||
| A03 | 多人上传 | 同名图片不覆盖,重试同一任务不重复发布 |
|
||||
| A04 | 后台上传 | Android 和 iOS 真机切后台、锁屏后按系统允许机制继续,受阻任务可恢复 |
|
||||
| A05 | 上传恢复 | 断网、凭证过期和 App 重启后任务记录完整、可重试 |
|
||||
| A06 | 自动发布 | 有效照片无需人工操作即可公开,损坏图片不进入公开列表 |
|
||||
| A07 | 大相册 | 在确认的测试量下分页稳定,不随已浏览照片无限增长内存 |
|
||||
| A08 | 大图操作 | 滑动顺序正确,关闭返回原位置,分享定位到正确照片 |
|
||||
| A09 | 点赞计数 | 连续点击累计,重复请求不重复加计,断线后能校准 |
|
||||
| A10 | 实时隔离 | 两个相册同时观看,事件仅送达所属相册 |
|
||||
| A11 | 嵌入组件 | 网站滚动时悬浮、自动更新、正确跳转,不破坏宿主样式 |
|
||||
| A12 | 多语言 | 所有入口可切换,主要提示无漏译,内容回退一致 |
|
||||
| A13 | 长期存储 | 正式照片无自动过期规则,上传端无长期 OSS 密钥 |
|
||||
| A14 | 浏览统计 | 有效访问累计且展示;跨标签页、重试和窗口内操作不重复计,轮播和后台预览不计 |
|
||||
| A15 | 连续拍摄 | 拍完自动准备入队,切相册不改变已有任务归属 |
|
||||
| A16 | 控制与隐藏 | 上传公开独立,批量隐藏后各端停止展示,重新公开不恢复隐藏图 |
|
||||
| A17 | 负责人交接 | 转移后权限正确,历史上传人和记录不变 |
|
||||
| A18 | 下载水印 | 开关组合合法,历史重生成可重试,不覆盖原图或破坏旧浏览图 |
|
||||
| A19 | 准备与历史 | 云端文件和空间异常有状态,跨设备仅恢复服务端历史 |
|
||||
| A20 | 资源保护 | 旧上传权限不能改变已发布原图,过期图片链接可刷新 |
|
||||
| A21 | 气泡体验 | 自己事件不重复,后台无积压,减少动画不改变计数 |
|
||||
| A22 | 展示位 | 后台切换相册不改官网,旧响应不串图,展示位权限正确 |
|
||||
| A23 | 用量控制 | 告警可触发,组件不可见暂停请求,不自动删除照片 |
|
||||
| A24 | 部署验证 | 海外观众 API 与实时同部署,国内 worker 独立,区域数据不共用跨境数据库 |
|
||||
| A25 | 海外直读 | 页面、列表、浏览点赞不逐次请求国内,图片经全球 CDN |
|
||||
| A26 | 内容同步 | 重复、乱序、中断和补发不复活已隐藏删除照片,不显示缺资源图片 |
|
||||
| A27 | 统计回传 | 重试不重复累计,国内最终与海外权威计数一致,并展示同步时间 |
|
||||
| A28 | 全球访问 | 目标地域测试冷缓存新图、原图下载与下线传播,记录实际时延 |
|
||||
| A29 | 用户接口同步 | 新增更新幂等,稳定 ID 不变;失败不误停用,不覆盖本地密码权限,支持状态与补拉 |
|
||||
|
||||
照片量、并发人数和响应时间尚未由业务确定。项目计划给出首轮测试建议,均需通过实测确认,不能解释为已具备容量或生产服务承诺。
|
||||
|
||||
## 7 待确认决策
|
||||
|
||||
| 编号 | 决策 | 建议 | 最晚确认时点 |
|
||||
|---|---|---|---|
|
||||
| D01 | 首发语言 | 简体中文和英文 | 原型定稿 |
|
||||
| D02 | 后端与 Web 技术栈 | 采用实施文档建议或公司标准 | 技术验证前 |
|
||||
| D03 | iOS 和 Android 分发方式 | 按公司设备和账号资质决定 | 第一周启动 |
|
||||
| D04 | 删除回收策略 | 先软删除,不自动永久清理 | 照片管理开发前 |
|
||||
| D05 | 数量并发与网络目标 | 采用分级压测逐步校准 | 技术验证结束 |
|
||||
| D06 | 下载水印与原图默认值 | 活动创建时明确设置 | 观众原型定稿 |
|
||||
| D07 | 主办信息与浏览去重参数 | 浏览统计已必做;确认主办字段及建议的 30 分钟窗口 | 原型定稿 |
|
||||
| D08 | 用户接口与凭据规则 | 确认稳定 ID、字段、鉴权、周期、停启用语义、初始密码及激活方式 | 用户同步开发前 |
|
||||
| D09 | 海外服务地域 | 全球 CDN 已确定;确认观众分布、海外服务器地域和测试点 | 部署方案定稿 |
|
||||
| D10 | 缓存与用量参数 | 资源失效窗口、轮询周期、容量和流量告警阈值 | 对应模块验收前 |
|
||||
|
||||
| D11 | 跨境同步参数 | 确认补漏周期、延迟告警、统计回传周期及中断下线策略 | 同步验收前 |
|
||||
|
||||
## 8 需求变更与参考
|
||||
|
||||
新增需求应记录编号、目的、验收方式及工期影响,再决定进入当前版本或后续版本。相册级公开模式、匿名连续点赞及无修图流程为当前基线,避免实现时回退到早期讨论过的登录点赞或单次点赞方案。
|
||||
|
||||
观众布局参考用户提供的截图:宣传图、活动信息、图片瀑布流。喔图仅作为业务流程参考,不构成对其界面素材或完整功能的复制要求。
|
||||
|
||||
公开参考资料:
|
||||
|
||||
- [喔图产品介绍](https://www.alltuu.com/cooperationPage)
|
||||
- [喔图上传与照片管理说明](https://faq.alltuu.com/a538/03/f140)
|
||||
- [喔图上传和发布状态说明](https://faq.alltuu.com/a538/godson/6505)
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
# 图片直播项目计划
|
||||
|
||||
版本:V1.3
|
||||
日期:2026-09-17
|
||||
关联文档:[需求说明书](./01-需求说明书.md) · [技术实施方案](./03-技术实施方案.md) · [优化方案](./04-优化方案.md)
|
||||
|
||||
本计划把第一版拆成可交付任务,并优先验证双平台后台上传。完成目标是交付 Flutter App、管理后台、观众网页、网站 JS 组件和支撑服务,跑通活动创建到照片传播的完整流程。
|
||||
|
||||
## 1 排期假设
|
||||
|
||||
目前没有确认开发人员和上线日期,因此采用相对周次 W1 至 W16,不设具体日历承诺。
|
||||
|
||||
用于估算的团队配置为:1 名 Flutter 工程师、1 名后端工程师、1 名 Web 工程师;另配兼职产品设计、测试和运维支持。测试在早期参与,不能只在最后一周介入。人员减少、缺乏原生移动端经验或外部审批延误,都需要重新排期。
|
||||
|
||||
增加海外服务及双向业务同步后,原 12 周基线暂增加 2 至 4 周,按 14 至 16 周估算,以 16 周安排为工作基线,W15 至 W16 用于试运行、修复与发布。以下工作量是规划估算,单位为角色人日,不含等待外部审核时间,不代表固定报价。W2 技术验证结束后重估。
|
||||
|
||||
## 2 交付清单
|
||||
|
||||
| 编号 | 交付物 | 完成标准 |
|
||||
|---|---|---|
|
||||
| P01 | 需求和原型 | 角色权限、三端页面、上传和互动状态明确 |
|
||||
| P02 | 技术验证报告 | Android 和 iOS 真机后台上传方案可行,异常边界有记录 |
|
||||
| P03 | 后端与管理后台 | 账号、相册、人员、照片及 OSS 处理闭环 |
|
||||
| P04 | Flutter App | 账号密码登录、相册配置、协作、上传记录和照片管理 |
|
||||
| P05 | 观众网页 | 多语言活动页、浏览次数、瀑布流、大图、下载分享、点赞气泡 |
|
||||
| P06 | JS 嵌入组件 | 可复制代码接入、悬浮轮播、自动更新和跳转 |
|
||||
| P07 | 验证材料 | 权限、弱网、兼容、大相册、实时互动测试报告 |
|
||||
| P08 | 上线与运维交接 | 国内海外部署配置、数据库迁移、备份恢复、监控和回滚步骤 |
|
||||
| P09 | 海外同步与全球分发 | 内容推送补漏、统计回传、CDN 与冷缓存地域测试报告 |
|
||||
|
||||
## 3 阶段安排
|
||||
|
||||
| 阶段 | 周次 | 主要工作 | 退出条件 |
|
||||
|---|---|---|---|
|
||||
| S1 需求原型 | W1 | 字段、权限、页面原型、账号分发条件确认 | 需求基线与待确认负责人明确 |
|
||||
| S2 技术验证 | W1 至 W2 | 原生后台传输、OSS、用户接口、海外网络与 CDN 验证 | 核心风险有实测结果,确定实施方式 |
|
||||
| S3 基础链路 | W3 至 W6 | 身份、相册、上传、处理、公开列表,基础 App 和后台 | 单张和批量上传自动发布端到端可用 |
|
||||
| S4 功能建设 | W7 至 W10 | 协作管理、队列恢复、大图、语言、点赞、下载分享 | 主要需求完成并通过模块测试 |
|
||||
| S5 优化与集成 | W11 至 W14 | 展示位、交接、双向同步、CDN、访问保护与跨地域测试 | 新增优化通过验收,功能冻结 |
|
||||
| S6 试运行发布 | W15 至 W16 | 模拟或真实活动、故障演练、修复、分发和交接 | 业务验收通过,发布检查和回滚准备完成 |
|
||||
|
||||
后台、App 和 Web 可在接口契约稳定后并行。多语言、日志和权限随各模块实施,不在末期集中补做。App 分发资格与域名准备从 W1 启动,外部审核周期另计。
|
||||
|
||||
## 4 工作分解
|
||||
|
||||
表中主责为岗位而非已指定人员。估算区间涵盖实现与开发自测,联调测试单列。依赖项表示开始或完成前所需条件。
|
||||
|
||||
| WBS | 工作包 | 主责 | 估算人日 | 依赖 | 交付证据 |
|
||||
|---|---|---|---|---|---|
|
||||
| T01 | 字段权限原型与验收清单 | 产品设计 | 4 至 6 | 无 | 原型、R01 至 R31 对应清单 |
|
||||
| T02 | 仓库工程环境与构建流水线 | 全端运维 | 3 至 5 | 技术选型 | 三端基础构建、环境说明 |
|
||||
| T03 | 双平台后台上传与 OSS 验证 | Flutter 后端 | 8 至 12 | 测试 Bucket 路径、真机 | 锁屏弱网过期恢复报告 |
|
||||
| T04 | 用户接口契约与同步验证 | 后端 测试 | 3 至 5 | 用户接口文档和测试凭据 | 稳定 ID、分页增量、异常和停用语义验证 |
|
||||
| T05 | 账号会话权限与后台账号管理 | 后端 Web | 6 至 8 | T01 T04 | 登录、停用、重置和越权用例 |
|
||||
| T06 | 相册分类语言与协作接口 | 后端 | 5 至 7 | T05 | API 契约及权限测试 |
|
||||
| T07 | OSS 授权处理发布与失败恢复 | 后端 | 7 至 10 | T03 T06 | 幂等上传发布、后台补偿 |
|
||||
| T08 | App 登录相册配置和协作页面 | Flutter | 7 至 9 | T04 T06 | 双平台可操作页面 |
|
||||
| T09 | App 上传队列与记录正式实现 | Flutter | 10 至 14 | T03 T07 | 重启恢复、失败重试、账号隔离 |
|
||||
| T10 | 后台相册照片和用量管理 | Web | 6 至 8 | T06 T07 | 配置、筛选、隐藏删除和用量 |
|
||||
| T11 | 观众活动页瀑布流和大图 | Web | 7 至 9 | T06 T07 | 大相册分页与滑动定位 |
|
||||
| T12 | 分享下载与浏览器适配 | Web 后端 | 3 至 5 | T11 | 照片链接、下载开关和微信引导 |
|
||||
| T13 | 点赞计数与实时气泡 | 后端 Web | 6 至 8 | T07 T11 | 去重、广播隔离和断线恢复 |
|
||||
| T14 | JS 悬浮组件与配置生成 | Web | 4 至 6 | T06 T11 | 独立网站接入样例 |
|
||||
| T15 | 全端语言验收与文案补齐 | 全端产品 | 3 至 5 | 各功能 | 语言切换与缺失回退检查 |
|
||||
| T16 | 全链路测试容量与缺陷修复 | 测试全端 | 10 至 15 | T08 至 T15、T19 至 T24、T26 至 T28、T30 | 测试报告、阻断问题关闭 |
|
||||
| T17 | 部署备份回滚与监控 | 运维后端 | 4 至 6 | T02 T07 | 部署和恢复演练记录 |
|
||||
| T18 | 试运行App分发与交接 | 全端测试 | 4 至 6 | T16 T17 T29 | 业务验收和发布记录 |
|
||||
| T19 | 相册浏览计数与前台展示 | 后端 Web | 2 至 3 | T06 T11 | 幂等计数、窗口去重及展示核对 |
|
||||
| T20 | 连拍活动控制交接与水印规则增量 | Flutter 后端 Web | 10 至 15 | T06 T08 T10 | A15 至 A18 验收,含历史水印任务 |
|
||||
| T21 | 准备状态与跨设备记录增量 | Flutter 后端 | 6 至 9 | T03 T09 | A19 验收与锁屏长队列恢复 |
|
||||
| T22 | 原图不可变与资源访问增量 | 后端 Web 运维 | 5 至 8 | T07 T12 | A20 验收与签名缓存失效测试 |
|
||||
| T23 | 展示位管理与不可见暂停增量 | 后端 Web | 3 至 5 | T06 T14 | A22 A23 验收,换相册不改站点 |
|
||||
| T24 | 气泡批次去重与动画偏好增量 | 后端 Web | 3 至 5 | T13 | A21 验收,自己的动画不重复 |
|
||||
| T25 | 海外部署与全球 CDN 可行性验证 | 后端 运维 测试 | 3 至 5 | T02、海外测试资源 | 目标地区冷缓存和服务直读报告 |
|
||||
| T26 | 国内到海外元数据同步与补漏 | 后端 Web 测试 | 8 至 12 | T06 T07 T25 | 版本去重、隐藏删除、补漏与同步状态 |
|
||||
| T27 | 海外计数权威与统计回传 | 后端 测试 运维 | 4 至 6 | T13 T19 T26 | 重复回传不加计、国内最终一致 |
|
||||
| T28 | 全球 CDN 接入与访问策略增量 | 运维 后端 Web | 3 至 5 | T22 T25 | 域名鉴权、缓存失效、冷缓存测量 |
|
||||
| T29 | 跨地域故障与上线验收 | 测试 后端 Web 运维 | 4 至 6 | T16 T26 T27 T28 | 中断恢复、下线传播、地域时延与告警 |
|
||||
| T30 | 用户定期同步与后台同步管理 | 后端 Web 运维 测试 | 5 至 8 | T04 T05 | 幂等更新、水位、定时补拉、日志与失败告警 |
|
||||
|
||||
|
||||
|
||||
合计约 156 至 231 角色人日。V1.3 用 T04 用户接口验证替代飞书验证,并增加 T30 同步实现,较 V1.2 增加 5 至 9 人日,仍按 16 周基线安排,接口契约未提供前不能视为固定工期。T04 在 W1 至 W2,T30 在 W3 至 W6 完成并纳入核心账号验收。V1.2 历史估算为 151 至 222 人日。V1.1 的 129 至 188 人日保留;T25 至 T29 是海外架构增量,增加 22 至 34 人日,不重复计算基础计数和 OSS 上传。T25 在 W1 至 W2 验证,T26 从 W3 起随核心接口推进,T27 和 T28 在功能期联调,T29 于 W13 至 W14 完成跨地域验收。T19 调整为 W7 至 W8。同步异常不得留到上线后补做。
|
||||
|
||||
## 4A 岗位工作量与资源检查
|
||||
|
||||
以下按各任务估算上限分配,单位为人日;这是估算分配,不是已落实人员。每行合计等于上方任务上限,避免多人主责被误认为每个人都要投入整项工作量。
|
||||
|
||||
| 任务 | Flutter | 后端 | Web | 产品设计 | 测试 | 运维 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| T01 | 0 | 0 | 0 | 6 | 0 | 0 |
|
||||
| T02 | 1 | 1 | 1 | 0 | 0 | 2 |
|
||||
| T03 | 8 | 3 | 0 | 0 | 1 | 0 |
|
||||
| T04 | 0 | 3 | 0 | 0 | 2 | 0 |
|
||||
| T05 | 0 | 4 | 3 | 0 | 1 | 0 |
|
||||
| T06 | 0 | 6 | 0 | 0 | 1 | 0 |
|
||||
| T07 | 0 | 8 | 0 | 0 | 1 | 1 |
|
||||
| T08 | 8 | 0 | 0 | 1 | 0 | 0 |
|
||||
| T09 | 12 | 0 | 0 | 0 | 2 | 0 |
|
||||
| T10 | 0 | 0 | 7 | 0 | 1 | 0 |
|
||||
| T11 | 0 | 0 | 8 | 1 | 0 | 0 |
|
||||
| T12 | 0 | 1 | 3 | 0 | 1 | 0 |
|
||||
| T13 | 0 | 4 | 3 | 0 | 1 | 0 |
|
||||
| T14 | 0 | 0 | 5 | 0 | 1 | 0 |
|
||||
| T15 | 1 | 0 | 1 | 2 | 1 | 0 |
|
||||
| T16 | 2 | 2 | 2 | 0 | 9 | 0 |
|
||||
| T17 | 0 | 1 | 0 | 0 | 1 | 4 |
|
||||
| T18 | 1 | 0 | 1 | 0 | 3 | 1 |
|
||||
| T19 | 0 | 1 | 1 | 0 | 1 | 0 |
|
||||
| T20 | 4 | 4 | 4 | 1 | 2 | 0 |
|
||||
| T21 | 6 | 1 | 0 | 0 | 2 | 0 |
|
||||
| T22 | 0 | 4 | 1 | 0 | 1 | 2 |
|
||||
| T23 | 0 | 1 | 3 | 0 | 1 | 0 |
|
||||
| T24 | 0 | 1 | 3 | 0 | 1 | 0 |
|
||||
| T25 | 0 | 2 | 0 | 0 | 1 | 2 |
|
||||
| T26 | 0 | 8 | 1 | 0 | 2 | 1 |
|
||||
| T27 | 0 | 4 | 0 | 0 | 1 | 1 |
|
||||
| T28 | 0 | 1 | 1 | 0 | 1 | 2 |
|
||||
| T29 | 0 | 1 | 1 | 0 | 3 | 1 |
|
||||
| T30 | 0 | 4 | 2 | 0 | 1 | 1 |
|
||||
| 合计 | 43 | 65 | 51 | 11 | 43 | 18 |
|
||||
|
||||
16 周按每周 5 个工作日约 80 人日计算,三名开发各自均需保留会议、评审、外部等待和非项目工作的空间。后端总量最高,需优先保障双向同步;Web 避免同时推进后台、观众页与组件三条主线;兼职测试也需要在集成期集中投入,不能假设仅偶尔验收。
|
||||
|
||||
排期容量检查还需按周落实到人:W1 至 W2 优先验证,W3 至 W6 核心链路,W7 至 W10 功能,W11 至 W14 优化集成,W15 至 W16 试运行发布。总量有余量并不证明所有依赖可在 14 周内完成;W2 后根据实际速度发布细化到人的排期。
|
||||
|
||||
## 5 关键路径与里程碑
|
||||
|
||||
关键路径为:后台上传验证 → 持久化上传队列 → OSS 核验和自动发布 → 海外可见与同步恢复验证 → 双平台恢复验证 → 试运行 → 发布。若后台上传验证未通过,不应把功能标记为完成后继续承诺原上线日期。
|
||||
|
||||
| 里程碑 | 建议时间 | 验收内容 | 验收岗位 |
|
||||
|---|---|---|---|
|
||||
| M1 需求基线 | W1 结束 | 已确认范围、建议项和未决策项明确 | 业务负责人、技术负责人 |
|
||||
| M2 技术可行 | W2 结束 | 后台上传、凭证过期、用户接口同步验证通过 | 技术负责人、测试 |
|
||||
| M3 核心链路 | W6 结束 | 国内上传后海外观众页自动看到照片 | 业务负责人、测试 |
|
||||
| M4 功能齐备 | W14 结束 | App、后台、观众、组件和多语言可用 | 产品、测试 |
|
||||
| M5 试运行通过 | W15 结束 | 多人现场操作、弱网及权限行为符合要求 | 业务负责人 |
|
||||
| M6 发布交接 | W16 结束 | 生产部署、App 分发、监控、回滚和交接完成 | 项目负责人、运维 |
|
||||
|
||||
## 6 首轮容量验证建议
|
||||
|
||||
业务尚未提供容量,以下仅是待评审的测试阶梯,不是生产承诺。采用固定图片大小、清晰度、网络和设备条件记录结果。
|
||||
|
||||
| 层级 | 相册照片数 | 同时上传人数 | 在线观众 | 目的 |
|
||||
|---|---|---|---|---|
|
||||
| L1 基础 | 1,000 | 3 | 100 | 验证完整流程和初步稳定性 |
|
||||
| L2 目标候选 | 10,000 | 10 | 1,000 | 评估首版资源与优化需求 |
|
||||
| L3 扩展探测 | 50,000 | 20 | 5,000 | 查找瓶颈,不作为默认上线门槛 |
|
||||
|
||||
照片数量测试可使用测试数据;实际上传吞吐和缩略图处理必须另用真实尺寸图片验证。在线数和点赞频率分别施压,避免只验证连接数而忽略消息负载。
|
||||
|
||||
建议候选指标:普通列表 API P95 小于 500ms;约定网络下活动页首批照片 P95 小于 3 秒;传输完成到发布 P95 小于 10 秒;点赞确认及广播 P95 小于 2 秒。W2 根据样本、部署规格和成本重新确认,上传耗时单独统计,不包含在图片处理时延内。
|
||||
|
||||
## 7 测试计划
|
||||
|
||||
### 7.1 开发阶段
|
||||
|
||||
核心权限、上传幂等、点赞幂等、排序分页和语言回退编写服务端测试。每个功能提交时验证对应验收编号,不编写只重复页面实现的低价值测试。
|
||||
|
||||
### 7.2 集成阶段
|
||||
|
||||
- Android 和 iOS 真机:前台、后台、锁屏、系统中止、强制退出、重启、低电量模式。
|
||||
- 网络:离线入队、上传中断网、Wi-Fi 与移动网络切换、临时授权过期。
|
||||
- 数据:同名不同文件、重复任务、HEIC、超限和损坏文件、处理失败、回调重复。
|
||||
- 权限:移除协作者、账号停用、相册下线、直接调用他人照片 API。
|
||||
- 观众:微信内浏览器、Android Chrome、iOS Safari 和桌面主流浏览器。
|
||||
- 实时:同相册多窗口、不同相册隔离、密集点赞、断线重连、隐藏照片后旧链接。
|
||||
- 嵌入:不同 CSS 框架、窄屏、组件多次加载、无图、宿主 CSP 限制。
|
||||
- 语言:中英切换、长英文排版、时区、未填写翻译和不同宣传图。
|
||||
- 优化:连拍切相册、准备失败、负责人转移、上传公开独立、历史水印失败、旧授权覆盖、跨标签浏览、自己气泡重复、展示位切换和用量告警。
|
||||
|
||||
用户同步专项:重复记录、改名改账号、分页中断、空响应、超时重试、并发补拉、旧版本覆盖、显式停用、本地锁定、密码权限不被覆盖及水位恢复。
|
||||
|
||||
### 7.3 发布门槛
|
||||
|
||||
需求文档 A01 至 A29 完成映射。浏览统计需验证首次访问、短时刷新、单图入口、跨窗口访问、重复请求和嵌入轮播排除。无数据丢失、越权、无法恢复上传等阻断缺陷;其他遗留问题写明影响和处理时间。完成一次数据库恢复与发布回滚演练,并形成容量实测报告。
|
||||
|
||||
跨境专项:断开内容同步、乱序重放、重复计数回传、重建海外副本、隐藏删除优先传递、同步积压告警;从确认的目标地域分别测新图冷缓存和缓存命中,不能只测国内网络。同步延迟与图片传输时延分开报告。
|
||||
|
||||
## 8 资源与外部依赖
|
||||
|
||||
| 资源 | 用途 | 提供时间 |
|
||||
|---|---|---|
|
||||
| 现有 OSS 地域与项目路径 | 临时授权、上传、资源分发 | W1 |
|
||||
| 用户接口文档和测试凭据 | 字段、唯一 ID、鉴权、分页、周期、停用语义 | W1 |
|
||||
| 域名和测试部署环境 | API、观众页、组件与回调 | W1 至 W2 |
|
||||
| Android 和 iOS 真机 | 后台上传和浏览器验收 | W1 |
|
||||
| macOS 构建环境与 Apple 开发资质 | iOS 编译签名与分发 | W1 启动 |
|
||||
| 活动样例与中英文案 | 原型和真实试运行 | W2 |
|
||||
| 海外服务器与测试节点 | 海外 API、数据库、同步接收和真实地域测试 | W1 |
|
||||
| 全球 CDN 域名和权限 | OSS 回源、观众鉴权、缓存失效与按需预热 | W1 至 W2 |
|
||||
| 开发和测试负责人 | 确认实际排期与缺陷责任 | 启动前 |
|
||||
|
||||
密钥通过安全配置渠道提供,不写入代码、文档或聊天记录。长期云资源费用另行测算,费用包括 OSS 容量、请求和出网流量、CDN、图片处理、数据库、服务实例与日志。
|
||||
|
||||
## 9 风险及处置
|
||||
|
||||
| 风险 | 影响 | 处置 |
|
||||
|---|---|---|
|
||||
| 后台传输与系统限制不匹配 | 上传中断、延误 | W1 至 W2 原生验证,保留可恢复队列 |
|
||||
| 图片量和流量未知 | 性能及成本失控 | 分级压测、用量统计、容量告警 |
|
||||
| OSS 凭证过期 | 长任务失败 | 验证刷新与后台恢复,不无限延长授权 |
|
||||
| 多人权限变更 | 越权发布 | 完成上传时再次校验权限 |
|
||||
| 匿名连续点赞被滥用 | 计数及广播负载 | 基础限速、幂等、动画聚合 |
|
||||
| App 分发或用户接口联调延迟 | 功能完成但无法使用 | W1 准备分发与接口样本,外部等待单独跟踪 |
|
||||
| 用户同步异常或语义不明 | 重复账号或误停用 | 稳定 ID、完整快照判断、本地权限隔离及审计 |
|
||||
| 海外地区与冷缓存 | CDN 回源及 API 远距离延迟 | 确认用户地区,分别测试冷缓存与命中 |
|
||||
| 跨境同步中断或乱序 | 新图延迟、旧状态覆盖 | 持久事件、版本校验、补漏及墓碑 |
|
||||
| 统计重复回传 | 国内计数虚增 | 海外权威累计快照,版本替换,不重复相加 |
|
||||
|
||||
## 10 项目跟踪方式
|
||||
|
||||
每个任务记录负责人、需求编号、依赖、预计完成周、状态、验收证据和风险。状态使用待开始、进行中、待联调、待验收、完成、阻塞。
|
||||
|
||||
每周检查里程碑和关键路径,每阶段演示真实完整流程。新增需求需先评估涉及页面、接口、存储和测试的影响,明确替换现有范围、延长排期或进入后续版本。
|
||||
|
||||
项目启动先完成 D01 至 D03、D08 的决策,并落实资源负责人;W2 结束后发布修订后的工期和容量基线。
|
||||
@@ -0,0 +1,484 @@
|
||||
# 图片直播项目技术实施方案
|
||||
|
||||
版本:V1.3
|
||||
日期:2026-09-17
|
||||
关联文档:[需求说明书](./01-需求说明书.md) · [项目计划](./02-项目计划.md) · [优化方案](./04-优化方案.md)
|
||||
|
||||
本方案用于指导第一版开发、联调和部署。Flutter、Android 与 iOS、第一期账号密码登录与用户接口定期拉取、现有国内 OSS、全球 CDN、国内管理与海外观众服务为已确认约束;具体服务端框架、语言数量、参数和资源规格均为建议,需在技术验证后冻结。
|
||||
|
||||
## 1 架构与选型
|
||||
|
||||
第一版按地域部署国内管理 API 与海外观众 API,两地使用独立数据库与缓存,不让海外请求直接查询国内数据库。海外 API 内部署 WebSocket 模块,国内图片处理 worker 独立。代码可复用公共模块,但域名、权限、凭据和路由按角色隔离。
|
||||
|
||||
| 层 | 建议实现 | 选择依据 |
|
||||
|---|---|---|
|
||||
| App | Flutter 界面与业务,平台原生后台上传桥接 | 双平台统一业务,后台任务由系统承接 |
|
||||
| 管理后台 | Vue 3 与 TypeScript | 表单、列表和管理操作 |
|
||||
| 观众端 | Vue 3 与 TypeScript,独立轻量入口 | 图片浏览、路由定位和互动 |
|
||||
| 嵌入组件 | 小型 TypeScript 加载器与 iframe | 隔离宿主样式,减少依赖冲突 |
|
||||
| API | NestJS 与 TypeScript | 统一模块边界及接口契约 |
|
||||
| 数据库 | PostgreSQL | 权限、关联数据、事务和可靠计数 |
|
||||
| 缓存与任务 | Redis,持久任务队列可采用 BullMQ | 分发、限速和异步处理;数据库保留任务源记录 |
|
||||
| 文件 | 现有阿里云 OSS | App 直传、原图长期存放 |
|
||||
| 实时 | WebSocket,故障时降级轮询 | 新图通知、点赞气泡和计数同步 |
|
||||
| 分发 | OSS 全球 CDN,已确定 | 图片直接由 CDN 分发,验证冷缓存回源 |
|
||||
|
||||
以上为建议选型,不表示已安装依赖或完成实现。公司已有技术栈可替换 API 和 Web 框架,但应保持下面的数据与状态约束。版本号在工程初始化时选择并锁定,不在当前文档写死未经验证的最新版。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
APP["工作人员 App"] --> CN["国内管理 API"]
|
||||
ADM["国内管理后台"] --> CN
|
||||
USRC["用户信息接口"] -->|国内定时拉取| USYNC["用户资料同步任务"]
|
||||
USYNC --> CN
|
||||
APP -->|临时授权直传| OSS["国内 OSS 原图与浏览资源"]
|
||||
CN --> CDB[("国内主数据库")]
|
||||
CN --> WORKER["国内图片处理 worker"]
|
||||
WORKER --> OSS
|
||||
WORKER --> CDB
|
||||
CDB --> OUT["持久事件与定时补漏"]
|
||||
OUT -->|版本化元数据| OVER["海外观众 API 与实时模块"]
|
||||
OVER --> ODB[("海外展示副本与互动数据库")]
|
||||
ODB -->|统计快照回传| CN
|
||||
USER["海外观众"] -->|页面 查询 点赞 浏览| OVER
|
||||
SLOT["官网 JS 展示位"] -->|轮询| OVER
|
||||
OSS -->|回源| CDN["全球 CDN"]
|
||||
USER -->|图片请求| CDN
|
||||
SLOT -->|轮播图片| CDN
|
||||
```
|
||||
|
||||
## 2 工程结构
|
||||
|
||||
建议使用单仓库,便于接口契约和多语言资源共同维护;此处只是规划,不自动创建业务工程。
|
||||
|
||||
```text
|
||||
photo/
|
||||
docs/ 需求、计划、实施和后续决策记录
|
||||
apps/mobile/ Flutter App 与 Android iOS 原生桥接
|
||||
apps/admin/ 管理后台
|
||||
apps/viewer/ 观众页与嵌入轮播页面
|
||||
packages/embed-loader/ 对外 JS 加载器
|
||||
packages/contracts/ 接口定义与事件类型
|
||||
packages/locales/ Web 公共翻译和词条约定
|
||||
services/api/ 国内管理与海外观众两个部署入口
|
||||
services/worker/ 图片处理与补偿任务
|
||||
services/api/realtime/ 海外观众 API 内的 WebSocket 模块
|
||||
services/sync/ 内容同步接收及统计回传任务
|
||||
infra/ 部署配置和迁移脚本
|
||||
```
|
||||
|
||||
环境划分为本地、测试和生产。测试与生产至少使用独立数据库、Redis 命名空间、OSS 对象前缀和凭证权限。不得让测试清理脚本操作现有业务照片。
|
||||
|
||||
## 2A 国内海外数据与同步
|
||||
|
||||
### 数据归属
|
||||
|
||||
| 数据 | 权威写入地点 | 另一端职责 |
|
||||
|---|---|---|
|
||||
| 工作人员本地账号、外部用户映射和管理权限 | 国内 | 不把密码、会话或工作人员令牌同步海外 |
|
||||
| 相册、翻译、分类、照片可见性、下载水印设置、展示位 | 国内 | 海外保存公开服务需要的展示副本 |
|
||||
| 原图与浏览资源 | 国内 OSS | 全球 CDN 分发,海外业务服务器不复制文件 |
|
||||
| 点赞和浏览计数、批次去重 | 海外 | 国内接收版本化累计快照,仅作统计展示 |
|
||||
|
||||
所有观众,包括从国内打开公开链接的观众,第一版统一写海外互动服务。国内管理员不能同时对这些权威计数加值。内容同步事件不能覆盖海外点赞数和浏览量。浏览和互动去重明细默认留在海外,国内仅收所需累计统计。
|
||||
|
||||
### 国内到海外内容同步
|
||||
|
||||
国内业务事务同时写 outbox,发送器推送签名鉴权的内部同步接口;海外事务内登记 inbox eventId、检查实体版本并更新展示副本,持久成功后应答。网络重试复用 eventId,不采用跨境数据库事务。
|
||||
|
||||
事件包含 schemaVersion、entityType、entityId、entityVersion、eventId 和 payload。每个实体只应用更新版本,旧版本及重复消息忽略;先到的照片缺相册或分类时暂存并补取依赖,不能显示残缺关系。首次全量快照携带水位,再回放其后的增量;定时按水位及清单对账,补发缺失数据。
|
||||
|
||||
海外必须保存隐藏删除墓碑及版本,不能因旧新增消息或全量补漏复活图片。相册下线和照片隐藏走高优先级队列;相册公开状态与照片可见性同时校验,旧的照片事件不能重新打开下线相册。展示位切换同样使用版本,避免返回旧活动。
|
||||
|
||||
同步元数据为资源键、衍生版本、尺寸和 CDN 路径,不同步容易过期的签名 URL。图片在 OSS 处理完成才发可用事件;海外收到后确认必要浏览资源可访问,再设为可见。一次海外探测不等于所有 CDN 节点已经缓存。
|
||||
|
||||
App 和后台分别显示国内处理状态与海外同步状态,包括同步中、海外可见、失败以及最近时间,不能把国内 ready 直接当作海外可见。具体目标延迟、重试次数和补漏周期在 T25 验证后冻结。
|
||||
|
||||
### 海外到国内统计回传
|
||||
|
||||
海外点赞事务、浏览去重和权威累计全部在当地完成。回传采用按照片或相册的累计快照,带独立单调 statVersion、来源及确认时间;国内仅以更高版本替换统计副本,绝不把相同累计值反复相加。重试、乱序及国内暂时不可用不会改变海外权威数。
|
||||
|
||||
国内后台标明统计同步时间,内容配置更新不得清空计数。海外恢复备份后,先核对已确认水位与统计副本,修复再恢复写入;不能直接切换权威地域产生双写。精确灾难恢复指标和快照保留时间在部署评审确认。
|
||||
|
||||
### 同步接口与运行边界
|
||||
|
||||
内部接口建议 POST /internal/sync/content-batches、POST /internal/sync/stat-snapshots,使用服务身份认证、请求签名或双向 TLS、时间戳与重放约束;接口不对匿名观众开放。发送成功不等于消费成功,必须以接收端持久化应答更新水位。持久记录同步失败和可重放批次,不静默丢弃。
|
||||
|
||||
跨境链路中断时海外继续提供已同步照片与当地互动,国内可继续上传并排队;国内显示同步延迟。无法保证此时的下线指令立刻到达海外,需提供运维可达的海外紧急停用入口。该入口只允许更严格的屏蔽,不修改国内业务权威数据;解除需明确操作,普通同步事件不得自动解除。
|
||||
|
||||
国内管理、海外公开页面和 API 使用不同域名配置。观众浏览、下载签名、点赞和气泡不逐次访问国内;海外以受限配置验证公开副本并签发 CDN 访问凭证,不持有 OSS 上传或管理权限。周期性探测可访问源站,不应变成每次浏览回源国内 API。
|
||||
|
||||
## 3 账号与权限
|
||||
|
||||
### 3.1 内部身份与第一期登录
|
||||
|
||||
管理后台与 App 共用内部 users.id,第一期仅账号密码登录,不提供飞书入口、OAuth 回调、绑定流程或相应密钥配置。外部用户接口用于拉取资料,不代表外部认证接口;是否使用外部密码校验尚未提出,实施基线为本地密码验证。
|
||||
|
||||
资料同步使用 source_system 与 external_user_id 组合唯一映射到 users.id,姓名、手机号、邮箱或账号变更不新建人员;登录账号冲突进入人工处理,不根据姓名自动合并。第一期不开放自行注册。
|
||||
|
||||
密码采用成熟密码哈希,登录会话可撤销。同步不得覆盖 password_hash、管理员角色、相册成员、个人语言偏好或管理员主动锁定状态。初始管理员受控初始化,导入用户默认待激活,管理员完成初始凭据及授权;自动激活、初始密码交付与首次修改在接口评审时确认。
|
||||
|
||||
### 3.1A 用户接口定期拉取
|
||||
|
||||
用户接口地址、鉴权方式、字段和速率约束待提供,不能假设现有接口路径或数据格式。建议逻辑字段为 externalUserId、loginAccount、displayName、department、sourceStatus、updatedAt 或 sourceVersion;手机号与邮箱仅按实际需要映射。先冻结字段归属和停启用语义,再开发适配器。
|
||||
|
||||
流程:国内定时调度或管理员手动触发 → 获得任务锁 → 读取上次成功水位 → 拉取分页数据 → 校验暂存 → 按外部 ID 幂等更新 → 提交批次与水位 → 记录结果与告警。支持增量时定期全量对账;只支持全量时逐页获取完整快照再做差异处理。
|
||||
|
||||
计划任务与手动补拉共用互斥锁,避免并行执行覆盖水位。超时、限速和失败按退避重试,保持原成功水位;旧 sourceVersion 不覆盖新数据。没有源版本时禁止并发批次,以已完成快照序号提交。部分无效记录先保存错误及可重试原始标识,再决定是否推进分页水位,不能静默漏人。
|
||||
|
||||
同步任务保存成功、部分失败或失败状态,记录开始结束、上次成功时间、读取新增更新停用及失败数量。分页不完整、空响应、接口故障不执行缺失用户停用;只有明确的停用字段或接口契约保证的完整删除快照,才进入停用处理。第一期默认不因缺失自动物理删除。
|
||||
|
||||
源状态、本地手动锁定和凭据激活分开保存。有效状态要求源有效、无本地锁定且凭据已激活。显式源停用后撤销会话、阻止续签及发布,保留相册和上传历史;上游恢复不能解除本地锁定,重新激活策略待确认。接口中断时已有本地有效账号可继续登录,但后台显示资料可能过期及同步告警。
|
||||
|
||||
上游凭据仅存国内服务端,不发送 App 或海外,不写入日志。后台仅有授权管理员可触发同步和查看脱敏错误。此任务与国内到海外展示元数据同步是两条不同链路,用户密码和用户资料明细不随展示副本推送海外。
|
||||
|
||||
### 3.2 资源权限
|
||||
|
||||
每个写操作以用户、相册角色、照片上传人共同判断。照片更新应查询 photo.album_id 和 uploader_id,不接受客户端声明的归属作为授权依据。
|
||||
|
||||
上传初始化、续签和完成确认均检查权限。协作者被移除后,已有对象可上传但不得公开;短期权限自然过期,后台登记孤立对象供人工或策略清理。
|
||||
|
||||
公开 API 只返回公开相册中的已发布照片,不泄露工作人员信息、原始存储键或内部任务错误堆栈。匿名访问标识只用于限速与幂等辅助,不代表真实观众身份。
|
||||
|
||||
转移相册负责人通过管理员事务操作:验证目标有效账号,更新 owner_id 和成员角色,按明确选择保留或移除原负责人,记录审计;不修改 photos.uploader_id。账号停用只撤销权限,不删除历史图片。
|
||||
|
||||
## 4 数据模型
|
||||
|
||||
建议所有主键使用全局唯一 ID,时间存 UTC,活动额外保存 IANA 时区。大整数计数通过明确接口类型传输,避免前端精度隐患。
|
||||
|
||||
| 表 | 主要字段 | 约束与用途 |
|
||||
|---|---|---|
|
||||
| users | id account password_hash source_status local_locked credential_status role locale | account 唯一,源状态与本地权限隔离 |
|
||||
| external_user_mappings | user_id source_system external_user_id source_version | 外部用户组合唯一,稳定映射内部 ID |
|
||||
| user_sync_runs | id trigger status cursor counts started_at finished_at | 定时及手动同步批次记录 |
|
||||
| user_sync_errors | run_id external_user_id error_code retry_state | 失败可见并可补偿 |
|
||||
| user_sync_state | source_system success_cursor snapshot_version last_success_at | 只更新已确认成功水位 |
|
||||
| sessions | user_id token_hash expires_at revoked_at | 支持注销与撤销 |
|
||||
| albums | id public_token owner_id start_at end_at timezone status default_locale upload_enabled public_enabled download_enabled original_download_enabled watermark_version | public_token 唯一,上传公开下载开关独立 |
|
||||
| album_translations | album_id locale title description venue address | album_id 与 locale 唯一 |
|
||||
| album_members | album_id user_id role status | 成员组合唯一 |
|
||||
| categories | id album_id sort_order | 归属于相册 |
|
||||
| category_translations | category_id locale name | 支持名称回退 |
|
||||
| album_assets | album_id kind locale object_key | 宣传图、封面和 Logo |
|
||||
| photos | id album_id uploader_id category_id object_key bytes width height shot_at uploaded_at published_at processing_state visibility like_count | 使用独立图片 ID,原图不覆盖 |
|
||||
| photo_variants | photo_id variant object_key bytes version | 缩略图、浏览图和水印图 |
|
||||
| upload_sessions | id user_id album_id client_task_id object_key expected_bytes multipart_id state expires_at | 用户与 client_task_id 唯一 |
|
||||
| photo_jobs | id photo_id type state attempt next_retry_at | 持久任务源记录 |
|
||||
| like_batches | photo_id client_session_id batch_id delta created_at | 会话批次唯一,防网络重试重复累计 |
|
||||
| outbox_events | id album_id type payload created_at delivered_at | 与业务事务共同提交 |
|
||||
| audit_logs | actor_id action resource_id metadata created_at | 管理、权限及删除操作可追踪 |
|
||||
| embed_slots | id public_token album_id enabled config_version settings | 网站绑定展示位,后台切换相册 |
|
||||
| embed_slot_members | slot_id user_id permission | 管理员授予展示位管理权 |
|
||||
| watermark_jobs | id album_id config_version state progress | 历史衍生图明确发起重生成 |
|
||||
| album_stats | album_id view_count updated_at | 相册累计浏览次数,数据库持久化 |
|
||||
| album_visit_sessions | album_id visitor_id last_counted_at | 相册与持久浏览器标识唯一,用于滚动窗口判断 |
|
||||
| album_visit_requests | album_id visitor_id request_id counted created_at | 请求唯一,重试返回原结果 |
|
||||
|
||||
点赞批次增长快,应按时间分区或设置去重窗口。窗口长度属于部署参数;过期批次请求应拒绝或校准,不能在删除去重记录后允许旧批次重新计数。
|
||||
|
||||
主要索引:公开列表按相册、可见性、发布时间、ID;照片管理按相册、上传人、分类、上传时间;热门按相册、可见性、点赞数、ID;任务按状态与下次重试时间。
|
||||
|
||||
## 5 OSS 上传与图片发布
|
||||
|
||||
### 5.1 对象组织
|
||||
|
||||
建议在现有 Bucket 下创建独立前缀,实际前缀在部署时确认:
|
||||
|
||||
```text
|
||||
photo-live/{env}/staging/{uploadId}/{photoId}.{ext}
|
||||
photo-live/{env}/albums/{albumId}/originals/{photoId}.{ext}
|
||||
photo-live/{env}/albums/{albumId}/variants/{photoId}/{version}/{variant}.{ext}
|
||||
photo-live/{env}/albums/{albumId}/assets/{assetId}.{ext}
|
||||
```
|
||||
|
||||
文件名不作为对象唯一标识。相同文件名的两张照片使用不同 photoId。重试同一任务继续使用相同上传会话,不能重新创建照片。内容哈希可用于提示重复,但不在未确认规则前静默合并不同人员或不同分类的照片。
|
||||
|
||||
### 5.2 授权与完成核验
|
||||
|
||||
1. App 提交相册、分类、文件大小、类型和 clientTaskId。
|
||||
2. API 校验身份、权限和限额,预创建上传会话及照片 ID。
|
||||
3. 返回 staging 对象范围的临时 STS 凭证或签名上传请求;客户端不得写入 originals 和 variants 路径,长期密钥只在服务端使用。
|
||||
4. App 通过平台后台任务传输。普通照片可以单文件上传,失败重新传输该文件;分片恢复作为大文件方案验证,不把不同文件的队列恢复误称为字节级断点续传。
|
||||
5. App 完成通知或 OSS 通知到达服务端,两种通知均可重复,处理必须幂等。
|
||||
6. API 核验对象、字节数和可用的完整性信息,不单独依赖客户端“成功”字段;分片 ETag 不能直接当作文件 MD5。
|
||||
7. 固定经核验的源版本或使用条件复制,将对象转入客户端不可写的 originals 路径,再以事务写入处理任务与事件源记录。失败重试复用相同正式目标,核验已经存在的文件,不覆盖。
|
||||
|
||||
授权过期先更新授权再重试,禁止把失败标为成功。iOS 长时间后台下如何获取新授权是技术验证重点,不假设 Dart 进程永远能运行。
|
||||
|
||||
### 5.3 原图保护与重试
|
||||
|
||||
staging 上传请求采用可验证的禁止覆盖约束;签名请求固定必要参数,或使用可验证的权限策略,不能仅依赖客户端自觉添加请求头。普通上传与分片完成分别验证约束。遇到对象已存在时比较会话期望大小和完整性信息,再确认已有结果,不直接重写。
|
||||
|
||||
正式原图只允许服务端受控创建。复制过程必须固定源版本,或使用源条件校验并在目标侧复核,避免核验与复制之间源文件变化。服务端写入成功而数据库事务失败时,补偿核验同一正式对象并重试入库,不生成多张照片。
|
||||
|
||||
### 5.4 图片处理
|
||||
|
||||
处理进程验证真实图片类型、解码大小、方向和格式,生成不同尺寸浏览图。JPEG、PNG 为基础输入;HEIC 解码转码须用真实 iPhone 样本验证。原图长期保留,衍生版本不覆盖原图。
|
||||
|
||||
浏览图建议去除定位等不必要 EXIF 信息。原图下载仍可能包含原始元数据,下载策略在产品配置中明确。
|
||||
|
||||
处理成功后以事务切换公开状态并产生 photo.published 事件。重试处理使用确定的版本键,避免产生多套无记录资源。失败采用有限自动重试、错误归类和人工重试入口。
|
||||
|
||||
### 5.5 状态分离
|
||||
|
||||
```text
|
||||
本地任务
|
||||
preparing → queued → uploading → uploaded → confirming → completed
|
||||
└ preparation_failed → preparing
|
||||
└ waiting_network / retryable_failed / cancelled
|
||||
|
||||
服务端处理
|
||||
pending_upload → uploaded → processing → ready
|
||||
└ processing_failed → processing
|
||||
|
||||
对外可见性
|
||||
unpublished → published ↔ hidden → deleted
|
||||
```
|
||||
|
||||
本地 completed 表示传输和确认完成;页面另外显示服务端处理中或已公开状态。deleted 是软删除状态,物理删除走独立流程。
|
||||
|
||||
### 5.6 补偿与长期保存
|
||||
|
||||
定期扫描确认超时会话、已入库但未入队任务、处理超时任务以及未发送事件。重试带退避和次数限制,最终失败进入运维可见列表。
|
||||
|
||||
现有 OSS 生命周期不得自动清理正式原图。分片残留、临时缓存和正式文件分路径处理;任何生命周期变更应先确认影响范围。对象存储持久性不替代误删除恢复策略,版本控制或备份及成本需要部署评审。
|
||||
|
||||
## 6 Flutter 后台上传
|
||||
|
||||
### 6.1 共同设计
|
||||
|
||||
Flutter 负责界面、权限交互和任务展示;原生层负责平台传输。使用本地数据库保存任务 ID、账号、相册、源文件位置、系统任务标识、重试信息及服务端会话。
|
||||
|
||||
连续拍摄每成功生成一张源文件就创建 preparing 任务,目标相册与分类在任务创建时固定;切换相册仅影响新任务。
|
||||
|
||||
每个文件先完成云端源文件获取并复制到受管理目录,确认空间和读权限后入队。准备失败单独记录,不伪装成上传失败;准备阶段不能保证在后台无限执行。账号切换时隔离队列;任务源文件仅在成功确认或明确取消后按策略清理。批量并发设为可配置小值,技术验证后确定,不能为上百张图片同时打开传输。
|
||||
|
||||
服务端返回已建立上传会话的历史,设备本地保留未准备和未上传队列。另一设备可查看服务端历史,但不能恢复原设备未传文件;接口返回 sourceDeviceId 或等效来源信息避免误导。
|
||||
|
||||
### 6.2 iOS
|
||||
|
||||
采用原生 background URLSession 文件上传,通过 Flutter 插件或自建平台桥接维护任务映射。后台文件传输由系统调度,不能用 Dart Timer 或常驻进程模拟。
|
||||
|
||||
后台上传使用实际文件;传输完成后持久记录原生回调,在系统允许的执行窗口内完成确认,必要时由服务端对象通知与补偿推动发布。重新启动时重连系统会话并校准本地任务。
|
||||
|
||||
用户强制退出后系统不会自动重新启动应用承接该类事件,需在下次启动恢复。低电量、网络调度等可能延迟执行,UI 应明确显示等待而非承诺固定后台完成时间。[Apple 后台传输说明](https://developer.apple.com/documentation/foundation/downloading-files-in-the-background)、[后台会话配置](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/background(withidentifier:))。
|
||||
|
||||
### 6.3 Android
|
||||
|
||||
按目标 Android 版本采用适用的用户发起数据传输任务;兼容路径结合系统任务调度和符合要求的前台服务通知。具体选择以目标 SDK、设备版本和原生验证结果为准。普通可延迟工作与用户正在等待的批量上传应区别处理。
|
||||
|
||||
需验证主流厂商后台管理、通知权限、网络切换和用户停止任务的行为。WorkManager 等调度机制本身不构成无限连续上传保证。[Android 数据传输选型](https://developer.android.com/develop/background-work/background-tasks/data-transfer-options)、[用户发起的数据传输任务](https://developer.android.com/develop/background-work/background-tasks/uidt)。
|
||||
|
||||
### 6.4 技术验证用例
|
||||
|
||||
批量 100 张真实照片作为起始样本,覆盖前台切后台、锁屏至少 15 分钟、网络中断与恢复、凭证过期、强制退出后重开、源文件缺失和存储不足。数字为验证建议,记录设备、系统、文件大小、成功数、恢复行为和重复记录数。
|
||||
|
||||
## 7 观众列表与全屏看图
|
||||
|
||||
相册关闭上传后拒绝新任务和续签,尚未完成核验的上传等待重新开放或管理员处理;已经核验的处理任务继续。处理 ready 与相册是否公开分离,公开查询要求 publicEnabled 且照片为 published,重新公开不恢复 hidden 或 deleted。
|
||||
|
||||
公开链接采用不可枚举的 publicToken,但不作为秘密身份凭证;持有链接者均可访问。所有公开接口仍检查相册在线状态和照片可见性。
|
||||
|
||||
使用游标分页,最新列表按 published_at 与 id 稳定排序。每页建议 30 至 60 张,最终以手机内存和网络测试调整。缩略图携带宽高以预留位置,减少加载跳动。
|
||||
|
||||
热门列表的点赞数会变化,建议使用带版本的短期排序快照。大图打开后保持已建立的浏览序列,补取相邻页面时沿用同一快照;主动刷新才切换新排序。
|
||||
|
||||
大图按需加载高清版本,只预取有限相邻图片;关闭时保留滚动锚点。单图分享路径建议使用相册 token 加 photoId,服务端验证两者归属。隐藏或删除照片的旧链接返回本地化不可用提示。
|
||||
|
||||
下载接口检查相册下载配置,返回短期有效的原图或允许版本链接。私有 OSS 的签名 URL 是短期访问凭证,不能写入永久分享链接。相册下线停止新授权,已签发链接在有效期内可能仍可访问,应在缓存和签名策略中记录这一边界。
|
||||
|
||||
### 7.1 相册浏览次数
|
||||
|
||||
浏览次数为已确认必做项。建议采用同一浏览器匿名标识同一相册 30 分钟内只累计一次的规则,窗口参数需业务确认,不将结果描述为去重人数。
|
||||
|
||||
观众页成功加载公开相册且文档可见后,提交独立访问上报请求;该接口不由照片列表读取、轮播、实时重连或图片加载自动触发。单图链接通过同一相册入口逻辑上报。后台预览使用独立预览路由,不调用计数接口。
|
||||
|
||||
服务端事务内先对 requestId 去重,再锁定或原子创建 album_visit_sessions 行,按服务端时间判断距离上次计数是否超过窗口。允许计数时原子增加 album_stats.view_count 并更新 last_counted_at;重复或窗口内请求仅返回最新总数。并发两个首次请求必须依靠唯一约束与事务重试保证只加一次。
|
||||
|
||||
返回 viewCount 和 counted 标志,观众页用权威值更新显示。GET 相册信息也返回 viewCount,便于首屏展示;失败重试复用 requestId。长停留不通过定时器增加次数。
|
||||
|
||||
会话与请求去重数据按确认的保留策略清理,旧请求过期后拒绝重放;累计总数长期保留。浏览标识使用同源随机持久浏览器标识,跨标签页共享;写入采用跨标签协调并读回一致值,无法持久存储时降级为当前页面标识,不要求登录,不使用高精度设备指纹。无法彻底识别机器人或清除标识后的同一人,因此仅提供基础限速并明确统计口径。
|
||||
|
||||
### 7.2 下载与水印版本
|
||||
|
||||
下载需要 downloadEnabled,原图还要求 originalDownloadEnabled。接口在签发时检查两级权限;禁止下载是产品控制,不能阻止保存已经展示的浏览图片。
|
||||
|
||||
水印配置每次修改生成新版本,默认只作用新照片。历史重生成由显式任务执行,绑定配置版本,逐张生成后原子切换 variants 引用,失败保留旧图。原图永远不被水印任务覆盖,旧衍生图清理独立执行。
|
||||
|
||||
### 7.3 全球 CDN 与访问失效
|
||||
|
||||
全球 CDN 为必做项,分别配置私有 OSS 回源授权与观众 URL 鉴权,并验证缓存键不会因去掉鉴权参数而泄露未授权资源。短期链接过期时客户端重新请求允许版本,禁止永久保存签名 URL。
|
||||
|
||||
隐藏或相册下线停止签发、关闭公开订阅并通知在线客户端清除对应内容;组件轮询下次更新时收敛。已有缓存和签发 URL 在既定窗口内失效,窗口需测试确认,不能承诺瞬时撤回所有资源。已有下载不在可撤回范围内。
|
||||
|
||||
## 8 点赞与实时广播
|
||||
|
||||
### 8.1 匿名连续点赞
|
||||
|
||||
客户端为浏览会话生成随机 sessionId,每一组待提交点击生成 batchId,提交 photoId、delta 和批次 ID。delta 有合理上限,服务端检查照片公开状态及基础频率限制,但不限制每人只能点赞一次。
|
||||
|
||||
服务端在同一事务内:插入唯一批次 → 原子增加 photos.like_count → 插入 outbox 事件。批次冲突时返回已确认结果,不重复增加。页面本地动画即时响应,最终以服务端确认的计数为准,失败时校准暂存增量。
|
||||
|
||||
数据库是累计计数的权威来源,不能仅将点赞数留在易失缓存。客户端提供的 delta 不能证明真实点击,只能通过上限与速率约束滥用;第一版计数是互动指标,不用于奖项或结算。
|
||||
|
||||
### 8.2 事件契约
|
||||
|
||||
```json
|
||||
{
|
||||
"eventId": "unique-event-id",
|
||||
"type": "photo.likes.updated",
|
||||
"albumId": "album-id",
|
||||
"photoId": "photo-id",
|
||||
"delta": 5,
|
||||
"total": 128,
|
||||
"countVersion": 128,
|
||||
"occurredAt": "2026-09-17T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
事件类型至少包含 photo.published、photo.hidden、photo.deleted、photo.likes.updated 和 album.updated。计数单调递增时可使用总数作为比较版本;如未来支持人工修正,应改为独立版本字段。客户端忽略过期版本、重复 eventId,不把同一广播再次加到本地总数。
|
||||
|
||||
### 8.3 广播与动画
|
||||
|
||||
实时订阅按相册隔离,服务端验证公开状态后加入对应频道。高频事件可按短时间窗和照片合并,广播最新 total 与累计 delta。客户端只展示有限数量的动画,动画数不等于点赞数。
|
||||
|
||||
发送失败可从 outbox 重试,消费者按 eventId 去重。广播可采用非持久实时通道;断线后通过 API 获取权威列表和计数,不要求重播气泡。
|
||||
|
||||
每个页面用 originPageId 与 batchId 关联本地已播放动画,广播仅包含随机临时标识,不暴露身份。合并广播保留源批次贡献或等效去重信息,页面只抑制自己的部分。后台页面不排队动画,前台恢复只校准计数;减少或关闭动画不改变请求与计数。广播发送和客户端展示前过滤已隐藏或删除照片。实时不可用时降级轮询,点赞请求仍经 API 确认。
|
||||
|
||||
## 9 JS 悬浮组件
|
||||
|
||||
下面的域名为占位示例,部署后由后台生成真实地址,不可直接作为可用代码交付。
|
||||
|
||||
```html
|
||||
<script
|
||||
src="https://YOUR-DOMAIN.example/embed/v1/loader.js"
|
||||
data-slot="PUBLIC_SLOT_TOKEN"
|
||||
data-lang="auto"
|
||||
data-position="right"
|
||||
data-interval="5000"
|
||||
defer>
|
||||
</script>
|
||||
```
|
||||
|
||||
展示位绑定相册,管理员管理全局展示位并授权操作人员,创建者只能将有管理权限的相册绑定到获授权展示位。切换产生 configVersion,不修改官网脚本。
|
||||
|
||||
加载器读取当前脚本配置,防止重复初始化,创建固定位置的 iframe。轮播页面部署在项目域名内,通过只读公开 API 获取近期照片;宿主站点无需接触内部业务 API 或工作人员令牌。
|
||||
|
||||
组件高度、收起等通信通过 postMessage,双方验证消息来源与允许的 origin,不接受任意页面发送的尺寸或跳转命令。iframe 配置只启用所需能力。宿主需允许 script-src 与 frame-src,嵌入页需配置允许的 frame-ancestors;不能笼统移除全站安全策略。
|
||||
|
||||
加载器版本路径长期稳定;内部轮播页面可独立更新。默认只拉取近期有限照片,新照片加入队列、旧照片淘汰;第一版定期拉取配置和近期照片,隐藏内容在下次成功更新时移除,不依赖点赞 WebSocket。轮询周期及失效上限在验收前确定;不可见时暂停,恢复时校准。切换配置后取消旧请求,比较 configVersion 丢弃晚到响应。用户关闭后记住当前会话选择,不强制反复弹出。
|
||||
|
||||
点击图片使用用户手势打开新标签页,链接附 photoId 与当前语言,并设置适当的 opener 隔离。移动端缩小或收起的具体尺寸在原型阶段确认。
|
||||
|
||||
## 10 接口草案
|
||||
|
||||
以下是资源边界草案,正式开发以 OpenAPI 契约为准。公开读取接口不接受工作人员写权限。
|
||||
|
||||
| 方法与路径 | 权限 | 用途 |
|
||||
|---|---|---|
|
||||
| POST /v1/auth/password | 登录入口 | 密码登录 |
|
||||
| POST /v1/auth/refresh | 有效刷新凭据 | 刷新会话 |
|
||||
| POST /v1/admin/users/{id}/activate | 管理员 | 激活已同步用户并初始化本地凭据 |
|
||||
| POST /v1/admin/user-sync/runs | 管理员 | 手动触发补拉,与定时任务共用锁 |
|
||||
| GET /v1/admin/user-sync/runs | 管理员 | 查看同步状态、历史和脱敏错误 |
|
||||
| PATCH /v1/admin/users/{id} | 管理员 | 本地停用、角色管理;上游字段只读 |
|
||||
| POST /v1/albums | 工作人员 | 创建相册 |
|
||||
| PATCH /v1/albums/{id} | 创建者或管理员 | 配置语言、上传公开和下载开关 |
|
||||
| POST /v1/admin/albums/{id}/transfer | 管理员 | 转移负责人并明确旧负责人角色 |
|
||||
| POST /v1/albums/{id}/photos/hide | 对应照片管理权限 | 批量隐藏,逐项返回结果 |
|
||||
| POST /v1/albums/{id}/watermark-jobs | 创建者或管理员 | 明确发起历史衍生图重生成 |
|
||||
| POST /v1/admin/embed-slots | 管理员 | 创建展示位并配置管理权限 |
|
||||
| PATCH /v1/embed-slots/{id} | 展示位权限及目标相册管理权限 | 切换相册或配置 |
|
||||
| GET /v1/public/embed-slots/{token} | 匿名 | 公开配置版本、相册和近期照片 |
|
||||
| POST /v1/albums/{id}/members | 创建者或管理员 | 添加已开通同事 |
|
||||
| DELETE /v1/albums/{id}/members/{userId} | 创建者或管理员 | 移除成员 |
|
||||
| POST /v1/uploads | 相册上传权限 | 幂等初始化任务 |
|
||||
| POST /v1/uploads/{id}/renew | 任务本人及有效权限 | 更新临时授权 |
|
||||
| POST /v1/uploads/{id}/complete | 任务本人及有效权限 | 核验并进入图片处理 |
|
||||
| GET /v1/uploads | 工作人员本人 | 获取服务端任务记录 |
|
||||
| PATCH /v1/photos/{id} | 对应照片管理权限 | 分类或隐藏 |
|
||||
| DELETE /v1/photos/{id} | 对应照片管理权限 | 软删除 |
|
||||
| GET /v1/public/albums/{token} | 匿名 | 活动信息及语言版本 |
|
||||
| POST /v1/public/albums/{token}/visits | 匿名及限速 | 幂等登记有效浏览,返回累计浏览次数 |
|
||||
| GET /v1/public/albums/{token}/photos | 匿名 | 分类排序游标分页 |
|
||||
| GET /v1/public/albums/{token}/photos/{id} | 匿名 | 单图定位 |
|
||||
| POST /v1/public/albums/{token}/photos/{id}/likes | 匿名及限速 | 批次幂等点赞 |
|
||||
| GET /v1/public/albums/{token}/photos/{id}/download | 匿名及下载设置 | 获取允许版本的短期链接 |
|
||||
|
||||
写操作携带幂等键或资源版本,错误返回稳定 code 和 requestId,显示文案在客户端按语言映射。日志不得记录密码、完整签名 URL、STS 密钥或用户接口凭据。
|
||||
|
||||
## 11 多语言实施
|
||||
|
||||
Flutter 使用本地化资源文件,Web 使用对应语言字典,共用词条命名约定但不要求直接共用不同运行时格式。构建时检查缺失键与插值参数。
|
||||
|
||||
语言优先级为手动选择、账号已存偏好、设备或浏览器、系统回退。匿名观众不要求账号偏好。活动内容按请求语言、活动默认语言顺序返回,并携带实际返回语言供界面判断。
|
||||
|
||||
UTC 时间与活动时区分开存储;展示时格式化,不随用户语言隐式改变活动时区。英文长文本和中文字体都纳入页面及组件尺寸验证。
|
||||
|
||||
## 12 部署与运维
|
||||
|
||||
### 12.1 部署前置
|
||||
|
||||
- 确认 OSS 地域、现有规则、项目前缀、RAM 最小权限和跨域需求;不直接改动其他业务配置。
|
||||
- 确认域名、TLS、用户接口鉴权和网络可达性。
|
||||
- 配置数据库、Redis、任务进程、实时入口及反向代理的连接超时。
|
||||
- iOS 构建需要 macOS、Xcode、签名与确定的分发方式;Android 配置签名和安装分发。
|
||||
- secrets 注入环境或密钥管理服务,不进入版本库。生产与测试凭证隔离。
|
||||
|
||||
### 12.2 发布步骤
|
||||
|
||||
1. 数据库备份,执行兼容旧版本的新增迁移。
|
||||
2. 部署 API 与处理进程,检查健康、授权和测试照片完整链路。
|
||||
3. 部署观众页、后台、组件及实时入口,核对静态资源版本。
|
||||
4. 对测试相册验证登录、后台上传、自动公开、连续点赞、分享下载、组件和双语言。
|
||||
5. 按确定方式分发 App;服务端保持必要的旧客户端兼容。
|
||||
6. 观察错误率、队列积压、上传失败、计数差异和资源用量,达到发布门槛后扩大使用。
|
||||
|
||||
### 12.3 监控
|
||||
|
||||
配置项目与相册容量和流量告警,明确阈值、统计延迟和接收人,日志与临时碎片单独制定保留周期;告警不自动删图或停止活动。
|
||||
|
||||
记录 API 时延与错误率、上传完成率、处理等待时长、失败任务数、实时连接数、广播延迟、数据库计数写入失败、OSS 容量和出网流量。业务日志使用 requestId、uploadId、photoId 关联。
|
||||
|
||||
图片处理异常时保留原图和待处理状态,不显示坏图;实时故障时降级轮询;API 不可用时客户端保留上传队列并退避重试。避免无限重试产生费用和负载。
|
||||
|
||||
### 12.4 备份和回滚
|
||||
|
||||
数据库定期备份并演练恢复。上传原图与业务元数据共同决定可恢复性,应核对数据库引用和 OSS 对象清单。建议的恢复点与恢复时间指标在部署资源评审后确定。
|
||||
|
||||
发布回滚优先恢复旧应用镜像和静态资源,数据库使用向前兼容迁移,避免自动执行破坏性逆迁移。停止有缺陷的处理进程后保留任务,修复后重放。永久删除、覆盖原图或生命周期变更不应作为日常回滚步骤。
|
||||
|
||||
### 12.5 跨地域发布与验收
|
||||
|
||||
先部署海外接收接口和数据库,验证鉴权与协议版本,再做首次快照、增量追赶及对账,达到同步水位后开放观众域名。后续升级采用兼容事件版本,先升级接收端再启用发送端新字段;回滚保留 inbox、outbox、水位和墓碑,不清空同步状态。
|
||||
|
||||
全球 CDN 设置国内 OSS 回源、观众 URL 鉴权和缓存规则,分别测目标地区冷缓存新图、命中浏览图与原图下载。只对必要浏览资源按测试结果选择预热,不能把预热等同于所有节点瞬时就绪。来源参数、签名和缓存键一起测试,下载关闭与隐藏失效需要在海外链路验收。
|
||||
|
||||
新增监控:内容同步积压与延迟、统计回传水位、墓碑对账、资源探测失败、海外 API 和 CDN 地域时延。告警区分国内图片处理慢、跨境同步慢、海外 API 慢和 CDN 回源慢。
|
||||
|
||||
必须演练断网重放、重复乱序、首次快照与增量并发、下线优先传递、海外紧急停用、统计重复回传以及国内恢复。两地分别备份并验证恢复后数据权威不会倒置。
|
||||
|
||||
## 13 实施启动清单
|
||||
|
||||
- [ ] 确认海外地域、观众域名、全球 CDN、同步周期和海外紧急停用负责人。
|
||||
- [ ] 确认建议技术栈、首发语言和目标系统版本。
|
||||
- [ ] 确认用户接口字段、同步周期、停启用和初始密码规则。
|
||||
- [ ] 获取 OSS 非敏感配置,建立项目隔离路径和权限。
|
||||
- [ ] 准备双平台真机、macOS 构建环境及 App 分发资质。
|
||||
- [ ] 完成后台上传和过期授权验证,再选择插件或自建原生桥接。
|
||||
- [ ] 冻结数据表、接口契约、事件格式和状态定义。
|
||||
- [ ] 对照需求 R01 至 R31 建立任务,对照 A01 至 A29 建立验收记录。
|
||||
- [ ] 试运行前确认容量、下载默认值、删除策略和监控负责人。
|
||||
|
||||
## 14 技术参考
|
||||
|
||||
以下为技术方案核验入口,访问日期为 2026-09-17。实际实现前结合目标系统版本复核;本文不承诺任何插件已满足全部后台场景。
|
||||
|
||||
- [Apple 后台文件传输](https://developer.apple.com/documentation/foundation/downloading-files-in-the-background)
|
||||
- [Apple 后台会话配置与强制退出边界](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/background(withidentifier:))
|
||||
- [Android 数据传输任务选择](https://developer.android.com/develop/background-work/background-tasks/data-transfer-options)
|
||||
- [Android 用户发起的数据传输](https://developer.android.com/develop/background-work/background-tasks/uidt)
|
||||
- [阿里云 OSS STS 临时授权](https://www.alibabacloud.com/help/en/oss/sts-temporary-access-authorization)
|
||||
- [阿里云上传临时授权方式](https://www.alibabacloud.com/help/en/oss/developer-reference/authorize-access-6)
|
||||
- 用户接口文档:待接口提供方交付,第一期不引用飞书接口。
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# 图片直播项目优化方案
|
||||
|
||||
版本:V1.3
|
||||
日期:2026-09-17
|
||||
关联文档:[需求说明书](./01-需求说明书.md) · [项目计划](./02-项目计划.md) · [技术实施方案](./03-技术实施方案.md)
|
||||
|
||||
本次优化补齐现场操作、异常恢复、文件保护、统计口径和运营维护,并降低第一版部署复杂度。以下优化纳入第一版文档基线;语言范围、时间窗口、容量、设备兼容和工期仍需验证,不视为已经实现或测试通过。
|
||||
|
||||
## 1 优化范围与优先级
|
||||
|
||||
P0 表示必须在核心开发前明确或验证,P1 表示第一版交付前完成。优先级用于实施排序,不代表 P1 自动延期。
|
||||
|
||||
| 编号 | 优化内容 | 优先级 | 需求映射 | 任务映射 | 验收映射 |
|
||||
|---|---|---|---|---|---|
|
||||
| O01 | 连续拍摄并自动入队,清楚显示目标相册 | P1 | R17 | T20 | A15 |
|
||||
| O02 | 上传开关与公开开关独立,支持批量隐藏 | P0 | R18 | T20 | A16 |
|
||||
| O03 | 管理员转移相册负责人,保留历史归属 | P1 | R19 | T20 | A17 |
|
||||
| O04 | 下载权限拆分与水印版本规则 | P0 | R20 | T20 T22 | A18 |
|
||||
| O05 | 照片准备阶段与本地服务端记录分离 | P0 | R21 | T21 | A19 |
|
||||
| O06 | 原图防覆盖及受控图片访问 | P0 | R22 | T22 | A20 |
|
||||
| O07 | 明确匿名浏览统计和跨标签页去重 | P0 | R16 R23 | T19 | A14 |
|
||||
| O08 | 气泡去重、后台暂停和减少动画 | P1 | R24 | T24 | A21 |
|
||||
| O09 | JS 绑定展示位,后台切换相册 | P1 | R25 | T23 | A22 |
|
||||
| O10 | 容量流量告警与组件节省流量 | P1 | R26 | T17 T23 | A23 |
|
||||
| O11 | API 与实时入口合并部署,任务进程独立 | P0 | 实施约束 | T02 | A24 |
|
||||
| O12 | 修正任务依赖并按岗位检查工期 | P0 | 管理约束 | 项目计划 | 里程碑评审 |
|
||||
|
||||
## 2 现场拍摄与照片准备
|
||||
|
||||
### 2.1 连续拍摄
|
||||
|
||||
工作人员先选择相册和分类,再进入连续拍摄模式。每次拍摄成功后自动创建准备任务,不要求逐张跳回列表确认。拍摄页常驻显示相册名称、分类、待上传数量和失败提示。
|
||||
|
||||
切换相册只影响后续照片,已入队任务的目标不可隐式修改。每张照片都有独立任务 ID;拍摄成功但写入本地失败时必须明确提示,不能显示“已加入队列”。
|
||||
|
||||
### 2.2 准备阶段
|
||||
|
||||
新增“准备中”和“准备失败”,涵盖系统相册文件获取、云端照片下载、本地空间、格式检查和复制。准备完成后才进入可后台传输的队列。
|
||||
|
||||
准备任务和上传任务分别统计,例如“准备 5 张,等待上传 20 张”。准备阶段不承诺可以无限后台执行;切后台前未准备完成的照片应保留可恢复状态,必要时提示回到 App。
|
||||
|
||||
本地任务包含实际文件和系统传输信息;服务端记录包含上传确认、发布结果和历史信息。换手机后可查看服务端历史,不能恢复另一台手机尚未上传的文件。新手机重新选择同一文件需明确创建新任务或进行重复提示。
|
||||
|
||||
## 3 相册控制与人员交接
|
||||
|
||||
相册采用独立的 uploadEnabled 和 publicEnabled 开关。关闭上传阻止新建任务和续签,已经完成核验的处理任务可继续处理;关闭公开立即停止公开 API 与实时订阅,新图片可处理就绪但不对外展示。
|
||||
|
||||
重新公开时,只展示符合照片可见性要求的图片,不自动恢复已隐藏或软删除的照片。活动结束时间本身不改变两个开关。
|
||||
|
||||
批量隐藏操作应返回逐项结果并记录操作者。观众列表、全屏图、轮播和待发送气泡均校验照片可见性;已有下载文件无法收回,缓存访问边界见第 5 节。
|
||||
|
||||
公司管理员可把负责人转移给有效工作人员,原上传人信息保持不变。原负责人是否保留协作者身份必须在转移操作中明确选择。停用账号不删除其历史照片,相册由管理员接管后再安排转移。
|
||||
|
||||
## 4 下载与水印
|
||||
|
||||
采用两个字段:downloadEnabled 和 originalDownloadEnabled。原图下载必须以允许下载为前提;关闭全部下载时隐藏下载操作并拒绝下载接口,但不能阻止截图或保存已展示的浏览图片。
|
||||
|
||||
允许下载且关闭原图时,提供当前配置允许的浏览版或水印版。原图始终保存,不把加水印图片覆盖到原图对象上。
|
||||
|
||||
水印配置带版本号。修改默认仅影响后续新照片;历史照片重生成由创建者或管理员明确发起,显示数量和进度,失败可重试。新衍生版本全部就绪前保留可用旧版本,更新单张照片引用必须原子化。旧版本清理通过独立策略执行,不在生成失败后直接删除。
|
||||
|
||||
## 5 文件不可变与访问链路
|
||||
|
||||
上传会话中的临时对象与已发布原图应有清楚边界。推荐先上传 staging 路径,服务端固定源版本或完成条件校验后复制到客户端无写权限的 originals 路径。后续图片处理只读取固定的原图对象。
|
||||
|
||||
仅随机文件名不够:同一会话的旧授权仍可能重写同一路径。上传路径应禁止覆盖,并对“对象已存在”执行完整性核验;分片完成与普通上传分别验证。不能只靠客户端自觉添加禁止覆盖参数,服务端签名或权限机制应能约束相应行为。
|
||||
|
||||
私有 OSS 和 CDN 回源授权、观众侧 CDN 访问鉴权是不同环节。接入 CDN 时必须确定签名、缓存键、有效期、刷新与隐藏后的失效方式。未采用 CDN 时也要处理短期 OSS 链接过期,长时间打开的大图可重新请求授权。
|
||||
|
||||
下线或隐藏后停止新签发链接、撤销频道并通知在线页面。对已签发 URL 和缓存定义可接受失效窗口,参数实测后确认;不能承诺未下载图片在所有缓存中瞬时消失。
|
||||
|
||||
参考:[OSS 禁止覆盖示例](https://github.com/aliyun/aliyun-oss-python-sdk/blob/master/examples/object_forbid_overwrite.py)、[CDN 与 OSS 访问说明](https://www.alibabacloud.com/help/en/cdn/use-cases/cdn-acceleration-oss-faq)。
|
||||
|
||||
## 6 浏览统计与互动
|
||||
|
||||
### 6.1 浏览次数
|
||||
|
||||
匿名观众使用同源持久浏览器标识,同一浏览器下多个标签页共享;标识创建发生并发时由服务端去重逻辑保护。同一浏览器、同一相册在建议的 30 分钟窗口内只计一次,窗口仍是待确认参数。
|
||||
|
||||
仅页面实际可见且相册信息加载成功后上报。翻图、语言切换、轮询、自动轮播、后台预览和资源请求不计数。超过窗口后的新页面访问可再计一次,原页面持续停留不定时增加次数。
|
||||
|
||||
这不是可靠的真实人数统计;不同浏览器、隐私模式或清除记录可能重新计数。浏览器禁止持久存储时降级为当前页面标识,明确降低去重精度,不影响浏览。
|
||||
|
||||
### 6.2 点赞与气泡
|
||||
|
||||
点击者先播放本地动画。服务端确认与广播通过源批次标识关联,当前页面忽略自己已经播放的批次;其他页面照常展示。若合并事件同时含自己和他人的点击,只抑制自己的部分。
|
||||
|
||||
后台标签页不积压动画,恢复后只同步最新计数。照片隐藏后,待发送和待展示的气泡都要过滤。支持关闭气泡或减少动画,不改变点赞数,也不影响其他观众。
|
||||
|
||||
## 7 网站展示位
|
||||
|
||||
官网接入时绑定 slotId,后台配置展示位指向哪个相册。更换活动时修改绑定即可,无需改动网站代码。组件只暴露公开配置,不携带管理权限。
|
||||
|
||||
展示位包括名称、启用状态、相册、默认语言、尺寸、位置和轮播参数。管理员管理全局展示位,创建者只能绑定有管理权限的相册到被授权的展示位,不能覆盖他人官网配置。
|
||||
|
||||
第一版用短周期轮询获取展示位配置和近期照片,周期参数经测试确定,不依赖点赞 WebSocket。切换相册时清空旧图与请求状态,避免旧响应覆盖新配置;后台返回配置版本供客户端校验。
|
||||
|
||||
组件页面不可见时暂停轮询与预取,恢复可见后重新校准。无图、相册下线或展示位停用时显示约定空态或收起;具体外观在原型确定。
|
||||
|
||||
## 8 部署与费用
|
||||
|
||||
V1.2 调整为国内管理服务与海外观众服务分别部署,海外 API 和 WebSocket 在同一服务进程内按模块组织,国内图片处理 worker 独立。保留未来独立实时进程的接口,不提前部署多个小服务。生产实例数量按可用性与压测结果配置。
|
||||
|
||||
费用模型至少区分原图、浏览版本、旧水印版本、请求、处理、下载流量、CDN、备份和日志。按十进制举例,1 万张 8 MB 原图约 80 GB;1 万观众每人浏览 100 张 300 KB 图片,约 300 GB 图片传输。这只是用量算例,不是价格估算,也未扣除浏览器缓存或计入原图下载。
|
||||
|
||||
设定项目及相册用量统计、费用或容量阈值与告警接收人。告警不自动删除照片,也不默认中断活动;是否需要限制原图下载或新增上传由管理员明确操作。
|
||||
|
||||
## 9 排期影响
|
||||
|
||||
V1.1 历史估算:原计划为 102 至 146 角色人日。增加 T20 至 T24 五个增量工作包,约 27 至 42 人日,修订为 129 至 188 人日。浏览统计精化并入 T19,部署简化并入 T02,费用告警并入 T17,不重复计入新增任务。
|
||||
|
||||
V1.1 当时采用 12 周基线;V1.2 增加国内海外同步和全球 CDN 验收后,当时为 151 至 222 人日、14 至 16 周估算,以 16 周安排为基线。岗位分配和任务依赖见项目计划,W2 技术验证后再次确认。
|
||||
|
||||
## 10 优化专项验收
|
||||
|
||||
- 连续拍摄时切相册不会改变已经入队照片的归属。
|
||||
- 云端照片未准备完、存储不足及源文件失效都有明确状态。
|
||||
- 锁屏批量上传遇到临时授权过期可恢复,无重复发布。
|
||||
- 停止公开后 API、频道、轮播和气泡行为一致。
|
||||
- 管理员转移负责人后旧照片上传人和审计信息保留。
|
||||
- 两个下载开关组合合法,历史水印重生成失败不破坏现有浏览图。
|
||||
- 旧凭证不能改变已发布原图,重试已有对象不会覆盖或误报成功。
|
||||
- 同浏览器多标签访问去重,自动轮播不增加浏览次数。
|
||||
- 本地气泡不会因自己的广播重复出现,减少动画不改变计数。
|
||||
- 展示位换相册不用修改官网,旧请求不能恢复旧相册内容。
|
||||
- 组件不可见时停止无效更新,流量与容量告警可验证。
|
||||
|
||||
## 11 仍待验证的参数
|
||||
|
||||
30 分钟浏览窗口、后台最长测试时长、图片格式与大小上限、上传并发、图片尺寸与质量、缓存失效窗口、轮询周期、用量阈值、目标 OS 版本及岗位实际投入。待定参数不阻止完成原型与技术试验,但必须在对应模块验收前冻结。
|
||||
|
||||
## 12 V1.2 海外访问增补
|
||||
|
||||
国内负责管理与上传,海外负责公开页面、查询、浏览点赞和气泡,现有国内 OSS 配合全球 CDN。图片文件不经跨境业务同步复制;国内向海外发送版本化展示元数据,海外向国内回传版本化统计快照。
|
||||
|
||||
新增 T25 至 T29,覆盖海外可行性、持久内容同步、统计回传、CDN 链路及故障验收。隐藏删除优先传递,定时对账补漏;权威数据按类型单点写入。V1.2 是海外架构变更记录;当前范围与估算见 V1.3 计划。
|
||||
|
||||
## 13 V1.3 账号与用户来源调整
|
||||
|
||||
第一期后台与 App 统一账号密码登录,飞书接入延期,不实现飞书授权或绑定。国内服务定期调用用户接口更新本地资料,支持任务记录、失败重试、手动补拉和稳定外部 ID 映射。
|
||||
|
||||
本地密码、权限和手动停用不受普通资料同步覆盖。新增用户激活、初始密码、同步周期和源停用字段需确认;空响应与部分分页失败不得误停用人员。T04 改为接口验证,新增 T30 同步管理,当前估算为 156 至 231 人日,16 周工作基线待接口验证后复核。
|
||||
@@ -0,0 +1,19 @@
|
||||
# 图片直播项目文档
|
||||
|
||||
版本:V1.3 · 更新日期:2026-09-17
|
||||
|
||||
本目录包含第一版项目需求、计划、技术实施与优化方案。建议按以下顺序阅读:
|
||||
|
||||
1. [需求说明书](./01-需求说明书.md):范围、角色、流程、功能和验收标准。
|
||||
2. [项目计划](./02-项目计划.md):工作分解、依赖、相对周次、工作量和交付门槛。
|
||||
3. [技术实施方案](./03-技术实施方案.md):架构、数据模型、接口、OSS、后台上传、实时互动、嵌入组件和部署。
|
||||
|
||||
4. [优化方案](./04-优化方案.md):12 项优化、需求任务验收映射、异常处理和排期影响。
|
||||
|
||||
5. [项目思维导图与计划导图](./diagrams/README.md):项目全景、业务流程、工作分解、里程碑和任务依赖,共七张图。
|
||||
|
||||
已确认的核心选型为 Flutter 双平台 App 与现有国内阿里云 OSS、全球 CDN、国内管理与海外观众服务。首发语种、服务端框架、测试容量和工期属于建议基线;优化已同步到原三份文档。项目计划按三名开发及兼职支持估算,约 156 至 231 角色人日,以 16 周排期为工作基线,需结合真实人员与技术验证结果修订。
|
||||
|
||||
当前只完成文档编制,尚未创建业务工程、接入用户接口或 OSS,也未执行产品功能与容量测试。
|
||||
|
||||
V1.3:第一期后台与 App 仅账号密码登录,飞书暂不接入;用户信息由指定接口定期拉取,密码与项目权限由本地管理。
|
||||
@@ -0,0 +1,60 @@
|
||||
mindmap
|
||||
root((公司图片直播项目))
|
||||
工作人员 App
|
||||
Flutter 双平台
|
||||
Android
|
||||
iOS
|
||||
登录与权限
|
||||
账号密码登录
|
||||
接口同步用户 不开放注册
|
||||
相册与协作
|
||||
创建配置 时间地点宣传图
|
||||
邀请同事 多人上传
|
||||
自己照片管理
|
||||
拍摄与上传
|
||||
连续拍摄和相册选图
|
||||
准备状态与上传队列
|
||||
后台锁屏上传与异常恢复
|
||||
本机队列与服务端历史
|
||||
管理后台
|
||||
定期拉取用户接口
|
||||
稳定 ID 幂等更新
|
||||
失败重试与手动补拉
|
||||
用户同步与账号激活停用
|
||||
全部相册与照片管理
|
||||
上传公开独立开关
|
||||
批量隐藏与负责人交接
|
||||
下载权限与水印版本
|
||||
网站展示位与用量告警
|
||||
观众网页
|
||||
扫码或分享链接免登录
|
||||
宣传图与活动信息
|
||||
浏览次数与跨标签去重
|
||||
瀑布流与分类
|
||||
全屏大图与左右滑动
|
||||
分享与按权限下载
|
||||
连续点赞与全相册气泡
|
||||
自身动画去重
|
||||
后台暂停与减少动画
|
||||
网站悬浮组件
|
||||
一段 JS 绑定展示位
|
||||
后台切换活动相册
|
||||
右侧悬浮轮播
|
||||
自动获取最新公开照片
|
||||
点击打开观众页
|
||||
不可见时暂停更新
|
||||
支撑服务
|
||||
内部身份与相册权限
|
||||
国内 OSS 直传和全球 CDN
|
||||
原图防覆盖与衍生图处理
|
||||
海外 API 与实时模块合并部署
|
||||
独立图片处理进程
|
||||
国内元数据推送与定时补漏
|
||||
海外浏览点赞与统计回传
|
||||
两地数据库 备份监控
|
||||
共用规则
|
||||
全端多语言
|
||||
无修图 无人工审核
|
||||
上传完成处理后自动公开
|
||||
隐藏删除与公开状态一致
|
||||
匿名计数不等于真实人数
|
||||
@@ -0,0 +1,109 @@
|
||||
# 图片直播项目全景导图
|
||||
|
||||
版本:V1.3 · 日期:2026-09-17
|
||||
依据:[需求说明书](../01-需求说明书.md) · [优化方案](../04-优化方案.md)
|
||||
相关:[业务流程](./02-业务流程.md) · [实施计划](./03-实施计划.md)
|
||||
|
||||
以下导图描述第一版规划范围,不表示功能已经实现。中文和英文为建议首发语种,服务端技术栈和容量参数待验证。
|
||||
|
||||
## 1 项目思维导图
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((公司图片直播项目))
|
||||
工作人员 App
|
||||
Flutter 双平台
|
||||
Android
|
||||
iOS
|
||||
登录与权限
|
||||
账号密码登录
|
||||
接口同步用户 不开放注册
|
||||
相册与协作
|
||||
创建配置 时间地点宣传图
|
||||
邀请同事 多人上传
|
||||
自己照片管理
|
||||
拍摄与上传
|
||||
连续拍摄和相册选图
|
||||
准备状态与上传队列
|
||||
后台锁屏上传与异常恢复
|
||||
本机队列与服务端历史
|
||||
管理后台
|
||||
定期拉取用户接口
|
||||
稳定 ID 幂等更新
|
||||
失败重试与手动补拉
|
||||
用户同步与账号激活停用
|
||||
全部相册与照片管理
|
||||
上传公开独立开关
|
||||
批量隐藏与负责人交接
|
||||
下载权限与水印版本
|
||||
网站展示位与用量告警
|
||||
观众网页
|
||||
扫码或分享链接免登录
|
||||
宣传图与活动信息
|
||||
浏览次数与跨标签去重
|
||||
瀑布流与分类
|
||||
全屏大图与左右滑动
|
||||
分享与按权限下载
|
||||
连续点赞与全相册气泡
|
||||
自身动画去重
|
||||
后台暂停与减少动画
|
||||
网站悬浮组件
|
||||
一段 JS 绑定展示位
|
||||
后台切换活动相册
|
||||
右侧悬浮轮播
|
||||
自动获取最新公开照片
|
||||
点击打开观众页
|
||||
不可见时暂停更新
|
||||
支撑服务
|
||||
内部身份与相册权限
|
||||
国内 OSS 直传和全球 CDN
|
||||
原图防覆盖与衍生图处理
|
||||
海外 API 与实时模块合并部署
|
||||
独立图片处理进程
|
||||
国内元数据推送与定时补漏
|
||||
海外浏览点赞与统计回传
|
||||
两地数据库 备份监控
|
||||
共用规则
|
||||
全端多语言
|
||||
无修图 无人工审核
|
||||
上传完成处理后自动公开
|
||||
隐藏删除与公开状态一致
|
||||
匿名计数不等于真实人数
|
||||
```
|
||||
|
||||
## 2 系统组成与数据关系
|
||||
|
||||
Flutter 和现有 OSS 已确定;其余组件为实施方案基线。国内管理与海外观众服务分区部署;海外实时模块与观众 API 同部署,国内 worker 独立;轮播读海外,图片经全球 CDN。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
APP["工作人员 App"] --> CN["国内管理 API"]
|
||||
ADM["国内管理后台"] --> CN
|
||||
USRC["用户信息接口"] -->|国内定时拉取| USYNC["用户资料同步任务"]
|
||||
USYNC --> CN
|
||||
APP -->|临时授权直传| OSS["国内 OSS 原图与浏览资源"]
|
||||
CN --> CDB[("国内主数据库")]
|
||||
CN --> WORKER["国内图片处理 worker"]
|
||||
WORKER --> OSS
|
||||
WORKER --> CDB
|
||||
CDB --> OUT["持久事件与定时补漏"]
|
||||
OUT -->|版本化元数据| OVER["海外观众 API 与实时模块"]
|
||||
OVER --> ODB[("海外展示副本与互动数据库")]
|
||||
ODB -->|统计快照回传| CN
|
||||
USER["海外观众"] -->|页面 查询 点赞 浏览| OVER
|
||||
SLOT["官网 JS 展示位"] -->|轮询| OVER
|
||||
OSS -->|回源| CDN["全球 CDN"]
|
||||
USER -->|图片请求| CDN
|
||||
SLOT -->|轮播图片| CDN
|
||||
```
|
||||
|
||||
## 3 权限速查
|
||||
|
||||
| 角色 | 管理范围 |
|
||||
|---|---|
|
||||
| 公司管理员 | 用户同步、账号激活、相册、照片、展示位和负责人转移 |
|
||||
| 相册创建者 | 自己创建的相册、协作者、全部相册照片及获授权展示位 |
|
||||
| 相册协作者 | 已加入相册的上传权限,管理自己上传的照片 |
|
||||
| 匿名观众 | 浏览、连续点赞、分享,按相册设置下载 |
|
||||
|
||||
长期保存不等于永久公开。关闭公开开关不删除原图;关闭上传也不自动关闭观众页面。
|
||||
@@ -0,0 +1,19 @@
|
||||
flowchart TB
|
||||
APP["工作人员 App"] --> CN["国内管理 API"]
|
||||
ADM["国内管理后台"] --> CN
|
||||
USRC["用户信息接口"] -->|国内定时拉取| USYNC["用户资料同步任务"]
|
||||
USYNC --> CN
|
||||
APP -->|临时授权直传| OSS["国内 OSS 原图与浏览资源"]
|
||||
CN --> CDB[("国内主数据库")]
|
||||
CN --> WORKER["国内图片处理 worker"]
|
||||
WORKER --> OSS
|
||||
WORKER --> CDB
|
||||
CDB --> OUT["持久事件与定时补漏"]
|
||||
OUT -->|版本化元数据| OVER["海外观众 API 与实时模块"]
|
||||
OVER --> ODB[("海外展示副本与互动数据库")]
|
||||
ODB -->|统计快照回传| CN
|
||||
USER["海外观众"] -->|页面 查询 点赞 浏览| OVER
|
||||
SLOT["官网 JS 展示位"] -->|轮询| OVER
|
||||
OSS -->|回源| CDN["全球 CDN"]
|
||||
USER -->|图片请求| CDN
|
||||
SLOT -->|轮播图片| CDN
|
||||
@@ -0,0 +1,90 @@
|
||||
# 图片直播业务流程导图
|
||||
|
||||
版本:V1.3 · 日期:2026-09-17
|
||||
依据:[需求说明书](../01-需求说明书.md) · [技术实施方案](../03-技术实施方案.md)
|
||||
相关:[项目全景](./01-项目全景.md) · [实施计划](./03-实施计划.md)
|
||||
|
||||
## 1 从创建活动到观众互动
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["接口同步资料并激活账号"] --> B["工作人员登录<br/>账号密码"]
|
||||
B --> C["创建配置相册<br/>宣传图 时间 地点 多语言 分类"]
|
||||
C --> D["邀请同事加入相册"]
|
||||
D --> E["拍摄或选图<br/>准备本地文件并入队"]
|
||||
E --> F["OSS 临时授权直传<br/>后台锁屏与失败恢复"]
|
||||
F --> G["服务端核验并固定原图<br/>生成浏览图和可选水印"]
|
||||
G --> H{"处理成功且<br/>允许对外展示?"}
|
||||
H -->|否| HOLD["保留待处理或非公开状态<br/>失败重试或等待开放"]
|
||||
HOLD -->|恢复后重新检查| H
|
||||
H -->|是| SYNC["国内事件推送与定时补漏<br/>海外核验资源与版本"]
|
||||
SYNC --> I["海外可见<br/>无需人工审核"]
|
||||
|
||||
C --> LINK["生成观众链接和二维码"]
|
||||
LINK --> V["观众免登录打开活动页"]
|
||||
I --> V
|
||||
COUNT --> STATS["统计快照回传国内<br/>版本替换不重复相加"]
|
||||
LIKE --> STATS
|
||||
V --> COUNT["上报有效浏览<br/>海外去重累计并展示"]
|
||||
V --> LIST["瀑布流 分类 最新或热门"]
|
||||
LIST --> FULL["点击全屏看图<br/>缩放 左右滑动"]
|
||||
FULL --> LIKE["连续点赞<br/>海外批次幂等累计"]
|
||||
LIKE --> BUBBLE["同相册在线观众气泡<br/>自身去重 密集动画合并"]
|
||||
FULL --> SHARE["分享定位当前照片"]
|
||||
FULL --> DL["按下载权限获取图片"]
|
||||
SHARE --> V
|
||||
|
||||
C --> SLOT["绑定官网展示位"]
|
||||
SLOT --> JS["官网接入一次 JS"]
|
||||
I --> ROTATE["轮询近期公开照片<br/>右侧悬浮轮播"]
|
||||
JS --> ROTATE
|
||||
ROTATE -->|点击照片| V
|
||||
```
|
||||
|
||||
浏览统计只由观众页实际访问触发;自动轮播、图片请求、分页和点赞不额外累计。30 分钟去重窗口为待确认参数。
|
||||
|
||||
## 2 上传与异常恢复
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
CAP["拍摄成功或选择照片"] --> PREP["准备中<br/>获取源文件 检查空间 本地复制"]
|
||||
PREP -->|失败| PF["准备失败<br/>保留原因和恢复入口"]
|
||||
PF -->|重新获取或释放空间| PREP
|
||||
PREP -->|成功| QUEUE["持久化队列<br/>固定账号 相册 分类 任务 ID"]
|
||||
QUEUE --> AUTH{"账号权限与<br/>上传开关有效?"}
|
||||
AUTH -->|否| WAIT["等待恢复权限或开放上传<br/>也可明确取消任务"]
|
||||
WAIT -->|条件恢复| AUTH
|
||||
AUTH -->|是| UP["原生系统传输至 OSS 暂存区"]
|
||||
UP -->|断网或授权过期| RETRY["保存任务<br/>等待网络或续签后重试"]
|
||||
RETRY --> AUTH
|
||||
UP -->|系统中止或强制退出| REOPEN["保留可恢复记录<br/>系统允许或下次启动后校准"]
|
||||
REOPEN --> AUTH
|
||||
UP -->|文件传输完成| CONFIRM["服务端核验与幂等确认<br/>固定不可覆盖的正式原图"]
|
||||
CONFIRM -->|失败| CF["确认失败或待补偿<br/>核验已有对象 不盲目重传"]
|
||||
CF --> CONFIRM
|
||||
CONFIRM --> JOB["处理图片与生成浏览资源"]
|
||||
JOB -->|失败| JF["记录处理失败<br/>有限重试或人工重试"]
|
||||
JF --> JOB
|
||||
JOB -->|完成| READY["处理就绪"]
|
||||
READY --> CHECK{"相册公开且<br/>照片未隐藏删除?"}
|
||||
CHECK -->|是| SYNC["元数据同步海外<br/>幂等 版本检查 定时补漏"]
|
||||
SYNC --> LIVE["海外资源可用后可见<br/>通知观众并进入轮播"]
|
||||
CHECK -->|否| INTERNAL["内部保留<br/>原图继续长期保存"]
|
||||
INTERNAL -->|公开状态变化后检查| CHECK
|
||||
```
|
||||
|
||||
“后台继续上传”依赖平台系统调度,不等于强制退出后持续执行。换手机可查看服务端历史,不能恢复另一台手机尚未上传的本地源文件。
|
||||
|
||||
## 3 相册控制规则
|
||||
|
||||
| 操作 | 上传处理 | 观众与轮播 | 文件 |
|
||||
|---|---|---|---|
|
||||
| 关闭上传 | 拒绝新任务和续签,已核验处理可继续 | 保持原公开设置 | 不删除 |
|
||||
| 关闭公开 | 内部上传处理可继续 | 停止公开查询和订阅,组件更新后收起或空态 | 长期保留 |
|
||||
| 批量隐藏 | 不影响其他照片上传 | 移除相关照片和待展示气泡 | 原图保留 |
|
||||
| 重新公开 | 不自动修改上传开关 | 仅展示符合可见性条件的照片 | 不恢复已隐藏或删除照片 |
|
||||
| 转移负责人 | 更新操作权限,已有任务重新校验 | 不改变相册公开地址 | 保留历史上传归属 |
|
||||
|
||||
已签发资源链接按约定缓存失效窗口处理,不能撤回已下载图片。
|
||||
|
||||
国内处理就绪与海外可见分开显示。同步中断不阻止海外访问既有内容,国内新增内容等待补发;下线优先同步,跨境中断时使用海外紧急停用通道。观众图片统一经全球 CDN,冷缓存首次访问单独验收。
|
||||
@@ -0,0 +1,32 @@
|
||||
flowchart TB
|
||||
A["接口同步资料并激活账号"] --> B["工作人员登录<br/>账号密码"]
|
||||
B --> C["创建配置相册<br/>宣传图 时间 地点 多语言 分类"]
|
||||
C --> D["邀请同事加入相册"]
|
||||
D --> E["拍摄或选图<br/>准备本地文件并入队"]
|
||||
E --> F["OSS 临时授权直传<br/>后台锁屏与失败恢复"]
|
||||
F --> G["服务端核验并固定原图<br/>生成浏览图和可选水印"]
|
||||
G --> H{"处理成功且<br/>允许对外展示?"}
|
||||
H -->|否| HOLD["保留待处理或非公开状态<br/>失败重试或等待开放"]
|
||||
HOLD -->|恢复后重新检查| H
|
||||
H -->|是| SYNC["国内事件推送与定时补漏<br/>海外核验资源与版本"]
|
||||
SYNC --> I["海外可见<br/>无需人工审核"]
|
||||
|
||||
C --> LINK["生成观众链接和二维码"]
|
||||
LINK --> V["观众免登录打开活动页"]
|
||||
I --> V
|
||||
COUNT --> STATS["统计快照回传国内<br/>版本替换不重复相加"]
|
||||
LIKE --> STATS
|
||||
V --> COUNT["上报有效浏览<br/>海外去重累计并展示"]
|
||||
V --> LIST["瀑布流 分类 最新或热门"]
|
||||
LIST --> FULL["点击全屏看图<br/>缩放 左右滑动"]
|
||||
FULL --> LIKE["连续点赞<br/>海外批次幂等累计"]
|
||||
LIKE --> BUBBLE["同相册在线观众气泡<br/>自身去重 密集动画合并"]
|
||||
FULL --> SHARE["分享定位当前照片"]
|
||||
FULL --> DL["按下载权限获取图片"]
|
||||
SHARE --> V
|
||||
|
||||
C --> SLOT["绑定官网展示位"]
|
||||
SLOT --> JS["官网接入一次 JS"]
|
||||
I --> ROTATE["轮询近期公开照片<br/>右侧悬浮轮播"]
|
||||
JS --> ROTATE
|
||||
ROTATE -->|点击照片| V
|
||||
@@ -0,0 +1,149 @@
|
||||
# 图片直播项目实施计划导图
|
||||
|
||||
版本:V1.3 · 日期:2026-09-17
|
||||
依据:[项目计划](../02-项目计划.md) · [优化方案](../04-优化方案.md)
|
||||
相关:[项目全景](./01-项目全景.md) · [业务流程](./02-业务流程.md)
|
||||
|
||||
以 16 周为工作基线,约 156 至 231 角色人日。假设 1 名 Flutter、1 名后端、1 名 Web 开发,加兼职产品设计、测试及运维。周次为相对时间,并非已承诺交付日期;W2 技术验证后重估。以下箭头表示交付依赖,不表示所有任务必须逐个串行。
|
||||
|
||||
## 1 工作分解思维导图
|
||||
|
||||
每个任务编号与项目计划一致,本图覆盖 T01 至 T30。分组用于阅读,不能替代任务依赖。
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((第一版实施计划))
|
||||
需求与基础验证
|
||||
T01 需求权限原型
|
||||
T02 工程环境与流水线
|
||||
T03 双平台后台上传验证
|
||||
T04 用户接口同步验证
|
||||
账号与核心服务
|
||||
T05 账号会话和权限
|
||||
T30 用户定期同步与后台管理
|
||||
T06 相册分类语言协作
|
||||
T07 OSS 核验处理发布
|
||||
Flutter App
|
||||
T08 登录相册与协作页面
|
||||
T09 上传队列和记录
|
||||
T21 准备状态与跨设备历史
|
||||
后台与活动管理
|
||||
T10 相册照片用量管理
|
||||
T20 连拍开关交接水印增量
|
||||
观众与传播
|
||||
T11 活动页瀑布流大图
|
||||
T12 分享下载与浏览器适配
|
||||
T13 点赞与实时气泡
|
||||
T19 浏览计数与展示
|
||||
T24 气泡去重与动画偏好
|
||||
官网嵌入
|
||||
T14 JS 悬浮组件
|
||||
T23 展示位与不可见暂停
|
||||
海外服务与同步
|
||||
T25 海外与全球 CDN 验证
|
||||
T26 内容同步与定时补漏
|
||||
T27 海外计数与统计回传
|
||||
T28 全球 CDN 访问策略
|
||||
T29 跨地域故障验收
|
||||
质量与发布
|
||||
T15 全端多语言验收
|
||||
T16 联调容量与修复
|
||||
T17 部署备份监控告警
|
||||
T18 试运行分发交接
|
||||
T22 原图保护与访问失效
|
||||
```
|
||||
|
||||
## 2 阶段与里程碑
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
START["启动准备<br/>人员 OSS 用户接口 真机 iOS 构建与分发条件"]
|
||||
START --> S1["W1 需求原型<br/>范围 权限 字段 页面 验收"]
|
||||
START --> S2["W1 至 W2 技术验证<br/>锁屏上传 海外与 CDN 验证 用户同步"]
|
||||
S1 --> M1["M1 需求基线<br/>W1 结束"]
|
||||
S2 --> G{"M2 技术可行<br/>W2 结束"}
|
||||
G -->|未通过| FIX["调整实现或范围<br/>修订工作量与排期"]
|
||||
FIX --> S2
|
||||
M1 --> CORE["W3 至 W6 核心链路<br/>国内管理上传 元数据同步 海外可见"]
|
||||
G -->|通过| CORE
|
||||
CORE --> M3["M3 端到端可用<br/>W6 结束"]
|
||||
M3 --> FUN["W7 至 W10 功能建设<br/>协作 队列 大图 互动 浏览统计 多语言"]
|
||||
FUN --> OPT["W11 至 W14 优化与集成<br/>交接 水印 双向同步 全球 CDN 跨地域测试"]
|
||||
OPT --> M4["M4 功能齐备<br/>W14 结束"]
|
||||
M4 --> PILOT["W15 现场试运行<br/>多人操作 弱网 故障恢复 验收"]
|
||||
PILOT --> M5["M5 试运行通过<br/>W15 结束"]
|
||||
M5 --> RELEASE["W16 修复与发布<br/>App 分发 监控 备份 回滚 交接"]
|
||||
RELEASE --> M6["M6 发布交接<br/>W16 结束"]
|
||||
```
|
||||
|
||||
多语言、权限、日志和测试随开发持续进行;W7 至 W10 是完成集成的窗口,并非首次开始这些工作。分发资质从 W1 准备,外部审核等待另行跟踪。
|
||||
|
||||
## 3 核心任务依赖
|
||||
|
||||
本图突出影响工期的依赖,完整前置条件以项目计划任务表为准。网站组件不依赖点赞模块,可在公开列表可用后推进。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
T01["T01 需求原型"] --> T05["T05 账号与权限"]
|
||||
T04["T04 用户接口验证"] --> T05
|
||||
T04 --> T30["T30 定时拉取与后台同步管理"]
|
||||
T05 --> T30
|
||||
T30 --> QA
|
||||
T05 --> T06["T06 相册与协作"]
|
||||
T03["T03 后台上传验证"] --> T07["T07 OSS 自动发布"]
|
||||
T06 --> T07
|
||||
T06 --> T08["T08 App 页面"]
|
||||
T04 --> T08
|
||||
T07 --> T09["T09 上传队列"]
|
||||
T03 --> T09
|
||||
T09 --> T21["T21 准备与历史"]
|
||||
T07 --> T10["T10 管理后台"]
|
||||
T08 --> T20["T20 连拍 控制 交接 水印"]
|
||||
T10 --> T20
|
||||
T07 --> T11["T11 观众浏览"]
|
||||
T11 --> T12["T12 分享下载"]
|
||||
T12 --> T22["T22 原图与访问保护"]
|
||||
T07 --> T22
|
||||
T11 --> T13["T13 实时点赞"]
|
||||
T13 --> T24["T24 动画体验"]
|
||||
T11 --> T19["T19 浏览统计"]
|
||||
T11 --> T14["T14 悬浮组件"]
|
||||
T14 --> T23["T23 展示位"]
|
||||
T20 --> QA["T15 语言验收与 T16 全链路验收<br/>覆盖全部功能与 A01 至 A29"]
|
||||
T21 --> QA
|
||||
T22 --> QA
|
||||
T23 --> QA
|
||||
T24 --> QA
|
||||
T19 --> QA
|
||||
T02["T02 工程与部署基础"] --> T17["T17 备份 监控 回滚"]
|
||||
T07 --> T17
|
||||
T25["T25 海外 CDN 验证"] --> T26["T26 内容同步"]
|
||||
T07 --> T26
|
||||
T26 --> T27["T27 统计回传"]
|
||||
T19 --> T27
|
||||
T13 --> T27
|
||||
T22 --> T28["T28 全球 CDN 接入"]
|
||||
T25 --> T28
|
||||
T26 --> QA
|
||||
T27 --> QA
|
||||
T28 --> QA
|
||||
QA --> T29["T29 跨地域验收"]
|
||||
T29 --> T18["T18 试运行 分发 交接"]
|
||||
T17 --> T18
|
||||
```
|
||||
|
||||
## 4 验收关口与资源
|
||||
|
||||
| 关口 | 必须看到的证据 |
|
||||
|---|---|
|
||||
| 技术可行 | 双平台长队列锁屏上传、断网与凭证过期恢复,无重复发布 |
|
||||
| 核心链路 | 多人从 App 上传,观众页自动显示正确照片 |
|
||||
| 功能齐备 | 全端多语言、权限、浏览计数、点赞、展示位和新增优化通过 |
|
||||
| 试运行 | 真实尺寸照片、现场网络、多人操作和故障恢复记录 |
|
||||
| 发布交接 | 无阻断缺陷,备份恢复和回滚演练完成,明确监控负责人 |
|
||||
|
||||
按任务估算上限分配:Flutter 43、后端 65、Web 51、产品设计 11、测试 43、运维 18 人日,合计 231 人日。后端需承担双向同步,Web 需要兼顾三个入口,测试在集成阶段需集中投入;总人日不能直接换算为最短日历工期。
|
||||
|
||||
## 5 下一步决策
|
||||
|
||||
明确实际人员、首发语言和 App 分发方式;准备现有 OSS 配置、用户接口、真机与 iOS 构建环境;优先执行 T03 与 T04,依据结果细化到人、到周的排期。
|
||||
@@ -0,0 +1,25 @@
|
||||
flowchart TB
|
||||
CAP["拍摄成功或选择照片"] --> PREP["准备中<br/>获取源文件 检查空间 本地复制"]
|
||||
PREP -->|失败| PF["准备失败<br/>保留原因和恢复入口"]
|
||||
PF -->|重新获取或释放空间| PREP
|
||||
PREP -->|成功| QUEUE["持久化队列<br/>固定账号 相册 分类 任务 ID"]
|
||||
QUEUE --> AUTH{"账号权限与<br/>上传开关有效?"}
|
||||
AUTH -->|否| WAIT["等待恢复权限或开放上传<br/>也可明确取消任务"]
|
||||
WAIT -->|条件恢复| AUTH
|
||||
AUTH -->|是| UP["原生系统传输至 OSS 暂存区"]
|
||||
UP -->|断网或授权过期| RETRY["保存任务<br/>等待网络或续签后重试"]
|
||||
RETRY --> AUTH
|
||||
UP -->|系统中止或强制退出| REOPEN["保留可恢复记录<br/>系统允许或下次启动后校准"]
|
||||
REOPEN --> AUTH
|
||||
UP -->|文件传输完成| CONFIRM["服务端核验与幂等确认<br/>固定不可覆盖的正式原图"]
|
||||
CONFIRM -->|失败| CF["确认失败或待补偿<br/>核验已有对象 不盲目重传"]
|
||||
CF --> CONFIRM
|
||||
CONFIRM --> JOB["处理图片与生成浏览资源"]
|
||||
JOB -->|失败| JF["记录处理失败<br/>有限重试或人工重试"]
|
||||
JF --> JOB
|
||||
JOB -->|完成| READY["处理就绪"]
|
||||
READY --> CHECK{"相册公开且<br/>照片未隐藏删除?"}
|
||||
CHECK -->|是| SYNC["元数据同步海外<br/>幂等 版本检查 定时补漏"]
|
||||
SYNC --> LIVE["海外资源可用后可见<br/>通知观众并进入轮播"]
|
||||
CHECK -->|否| INTERNAL["内部保留<br/>原图继续长期保存"]
|
||||
INTERNAL -->|公开状态变化后检查| CHECK
|
||||
@@ -0,0 +1,40 @@
|
||||
mindmap
|
||||
root((第一版实施计划))
|
||||
需求与基础验证
|
||||
T01 需求权限原型
|
||||
T02 工程环境与流水线
|
||||
T03 双平台后台上传验证
|
||||
T04 用户接口同步验证
|
||||
账号与核心服务
|
||||
T05 账号会话和权限
|
||||
T30 用户定期同步与后台管理
|
||||
T06 相册分类语言协作
|
||||
T07 OSS 核验处理发布
|
||||
Flutter App
|
||||
T08 登录相册与协作页面
|
||||
T09 上传队列和记录
|
||||
T21 准备状态与跨设备历史
|
||||
后台与活动管理
|
||||
T10 相册照片用量管理
|
||||
T20 连拍开关交接水印增量
|
||||
观众与传播
|
||||
T11 活动页瀑布流大图
|
||||
T12 分享下载与浏览器适配
|
||||
T13 点赞与实时气泡
|
||||
T19 浏览计数与展示
|
||||
T24 气泡去重与动画偏好
|
||||
官网嵌入
|
||||
T14 JS 悬浮组件
|
||||
T23 展示位与不可见暂停
|
||||
海外服务与同步
|
||||
T25 海外与全球 CDN 验证
|
||||
T26 内容同步与定时补漏
|
||||
T27 海外计数与统计回传
|
||||
T28 全球 CDN 访问策略
|
||||
T29 跨地域故障验收
|
||||
质量与发布
|
||||
T15 全端多语言验收
|
||||
T16 联调容量与修复
|
||||
T17 部署备份监控告警
|
||||
T18 试运行分发交接
|
||||
T22 原图保护与访问失效
|
||||
@@ -0,0 +1,18 @@
|
||||
flowchart TB
|
||||
START["启动准备<br/>人员 OSS 用户接口 真机 iOS 构建与分发条件"]
|
||||
START --> S1["W1 需求原型<br/>范围 权限 字段 页面 验收"]
|
||||
START --> S2["W1 至 W2 技术验证<br/>锁屏上传 海外与 CDN 验证 用户同步"]
|
||||
S1 --> M1["M1 需求基线<br/>W1 结束"]
|
||||
S2 --> G{"M2 技术可行<br/>W2 结束"}
|
||||
G -->|未通过| FIX["调整实现或范围<br/>修订工作量与排期"]
|
||||
FIX --> S2
|
||||
M1 --> CORE["W3 至 W6 核心链路<br/>国内管理上传 元数据同步 海外可见"]
|
||||
G -->|通过| CORE
|
||||
CORE --> M3["M3 端到端可用<br/>W6 结束"]
|
||||
M3 --> FUN["W7 至 W10 功能建设<br/>协作 队列 大图 互动 浏览统计 多语言"]
|
||||
FUN --> OPT["W11 至 W14 优化与集成<br/>交接 水印 双向同步 全球 CDN 跨地域测试"]
|
||||
OPT --> M4["M4 功能齐备<br/>W14 结束"]
|
||||
M4 --> PILOT["W15 现场试运行<br/>多人操作 弱网 故障恢复 验收"]
|
||||
PILOT --> M5["M5 试运行通过<br/>W15 结束"]
|
||||
M5 --> RELEASE["W16 修复与发布<br/>App 分发 监控 备份 回滚 交接"]
|
||||
RELEASE --> M6["M6 发布交接<br/>W16 结束"]
|
||||
@@ -0,0 +1,47 @@
|
||||
flowchart TB
|
||||
T01["T01 需求原型"] --> T05["T05 账号与权限"]
|
||||
T04["T04 用户接口验证"] --> T05
|
||||
T04 --> T30["T30 定时拉取与后台同步管理"]
|
||||
T05 --> T30
|
||||
T30 --> QA
|
||||
T05 --> T06["T06 相册与协作"]
|
||||
T03["T03 后台上传验证"] --> T07["T07 OSS 自动发布"]
|
||||
T06 --> T07
|
||||
T06 --> T08["T08 App 页面"]
|
||||
T04 --> T08
|
||||
T07 --> T09["T09 上传队列"]
|
||||
T03 --> T09
|
||||
T09 --> T21["T21 准备与历史"]
|
||||
T07 --> T10["T10 管理后台"]
|
||||
T08 --> T20["T20 连拍 控制 交接 水印"]
|
||||
T10 --> T20
|
||||
T07 --> T11["T11 观众浏览"]
|
||||
T11 --> T12["T12 分享下载"]
|
||||
T12 --> T22["T22 原图与访问保护"]
|
||||
T07 --> T22
|
||||
T11 --> T13["T13 实时点赞"]
|
||||
T13 --> T24["T24 动画体验"]
|
||||
T11 --> T19["T19 浏览统计"]
|
||||
T11 --> T14["T14 悬浮组件"]
|
||||
T14 --> T23["T23 展示位"]
|
||||
T20 --> QA["T15 语言验收与 T16 全链路验收<br/>覆盖全部功能与 A01 至 A29"]
|
||||
T21 --> QA
|
||||
T22 --> QA
|
||||
T23 --> QA
|
||||
T24 --> QA
|
||||
T19 --> QA
|
||||
T02["T02 工程与部署基础"] --> T17["T17 备份 监控 回滚"]
|
||||
T07 --> T17
|
||||
T25["T25 海外 CDN 验证"] --> T26["T26 内容同步"]
|
||||
T07 --> T26
|
||||
T26 --> T27["T27 统计回传"]
|
||||
T19 --> T27
|
||||
T13 --> T27
|
||||
T22 --> T28["T28 全球 CDN 接入"]
|
||||
T25 --> T28
|
||||
T26 --> QA
|
||||
T27 --> QA
|
||||
T28 --> QA
|
||||
QA --> T29["T29 跨地域验收"]
|
||||
T29 --> T18["T18 试运行 分发 交接"]
|
||||
T17 --> T18
|
||||
@@ -0,0 +1,25 @@
|
||||
# 图片直播项目导图
|
||||
|
||||
版本:V1.3 · 日期:2026-09-17
|
||||
|
||||
与上级目录的 V1.3 文档同步,包含三份导图文档、七张图及对应 Mermaid 源文件。
|
||||
|
||||
| 导图文档 | 内容 |
|
||||
|---|---|
|
||||
| [项目全景](./01-项目全景.md) | 功能思维导图、系统组成与数据关系 |
|
||||
| [业务流程](./02-业务流程.md) | 活动全流程、上传与异常恢复 |
|
||||
| [实施计划](./03-实施计划.md) | 30 项工作分解、16 周阶段里程碑、关键任务依赖 |
|
||||
|
||||
支持 Mermaid 的 Markdown 查看器可直接渲染文档中的图;同目录的 `.mmd` 文件可在 Mermaid 编辑工具中单独打开或转换为图片。普通文本查看器会显示可编辑源码。
|
||||
|
||||
## 单图源文件
|
||||
|
||||
- [项目功能思维导图](./01-project-mindmap.mmd)
|
||||
- [系统组成图](./02-system-overview.mmd)
|
||||
- [业务全流程图](./03-business-flow.mmd)
|
||||
- [上传与异常恢复图](./04-upload-recovery.mmd)
|
||||
- [工作分解思维导图](./05-work-breakdown.mmd)
|
||||
- [阶段与里程碑图](./06-milestones.mmd)
|
||||
- [核心任务依赖图](./07-task-dependencies.mmd)
|
||||
|
||||
范围来源:[需求说明书](../01-需求说明书.md)、[项目计划](../02-项目计划.md)、[技术实施方案](../03-技术实施方案.md)、[优化方案](../04-优化方案.md)。导图不代表任务已经完成,16 周为估算基线。
|
||||
@@ -0,0 +1,649 @@
|
||||
# P1a 后端核心链路 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 搭建 monorepo 工程底座并交付国内单地域后端核心链路:密码登录 → 建相册(多语言/成员/分类)→ 上传会话(幂等、临时凭证)→ 完成核验 → worker 图片处理 → 自动发布 → 公开读 API(游标分页、语言回退、短期签名 URL),全程 API 级 E2E 验证。
|
||||
|
||||
**Architecture:** NestJS 单代码库(P1a 仅 cn-app 部署入口)+ Prisma(MySQL) + BullMQ/Redis + sharp worker;对象存储经 StoragePort 接口访问 MinIO(生产替换 OSS 适配器);契约(OpenAPI + 事件 + 错误码)在 packages/contracts 单源生成类型。对齐《实施总纲》P1 与技术实施方案 §3-§7。
|
||||
|
||||
**Tech Stack:** Node 20 · pnpm 9 · Turborepo · NestJS 10 · Prisma ^5.18 (MySQL 8.4 容器) · BullMQ 5 · Redis 7 · @aws-sdk/client-s3(MinIO S3 兼容)· sharp · @node-rs/argon2 · Jest + supertest · OpenAPI 3.1 + openapi-typescript
|
||||
|
||||
**对应验收:** A01(API 级)、A02、A03、A06、A13(凭证设计级);A07 后端部分。P1a 完成后另册编写 P1b(viewer + admin 最小页)计划。
|
||||
|
||||
---
|
||||
|
||||
## 前置确认(用户关口,开工前登记)
|
||||
|
||||
- [ ] D01 首发语言:按建议值 zh-CN、en 开发(不确认则默认执行)
|
||||
- [x] D12 生产 MySQL 版本:已确认 8.0+(2026-09-17);本地容器用 MySQL 8.4 对齐,迁移仅使用 8.0 兼容特性。**注意**:库级默认校对 `utf8mb4_0900_bin` 需 MySQL ≥ 8.0.30——部署前在验收记录登记两台 ECS 的精确小版本;若低于 8.0.30 则回退 `utf8mb4_bin`(同样大小写敏感)并重跑语义测试。
|
||||
- [ ] 本机环境:`node -v` ≥ 20、`pnpm -v` ≥ 9、`docker -v` 与 `docker compose version` 可用(Windows + Docker Desktop)
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M0 工程底座(T02)
|
||||
|
||||
### Task 1: git 仓库与目录骨架
|
||||
|
||||
**Files:**
|
||||
- Create: `.gitignore`, `.editorconfig`, `.nvmrc`, `README.md`, `docs/plans/progress/.gitkeep`
|
||||
|
||||
- [ ] **Step 1: 初始化仓库与目录**
|
||||
|
||||
```bash
|
||||
cd /d/project/photo
|
||||
git init -b main
|
||||
mkdir -p apps packages services infra docs/plans/progress
|
||||
```
|
||||
|
||||
`.gitignore`:
|
||||
|
||||
```gitignore
|
||||
node_modules/
|
||||
dist/
|
||||
.turbo/
|
||||
coverage/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
.DS_Store
|
||||
```
|
||||
|
||||
`.nvmrc`:`20`
|
||||
|
||||
- [ ] **Step 2: 首次提交**
|
||||
|
||||
```bash
|
||||
git add -A && git commit -m "chore: init repository skeleton"
|
||||
```
|
||||
|
||||
Expected: 首个提交生成,主干 main。
|
||||
|
||||
### Task 2: pnpm workspace + Turborepo
|
||||
|
||||
**Files:**
|
||||
- Create: `package.json`, `pnpm-workspace.yaml`, `turbo.json`
|
||||
|
||||
- [ ] **Step 1: 写入三个根配置**
|
||||
|
||||
`pnpm-workspace.yaml`:
|
||||
|
||||
```yaml
|
||||
packages:
|
||||
- apps/*
|
||||
- packages/*
|
||||
- services/*
|
||||
```
|
||||
|
||||
`package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "photo-live",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.12.0",
|
||||
"engines": { "node": ">=20" },
|
||||
"scripts": {
|
||||
"lint": "turbo run lint",
|
||||
"typecheck": "turbo run typecheck",
|
||||
"test": "turbo run test",
|
||||
"build": "turbo run build",
|
||||
"db:migrate": "dotenv -e .env -- pnpm --filter @photo/db-cn exec prisma migrate dev",
|
||||
"db:generate": "dotenv -e .env -- pnpm --filter @photo/db-cn exec prisma generate",
|
||||
"db:studio": "dotenv -e .env -- pnpm --filter @photo/db-cn exec prisma studio",
|
||||
"dev:api": "dotenv -e .env -- pnpm --filter @photo/api-cn start",
|
||||
"dev:worker": "dotenv -e .env -- pnpm --filter @photo/worker start",
|
||||
"e2e": "turbo run test:e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.3.0",
|
||||
"typescript": "^5.6.0",
|
||||
"prettier": "^3.3.0",
|
||||
"dotenv-cli": "^7.4.0",
|
||||
"eslint": "^9.0.0",
|
||||
"typescript-eslint": "^8.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`turbo.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
|
||||
"lint": {},
|
||||
"typecheck": { "dependsOn": ["^build"] },
|
||||
"test": { "dependsOn": ["^build"] },
|
||||
"test:e2e": { "dependsOn": ["^build"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证 workspace**
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Expected: lockfile 生成,无 workspace 报错。
|
||||
|
||||
- [ ] **Step 3: Commit** `chore: pnpm workspace and turbo pipeline`
|
||||
|
||||
### Task 3: docker-compose 基础设施(cn 侧)
|
||||
|
||||
**Files:**
|
||||
- Create: `infra/docker-compose.yml`, `.env.example`
|
||||
|
||||
- [ ] **Step 1: 编写 compose(P1a 仅 cn profile)**
|
||||
|
||||
```yaml
|
||||
# infra/docker-compose.yml
|
||||
name: photo-live
|
||||
services:
|
||||
mysql-cn:
|
||||
image: mysql:8.4
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_bin
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: photo_cn
|
||||
ports: ["3306:3306"]
|
||||
volumes: [mysql-cn-data:/var/lib/mysql]
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
|
||||
interval: 5s
|
||||
retries: 12
|
||||
redis-cn:
|
||||
image: redis:7-alpine
|
||||
ports: ["6379:6379"]
|
||||
healthcheck: { test: ["CMD", "redis-cli", "ping"], interval: 5s, retries: 12 }
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: photo-dev
|
||||
MINIO_ROOT_PASSWORD: photo-dev-secret
|
||||
ports: ["9000:9000", "9001:9001"]
|
||||
volumes: [minio-data:/data]
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
depends_on: { minio: { condition: service_started } }
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
until mc alias set dev http://minio:9000 photo-dev photo-dev-secret; do sleep 1; done;
|
||||
mc mb -p dev/photo-dev || true;
|
||||
mc anonymous set none dev/photo-dev;"
|
||||
volumes:
|
||||
mysql-cn-data:
|
||||
minio-data:
|
||||
```
|
||||
|
||||
说明:collation 取 `utf8mb4_0900_bin`(大小写敏感、二进制安全)作为**库级默认**,直接化解 token 大小写语义差异(总纲 PG→MySQL 复核项);业务文本列如需不区分大小写再按列显式设定。本机端口冲突(3306/6379/9000/9001 已被占用,Windows 常见本机 MySQL 服务)时改宿主侧映射并同步 `.env`。
|
||||
|
||||
`.env.example`(仓库根):
|
||||
|
||||
```env
|
||||
DATABASE_URL_CN=mysql://root:root@localhost:3306/photo_cn?connection_limit=10
|
||||
REDIS_URL_CN=redis://localhost:6379
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
S3_REGION=us-east-1
|
||||
S3_ACCESS_KEY=photo-dev
|
||||
S3_SECRET_KEY=photo-dev-secret
|
||||
S3_BUCKET=photo-dev
|
||||
SIGNED_URL_TTL_SECONDS=900
|
||||
AUTH_SESSION_TTL_SECONDS=2592000
|
||||
UPLOAD_MAX_BYTES=52428800
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 启动并验证**
|
||||
|
||||
```bash
|
||||
cd infra && docker compose up -d && docker compose ps
|
||||
```
|
||||
|
||||
Expected: mysql-cn / redis-cn / minio / minio-init 均 healthy 或 exited(0)(init 为一次性任务)。
|
||||
|
||||
- [ ] **Step 3: Commit** `chore(infra): cn-side mysql/redis/minio compose`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M1 契约(packages/contracts)
|
||||
|
||||
### Task 4: contracts 包骨架 + 错误码 + OpenAPI 基座
|
||||
|
||||
**Files:**
|
||||
- Create: `packages/contracts/package.json`, `packages/contracts/tsconfig.json`, `packages/contracts/openapi.yaml`, `packages/contracts/src/errors.ts`, `packages/contracts/src/index.ts`
|
||||
|
||||
- [ ] **Step 1: 包配置**
|
||||
|
||||
`package.json`(name `@photo/contracts`,scripts: `gen`、`lint:api`、`build`、`typecheck`、`test`)依赖 `openapi-typescript@^7`、`@redocly/cli@^1` 作为 devDeps;`src/index.ts` 导出 errors 与生成类型。
|
||||
|
||||
- [ ] **Step 2: 错误码(稳定 code,客户端按语言映射文案)**
|
||||
|
||||
```ts
|
||||
// packages/contracts/src/errors.ts
|
||||
export const ErrorCodes = {
|
||||
AUTH_INVALID_CREDENTIALS: 'AUTH_INVALID_CREDENTIALS',
|
||||
AUTH_TOKEN_EXPIRED: 'AUTH_TOKEN_EXPIRED',
|
||||
AUTH_FORBIDDEN: 'AUTH_FORBIDDEN',
|
||||
ALBUM_NOT_FOUND: 'ALBUM_NOT_FOUND',
|
||||
ALBUM_NOT_PUBLIC: 'ALBUM_NOT_PUBLIC',
|
||||
MEMBER_CONFLICT: 'MEMBER_CONFLICT',
|
||||
ALBUM_UPLOAD_DISABLED: 'ALBUM_UPLOAD_DISABLED',
|
||||
UPLOAD_SESSION_CONFLICT: 'UPLOAD_SESSION_CONFLICT',
|
||||
UPLOAD_VERIFY_FAILED: 'UPLOAD_VERIFY_FAILED',
|
||||
PHOTO_NOT_FOUND: 'PHOTO_NOT_FOUND',
|
||||
PHOTO_NOT_PUBLIC: 'PHOTO_NOT_PUBLIC',
|
||||
VALIDATION_FAILED: 'VALIDATION_FAILED',
|
||||
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
||||
} as const;
|
||||
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
||||
```
|
||||
|
||||
- [ ] **Step 3: OpenAPI 基座**(3.1;`info.title=photo-live`;servers `/v1`;components: `Error{code,message,requestId}`、`CursorPage{items,nextCursor}`;securitySchemes: bearerAuth)。路径骨架先落 auth 三条 + 错误响应复用。
|
||||
|
||||
- [ ] **Step 4: 类型生成脚本验证**
|
||||
|
||||
`pnpm --filter @photo/contracts gen && pnpm --filter @photo/contracts lint:api`
|
||||
Expected: `src/generated/openapi.d.ts` 生成;redocly 0 错误。
|
||||
|
||||
- [ ] **Step 5: Commit** `feat(contracts): openapi base, error codes, type generation`
|
||||
|
||||
### Task 5: 业务端点契约(albums/members/categories/uploads/photos/public)
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/contracts/openapi.yaml`
|
||||
|
||||
- [ ] **Step 1: 补全 P1a 路径与 schema**(字段以技术实施方案 §4/§10 为准):
|
||||
- `POST /albums`(title 多语言必填、时间、时区默认 Asia/Shanghai、地点、开关默认值 upload/public=true、download=true、original=false、defaultLocale)
|
||||
- `GET /albums`、`GET /albums/{id}`、`PATCH /albums/{id}`(仅创建者/管理员可写;开关字段独立)
|
||||
- `POST/GET/DELETE /albums/{id}/members`;`POST /albums/{id}/categories` + `PATCH/DELETE /categories/{id}`
|
||||
- `POST /uploads`:请求 `{albumId, categoryId?, clientTaskId, filename, bytes, contentType}`,响应 `{uploadId, photoId, objectKey, credential:{mode,url,method,headers,expiresAt}}`;`POST /uploads/{id}/renew`;`POST /uploads/{id}/complete` → `{photoId, processingState}`;`GET /uploads`
|
||||
- `PATCH /photos/{id}`(`{visibility: hidden|published}`)、`DELETE /photos/{id}`(软删除)
|
||||
- `GET /public/albums/{token}`( translations 按请求 locale 回退、categories、assets、`viewCount: null` 占位、timezone、startAt/endAt ISO)
|
||||
- `GET /public/albums/{token}/photos`(query: category、locale、cursor、limit≤60;item 含 `photoId,width,height,thumb{url,expiresAt},browse{url,expiresAt},likeCount,publishedAt`)
|
||||
- `GET /public/albums/{token}/photos/{photoId}`(单图定位;同时作为签名 URL 过期后的刷新入口)
|
||||
- [ ] **Step 2: 生成 + lint 通过**(同 Task 4 命令)
|
||||
- [ ] **Step 3: Commit** `feat(contracts): p1a endpoint contracts`
|
||||
|
||||
### Task 6: 事件契约 + 用户确认点
|
||||
|
||||
**Files:**
|
||||
- Create: `packages/contracts/src/events.ts`
|
||||
|
||||
- [ ] **Step 1: 事件类型**(对齐技术实施方案 §8.2;P1a 无消费者,事件仅持久化到 outbox,实际会产生 photo.published / photo.hidden / photo.deleted 三类)
|
||||
|
||||
```ts
|
||||
export interface OutboxEvent<T = unknown> {
|
||||
eventId: string; // cuid,持久唯一
|
||||
type: 'photo.published' | 'photo.hidden' | 'photo.deleted' | 'photo.likes.updated' | 'album.updated';
|
||||
albumId: string;
|
||||
photoId?: string;
|
||||
entityVersion: number; // 单调递增,P2 inbox 消费依据
|
||||
payload: T;
|
||||
occurredAt: string; // ISO UTC
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 单测**:类型测试——合法 `OutboxEvent{type:'photo.published'}` 编译通过,非法 type 用 `@ts-expect-error` 验证编译期拒绝。
|
||||
|
||||
```bash
|
||||
pnpm --filter @photo/contracts test
|
||||
```
|
||||
|
||||
Expected: PASS。
|
||||
|
||||
- [ ] **Step 3: ⏸ 用户确认点:请用户过目 openapi.yaml(接口形态、字段命名、分页方式)后再进入 M2。**
|
||||
- [ ] **Step 4: Commit** `feat(contracts): outbox event contract types`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M2 服务骨架与数据层
|
||||
|
||||
### Task 7: NestJS api 骨架、测试与 Lint 工具链、env 加载约定
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/package.json`(name `@photo/api-cn`)、`tsconfig.json`、`nest-cli.json`、`src/main-cn.ts`、`src/app-cn.module.ts`、`src/health.controller.ts`、`src/request-id.middleware.ts`、`src/all-exceptions.filter.ts`、`services/api/jest.config.ts`、`services/api/test/load-env.ts`、`services/api/test/reset.ts`、根 `eslint.config.js`
|
||||
|
||||
- [ ] **Step 1: env 加载约定(全仓库唯一机制,解决 pnpm --filter cwd 在包目录的问题)**:根 `.env` 为唯一 env 文件。开发/迁移脚本经根 `dotenv-cli` 注入(Task 2 scripts 已配置);Jest 经 `services/api/test/load-env.ts`(`dotenv.config({ path: join(__dirname, '../../../.env') })`)作为 `setupFiles` 载入;运行时代码只读 `process.env`。
|
||||
- [ ] **Step 2: 依赖与入口**:deps `@nestjs/common @nestjs/core @nestjs/platform-express nest-pino pino-http`;devDeps `jest ts-jest @types/jest supertest @types/supertest @nestjs/testing`;scripts:`test`(`*.spec.ts` 单测)、`test:e2e`(`test/*.e2e-spec.ts`,前置 compose 依赖)、`typecheck`、`lint`、`start`。`test/reset.ts` 为 e2e 共用隔离工具:每个 e2e spec `beforeAll` 调用——truncate 全部 cn 表 + 删除 MinIO `photo-live/dev` 前缀对象(防止 spec 间唯一约束冲突与脏数据)。入口监听 `PORT`(默认 8080),`app.setGlobalPrefix('v1')`,注册 request-id 中间件(无则生成 cuid,挂 `res.setHeader('x-request-id')`)、全局异常过滤器(映射 `ErrorCodes`,响应 `{code,message,requestId}`,不泄露堆栈)、`/v1/health` 返回 `{status:'ok'}`。
|
||||
- [ ] **Step 3: 根 ESLint flat config**(eslint + typescript-eslint,覆盖 packages/services/apps),各包(contracts、storage、db-cn、api、worker)`lint` 脚本接 `eslint .`,确保 turbo 不静默跳过。ts-jest 保持类型检查模式(非 isolatedModules),使 `@ts-expect-error` 负向断言生效。
|
||||
- [ ] **Step 4: 启动验证**
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
pnpm lint && pnpm typecheck
|
||||
pnpm dev:api &
|
||||
until curl -s localhost:8080/v1/health; do sleep 1; done # 等待启动 → {"status":"ok"}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit** `feat(api): cn-app skeleton, toolchain, env loading convention`
|
||||
|
||||
### Task 8: Prisma cn schema —— 身份与审计域
|
||||
|
||||
**Files:**
|
||||
- Create: `packages/db-cn/package.json`(name `@photo/db-cn`;deps `@prisma/client`,devDeps `prisma`;api 与 worker 共享此 client,解决 worker 访问 cn Prisma 的归属问题)、`packages/db-cn/prisma/schema.prisma`(cn schema)、`packages/db-cn/src/index.ts`(导出 PrismaClient)、`services/api/src/prisma/prisma.module.ts`(封装 @photo/db-cn 供注入)
|
||||
- Test: `services/api/test/db.semantics.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 模型**(MySQL;时间 `@db.DateTime(6)`;ID cuid;账号字段唯一;连接池上限复核项由 `DATABASE_URL_CN` 的 `?connection_limit=10` 承担,PrismaService 不另行放大):
|
||||
|
||||
```prisma
|
||||
model users {
|
||||
id String @id @default(cuid())
|
||||
account String @unique @db.VarChar(64)
|
||||
passwordHash String @db.VarChar(255)
|
||||
displayName String @db.VarChar(128)
|
||||
role String @default("staff") // staff|admin(公司角色;相册角色另表)
|
||||
sourceStatus String @default("local") // local|active|disabled(P4a 同步用)
|
||||
localLocked Boolean @default(false)
|
||||
credentialStatus String @default("active")
|
||||
locale String? @db.VarChar(16)
|
||||
createdAt DateTime @default(now()) @db.DateTime(6)
|
||||
sessions sessions[]
|
||||
auditLogs audit_logs[]
|
||||
}
|
||||
model sessions {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
tokenHash String @unique @db.VarChar(64)
|
||||
expiresAt DateTime @db.DateTime(6) // 长效会话期限(默认 30 天,AUTH_SESSION_TTL_SECONDS 可调;不做短时效 access/refresh 双层——用户决策 2026-09-17)
|
||||
revokedAt DateTime? @db.DateTime(6) // 注销/停用撤销
|
||||
user users @relation(fields: [userId], references: [id])
|
||||
}
|
||||
model audit_logs {
|
||||
id String @id @default(cuid())
|
||||
actorId String?
|
||||
action String @db.VarChar(64)
|
||||
resourceId String? @db.VarChar(64)
|
||||
metadata Json?
|
||||
createdAt DateTime @default(now()) @db.DateTime(6)
|
||||
actor users? @relation(fields: [actorId], references: [id])
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 迁移**
|
||||
|
||||
```bash
|
||||
pnpm db:migrate -- --name init_identity
|
||||
```
|
||||
|
||||
Expected: migration SQL 生成并应用。
|
||||
|
||||
- [ ] **Step 3: 写语义复核 E2E 测试**(TDD:先写测试)——UTC 往返(写入+读回同为 UTC 瞬间)、`utf8mb4_0900_bin` 下 `public_token` 大小写敏感查询、audit 插入。运行 `pnpm --filter @photo/api-cn test:e2e`,Expected: PASS。
|
||||
- [ ] **Step 4: Commit** `feat(api): identity and audit schema with mysql semantics tests`
|
||||
|
||||
### Task 9: Prisma cn schema —— 相册域
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/db-cn/prisma/schema.prisma`
|
||||
|
||||
- [ ] **Step 1: 模型**:`albums`(publicToken `@unique @db.VarChar(32)`、ownerId、startAt/endAt、timezone、status active|offline、defaultLocale、uploadEnabled/publicEnabled/downloadEnabled/originalDownloadEnabled 默认 true/true/true/false、watermarkVersion 默认 0)、`album_translations`(`@@unique([albumId, locale])`,title/description/venue/address)、`album_members`(`@@unique([albumId, userId])`,role owner|collaborator,status)、`categories`(albumId、sortOrder)+ `category_translations`(`@@unique([categoryId, locale])`)、`album_assets`(kind banner|cover|logo、locale?、objectKey)。
|
||||
- [ ] **Step 2: 迁移** `--name album_domain`;Step 3: Commit `feat(api): album domain schema`
|
||||
|
||||
### Task 10: Prisma cn schema —— 照片与上传域 + outbox
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/db-cn/prisma/schema.prisma`
|
||||
|
||||
- [ ] **Step 1: 模型**:
|
||||
|
||||
```prisma
|
||||
model photos {
|
||||
id String @id @default(cuid())
|
||||
albumId String
|
||||
uploaderId String
|
||||
categoryId String?
|
||||
objectKey String @unique @db.VarChar(255) // originals 正式键
|
||||
bytes Int @default(0)
|
||||
width Int?
|
||||
height Int?
|
||||
shotAt DateTime? @db.DateTime(6)
|
||||
uploadedAt DateTime? @db.DateTime(6)
|
||||
publishedAt DateTime? @db.DateTime(6)
|
||||
processingState String @default("pending_upload") // pending_upload|uploaded|processing|ready|processing_failed
|
||||
errorCode String? @db.VarChar(64)
|
||||
visibility String @default("unpublished") // unpublished|published|hidden|deleted
|
||||
likeCount Int @default(0) // P2 起为统计副本
|
||||
entityVersion Int @default(1)
|
||||
@@index([albumId, visibility, publishedAt, id])
|
||||
@@index([albumId, uploaderId, uploadedAt])
|
||||
}
|
||||
model photo_variants {
|
||||
photoId String
|
||||
variant String // thumb|browse
|
||||
objectKey String @unique @db.VarChar(255)
|
||||
bytes Int
|
||||
version Int @default(1)
|
||||
width Int?
|
||||
height Int?
|
||||
@@unique([photoId, variant])
|
||||
}
|
||||
model upload_sessions {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
albumId String
|
||||
clientTaskId String @db.VarChar(128)
|
||||
photoId String @unique
|
||||
objectKey String @unique @db.VarChar(255) // staging 键
|
||||
expectedBytes Int
|
||||
contentType String @db.VarChar(128)
|
||||
multipartId String?
|
||||
state String @default("pending_upload") // pending_upload|uploaded|confirmed|failed
|
||||
expiresAt DateTime @db.DateTime(6)
|
||||
createdAt DateTime @default(now()) @db.DateTime(6)
|
||||
@@unique([userId, clientTaskId]) // 幂等锚点(A03)
|
||||
}
|
||||
model photo_jobs {
|
||||
id String @id @default(cuid())
|
||||
photoId String
|
||||
type String @default("process")
|
||||
state String @default("pending") // pending|active|done|failed
|
||||
attempt Int @default(0)
|
||||
errorCode String? @db.VarChar(64)
|
||||
nextRetryAt DateTime? @db.DateTime(6)
|
||||
updatedAt DateTime @updatedAt @db.DateTime(6)
|
||||
@@unique([photoId, type])
|
||||
@@index([state, nextRetryAt])
|
||||
}
|
||||
model outbox_events {
|
||||
id String @id @default(cuid())
|
||||
type String @db.VarChar(64)
|
||||
albumId String @db.VarChar(64)
|
||||
photoId String? @db.VarChar(64)
|
||||
entityVersion Int
|
||||
payload Json
|
||||
occurredAt DateTime @default(now()) @db.DateTime(6)
|
||||
deliveredAt DateTime? @db.DateTime(6)
|
||||
@@index([deliveredAt, occurredAt])
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 迁移** `--name photo_upload_domain`;Step 3: Commit `feat(api): photo, upload, outbox schema`
|
||||
|
||||
### Task 11: StoragePort 接口 + MinIO 适配器
|
||||
|
||||
**Files:**
|
||||
- Create: `packages/storage/package.json`、`src/port.ts`、`src/minio.adapter.ts`、`src/index.ts`
|
||||
- Test: `packages/storage/test/minio.adapter.spec.ts`(真实 MinIO 集成测试)
|
||||
|
||||
- [ ] **Step 1: 端口定义**(生产 OSS 适配器实现同一接口,G1 验证差异)
|
||||
|
||||
```ts
|
||||
export interface UploadCredential { mode: 'presigned'; url: string; method: 'PUT'; headers: Record<string,string>; expiresAt: string; }
|
||||
export interface StoragePort {
|
||||
presignPut(objectKey: string, bytes: number, contentType: string, ttlSeconds: number): Promise<UploadCredential>;
|
||||
presignGet(objectKey: string, ttlSeconds: number): Promise<string>;
|
||||
head(objectKey: string): Promise<{ bytes: number; etag: string } | null>;
|
||||
getStream(objectKey: string): Promise<NodeJS.ReadableStream>;
|
||||
put(objectKey: string, body: Buffer, contentType: string): Promise<{ etag: string }>;
|
||||
/** 复制到正式键;目标已存在时校验一致性并幂等返回(禁止覆盖语义)。 */
|
||||
copyIfAbsent(src: string, dest: string, expectedBytes: number): Promise<{ created: boolean; etag: string }>;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: MinIO 实现**:`@aws-sdk/client-s3` + `s3-request-presigner`;`copyIfAbsent` = head(dest) 存在→比对 bytes 一致返回 `{created:false}`、不一致抛 `UPLOAD_VERIFY_FAILED`;不存在→`CopyObject` 后复核。测试覆盖:presign PUT 可写入、head 字节数、copyIfAbsent 幂等(二次调用 created:false)、目标不一致抛错。
|
||||
- [ ] **Step 3: 运行** `pnpm --filter @photo/storage test`(compose 已起 MinIO)。Expected: PASS。
|
||||
- [ ] **Step 4: Commit** `feat(storage): storage port and minio adapter with forbid-overwrite semantics`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M3 身份与权限(T05 后端)
|
||||
|
||||
### Task 12: 密码哈希与受控初始管理员
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/auth/password.ts`、`services/api/scripts/seed-admin.ts`
|
||||
- Test: `services/api/test/password.spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败测试**:hash/verify(@node-rs/argon2,memoryCost 默认)、错误口令 verify=false。
|
||||
- [ ] **Step 2: 实现 + seed 脚本**(读 env `ADMIN_ACCOUNT/ADMIN_PASSWORD`,不存在则插入 admin 角色,存在则跳过——幂等)。
|
||||
- [ ] **Step 3: 测试通过;Commit** `feat(api): argon2 password and idempotent admin seed`
|
||||
|
||||
### Task 13: 登录 / 刷新 / 注销
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/auth/auth.controller.ts`、`auth.service.ts`、`auth.module.ts`、`session.guard.ts`
|
||||
- Test: `services/api/test/auth.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:正确账号密码→200 + sessionToken(长效单 token,默认 30 天)+ expiresAt + audit `auth.login`;错误密码→401 `AUTH_INVALID_CREDENTIALS`;停用(sourceStatus=disabled 或 localLocked)→401(不区分文案);**未激活(credentialStatus=pending)→401**(A01"未激活与停用账号无法进入");refresh→滑动续期(持有有效 token 延长 expiresAt,仍为同一 token,不做旋转);logout→revokedAt 后 401 `AUTH_TOKEN_EXPIRED`。
|
||||
- [ ] **Step 2: 实现**:token=32B random hex,库存 sha256;会话期限由 `AUTH_SESSION_TTL_SECONDS`(默认 30 天)控制;登录时校验 `credentialStatus='active'`;`SessionGuard` 校验 tokenHash 未撤销未过期,挂 `req.user`。
|
||||
- [ ] **Step 3: E2E 通过(A01 API 级);Commit** `feat(api): password login, sliding renewal, revocable sessions`
|
||||
|
||||
### Task 14: 相册角色权限服务(A02 基石)
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/albums/permissions.service.ts`
|
||||
- Test: `services/api/test/permissions.spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败测试**:`canViewAlbum/canManageAlbum/canManagePhoto`——admin 全通过;创建者管本相册、不能管他人相册;协作者只能传/管自己照片;非成员拒绝;被移除成员(members 无行)拒绝(逐次查库,不信客户端声明)。
|
||||
- [ ] **Step 2: 实现**(方法内部 join members+albums+photos.uploaderId 校验)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(api): server-side album role permission checks`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M4 相册与协作(T06)
|
||||
|
||||
### Task 15: 相册创建与多语言
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/albums/albums.controller.ts`、`albums.service.ts`、`albums.module.ts`(含 translations/categories 模型引用)
|
||||
- Test: `services/api/test/albums.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:POST /albums 带 zh-CN+en translations → 201 返回 publicToken(`^[0-9a-z]{20,32}$`);title 缺失→`VALIDATION_FAILED`;endAt<startAt→400;owner 自动入 album_members(role=owner) + audit。
|
||||
- [ ] **Step 2: 实现**(事务:albums + translations + members(owner) + audit)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(api): album creation with translations and owner membership`
|
||||
|
||||
### Task 16: 相册查询 / 更新与开关
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:创建者 PATCH 改 title/开关→200 且 `uploadEnabled/publicEnabled` 独立变化;协作者 PATCH→403 `AUTH_FORBIDDEN`(A02);GET 列表仅返回自己参与或 admin 全量。
|
||||
- [ ] **Step 2: 实现(配置变更写 audit `album.update`);通过;Commit** `feat(api): album update with independent upload/public switches`
|
||||
|
||||
### Task 17: 成员与分类管理
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:创建者邀请已存在账号→201;重复邀请→`MEMBER_CONFLICT`;移除后该成员 complete 上传→403(Task 21 回归);分类增改排序→列表按 sortOrder。
|
||||
- [ ] **Step 2: 实现(成员变更写 audit);通过;Commit** `feat(api): members and categories management`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M5 上传与自动发布(T07)
|
||||
|
||||
### Task 18: POST /v1/uploads 幂等初始化
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/uploads/uploads.controller.ts`、`uploads.service.ts`、`uploads.module.ts`
|
||||
- Test: `services/api/test/uploads.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**(A03):同 `clientTaskId` 两次调用→**同 uploadId+photoId**(upsert 语义,非 409);不同 clientTaskId 同 filename→两个会话两个 photoId;非成员→403;`uploadEnabled=false`→409 `ALBUM_UPLOAD_DISABLED`;`bytes > UPLOAD_MAX_BYTES`(默认 50MB)→`VALIDATION_FAILED`;`contentType` ∉ {image/jpeg, image/png, image/heic}(heic 解码能力待 G2 验证标注)→`VALIDATION_FAILED`;响应含有效 presign credential(用 StoragePort 实际签名)。
|
||||
- [ ] **Step 2: 实现**:入参校验(bytes 上限、类型白名单、相册上传开关与成员权)→ 事务内 upsert(`@@unique([userId, clientTaskId])` 冲突→查回既有会话且校验 album/category 一致,不一致→`UPLOAD_SESSION_CONFLICT`);staging 键 = `photo-live/dev/staging/{uploadId}/{photoId}.{ext}`(会话 objectKey);photo 预创建(visibility=unpublished),其 `objectKey` 在 init 即写为最终 originals 键 `photo-live/dev/albums/{albumId}/originals/{photoId}.{ext}`(staging 与正式键分离,杜绝二义)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(api): idempotent upload session init with presigned credential`
|
||||
|
||||
### Task 19: 续签与完成核验(含受控复制)
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:`renew` 返回新 presign(同 objectKey);`renew` 在 `uploadEnabled=false`→409 `ALBUM_UPLOAD_DISABLED`(§4.9 关闭上传拒绝续签);被移除成员 `renew`→403;`complete`:
|
||||
- 对象未上传→404/400 `UPLOAD_VERIFY_FAILED`;
|
||||
- 字节数不符→`UPLOAD_VERIFY_FAILED`;
|
||||
- 成功→photo(uploadedAt、bytes) + photo_jobs(pending) + session(confirmed) 同事务;
|
||||
- **重复 complete(A03)**→200 幂等,不产生第二个 photo/job/outbox;
|
||||
- 被移除成员 complete→403;
|
||||
- `GET /uploads` 返回本人会话历史(state 联查 photo.processingState,App 服务端历史的后端前置)。
|
||||
- [ ] **Step 2: 实现**:head(staging) 校验 bytes → `copyIfAbsent(staging→originals:{albumId}/originals/{photoId}.ext)` → 事务更新三表 → enqueue BullMQ(队列名常量 `PHOTO_PROCESS` 由 `@photo/contracts` 导出,api 与 worker 共享;提交后入队,队列不可用不回滚,靠 Task 21 sweep 补偿)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(api): upload verify, forbid-overwrite copy, idempotent complete`
|
||||
|
||||
### Task 20: worker 服务与 sharp 处理流水线
|
||||
|
||||
**Files:**
|
||||
- Create: `services/worker/package.json`(name `@photo/worker`)、`src/main.ts`、`src/process.processor.ts`、`src/pipeline.ts`、`src/sweep.ts`
|
||||
- Test: `services/worker/test/pipeline.spec.ts`(真实 jpeg/png 样本 + 一张损坏文件 + 一张 HEIC 样本记录结果)
|
||||
|
||||
- [ ] **Step 1: 失败测试**:pipeline(buffer)→输出 thumb(≤480w) 与 browse(≤1920w) 两 Buffer,EXIF 已清(无 GPS/ orientations 元数据)、宽高返回;损坏 buffer→`DECODE_FAILED`;HEIC 样本(真实 iPhone 样本置于 `services/worker/test/fixtures/sample.heic`,暂缺时该用例 skip 并登记验收记录,不阻塞)→若 sharp 可解码则通过否则标记 `UNSUPPORTED_TYPE` 并**登记到验收记录**(总纲风险项)。
|
||||
- [ ] **Step 2: 实现**:worker package 依赖 `@photo/db-cn`(与 api 共享同一 Prisma client);`sharp(buf).rotate()` 自动方向 → `resize({width, withoutEnlargement:true}).jpeg({quality})`(thumb q75 / browse q82,不调用 withMetadata 即清 EXIF)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(worker): sharp pipeline with orientation, variants, exif strip`
|
||||
|
||||
### Task 21: 处理消费者、发布事务与补偿扫描
|
||||
|
||||
**Files:**
|
||||
- Create: `services/worker/src/publish.service.ts`
|
||||
- Test: `services/worker/test/publish.e2e-spec.ts`(真实 MySQL/Redis/MinIO;worker package scripts 含 `test` 与 `test:e2e`,后者纳入根 `pnpm e2e` 的 turbo 任务)
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**(A06):photo_jobs(pending) → 消费者处理:variants put 上传 → **单事务**:photo_variants upsert + photos(ready, published, publishedAt=now, entityVersion+1) + outbox_events(photo.published, entityVersion 同步) → 断言三表一致且事件 payload 符合 contracts/events.ts;重复投递(同 photoId)→ 幂等无二套 variants;损坏文件→3 次退避后 `processing_failed` 且不公开。
|
||||
- [ ] **Step 2: 实现**:BullMQ `attempts:3, backoff:exponential`;`sweep.ts` 每 30s 认领 `state='pending' AND (nextRetryAt IS NULL OR nextRetryAt < now())`(SQL NULL 语义;含 complete 后入队失败的孤儿任务,总纲 §5.6 补偿)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(worker): publish transaction with outbox event and compensating sweep`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M6 公开读与可见性(T11 部分 + V4)
|
||||
|
||||
### Task 22: 相册公开信息与语言回退
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/public/public.controller.ts`、`public.service.ts`、`public.module.ts`
|
||||
- Test: `services/api/test/public.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:`GET /public/albums/{token}` locale=en 但缺 en 翻译→回退 defaultLocale 且响应携带 `resolvedLocale`;offline 或 publicEnabled=false→404 `ALBUM_NOT_PUBLIC`;返回 categories(名称同样回退)、banner 签名 URL(fixture:直接经 Prisma/StoragePort 预置 album_assets 行 + MinIO 对象——上传链路在 P1b)、timezone、`viewCount:null`。
|
||||
- [ ] **Step 2: 实现;通过;Commit** `feat(api): public album info with locale fallback`
|
||||
|
||||
### Task 23: 公开照片列表(游标分页 + 分类过滤 + 签名 URL)
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:seed 35 张已发布→limit=30 首页 30 条 + nextCursor;翻页得余 5 条且无重复(跨页稳定,A07 后端级);`category` 过滤正确;仅 `visibility=published`(hidden/deleted 不出现);item 含 thumb/browse 签名 URL 与 `expiresAt`;同秒发布的多张排序按 id 稳定。
|
||||
- [ ] **Step 2: 实现**:cursor=base64(`{publishedAt,id}`),WHERE `(publishedAt,id) < (cursor)` 语义(MySQL 行值比较或等价 OR 条件,D12 复核项)。
|
||||
- [ ] **Step 3: 通过;Commit** `feat(api): public photo feed with stable cursor pagination`
|
||||
|
||||
### Task 24: 单图定位 / 隐藏 / 软删除
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**:单图路由命中已发布照片→200(并作为 URL 刷新入口返回新签名);隐藏后单图与列表均不可见(404 `PHOTO_NOT_PUBLIC`,本地化文案键);uploader 隐藏自己照片 OK、协作者隐藏他人照片→403(A02);软删除后不可见;隐藏/删除写 audit;outbox 追加 `photo.hidden`/`photo.deleted` 事件(P2 消费);管理员/创建者重新公开(hidden→published)→ 列表恢复可见(§5.5 状态机;协作者能否自行恢复属需求待确认项,P1a 默认拒绝并登记)。
|
||||
- [ ] **Step 2: 实现;通过;Commit** `feat(api): photo visibility lifecycle with audit and outbox events`
|
||||
|
||||
---
|
||||
|
||||
## 里程碑 M7 端到端与验收
|
||||
|
||||
### Task 25: 全链路 API E2E(A01/A02/A03/A06 证据)
|
||||
|
||||
**Files:**
|
||||
- Test: `services/api/test/e2e.full-chain.e2e-spec.ts`
|
||||
|
||||
- [ ] **Step 1: 失败 E2E**(一个 spec 串全链路):
|
||||
1. seed-admin → 登录 admin → 建 album(zh+en) + 分类 + 邀请 staffB;
|
||||
2. staffB 登录 → **同名文件两次上传**(不同 clientTaskId)→ 两个 presign PUT 实际写入 MinIO → 各 complete(其一 complete 调两次);
|
||||
3. 运行 worker(测试内 `sweepOnce()`)→ 两照片 ready+published,outbox 各一条 photo.published,**无第三条**(A03);
|
||||
4. 公开 API:列表 2 张、翻页、语言回退、签名 URL 可 GET 到图片字节;
|
||||
5. 隐藏其中一张 → 列表剩 1、单图 404;
|
||||
6. admin 软删除另一张 → 空列表;audit 全程可查。
|
||||
- [ ] **Step 2: 通过;Commit** `test(api): full-chain e2e covering A01 A02 A03 A06`
|
||||
|
||||
### Task 26: 回归门与 P1a 验收记录
|
||||
|
||||
- [ ] **Step 1: 全量回归**:`pnpm lint && pnpm typecheck && pnpm test && pnpm e2e`(单测与 e2e 分列)。Expected: 全绿。
|
||||
- [ ] **Step 2: 写验收记录** `docs/plans/progress/P1a-验收记录.md`:A01(API 级证据+App 端留 P3)、A02、A03、A06、A13(凭证设计级:无长期客户端密钥、签名 TTL 受 env 控制;OSS 生命周期核验随 G1/P5)、HEIC 实测结果、未尽事项。
|
||||
- [ ] **Step 3: 打 tag** `git tag p1a`;Commit `docs: p1a acceptance record`。
|
||||
|
||||
---
|
||||
|
||||
## 执行注意
|
||||
|
||||
- 每任务 TDD 节奏不可跳步:先失败测试→实现→通过→提交;集成/E2E 测试统一跑在 compose 依赖上(`docker compose up -d` 前置)。
|
||||
- e2e 隔离:每个 e2e spec `beforeAll` 调 `test/reset.ts`(truncate cn 全表 + 清 MinIO `photo-live/dev` 前缀);worker/storage 测试沿用同约定(各自包内复制 reset 工具或引用)。
|
||||
- env:命令一律在仓库根执行;根 `.env` 为唯一 env 文件,脚本经 dotenv-cli 注入、测试经 `test/load-env.ts` 载入(Task 7 约定;`services/worker/test` 与 `packages/storage/test` 复制同约定 load-env.ts——两者到仓库根同为三级向上,路径一致);新增 env 变量必须同步 `.env.example`;密钥不入库。
|
||||
- 发现契约需变更时:先改 `packages/contracts` 并重新生成类型、在任务提交信息中登记,再改消费端(契约单一事实源)。
|
||||
- 与总纲冲突处以总纲为准并回写修订;HEIC、MySQL 行值比较等 D12 复核项结论写入验收记录。
|
||||
- P1a 完成后:编写 P1b 计划(viewer 基础页 + admin 最小页 + **宣传图/封面/Logo 上传链路(album_assets 的 presign+确认,复用照片上传机制,R09 依赖)** + Playwright 冒烟)。
|
||||
@@ -0,0 +1,179 @@
|
||||
# 图片直播项目实施总纲
|
||||
|
||||
版本:V1.0
|
||||
日期:2026-09-17
|
||||
执行模式:Claude Code 代理开发 + 显式人工关口
|
||||
关联文档:[需求说明书](../01-需求说明书.md) · [项目计划](../02-项目计划.md) · [技术实施方案](../03-技术实施方案.md) · [优化方案](../04-优化方案.md)
|
||||
|
||||
本总纲把 V1.3 文档基线拆解为代理可执行的实施阶段序列,定义工程结构、技术选型锁定、阶段边界、人工关口与决策挂起清单。它不替代上述四份文档:需求以 R01 至 R31 为准,验收以 A01 至 A29 为准,工作包编号 T01 至 T30 沿用项目计划。各阶段的详细可执行计划在进入该阶段时另册编写(见 docs/plans/ 目录)。
|
||||
|
||||
## 1 执行模式与技术栈锁定
|
||||
|
||||
开发主体为 Claude Code 代理,按"每阶段交付可在本地完整运行、可自动化验证的端到端增量"推进;无法由代理完成的验证(真机、真实云资源、外部接口)显式登记为人工关口,不阻塞代理开发,但在对应阶段出口必须闭环。
|
||||
|
||||
D02 技术栈决策本次锁定如下,后续变更需评估返工范围后修订本总纲:
|
||||
|
||||
| 层 | 选型 | 说明 |
|
||||
|---|---|---|
|
||||
| App | Flutter,原生桥接(iOS background URLSession / Android 用户发起传输任务 + 前台服务) | P3 创建,双平台 |
|
||||
| 管理后台 | Art Design Pro 模板(Vue 3 + Vite + TypeScript + Element Plus) | 锁定模板版本;P1 尾部接入 |
|
||||
| 观众页 | Vue 3 + Vite + TypeScript 轻量入口 | 独立于后台 |
|
||||
| 嵌入组件 | TypeScript 加载器 + 项目域内 iframe 轮播页 | P4b |
|
||||
| API | NestJS + TypeScript,单代码库双部署入口 cn-app / ov-app | 模块按部署角色组织 |
|
||||
| ORM | Prisma(MySQL 方言),cn 与 ov 各自独立 schema | 去重窗口清理等以原生 SQL / 调度任务实现 |
|
||||
| 数据库 | MySQL 8 ×2:生产复用国内与海外 ECS 上 1Panel 既有 MySQL 容器,各建独立库与独立账号;本地开发用同版本 MySQL 容器保持方言一致 | 双库结构不同、互不直连,符合 A24 |
|
||||
| 队列与缓存 | BullMQ + Redis ×2 | cn 承任务队列;ov 承限速与实时辅助(独立实例与命名空间) |
|
||||
| 图片处理 | sharp(libvips) | HEIC 解码为验证关口,见 G2 |
|
||||
| 对象存储 | StoragePort 适配层:开发环境 MinIO,生产阿里云 OSS | 见第 3 节 |
|
||||
| 实时 | NestJS WebSocket(ws 适配器),部署于 ov-app | P2 |
|
||||
| 测试 | 服务端 Jest + supertest;Web 端 Vitest;E2E Playwright | 基础设施依赖 docker-compose 固定版本 |
|
||||
| Monorepo | pnpm workspaces + Turborepo | Flutter 子树用原生工具链 |
|
||||
|
||||
## 2 工程结构
|
||||
|
||||
```text
|
||||
photo/
|
||||
docs/
|
||||
plans/ 实施总纲、各阶段详细计划与验收记录
|
||||
apps/
|
||||
mobile/ Flutter App(P3 创建)
|
||||
admin/ 管理后台(Art Design Pro)
|
||||
viewer/ 观众页
|
||||
packages/
|
||||
contracts/ OpenAPI 契约 + 生成 TS 类型 + 事件契约 + 稳定错误码
|
||||
locales/ Web 公共翻译词条
|
||||
embed-loader/ JS 悬浮组件加载器(P4b)
|
||||
storage/ StoragePort 接口与 MinIO / OSS 适配器
|
||||
services/
|
||||
api/ NestJS:cn-app 与 ov-app 两个部署入口 + 共享领域模块
|
||||
worker/ 图片处理 worker(BullMQ 消费者 + sharp)
|
||||
sync/ outbox 发送 / inbox 接收 / 统计回传(P2)
|
||||
infra/
|
||||
docker-compose.yml MySQL×2、Redis×2、MinIO(P1 仅启用 cn 侧,P2 起以 profiles 启用 ov 侧;版本对齐生产 1Panel 环境)
|
||||
migrations/ 原生 SQL 与清理任务(去重窗口等)
|
||||
deploy/ 部署配置(P5)
|
||||
```
|
||||
|
||||
Schema 所有权:国内库表(users、albums、photos、upload_sessions、outbox_events 等)归 `services/api` 的 cn schema 管理;海外库表(展示副本、inbox、点赞批次、浏览去重、统计权威)归 ov schema 管理。两端共享的数据形状只经 `packages/contracts` 传递,禁止互相引用对方 schema 生成物。
|
||||
|
||||
契约优先:`packages/contracts` 是接口与事件的单一事实源,三端类型由其生成;契约任务完成处设用户确认点后再开发消费端。
|
||||
|
||||
## 3 代理执行的核心工程决策
|
||||
|
||||
1. 本地双地域仿真:docker-compose 启动两套独立 MySQL 与 Redis;cn-api(:8080) 与 ov-api(:8081) 双进程;sync 服务经 HTTP 互推。跨境同步、墓碑、乱序去重、统计回传全部在集成测试中自动化验证,不依赖真实海外资源(真实部署见 P5)。
|
||||
2. 存储适配层:`packages/storage` 定义 StoragePort(签发临时上传凭证、head 核验、服务端受控复制、禁止覆盖语义)。开发环境用 MinIO(presigned);生产用阿里云 OSS 适配器。生产适配器优先返回签名 PUT URL(与 MinIO 形态统一,App 保持单一 PUT 上传代码路径);STS/分片作为大文件增强备选,经 G1 验证后决定。凭证形态差异在接口上归一化,适配器各自的特殊语义用独立测试覆盖;真实 OSS 差异集中在 G1/G2 关口验证。
|
||||
3. 用户源接口 Mock:按技术实施方案 3.1A 假设字段实现 mock provider 与适配器接口,同步调度、水位、幂等、失败不停用等逻辑先行开发测试;真实接口契约(D08)到位后仅替换适配器。配套 fixtures 覆盖改名、停用、分页中断、空响应、增量等场景。
|
||||
|
||||
横切关注点自 P1 内建:结构化日志贯穿三端并以 requestId / uploadId / photoId 关联;稳定错误码 + 本地化文案映射;audit_logs 记录管理与权限操作;/health 端点;密钥经环境注入且不入库。
|
||||
|
||||
## 4 阶段划分与交付物
|
||||
|
||||
| 阶段 | 可运行交付物 | T 映射 | A 映射 | 出口门 | 人工关口 |
|
||||
|---|---|---|---|---|---|
|
||||
| P1 底座 + 国内核心链路 | compose 一键起环境;密码登录→建相册→上传会话→核验→处理→自动发布→观众页瀑布流/大图/短期签名 URL 刷新(读 cn 公开 API);可见性状态机与单张隐藏/软删除;seed 与脚本化 E2E;admin 最小页(后段) | T02 T05(后端部分)T06 T07,T11 部分 | A01(API 与后台级,App 端随 P3)A02 A03 A06 A13(凭证设计级,OSS 生命周期全量核验随 G1/P5);A07/A08 基础级 | 回归全绿 + 验收记录;可选 G1 | G1(可选) |
|
||||
| P2 双地域仿真 + 互动 | ov-app + outbox/inbox 同步(版本、墓碑、高优先级下线、补漏对账);点赞批次幂等;WebSocket 气泡(自身批次过滤、后台暂停);浏览统计窗口去重;统计快照回传;观众页切换至 ov-api 并新增最新/热门排序 | T25(仿真部分)T26 T13 T19 T27 T24,T11 完成 | A09 A10 A14 A21 A26 A27(仿真级) | 回归全绿 + 验收记录;D07/D11 初值确认 | 无 |
|
||||
| P3 Flutter App | 登录、相册配置、协作、持久化上传队列(准备阶段状态机、账号隔离、跨设备服务端历史)、连拍入队、原生后台传输桥接 | T03 T08 T09 T21 | A04 A05 A15 A19(代码 + 模拟器级) | 回归全绿 + 验收记录 | G2(强制)、G3(资源) |
|
||||
| P4a 后台完备 + 账号治理 | Art Design Pro 全功能后台:相册/照片/用量管理与告警、上传公开开关、批量隐藏、负责人交接、水印版本与历史重生成、下载两级控制、后台语言切换与账号管理(激活停用/重置密码/角色)、用户定期同步任务与管理页(mock 源) | T10 T20 T22 T12 T30 T04(mock 部分) | A16 A17 A18 A20 A23 A29(mock 级) | 回归全绿 + 验收记录;D06 前置 | G4(真实接口) |
|
||||
| P4b 传播组件 | embed-loader + 展示位管理 + 轮播 iframe:configVersion 校验、旧响应丢弃、不可见暂停、postMessage 白名单、data-lang 语言配置 | T14 T23 T15 | A11 A12 A22;A23 组件部分 | 回归全绿 + 验收记录 + 全端语言验收(T15/A12) | 无 |
|
||||
| P5 真实环境接入 | 真 OSS/STS 适配器验证、海外部署与域名、全球 CDN 接入与失效策略、用户接口真实对接、跨地域故障演练、容量压测、试运行与分发交接 | T16 T17 T25(真实)T28 T29 T18 | A24 A25 A28 + 全量 A 回归 + 容量报告 | 发布门槛(对齐项目计划 7.3) | G5、G6 |
|
||||
|
||||
阶段顺序 P1 → P2 → P3 → P4a → P4b → P5。P3 与 P4a 在 P2 契约稳定后可并行;P4b 依赖 P2 的公开列表契约与同步通道(展示位事件扩展),其 T15 全端语言验收还需 P3 完成;P5 依赖全部前序。
|
||||
|
||||
每阶段出口执行统一回归门:lint + typecheck + 单元 + 集成 + E2E 全绿,并产出《阶段验收记录》(docs/plans/progress/,逐项对照 A 编号登记证据与未尽事项)。
|
||||
|
||||
## 5 决策挂起清单
|
||||
|
||||
| 决策 | 内容 | 最晚确认时点 | 本次状态 |
|
||||
|---|---|---|---|
|
||||
| D01 | 首发语言(建议 zh-CN、en) | P1 开始前 | 待确认,P1 按建议值开发词条结构 |
|
||||
| D02 | 后端与 Web 技术栈 | — | 已锁定(本总纲第 1 节) |
|
||||
| D03 | iOS/Android 分发方式 | P3 开始前(资源自首周准备) | 待确认 |
|
||||
| D04 | 删除回收策略 | 永久清理规则 P4a 前 | 软删除基线已随 V4 纳入 P1 |
|
||||
| D05 | 数量并发与网络目标 | P5 压测计划前 | 待确认,按项目计划 L1/L2 阶梯准备 |
|
||||
| D06 | 下载水印与原图默认值 | P4a 开始前 | 待确认 |
|
||||
| D07 | 浏览统计窗口(建议 30 分钟)与主办字段 | P2 验收前给初值 | 待确认 |
|
||||
| D08 | 用户接口契约(稳定 ID、字段、鉴权、停启用语义) | P4a 开始前 | 待确认,mock 先行 |
|
||||
| D09 | 海外 ECS 地域规格确认与测试点 | P5 开始前 | 海外 ECS 已就绪(1Panel + Docker + MySQL),规格与地域待登记 |
|
||||
| D10 | 缓存失效窗口、轮询周期、用量阈值 | 对应模块(P4b/P5)验收前 | 待确认 |
|
||||
| D11 | 跨境同步参数(补漏周期、回传周期、中断策略) | P2 验收前给初值,P5 实测冻结 | 待确认 |
|
||||
| D12 | 两台 ECS 的 MySQL 版本、字符集与配置确认 | P1 开始前 | 已确认为 8.0+(2026-09-17),本地容器用 8.4 对齐 |
|
||||
| D13 | 代码托管平台与 CI 接入 | P5 镜像构建链路前(建议 P2 前) | 待确认,本地 git + 脚本先行 |
|
||||
|
||||
## 6 人工关口与外部资源
|
||||
|
||||
| 关口 | 内容 | 时点 | 阻塞范围 |
|
||||
|---|---|---|---|
|
||||
| G1 | 真实 OSS 冒烟:presign/STS、head 核验、禁止覆盖、staging→originals 受控复制、跨域、现有 Bucket 生命周期规则对项目前缀的影响检查 | P1 出口(有凭证即执行,无凭证显式跳过并登记) | OSS 适配器定稿 |
|
||||
| G2 | 真机双平台 + 真 OSS 组合验证:锁屏、切后台、厂商后台管理、凭证过期恢复、HEIC 真机样本解码(对齐技术验证用例 6.4) | P3 出口(强制) | App 上传链路发布 |
|
||||
| G3 | macOS/Xcode 构建链、签名与分发资质 | P3 资源(自 P1 起准备) | iOS 构建 |
|
||||
| G4 | 用户接口真实契约与测试凭据 | P4a | T30 真实对接 |
|
||||
| G5 | 海外 ECS(已装 1Panel + Docker + MySQL)规格与地域确认、域名、全球 CDN 权限 | P5 | 海外部署与 CDN |
|
||||
| G6 | 试运行真实活动与业务验收 | P5 末 | 发布 |
|
||||
|
||||
## 7 工程治理
|
||||
|
||||
- 版本库:项目当前未初始化 git。P1 第一个任务执行 `git init`,trunk-based 单主干,约定式提交信息,每任务至少一次提交,阶段完成打 tag(p1、p2…)。
|
||||
- 分支与回退:阶段 tag 作为回退基线;数据库迁移保持向前兼容,不做破坏性逆迁移(对齐技术实施方案 12.4)。
|
||||
- 密钥:全部经 .env 注入,仓库只提交 .env.example;不提交任何 OSS、接口、签名凭据。
|
||||
- 验收留痕:每阶段验收记录对照 A 编号登记;代理执行的关键决策(如契约变更)记录在阶段计划文档内。
|
||||
- 代码托管与 CI:本地 git 起步即满足回归门;仓库托管平台与 CI 接入为待确认项(D13),确认前以本地脚本保证质量,P5 镜像构建发布链路随之补齐。
|
||||
- T01 原型的替代:纸面原型由"契约评审(用户确认点)+ P1 即可运行的观众页与后台最小页"替代,页面字段与权限以 R 编号与契约为准。
|
||||
|
||||
## 8 各阶段范围明细
|
||||
|
||||
### P1 底座 + 国内核心链路
|
||||
|
||||
包含:
|
||||
|
||||
- git 初始化、monorepo 脚手架(pnpm + Turborepo)、compose(MySQL/Redis/MinIO,cn 侧,版本对齐生产)、基础 lint/typecheck/test 脚本。
|
||||
- packages/contracts:P1 范围 OpenAPI 契约(auth、albums、members、uploads、photos、public read)+ 事件契约(photo.published 等)+ 错误码;完成后设用户确认点。
|
||||
- Prisma cn schema 与迁移(MySQL 方言):users、sessions、albums、album_translations、album_members、categories、category_translations、album_assets、photos、photo_variants、upload_sessions、photo_jobs、outbox_events、audit_logs。
|
||||
- 数据模型 PG→MySQL 语义复核清单:唯一约束与 upsert 行为、时间精度与 UTC 存储、排序与游标稳定性、token 大小写敏感(显式 binary 校对或受限字符集)、连接池上限。
|
||||
- cn-api:密码登录/刷新/注销(成熟密码哈希、会话可撤销、登录失败基础限速);相册 CRUD 与多语言字段、成员与分类;上传会话幂等初始化(clientTaskId 唯一)、临时凭证签发(StoragePort)、续签、完成核验(head + 字节数 + 完整性)、staging→originals 受控复制(禁止覆盖);照片可见性状态机与单张隐藏/软删除;公开读接口(相册信息按语言回退、游标分页稳定排序、单图定位);图片短期签名 URL 签发与过期刷新;outbox 事务写入;CORS 来源按环境配置;/health。
|
||||
- worker:BullMQ 消费者 + sharp 流水线(真实类型校验、解码、方向校正、缩略图/浏览图、EXIF 清理、尺寸记录),事务切换公开状态并写 photo.published 事件;失败归类与有限重试。
|
||||
- viewer 基础页:活动信息区(宣传图、多语言回退、浏览次数占位、活动时间按相册时区展示)、双列瀑布流(宽高预留、按需渲染/懒加载)、大图基础交互(左右切换、双指缩放、关闭回原位)、单图分享定位路由、空相册本地化空态、新照片提示(轮询)、分类筛选、zh-CN/en 切换;最新/热门排序切换依赖点赞数,随 P2 提供。
|
||||
- admin 最小页(后段低优先级):Art Design Pro 登录对接本地会话、相册列表/创建/编辑、成员管理。
|
||||
- 宣传图/封面/Logo 上传接口与 viewer 展示(album_assets 的 presign+确认,复用照片上传机制)随 P1b 交付(R09 依赖,P1a 先建表不出接口)。
|
||||
- E2E:脚本上传(多文件、同名、重复完成通知)→ worker 处理 → 公开 API 可见 → Playwright 观众页冒烟。
|
||||
|
||||
不包含:海外同步与互动(P2)、Flutter(P3)、批量隐藏/交接/水印/下载控制/用量(P4a)、展示位与组件(P4b)、真实 OSS 常态接入(G1/P5)。
|
||||
|
||||
P1 内部再切 1a(后端链路,API 级 E2E 先行)与 1b(观众页与 admin 界面);admin 最小页如遇模板接入风险可顺延至 P4a,不阻塞 P1 出口。
|
||||
|
||||
### P2 双地域仿真 + 互动
|
||||
|
||||
包含:ov schema(展示副本、inbox、水位、点赞批次、浏览会话与请求去重、统计权威)与 like_batches 去重窗口方案(MySQL 表分区要求分区键进入全部唯一键、会破坏批次唯一约束,故采用索引 + 定时清理任务 + 过期批次拒绝重放替代分区,清理任务纳入 infra/migrations 与调度);sync 服务(outbox 发送器签名鉴权、inbox 幂等消费、版本与墓碑、高优先级下线队列、定时补漏对账);海外紧急停用入口(独立于同步链路的 ov 侧运维端点,跨境中断时可达:仅允许更严屏蔽、不修改国内权威数据、不因普通同步事件自动解除——技术方案 §2A);ov-app 公开 API + WebSocket(相册隔离订阅、点赞批次幂等、合并广播、自身批次过滤、断线降级轮询);浏览统计(跨标签页共享匿名标识、窗口去重、requestId 幂等、并发首访唯一、排除规则:分页请求/图片资源请求/轮播/后台预览不计入,后台预览走独立路由);统计快照回传(statVersion 单调替换);观众页切换 ov-api 并接入实时更新,新增最新/热门排序切换(热门按点赞数版本化短期快照)。D07、D11 初值在验收前确认。
|
||||
|
||||
### P3 Flutter App
|
||||
|
||||
包含:登录与账号隔离、修改密码;App 内语言切换(zh-CN/en,跟随设备与手动选择);相册配置与协作页面(含相册分享链接与二维码入口);本地任务库与状态机(preparing/preparation_failed/queued/uploading/…);准备阶段(源文件获取、空间检查);连续拍摄自动入队;原生后台上传桥接(iOS URLSession background、Android 用户发起传输任务/前台服务)与任务映射恢复;凭证过期续签(单文件 PUT 为上传基线,分片上传作为后续大文件验证项);跨设备服务端历史。出口强制 G2 真机验证:未通过不标志 P3 完成、不阻塞 P4a/P4b 推进,但阻塞 P5 试运行并触发重估。
|
||||
|
||||
### P4a 后台完备 + 账号治理
|
||||
|
||||
包含:后台全功能(相册/照片管理、筛选、批量隐藏逐项结果、上传公开独立开关、负责人转移、用量统计与阈值告警(含统计数字与同步时间后台展示)、相册分享链接与二维码展示、后台语言切换、账号管理:激活停用/重置密码/角色);水印配置版本化与历史重生成任务(进度、可重试、不覆盖原图、原子切换引用);下载两级控制与微信等浏览器引导;用户定期同步任务(调度锁、水位、幂等、失败记录、手动补拉、状态页)接 mock 源。G4 后替换真实适配器。
|
||||
|
||||
### P4b 传播组件
|
||||
|
||||
包含:embed-loader(版本化地址、防重复初始化、postMessage 白名单、data-lang 语言配置);展示位管理与权限(展示位实体与 configVersion 纳入 cn→ov 内容同步,海外公开组件 API 读取本地副本,对应 R28);轮播 iframe(configVersion、旧响应丢弃、近期照片队列、不可见暂停、空态);接入样例站点与宿主 CSP 说明;出口执行 T15 全端语言验收与文案补齐(App、后台、观众页、组件四端切换、回退与漏译检查,对应 A12)。
|
||||
|
||||
### P5 真实环境接入
|
||||
|
||||
包含:OSS 真实适配器与 STS 验证;国内与海外两台 ECS(均已装 1Panel + Docker + 既有 MySQL 容器)上的部署:镜像与 compose 编排交由 1Panel 容器管理、域名与 TLS 经 1Panel OpenResty 反代、复用既有 MySQL 容器各建独立库与账号、Redis 以容器部署、1Panel 计划备份并演练恢复、数据库迁移由发布流程执行(不手工进容器操作);海外首次全量快照与增量追赶;全球 CDN 回源、URL 鉴权、缓存失效与预热策略;用户接口真实对接;跨地域故障演练(断网重放、乱序、重复回传、紧急停用、恢复);容量压测(L1/L2)与实测报告;监控告警、回滚演练;试运行与 App 分发交接(服务端保持必要的旧客户端兼容)。
|
||||
|
||||
## 9 风险与应对(实施总纲层)
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
|---|---|---|
|
||||
| MinIO 与 OSS 语义差异(STS 形态、禁止覆盖、分片) | 适配器返工 | StoragePort 归一化 + G1 前置冒烟 + G2 真机组合验证 |
|
||||
| 数据模型从 PG 假设迁移到 MySQL 的语义差异 | 去重、并发、排序行为偏差 | P1 首批任务含语义复核清单(唯一约束、upsert、UTC 时间精度、token 校对、游标排序);去重窗口替代分区,P2 用真实并发事务测试验证 |
|
||||
| 复用 1Panel 既有 MySQL 容器 | 与其他业务争用资源、备份耦合 | 独立库 + 独立账号 + 连接池上限;纳入 1Panel 计划备份并演练恢复;用量告警(A23)覆盖数据库资源 |
|
||||
| 生产 MySQL 实际版本低于 8.0 | 迁移与去重 SQL 兼容性问题 | D12 于 P1 开始前确认;必要时在 1Panel 新建 8.x 容器专用本项目,原容器不动 |
|
||||
| sharp HEIC 解码在 Windows 预编译不可用 | iPhone 照片失败 | P1 用样本 HEIC 验证;不可用则换 libheif 路线并登记 |
|
||||
| Art Design Pro 模板升级或裁剪成本 | 后台进度受阻 | 锁定模板版本;接入置于 P1 尾部不阻塞主线 |
|
||||
| 本地仿真与真实跨境网络差异 | P5 问题集中暴露 | 同步语义全部版本化/幂等;P5 演练清单对齐项目计划 7.3 |
|
||||
| 决策挂起未按时确认 | 阶段出口阻塞 | 第 5 节清单随阶段计划再次提醒,未确认按建议值开发并标记 |
|
||||
| 代理长周期执行上下文漂移 | 实现偏离文档 | 每阶段独立计划 + 验收记录 + 契约单一事实源 |
|
||||
|
||||
## 10 与项目计划(S1-S6 / 岗位人日)的关系
|
||||
|
||||
本总纲面向代理执行重新纵切,阶段与 S1-S6 不一一对应:S1 需求基线由本总纲 + 契约评审替代;S2 技术验证拆入 G1(P1 出口可选)与 G2/G3(P3)前置;S3 对应 P1;S4 对应 P2 + P3 + P4a 主体;S5 对应 P4a/P4b + P5 前段;S6 对应 P5 末段。T01-T30 全覆盖(T01 由契约评审与早可用页面替代;T04 由 P4a 的 mock 契约先行 + G4/P5 真实接口验证替代;T15 落在 P4b 出口的全端语言验收)。原岗位人日估算(156 至 231)作为规模参照,代理执行的实际节奏以阶段验收记录为准;对外承诺的日历工期仍以项目计划 W1-W16 基线管理,两者并行不悖。
|
||||
Reference in New Issue
Block a user