新增创作者公开主页两只读端点(契约+实现+单测,全 additive):
1) GET /app-api/project/public/creator/{creatorId} @PermitAll
→ PageResult<PublicCreatorProjectRespVO>{id,title,summary,coverUrl,ageRating,
playCount,likeCount,publishTime,creatorId,creatorName};仅 status==4 已发布作品。
2) GET /app-api/telemetry/public/creator/{creatorId}/summary @PermitAll
→ {creatorId,publishedCount,totalPlayCount,totalLikeCount};对该创作者 status==4 作品聚合。
跨创作者公开读(#1 正确性风险)处理:本仓 DataPermission 仅注册 system DeptDataPermissionRule
(仅作用 AdminUserDO/DeptDO 表),game_project 无任何 DataPermission 规则 → MP 拦截器不追加过滤;
project 的"创作者只见自己数据"一向由 Service/Mapper 手动 eq(creatorUserId) 强制而非拦截器。
故公开读只需按"目标 creatorId(路径参数,非登录人)"+status==4 等值即天然跨创作者,无需 @DataPermission(enable=false)。
口径:publishTime 取 update_time 近似(项目表无 publish_time 列,同 count-published 口径);
公开统计经 project-api getCreatorPublishedSummary 取(发布态权威+play/like 计数列均在 project);
creatorName 经既有 AdminUserApi.getUser().nickname 尽力解析(查不到为 null,无 PII)。
单测(Mockito,test goal GREEN):
- ProjectMapperTest(新):SQL 形态守卫——公开查询恒 status==PUBLISHED(4)+按 creator_user_id 过滤,不放行下架(5)/封禁(6);
- ProjectServiceImplTest(+5):跨创作者读(走 selectPublishedPageByCreator 非 selectMyPage)/creatorName 兜底/空/聚合/零;
- GameStatServiceImplTest(+2):跨创作者经 project-api 聚合(不走"我的作品"归属链)/null DTO 兜零。
mvn -pl game-module-project/...-server,game-module-telemetry/...-server -am test → project 32/32 + telemetry 46/46 绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
272 lines
12 KiB
YAML
272 lines
12 KiB
YAML
openapi: 3.0.3
|
||
# 契约 #1 API | 模块:project(黄金模板)| owner:WS1 lead 主笔,全员 review
|
||
# 端:/app-api(产品端 game-studio,用户 Token+DataPermission) /admin-api(管理端 game-admin,RBAC);前缀由 huijing 框架按 controller.app/admin 包名自动添加
|
||
# 错误码段:project = 1-100-***-***
|
||
# 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功
|
||
info:
|
||
title: 绘境AI project 模块 API
|
||
version: 1.0.0
|
||
description: 游戏项目 CRUD / 状态机 / 版本 / 草稿 / 发布编排 / 审核 / 专区。前端据此 vite-plugin-mock 自动生成 mock。
|
||
|
||
servers:
|
||
- url: http://localhost:48080
|
||
description: 本地(Swagger/Knife4j http://localhost:48080/doc.html)
|
||
|
||
paths:
|
||
/app-api/project/my:
|
||
get:
|
||
tags: [app-project]
|
||
summary: 我的项目列表(创作者只见自己数据)
|
||
parameters:
|
||
- { name: status, in: query, required: false, schema: { type: integer }, description: 按状态机筛选 }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultProjectPage' }
|
||
|
||
/app-api/project:
|
||
post:
|
||
tags: [app-project]
|
||
summary: 创建项目(初始 status=0 草稿)
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/ProjectCreateReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 返回新建项目 ID
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultLong' }
|
||
|
||
/app-api/project/{id}:
|
||
get:
|
||
tags: [app-project]
|
||
summary: 项目详情
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultProject' }
|
||
|
||
/app-api/project/{id}/draft:
|
||
put:
|
||
tags: [app-project]
|
||
summary: 保存草稿(仅 status=0 可改;标题/简介/封面/标签/适龄)
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/ProjectDraftReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
/app-api/project/{id}/publish:
|
||
post:
|
||
tags: [app-project]
|
||
summary: 提交发布(发布前门禁聚合 → 进审核 BPM;统一发布编排 compliance→runtime→feed 失败回滚 ←G6)
|
||
description: >-
|
||
归属/草稿态为安全前置,失败抛异常(code≠0)。发布前门禁聚合为 gates:全部通过则 admitted=true 并置 status=1 审核中;
|
||
任一拦截则 admitted=false 且不改状态机(无副作用),创作者据 gates 修复后重提。
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/ProjectPublishReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功(含发布前门禁聚合结果 admitted + gates)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultProjectPublish' }
|
||
|
||
/app-api/project/public/creator/{creatorId}:
|
||
get:
|
||
tags: [app-project]
|
||
summary: 创作者公开作品聚合(P-OPN-08,@PermitAll 跨创作者公开读)
|
||
description: >-
|
||
创作者主页对外展示其公开作品列表。公开 = 项目 status==4(已发布) 仅此一态(排除 5下架/6封禁/草稿/审核中等);
|
||
无 PII,仅作品基础信息 + 聚合计数。匿名可访问(@PermitAll),按 creatorId 路径参数取任意创作者数据(非登录人,
|
||
故不受创作者归属过滤)。creatorName 尽力解析(查不到为 null),creatorId 必返。
|
||
parameters:
|
||
- { name: creatorId, in: path, required: true, schema: { type: integer, format: int64 }, description: 创作者用户 ID }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功(仅 status==4 已发布作品)
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultPublicCreatorProjectPage' }
|
||
|
||
/admin-api/project/review/page:
|
||
get:
|
||
tags: [admin-project]
|
||
summary: 审核队列(管理端 RBAC)
|
||
parameters:
|
||
- { name: status, in: query, required: false, schema: { type: integer }, description: 默认取 status=1 审核中 }
|
||
- { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } }
|
||
- { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultProjectPage' }
|
||
|
||
/admin-api/project/review:
|
||
post:
|
||
tags: [admin-project]
|
||
summary: 审核决策(通过/拒绝/下架;锁风门 Gate 二态聚合)
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/ReviewReqVO' }
|
||
responses:
|
||
'200':
|
||
description: 成功
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CommonResultBoolean' }
|
||
|
||
components:
|
||
schemas:
|
||
# ---- Huijing CommonResult 信封 ----
|
||
CommonResultBoolean:
|
||
type: object
|
||
properties:
|
||
code: { type: integer, description: '0=成功,非0=错误码 1-100-***-***' }
|
||
data: { type: boolean }
|
||
msg: { type: string }
|
||
CommonResultLong:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { type: integer, format: int64, description: 新建项目 ID }
|
||
msg: { type: string }
|
||
CommonResultProject:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/ProjectRespVO' }
|
||
msg: { type: string }
|
||
CommonResultProjectPublish:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data: { $ref: '#/components/schemas/ProjectPublishRespVO' }
|
||
msg: { type: string }
|
||
CommonResultProjectPage:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data:
|
||
type: object
|
||
properties:
|
||
list: { type: array, items: { $ref: '#/components/schemas/ProjectRespVO' } }
|
||
total: { type: integer, format: int64 }
|
||
msg: { type: string }
|
||
CommonResultPublicCreatorProjectPage:
|
||
type: object
|
||
properties:
|
||
code: { type: integer }
|
||
data:
|
||
type: object
|
||
properties:
|
||
list: { type: array, items: { $ref: '#/components/schemas/PublicCreatorProjectRespVO' } }
|
||
total: { type: integer, format: int64 }
|
||
msg: { type: string }
|
||
|
||
# ---- 请求/响应 VO ----
|
||
ProjectCreateReqVO:
|
||
type: object
|
||
required: [title]
|
||
properties:
|
||
title: { type: string, maxLength: 60, description: 标题 }
|
||
templateId: { type: string, description: '玩法模板 ID(可选,缺省 generic:W-CLEAN 废玩法模板层后建项目无需选模板,入口将空 templateId 归一为 generic 单一通用口径)' }
|
||
ProjectDraftReqVO:
|
||
type: object
|
||
properties:
|
||
title: { type: string, maxLength: 60 }
|
||
summary: { type: string, maxLength: 500 }
|
||
coverUrl: { type: string }
|
||
tags: { type: array, items: { type: string }, maxItems: 10 }
|
||
ageRating: { type: string, enum: [all, '8+', '12+', '16+'] }
|
||
ProjectPublishReqVO:
|
||
type: object
|
||
required: [versionId, launchZoneId]
|
||
properties:
|
||
versionId: { type: integer, format: int64, description: 发布的版本 ID }
|
||
launchZoneId: { type: integer, format: int64, description: 发布去向专区(launchZone ←G1) }
|
||
GateResultVO:
|
||
type: object
|
||
required: [key, pass]
|
||
properties:
|
||
key: { type: string, description: '门禁键(稳定标识,如 age;Phase C 增 compliance)' }
|
||
label: { type: string, description: 门禁中文标签(展示用) }
|
||
pass: { type: boolean, description: 是否通过 }
|
||
code: { type: string, description: '拦截时错误码字符串(pass=true 时为 null;复用对应模块错误码段,不新增)' }
|
||
message: { type: string, description: 拦截时提示文案(pass=true 时为 null) }
|
||
ProjectPublishRespVO:
|
||
type: object
|
||
required: [admitted, gates]
|
||
properties:
|
||
admitted: { type: boolean, description: '是否准入(全部门禁通过=true;任一拦截=false 且不改状态机)' }
|
||
gates: { type: array, items: { $ref: '#/components/schemas/GateResultVO' }, description: 发布前门禁逐项结果 }
|
||
ReviewReqVO:
|
||
type: object
|
||
required: [gameId, versionId, decision]
|
||
properties:
|
||
gameId: { type: integer, format: int64 }
|
||
versionId: { type: integer, format: int64 }
|
||
decision: { type: integer, enum: [1, 2, 3], description: '1通过 2拒绝 3下架' }
|
||
reason: { type: string, maxLength: 500, description: 拒绝/下架理由(回填创作者) }
|
||
ProjectRespVO:
|
||
type: object
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
title: { type: string }
|
||
summary: { type: string }
|
||
coverUrl: { type: string }
|
||
tags: { type: array, items: { type: string } }
|
||
templateId: { type: string }
|
||
ageRating: { type: string }
|
||
status: { type: integer, description: '0草稿 1审核中 2已通过 3已拒绝 4已发布 5已下架 6已封禁' }
|
||
currentVersionId: { type: integer, format: int64 }
|
||
launchZoneId: { type: integer, format: int64 }
|
||
featured: { type: boolean }
|
||
playCount: { type: integer, format: int64 }
|
||
likeCount: { type: integer, format: int64 }
|
||
createTime: { type: string, format: date-time }
|
||
# P-OPN-08 创作者公开作品卡(对外展示,无 PII;仅 status==4 已发布作品才会出现在列表)
|
||
PublicCreatorProjectRespVO:
|
||
type: object
|
||
properties:
|
||
id: { type: integer, format: int64, description: 游戏项目 ID }
|
||
title: { type: string, description: 标题 }
|
||
summary: { type: string, description: 简介 }
|
||
coverUrl: { type: string, description: 封面图 URL }
|
||
ageRating: { type: string, description: '适龄提示:all/8+/12+/16+' }
|
||
playCount: { type: integer, format: int64, description: 累计试玩数 }
|
||
likeCount: { type: integer, format: int64, description: 累计点赞数 }
|
||
publishTime: { type: string, format: date-time, description: '发布时间(项目表无独立 publish_time 列,取 update_time 近似,同 count-published 口径)' }
|
||
creatorId: { type: integer, format: int64, description: 创作者用户 ID(必返,作主页归属锚) }
|
||
creatorName: { type: string, description: 创作者昵称(尽力解析;查不到为 null,无 PII) }
|