feat(contracts): embed slots admin and public endpoints

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 21:49:09 +08:00
parent e9a7dc8740
commit eb51069a12
4 changed files with 879 additions and 0 deletions
+440
View File
@@ -45,6 +45,11 @@ tags:
description: 照片可见性调整(单项与批量)与软删除(工作人员)
- name: admin
description: 管理辅助查询与账号治理(用户管理、相册交接、用户同步、用量统计)
- name: embed-slots
description: |
展示位管理(P4b 悬浮轮播组件挂载点):创建、设置与相册绑定/解绑。
权限简化决策:仅公司 admin 可管理展示位(成员授权面 P5 按需补);
无删除端点——enabled=false 即下线(软停用,公开端随即 404)。
- name: public
description: 观众匿名只读(相册信息与公开照片)
- name: interactions
@@ -1636,6 +1641,141 @@ paths:
'500':
$ref: '#/components/responses/InternalError'
/admin/embed-slots:
get:
operationId: listEmbedSlots
tags: [embed-slots]
summary: 展示位列表
description: |
展示位(悬浮轮播组件挂载点)治理列表,游标分页(按 createdAt 与 ID
稳定倒序);仅公司 admin 可用(P4b 权限简化:成员授权面 P5 按需补)。
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/CursorQuery'
- name: limit
in: query
description: 每页条数。
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
'200':
description: 展示位列表页。
content:
application/json:
schema:
$ref: '#/components/schemas/EmbedSlotList'
'400':
description: 查询参数校验失败(VALIDATION_FAILED;cursor/limit 非法)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 仅 admin 可管理展示位(AUTH_FORBIDDEN)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
post:
operationId: createEmbedSlot
tags: [embed-slots]
summary: 创建展示位
description: |
创建展示位(仅 admin):服务端生成不可枚举 publicToken(嵌入代码
data-slot 引用);初始 enabled=true、albumId=null(未绑定,公开端
404)、configVersion=1。settings 整体缺省时按各字段缺省值落库。
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/EmbedSlotCreateRequest'
responses:
'201':
description: 创建后的展示位条目。
content:
application/json:
schema:
$ref: '#/components/schemas/EmbedSlot'
'400':
description: 请求体校验失败(VALIDATION_FAILED;name 长度非法、settings 字段越界或取值非法)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 仅 admin 可管理展示位(AUTH_FORBIDDEN)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
/admin/embed-slots/{id}:
parameters:
- $ref: '#/components/parameters/EmbedSlotId'
patch:
operationId: updateEmbedSlot
tags: [embed-slots]
summary: 更新展示位
description: |
展示位部分更新(仅 admin):name/enabled/settings/albumId 任一管理面
变更均递增 configVersion 并触发 slot-state 同步事件(嵌入端配置版本
变化即整体重置渲染)。albumId 语义:非 null 为绑定/换绑目标相册
(相册须存在,不存在 404 ALBUM_NOT_FOUND),null 为解绑(公开端随即
404);albumId 值本身变化(含换绑与解绑)即视为配置变更。settings
为整体替换(提供时字段齐全)。无删除端点:enabled=false 即下线。
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/EmbedSlotUpdateRequest'
responses:
'200':
description: 更新后的展示位条目。
content:
application/json:
schema:
$ref: '#/components/schemas/EmbedSlot'
'400':
description: 参数校验失败(VALIDATION_FAILED;name 长度非法、settings 字段越界或取值非法、空请求体)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 仅 admin 可管理展示位(AUTH_FORBIDDEN)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: 展示位不存在(NOT_FOUND),或绑定目标相册不存在(ALBUM_NOT_FOUND)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
# ─────────────────────────── public ───────────────────────────
/public/albums/{token}:
@@ -1763,6 +1903,72 @@ paths:
'500':
$ref: '#/components/responses/InternalError'
/public/embed-slots/{slotToken}:
parameters:
- $ref: '#/components/parameters/EmbedSlotToken'
get:
operationId: getPublicEmbedSlot
tags: [public]
summary: 展示位公开数据(嵌入组件数据源)
description: |
悬浮轮播组件的数据源(ov 域部署,匿名只读;嵌入 iframe 轮询调用):
返回展示位绑定相册的近期已发布照片(≤20,按 publishedAt 与 ID 稳定
倒序,小尺寸缩略图省流量)与相册摘要。lang 语义:auto(缺省)或取值
不支持时按相册 defaultLocale 回退(不报 400,与公开相册一致)。
- 防探测:slotToken 不存在、展示位停用(enabled=false)或未绑定相册
一律 404 NOT_FOUND(同码同文案,不区分原因,避免枚举探测)。
- 条件请求(A23 省流量):200/304 响应携带强 ETag,取值为四元组
`"{configVersion}-{photoCount}-{maxPublishedAtMs}-{maxPhotoId}"`
(含照片条数与最新发布时间——新照片发布必变,避免仅比对实体版本
时新照片小版本导致 304 陈旧);请求携带 If-None-Match 且匹配时
返回 304(无响应体,仍携带 ETag)。
security: []
parameters:
- name: lang
in: query
description: 内容语言;auto(缺省)按相册 defaultLocale,取值不支持时静默回退(不报 400)。
schema:
$ref: '#/components/schemas/EmbedSlotLang'
- name: If-None-Match
in: header
description: 条件请求:先前响应返回的 ETag 值;匹配则返回 304。
schema:
type: string
responses:
'200':
description: 展示位公开数据。
headers:
ETag:
description: 强校验器(组成与失效语义见端点 description)。
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/PublicEmbedSlot'
'304':
description: 内容未变(If-None-Match 匹配);无响应体。
headers:
ETag:
description: 当前 ETag(与匹配值一致)。
schema:
type: string
'400':
description: 查询参数校验失败(VALIDATION_FAILED;lang 取值不支持时不报错,按相册 defaultLocale 回退)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: 展示位不存在、已停用或未绑定相册(NOT_FOUND;不区分以避免枚举)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
# ─────────────────────────── interactions ───────────────────────────
/public/albums/{token}/photos/{photoId}/likes:
@@ -2242,6 +2448,20 @@ components:
description: 相册公开令牌(不可枚举,持有即可访问)。
schema:
type: string
EmbedSlotId:
name: id
in: path
required: true
description: 展示位 ID。
schema:
type: string
EmbedSlotToken:
name: slotToken
in: path
required: true
description: 展示位公开令牌(不可枚举,持有即可访问;格式见 EmbedSlot.publicToken)。
schema:
type: string
CursorQuery:
name: cursor
in: query
@@ -3775,6 +3995,154 @@ components:
items:
$ref: '#/components/schemas/UsageAlert'
# ── 展示位(管理端,P4b) ──
EmbedSlotLang:
type: string
description: |
展示位内容语言(设置与公开查询共用):auto=按相册 defaultLocale,
其余为指定语言(取值不支持时服务端静默回退相册 defaultLocale)。
enum:
- auto
- zh-CN
- en
EmbedSlotSettings:
type: object
description: |
展示位展示设置。整体提交语义:创建时 settings 可整体缺省(各字段按
下列缺省值落库),提供时字段齐全;更新为整体替换(未单独提交
settings 则保持不变)。响应中字段恒齐全。
required: [position, size, intervalMs, lang, collapsedDefault]
properties:
position:
type: string
description: 悬浮位置(宿主页面左/右侧)。
enum:
- left
- right
default: right
size:
type: string
description: 组件尺寸(sm=窄屏降级档,宿主 viewport<480px 时自动降为 sm)。
enum:
- sm
- md
default: md
intervalMs:
type: integer
minimum: 3000
maximum: 30000
description: 轮播切换间隔毫秒。
default: 5000
lang:
$ref: '#/components/schemas/EmbedSlotLang'
collapsedDefault:
type: boolean
description: 初始是否收起(收起后仅展示小把手,点击展开)。
default: false
EmbedSlot:
type: object
description: |
展示位条目(管理视角)。albumId/albumTitle 为当前绑定相册(null=未
绑定,公开端 404);albumTitle 按相册 defaultLocale 解析(管理列表
展示用)。任一管理面变更递增 configVersion。
required:
- id
- publicToken
- name
- enabled
- albumId
- albumTitle
- configVersion
- settings
- updatedAt
properties:
id:
type: string
readOnly: true
description: 展示位 ID。
publicToken:
type: string
readOnly: true
pattern: '^[0-9a-z]{20,32}$'
description: 展示位公开令牌(嵌入代码 data-slot 引用,不可枚举)。
name:
type: string
minLength: 1
maxLength: 64
description: 展示位名称(管理标识)。
enabled:
type: boolean
description: 是否启用;false 即下线(公开端 404,等同删除语义)。
albumId:
type: [string, 'null']
readOnly: true
description: 绑定相册 ID;未绑定为 null。
albumTitle:
type: [string, 'null']
readOnly: true
description: 绑定相册标题(按相册 defaultLocale 解析);未绑定为 null。
configVersion:
type: integer
minimum: 1
readOnly: true
description: 配置版本(任一管理面变更递增;嵌入端据此整体重置渲染,亦为公开端 ETag 组成部分)。
settings:
$ref: '#/components/schemas/EmbedSlotSettings'
updatedAt:
type: string
format: date-time
readOnly: true
description: 最近更新时间。
EmbedSlotList:
allOf:
- $ref: '#/components/schemas/CursorPageMeta'
- type: object
required: [items]
properties:
items:
type: array
description: 展示位条目(按 createdAt 与 ID 稳定倒序)。
items:
$ref: '#/components/schemas/EmbedSlot'
EmbedSlotCreateRequest:
type: object
description: 创建展示位请求;settings 可整体缺省(各字段按缺省值落库),提供时字段齐全。
required: [name]
properties:
name:
type: string
minLength: 1
maxLength: 64
description: 展示位名称。
settings:
$ref: '#/components/schemas/EmbedSlotSettings'
EmbedSlotUpdateRequest:
type: object
description: |
展示位部分更新请求(仅提交的字段被修改,空请求体 400)。settings 为
整体替换(提供时字段齐全);albumId:非 null 绑定/换绑目标相册(相册
须存在),null 解绑(公开端随即 404)——albumId 值变化即 configVersion+1。
properties:
name:
type: string
minLength: 1
maxLength: 64
description: 展示位名称。
enabled:
type: boolean
description: 启停;false 即下线(软停用,无删除端点)。
settings:
$ref: '#/components/schemas/EmbedSlotSettings'
albumId:
type: [string, 'null']
description: 绑定相册 ID;null 解绑。
# ── 公开(观众端) ──
SignedUrl:
@@ -3958,6 +4326,78 @@ components:
items:
$ref: '#/components/schemas/PublicPhoto'
# ── 展示位公开数据(观众端,ov 域,P4b) ──
PublicEmbedAlbum:
type: object
description: 展示位绑定相册摘要(title 已按 lang→相册 defaultLocale 回退解析)。
required: [publicToken, title, defaultLocale]
properties:
publicToken:
type: string
readOnly: true
pattern: '^[0-9a-z]{20,32}$'
description: 相册公开令牌(点击照片跳转 viewer 相册页用)。
title:
type: string
readOnly: true
description: 相册标题(实际返回语言)。
defaultLocale:
$ref: '#/components/schemas/Locale'
PublicEmbedPhoto:
type: object
description: |
展示位照片条目(小尺寸缩略图省流量,与公开列表的 browse 大图区分;
签名 URL 为短期凭证,过期后经本端点刷新)。
required: [photoId, thumb, width, height]
properties:
photoId:
type: string
readOnly: true
description: 照片 ID(跳转 viewer 单图定位用)。
thumb:
$ref: '#/components/schemas/SignedUrl'
width:
type: integer
readOnly: true
description: 缩略图宽度(像素);用于占位,减少加载跳动。
height:
type: integer
readOnly: true
description: 缩略图高度(像素)。
PublicEmbedSlot:
type: object
description: |
展示位公开数据(嵌入组件数据源;ov 只读副本)。photos 为近期已发布
照片,按 publishedAt 与 ID 稳定倒序,上限 20(空数组=相册暂无已发布
照片,嵌入端展示空态卡)。album 当前恒非 null(未绑定/停用的展示位
公开端一律 404 防探测);null 为契约前瞻保留,嵌入端按空态处理。
required: [slotToken, configVersion, album, photos]
properties:
slotToken:
type: string
readOnly: true
pattern: '^[0-9a-z]{20,32}$'
description: 展示位公开令牌(回显请求路径)。
configVersion:
type: integer
minimum: 1
readOnly: true
description: 配置版本(嵌入端变更检测:版本变化即整体重置渲染)。
album:
description: 绑定相册摘要;未绑定为 null(当前部署不会出现,见 schema 描述)。
anyOf:
- $ref: '#/components/schemas/PublicEmbedAlbum'
- type: 'null'
photos:
type: array
maxItems: 20
description: 近期照片(publishedAt 与 ID 稳定倒序,小尺寸缩略图)。
items:
$ref: '#/components/schemas/PublicEmbedPhoto'
# ── 互动(观众端,ov 域) ──
DownloadVariant: