feat(contracts,api): UI-01 cover, preview, scope=own and embed config extensions

契约扩展(全部可选/可空,旧客户端零感知)+ cn/ov 实现 + e2e 权限矩阵:
- AlbumSummary += cover(kind=cover state=effective 最新行的短期签名 URL,
  列表/详情按页批量组装非 N+1;未配置为 null)
- AdminPhoto += preview{thumb,browse}(变体齐备且未删除才签发;隐藏仅
  admin/owner;协作者仅自己上传,scope=own 服务端过滤 uploaderId,
  成员资格逐请求裁决)
- listAlbumPhotos += scope=all|own(缺省 all 行为不变:仅 owner/admin,
  协作者 403;own 任何有效成员)
- EmbedSlotSettings += imageFit/offsetX/offsetBottom/zIndex(cn PATCH 校验
  → slot-state 投影 → ov 公开回读;settings 为 Json 列免迁移,读侧
  normalizeStoredSettings 兜旧形状默认值)
- Flutter 模型同步(SignedUrl/PhotoPreview/AlbumPhotoScope,可空容错)

e2e:ui01-contracts 9 例(封面选取/字节取回/N+1 计数断言、preview 权限
矩阵、scope 过滤与 403/404/400)+ embed ⑨ 配置链路;全量 e2e 257 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-21 14:09:35 +08:00
parent 3b5f8d2d77
commit 8f4ad11bd5
21 changed files with 1428 additions and 40 deletions
+93 -2
View File
@@ -949,6 +949,11 @@ paths:
相册照片管理列表(区别于公开列表):全部状态可见(unpublished/published/
hidden/deleted),条目含 uploader 脱敏信息。按 uploadedAt 与 ID 稳定倒序
游标分页。
scope(UI-01 可选扩展,缺省 all=既有行为不变):协作者显式查询自己
上传的照片——服务端过滤 uploaderId=当前用户,成员资格逐请求校验;
仅 owner/admin 可用 all(协作者 all → 403)。条目 preview 按请求者
权限逐张裁决(见 PhotoPreview)。
security:
- bearerAuth: []
parameters:
@@ -962,6 +967,17 @@ paths:
description: 分类 ID 过滤。
schema:
type: string
- name: scope
in: query
description: |
数据范围(UI-01):all=本相册全部照片(缺省,仅 owner/admin);
own=仅当前用户上传(任何有效成员,服务端过滤)。
schema:
type: string
enum:
- all
- own
default: all
- $ref: '#/components/parameters/CursorQuery'
- name: limit
in: query
@@ -987,7 +1003,9 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 仅创建者或管理员可访问管理列表(AUTH_FORBIDDEN)。
description: |
仅创建者或管理员可访问全量管理列表(scope=all 或缺省时协作者
AUTH_FORBIDDEN);scope=own 需相册有效成员。
content:
application/json:
schema:
@@ -1915,6 +1933,8 @@ paths:
返回展示位绑定相册的近期已发布照片(≤20,按 publishedAt 与 ID 稳定
倒序,小尺寸缩略图省流量)与相册摘要。lang 语义:auto(缺省)或取值
不支持时按相册 defaultLocale 回退(不报 400,与公开相册一致)。
settings(UI-01)返回后台配置的归一化全集(嵌入端应用后台配置,
data-* 覆盖优先)。
- 防探测:slotToken 不存在、展示位停用(enabled=false)或未绑定相册
一律 404 NOT_FOUND(同码同文案,不区分原因,避免枚举探测)。
@@ -2797,6 +2817,16 @@ components:
统计副本最近同步时间(P2 A27:ov→cn 统计快照在本相册的应用时刻);
null 表示尚无回传应用(viewCountReplica 副本位仍为初始 0)。照片级
点赞副本(PhotoRecord.likeCount)的同步时间不经相册行表达。
cover:
description: |
相册封面摘要(UI-01,readOnly 可选扩展):当前生效封面资产
(album_assets kind=cover、state=effective 的最新一行)的短期
签名 URL;未配置封面为 null。列表端点按页批量组装(非逐相册
查询);旧客户端忽略本字段不受影响。
readOnly: true
anyOf:
- $ref: '#/components/schemas/SignedUrl'
- type: 'null'
AlbumList:
allOf:
@@ -3513,6 +3543,21 @@ components:
readOnly: true
description: 展示姓名(来自用户资料)。
PhotoPreview:
type: object
description: |
管理端照片预览(UI-01,readOnly):thumb/browse 短期签名 URL 与过期
时间。签发条件(服务端逐张裁决):请求者有该照片管理权(admin 全部 /
相册 owner 本相册全部 / 协作者仅自己上传)且照片有有效变体、未删除;
隐藏照片仅 admin/owner 签发。不满足条件时 AdminPhoto.preview 为 null
(PhotoRecord 本身仍如实返回)。URL 过期后经列表重取刷新。
required: [thumb, browse]
properties:
thumb:
$ref: '#/components/schemas/SignedUrl'
browse:
$ref: '#/components/schemas/SignedUrl'
AdminPhoto:
allOf:
- $ref: '#/components/schemas/PhotoRecord'
@@ -3521,6 +3566,14 @@ components:
properties:
uploader:
$ref: '#/components/schemas/AdminPhotoUploader'
preview:
description: |
受权限管理的照片预览(UI-01,readOnly 可选扩展;无变体/已删除/
无权签发时为 null)。旧客户端忽略本字段不受影响。
readOnly: true
anyOf:
- $ref: '#/components/schemas/PhotoPreview'
- type: 'null'
AdminPhotoList:
allOf:
@@ -4012,7 +4065,9 @@ components:
description: |
展示位展示设置。整体提交语义:创建时 settings 可整体缺省(各字段按
下列缺省值落库),提供时字段齐全;更新为整体替换(未单独提交
settings 则保持不变)。响应中字段恒齐全。
settings 则保持不变)。响应中字段恒齐全。UI-01 新增字段
(imageFit/offsetX/offsetBottom/zIndex)为可选扩展:提交时可缺省
(按默认值归一化落库),旧 JSON/旧客户端零影响;响应恒为归一化全集。
required: [position, size, intervalMs, lang, collapsedDefault]
properties:
position:
@@ -4041,6 +4096,33 @@ components:
type: boolean
description: 初始是否收起(收起后仅展示小把手,点击展开)。
default: false
imageFit:
type: string
description: |
轮播图片适配方式(UI-01):contain=完整展示于 4:3 舞台(中性底);
cover=裁切填满舞台。可缺省(默认 contain)。
enum:
- contain
- cover
default: contain
offsetX:
type: integer
minimum: 0
maximum: 120
description: 组件距宿主页面边缘的水平偏移(像素,UI-01)。可缺省。
default: 16
offsetBottom:
type: integer
minimum: 0
maximum: 240
description: 组件距宿主页面底部的垂直偏移(像素,UI-01)。可缺省。
default: 24
zIndex:
type: integer
minimum: 1
maximum: 2147483000
description: 组件在宿主页面的层级(UI-01)。可缺省。
default: 1000
EmbedSlot:
type: object
@@ -4374,6 +4456,8 @@ components:
照片,按 publishedAt 与 ID 稳定倒序,上限 20(空数组=相册暂无已发布
照片,嵌入端展示空态卡)。album 当前恒非 null(未绑定/停用的展示位
公开端一律 404 防探测);null 为契约前瞻保留,嵌入端按空态处理。
settings(UI-01 可选扩展)为后台配置的归一化全集(缺字段按默认值
补齐)——嵌入端据此应用后台配置,data-* 覆盖优先于它。
required: [slotToken, configVersion, album, photos]
properties:
slotToken:
@@ -4386,6 +4470,13 @@ components:
minimum: 1
readOnly: true
description: 配置版本(嵌入端变更检测:版本变化即整体重置渲染)。
settings:
description: |
展示设置(UI-01 可选扩展;ov 副本 JSON 按契约默认值归一化后返回,
恒为全集)。旧客户端忽略本字段不受影响。
readOnly: true
allOf:
- $ref: '#/components/schemas/EmbedSlotSettings'
album:
description: 绑定相册摘要;未绑定为 null(当前部署不会出现,见 schema 描述)。
anyOf: