feat(contracts): 广告位 openapi 端点/schema 与 AD_UPLOAD_MAX_BYTES,类型再生

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-28 22:16:12 +08:00
parent af8f3b78dd
commit ec55d5718b
4 changed files with 947 additions and 1 deletions
+448
View File
@@ -50,6 +50,11 @@ tags:
展示位管理(P4b 悬浮轮播组件挂载点):创建、设置与相册绑定/解绑。
权限简化决策:仅公司 admin 可管理展示位(成员授权面 P5 按需补);
无删除端点——enabled=false 即下线(软停用,公开端随即 404)。
- name: ads
description: |
广告位管理(观众页相册头部轮播素材):创建、换图、启停、排序与相册
绑定/全局投放。仅公司 admin 可管理;图片两步直传(staging 预签名 →
创建/更新时核验转存),不走变体流水线、原图直出(上限 2MB)。
- name: public
description: 观众匿名只读(相册信息与公开照片)
- name: interactions
@@ -1942,6 +1947,202 @@ paths:
'500':
$ref: '#/components/responses/InternalError'
/admin/ads:
get:
operationId: listAdminAds
tags: [ads]
summary: 广告位列表
description: |
广告位治理列表(仅 admin):按 sortOrder 升序、并列 createdAt 升序
全量返回(运营量级 <20,不分页);imageUrl 为现签 GET,过期后重拉
本列表刷新。albumIds=null 为全局投放。
security:
- bearerAuth: []
responses:
'200':
description: 广告位列表(全量)。
content:
application/json:
schema:
$ref: '#/components/schemas/AdminAdListResponse'
'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: createAd
tags: [ads]
summary: 创建广告位
description: |
创建广告位(仅 admin):服务端核验 staging 对象(存在 + bytes +
contentType 与申明一致,banner complete 同口径)后 copyIfAbsent
转存版本化终键,落库并写 outbox ad.updated(entityVersion=1)。
albumIds 未提供或空数组归一为 null(全局投放);元素须为存在的相册
ID(相册删除不级联清理,容失效见设计 §2.1)。
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AdCreateRequest'
responses:
'201':
description: 创建后的广告位条目。
content:
application/json:
schema:
$ref: '#/components/schemas/AdminAdItem'
'400':
description: |
参数校验失败:linkUrl 非 http(s)/超长、albumIds 含不存在的相册
(VALIDATION_FAILED),或 staging 核验失败——对象缺失/字节或类型
与申明不一致(UPLOAD_VERIFY_FAILED)。
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/ads/uploads:
post:
operationId: initAdUpload
tags: [ads]
summary: 初始化广告图直传
description: |
广告图两步直传第一步(仅 admin):签发 staging 对象的临时直传凭证,
键 photo-live/{env}/ads/staging/{cuid}.{ext}。字节上限收紧为 2MB
(AD_UPLOAD_MAX_BYTES:广告不走变体流水线、原图直出且轮播多张同屏,
独立于照片域 UPLOAD_MAX_BYTES 的 50MB,设计 §3);类型白名单
image/jpeg|png|heic。无会话概念(重复转存由创建端 copyIfAbsent
禁止覆盖语义承担)。
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AdUploadInitRequest'
responses:
'201':
description: staging 对象键与临时直传凭证。
content:
application/json:
schema:
$ref: '#/components/schemas/AdUploadInitResponse'
'400':
description: 参数校验失败(VALIDATION_FAILED):超过 2MB 限额、类型不接受或文件名扩展名非法。
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/ads/{adId}:
parameters:
- $ref: '#/components/parameters/AdId'
patch:
operationId: updateAd
tags: [ads]
summary: 更新广告位
description: |
广告位部分更新(仅 admin):title/linkUrl/enabled/sortOrder/albumIds
任一变更递增 entityVersion 并重发整行快照(ad.updated;停用 true→false
走 high 优先级)。albumIds:未提供=不动,null=转全局,数组=整体替换;
换图时 imageStagingKey/imageBytes/imageContentType 三者必须同时提供
(核验后转存新版本键,旧对象不回收,OSS 生命周期兜底)。
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AdUpdateRequest'
responses:
'200':
description: 更新后的广告位条目。
content:
application/json:
schema:
$ref: '#/components/schemas/AdminAdItem'
'400':
description: |
参数校验失败(VALIDATION_FAILED):字段非法、换图三字段未同时提供
或 staging 核验失败(UPLOAD_VERIFY_FAILED)。
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: 广告位不存在(AD_NOT_FOUND)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
delete:
operationId: deleteAd
tags: [ads]
summary: 硬删除广告位
description: >-
硬删行并写 ad.deleted 墓碑(high 优先级;ov 删 ads_ov 投影行且不被
旧事件复活)。cn 侧数据不保留、无恢复端点;重复删除 404。
security:
- bearerAuth: []
responses:
'204':
description: 已删除。
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: 仅 admin 可管理广告位(AUTH_FORBIDDEN)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: 广告位不存在(AD_NOT_FOUND)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
# ─────────────────────────── public ───────────────────────────
/public/albums/{token}:
@@ -2069,6 +2270,36 @@ paths:
'500':
$ref: '#/components/responses/InternalError'
/public/albums/{token}/ads:
parameters:
- $ref: '#/components/parameters/AlbumPublicToken'
get:
operationId: listPublicAds
tags: [public]
summary: 公开广告列表
description: |
观众页相册头部轮播数据(ov 域部署):返回「全局投放(albumIds=null)
∨ 绑定本相册」的启用广告,按 sortOrder 升序、并列 createdAt 升序;
imageUrl 为现签 GET(TTL=SIGNED_URL_TTL_SECONDS)。无广告返回空
items(非 404)。量级假设广告 ≤ 百条:ads_ov 全表捞 + 应用层过滤,
不加独立限速档(与公开相册读同量级,设计 §5.2)。
security: []
responses:
'200':
description: 公开广告列表(可为空数组)。
content:
application/json:
schema:
$ref: '#/components/schemas/PublicAdListResponse'
'404':
description: 相册不存在或未公开(ALBUM_NOT_PUBLIC,不区分以避免枚举)。
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/InternalError'
/public/embed-slots/{slotToken}:
parameters:
- $ref: '#/components/parameters/EmbedSlotToken'
@@ -2630,6 +2861,13 @@ components:
description: 展示位公开令牌(不可枚举,持有即可访问;格式见 EmbedSlot.publicToken)。
schema:
type: string
AdId:
name: adId
in: path
required: true
description: 广告位 ID。
schema:
type: string
CursorQuery:
name: cursor
in: query
@@ -4526,6 +4764,186 @@ components:
type: [string, 'null']
description: 绑定相册 ID;null 解绑。
# ── 广告位(管理端,cn 域,2026-09-28) ──
AdminAdItem:
type: object
description: |
广告位条目(管理视角)。albumIds 为绑定相册 ID 列表(null=全局投放,
空数组归一为 null);imageUrl 为现签 GET(TTL=SIGNED_URL_TTL_SECONDS),
过期后重拉列表刷新。
required:
- id
- imageObjectKey
- imageUrl
- linkUrl
- enabled
- sortOrder
- albumIds
- createdAt
- updatedAt
properties:
id:
type: string
readOnly: true
description: 广告位 ID。
title:
type: [string, 'null']
description: 后台备注名(不展示观众)。
imageObjectKey:
type: string
readOnly: true
description: 版本化终键(换图递增版本,禁止覆盖)。
imageUrl:
type: string
readOnly: true
description: 现签 GET(TTL=SIGNED_URL_TTL_SECONDS)。
linkUrl:
type: string
description: 跳转链接(观众点击新开标签)。
enabled:
type: boolean
description: 是否启用;false 公开端不可见(ov 投影行删除,无墓碑下线)。
sortOrder:
type: integer
description: 排序权重(升序,并列按 createdAt 升序决胜)。
albumIds:
description: 绑定相册 ID 列表;null=全局投放(未绑定)。
type: [array, 'null']
items:
type: string
createdAt:
type: string
format: date-time
readOnly: true
description: 创建时间。
updatedAt:
type: string
format: date-time
readOnly: true
description: 最近更新时间。
AdminAdListResponse:
type: object
description: 广告位列表(全量不分页;sortOrder 升序、并列 createdAt 升序)。
required: [items]
properties:
items:
type: array
items:
$ref: '#/components/schemas/AdminAdItem'
AdCreateRequest:
type: object
description: |
创建广告位请求;albumIds 未提供或空数组=全局投放(落库归一 null),
元素须为存在的相册 ID(去重后校验)。
required: [linkUrl, imageStagingKey, imageBytes, imageContentType]
properties:
title:
type: string
maxLength: 120
description: 后台备注名(不展示观众)。
linkUrl:
type: string
maxLength: 2048
pattern: '^https?://'
description: 跳转链接(http/https)。
imageStagingKey:
type: string
description: POST /admin/ads/uploads 返回的 staging 对象键。
imageBytes:
type: integer
minimum: 1
maximum: 2097152
description: 申明字节数(须与 staging 对象一致;上限 AD_UPLOAD_MAX_BYTES=2MB)。
imageContentType:
type: string
enum: [image/jpeg, image/png, image/heic]
description: 申明类型(须与 staging 对象一致);image/heic 为暂定项,解码能力待 G2 真机验证后确认。
sortOrder:
type: integer
minimum: 0
description: 排序权重(缺省 0)。
albumIds:
type: array
items:
type: string
maxLength: 64
description: 绑定相册 ID 列表;未提供或空=全局。
AdUpdateRequest:
type: object
description: |
部分更新请求(仅提交的字段被修改)。albumIds:未提供=不动,null=转
全局,数组=整体替换;imageStagingKey/imageBytes/imageContentType 三者
必须同时提供(换图,服务端核验 staging 后转存新版本键)。
properties:
title:
type: string
maxLength: 120
description: 后台备注名(不展示观众)。
linkUrl:
type: string
maxLength: 2048
pattern: '^https?://'
description: 跳转链接(http/https)。
enabled:
type: boolean
description: 启停;false 公开端不可见(停用事件走 high 优先级)。
sortOrder:
type: integer
minimum: 0
description: 排序权重。
albumIds:
description: 绑定相册 ID 列表;null=转全局,数组=整体替换。
type: [array, 'null']
items:
type: string
maxLength: 64
imageStagingKey:
type: string
description: 换图用 staging 对象键(与 imageBytes/imageContentType 同时提供)。
imageBytes:
type: integer
minimum: 1
maximum: 2097152
description: 换图申明字节数(上限 AD_UPLOAD_MAX_BYTES=2MB)。
imageContentType:
type: string
enum: [image/jpeg, image/png, image/heic]
description: 换图申明类型。
AdUploadInitRequest:
type: object
description: 广告图直传初始化请求(无会话概念,幂等由创建端 copyIfAbsent 承担)。
required: [filename, bytes, contentType]
properties:
filename:
type: string
maxLength: 255
description: 原始文件名(取扩展名定 staging 键)。
bytes:
type: integer
minimum: 1
maximum: 2097152
description: 申明字节数(上限 AD_UPLOAD_MAX_BYTES=2MB,超限 400)。
contentType:
type: string
enum: [image/jpeg, image/png, image/heic]
description: MIME 类型白名单;image/heic 为暂定项,解码能力待 G2 真机验证后确认。
AdUploadInitResponse:
type: object
required: [objectKey, credential]
properties:
objectKey:
type: string
readOnly: true
description: staging 键 photo-live/{env}/ads/staging/{cuid}.{ext}(创建/更新时引用)。
credential:
$ref: '#/components/schemas/UploadCredential'
# ── 公开(观众端) ──
SignedUrl:
@@ -4792,6 +5210,35 @@ components:
items:
$ref: '#/components/schemas/PublicEmbedPhoto'
# ── 广告位公开数据(观众端,ov 域,2026-09-28) ──
PublicAd:
type: object
description: 观众端广告条目(仅展示所需字段,广告标识与关闭由前端渲染)。
required: [adId, imageUrl, linkUrl]
properties:
adId:
type: string
readOnly: true
description: 广告位 ID。
imageUrl:
type: string
readOnly: true
description: 现签 GET(TTL=SIGNED_URL_TTL_SECONDS,过期后重拉本端点刷新)。
linkUrl:
type: string
description: 跳转链接(新开标签)。
PublicAdListResponse:
type: object
description: 公开广告列表(无广告为空 items,非 404)。
required: [items]
properties:
items:
type: array
items:
$ref: '#/components/schemas/PublicAd'
# ── 互动(观众端,ov 域) ──
DownloadVariant:
@@ -4908,6 +5355,7 @@ components:
- photo
- asset
- slot-state
- ad
SyncPriority:
type: string