Files
photo-live/docs/03-技术实施方案.md
T
浅念 d973c714f0 chore: init repository skeleton
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-17 16:13:49 +08:00

39 KiB

图片直播项目技术实施方案

版本:V1.3
日期:2026-09-17
关联文档:需求说明书 · 项目计划 · 优化方案

本方案用于指导第一版开发、联调和部署。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 框架,但应保持下面的数据与状态约束。版本号在工程初始化时选择并锁定,不在当前文档写死未经验证的最新版。

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 工程结构

建议使用单仓库,便于接口契约和多语言资源共同维护;此处只是规划,不自动创建业务工程。

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 下创建独立前缀,实际前缀在部署时确认:

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 状态分离

本地任务
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 后台传输说明、后台会话配置。

6.3 Android

按目标 Android 版本采用适用的用户发起数据传输任务;兼容路径结合系统任务调度和符合要求的前台服务通知。具体选择以目标 SDK、设备版本和原生验证结果为准。普通可延迟工作与用户正在等待的批量上传应区别处理。

需验证主流厂商后台管理、通知权限、网络切换和用户停止任务的行为。WorkManager 等调度机制本身不构成无限连续上传保证。Android 数据传输选型、用户发起的数据传输任务。

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 事件契约

{
  "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 悬浮组件

下面的域名为占位示例,部署后由后台生成真实地址,不可直接作为可用代码交付。

<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。实际实现前结合目标系统版本复核;本文不承诺任何插件已满足全部后台场景。