Files
xuetang/server/docs/tang-detective.openapi.yaml
2026-09-09 14:47:29 +08:00

241 lines
8.9 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.1.0
info:
title: 唐侦探独立章节存档
version: 1.0.0
description: >-
使用原小程序 token,仅操作登录用户自己的 season-01 ID 存档。
不接收健康回答、正文、快照或客户端用户身份。业务错误沿用 HTTP 200/code 约定。
servers:
- url: /api
security:
- MiniProgramToken: []
paths:
/tang/catalog:
get:
operationId: getTangDetectiveCatalog
summary: 读取部署版本的 ID 白名单
responses:
'200':
description: code=1 为目录,code=0/-1 为错误
content:
application/json:
schema:
oneOf:
- type: object
required: [code, show, msg, data]
properties:
code: {const: 1}
show: {const: 0}
msg: {type: string}
data: {$ref: '#/components/schemas/Catalog'}
- {$ref: '#/components/schemas/Failure'}
/tang/progress:
get:
operationId: getTangDetectiveProgress
summary: 只读当前登录用户存档,无存档返回 revision=0 的默认值
responses:
'200':
$ref: '#/components/responses/ProgressResponse'
/tang/saveProgress:
post:
operationId: saveTangDetectiveProgress
summary: 版本匹配时替换整份 ID 投影或重新开始故事
description: >-
原始请求体最多 32768 字节。最后一次 request_id 的相同规范化内容可幂等重试。
必须匹配 base_revision 和 story_generation;冲突不覆盖、不自动合并。
reset_story 必须提交空故事字段,收藏为当前服务器与该请求的已验证卡 ID 并集。
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SaveRequest'}
responses:
'200':
$ref: '#/components/responses/ProgressResponse'
components:
securitySchemes:
MiniProgramToken:
type: apiKey
in: header
name: token
description: 沿用现有 LoginMiddleware;不是 Authorization,也不放入请求正文。
responses:
ProgressResponse:
description: >-
必须检查 codecode=1 的 data 是本人确认存档。
此控制器的响应含 Cache-Control no-store。
headers:
Cache-Control:
schema: {type: string, const: no-store}
content:
application/json:
schema:
oneOf:
- {$ref: '#/components/schemas/ProgressSuccess'}
- {$ref: '#/components/schemas/Failure'}
schemas:
ChapterId:
type: string
pattern: '^S01-C(0[1-9]|1[0-5])$'
EventId:
type: string
pattern: '^S01-H(0[1-9]|[1-5][0-9]|60)$'
PageId:
type: string
pattern: '^S01-C(0[1-9]|1[0-5])-P0[1-8]$'
CardId:
type: string
pattern: '^S01-C(0[1-9]|1[0-5])-MC01$'
Catalog:
type: object
additionalProperties: false
required: [game_id, schema_version, content_version, max_body_bytes, chapters]
properties:
game_id: {const: tang-detective}
schema_version: {const: 1}
content_version: {const: season-01}
max_body_bytes: {const: 32768}
chapters:
type: array
minItems: 15
maxItems: 15
items:
type: object
additionalProperties: false
required: [chapter_id, chapter_number, hotspot_ids, page_ids, memory_card_id]
properties:
chapter_id: {$ref: '#/components/schemas/ChapterId'}
chapter_number: {type: integer, minimum: 1, maximum: 15}
hotspot_ids:
type: array
minItems: 4
maxItems: 4
uniqueItems: true
items: {$ref: '#/components/schemas/EventId'}
page_ids:
type: array
minItems: 8
maxItems: 8
uniqueItems: true
items: {$ref: '#/components/schemas/PageId'}
memory_card_id: {$ref: '#/components/schemas/CardId'}
Reader:
type: object
additionalProperties: false
required: [currentPageId, completedEventIds, chapterFinished]
properties:
currentPageId: {$ref: '#/components/schemas/PageId'}
completedEventIds:
type: array
maxItems: 4
uniqueItems: true
items: {$ref: '#/components/schemas/EventId'}
chapterFinished: {type: boolean}
description: >-
事件必须是所属章节的连续前缀且与 completedHotspots 相同;
chapterFinished 必须与 completedChapters 成员关系一致,为 true 时须有四事件。
未 finished 仅允许 P01 至 P(03+事件数),最多 P07P08 必须 finished。
Progress:
type: object
additionalProperties: false
required: [completedHotspots, completedChapters, lastChapter, collectedMemoryCards, comicReaderByChapter, lastPageId]
properties:
completedHotspots:
type: object
maxProperties: 15
propertyNames: {$ref: '#/components/schemas/ChapterId'}
additionalProperties:
type: array
maxItems: 4
uniqueItems: true
items: {$ref: '#/components/schemas/EventId'}
completedChapters:
type: array
maxItems: 15
uniqueItems: true
items: {$ref: '#/components/schemas/ChapterId'}
lastChapter: {type: integer, minimum: 1, maximum: 15}
collectedMemoryCards:
type: array
maxItems: 15
uniqueItems: true
items: {$ref: '#/components/schemas/CardId'}
comicReaderByChapter:
type: object
maxProperties: 15
propertyNames: {$ref: '#/components/schemas/ChapterId'}
additionalProperties: {$ref: '#/components/schemas/Reader'}
lastPageId:
oneOf:
- {const: ''}
- {$ref: '#/components/schemas/PageId'}
description: >-
空映射必须是 {},不是 []。有已完成事件或 finished 的章节必须有 reader。
非空 lastPageId 必须等于 lastChapter 对应 reader.currentPageId。
跨字段成员关系、顺序和解锁约束由 TangDetectiveProgress 验证。
SaveRequest:
type: object
additionalProperties: false
required: [schema_version, content_version, base_revision, story_generation, request_id, operation, progress]
properties:
schema_version: {type: integer, const: 1}
content_version: {const: season-01}
base_revision: {type: integer, minimum: 0, maximum: 2147483646}
story_generation: {type: integer, minimum: 0, maximum: 2147483646}
request_id: {type: string, pattern: '^[A-Za-z0-9_-]{16,64}$'}
operation: {enum: [replace, reset_story]}
progress: {$ref: '#/components/schemas/Progress'}
allOf:
- if:
properties:
operation: {const: reset_story}
then:
properties:
progress:
properties:
completedHotspots: {maxProperties: 0}
completedChapters: {maxItems: 0}
lastChapter: {const: 1}
comicReaderByChapter: {maxProperties: 0}
lastPageId: {const: ''}
State:
type: object
additionalProperties: false
required: [user_id, schema_version, content_version, revision, story_generation, progress]
properties:
user_id: {type: integer, minimum: 1}
schema_version: {const: 1}
content_version: {const: season-01}
revision: {type: integer, minimum: 0}
story_generation: {type: integer, minimum: 0}
progress: {$ref: '#/components/schemas/Progress'}
idempotent:
type: boolean
description: 仅保存响应提供;true 表示重复确认最后一次提交,未重复写入。
ProgressSuccess:
type: object
required: [code, show, msg, data]
properties:
code: {const: 1}
show: {const: 0}
msg: {type: string}
data: {$ref: '#/components/schemas/State'}
Failure:
type: object
required: [code, show, msg, data]
properties:
code: {type: integer, enum: [0, -1]}
show: {type: integer, enum: [0, 1]}
msg: {type: string}
data:
oneOf:
- type: object
additionalProperties: false
required: [error_code]
properties:
error_code:
enum: [INVALID_REQUEST, PAYLOAD_TOO_LARGE, UNSUPPORTED_CONTENT_VERSION, PROGRESS_CONFLICT, IDEMPOTENCY_CONFLICT, METHOD_NOT_ALLOWED, UNSUPPORTED_MEDIA_TYPE, AUTH_REQUIRED, STORAGE_UNAVAILABLE]
- type: array
maxItems: 0
description: 原登录中间件可能提前返回空 data;登录过期 code=-1。