241 lines
8.9 KiB
YAML
241 lines
8.9 KiB
YAML
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: >-
|
||
必须检查 code;code=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+事件数),最多 P07;P08 必须 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。
|