- access_key 改用 subtle.ConstantTimeCompare 常量时间比较,防时序攻击 - 新增 server.protect_all 配置:开启后业务接口(/search、/content 等)也要求访问密钥 - 代理路由(/audio/proxy/、/manga/image/)不参与保护,其 URL 由服务端生成不含 key - 新增 /healthz 存活探针(不校验密钥,供容器/反代使用) - openapi.yaml 补 /healthz 定义,Unauthorized 描述说明 protect_all 行为 - 新增 3 个测试:保护开关放行、healthz、错误响应行为
816 lines
25 KiB
YAML
816 lines
25 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: 番茄小说 API
|
|
version: 2.0.0
|
|
description: |
|
|
番茄小说非官方 API 服务,支持书籍搜索、详情、目录、正文、批量接口、
|
|
段评/书评、漫画、音频/有声书、视频与分类浏览。
|
|
|
|
多数接口直接转发上游 JSON(结构随上游版本变化,`data` 字段以实际返回为准);
|
|
错误统一返回 `{"code": -1, "msg": "..."}`。
|
|
需要鉴权的端点(`/status`、`/self-info`、`/vacuum`)通过查询参数 `key`
|
|
或请求头 `X-Access-Key` 携带访问密钥,未配置 `server.access_key` 时不启用验证。
|
|
license:
|
|
name: MIT
|
|
identifier: MIT
|
|
servers:
|
|
- url: http://localhost:40180
|
|
description: 本地开发
|
|
tags:
|
|
- name: 系统
|
|
description: 服务状态、设备信息与数据库维护
|
|
- name: 书籍
|
|
description: 搜索、详情、目录与批量详情
|
|
- name: 内容
|
|
description: 章节正文、批量正文与摘要
|
|
- name: 评论
|
|
description: 段落评论与书评
|
|
- name: 多媒体
|
|
description: 音频/有声书、视频、漫画及其代理
|
|
- name: 分类
|
|
description: 分类首页与分类详情
|
|
- name: 静态资源
|
|
description: 表情图片等静态文件
|
|
|
|
paths:
|
|
/:
|
|
get:
|
|
tags: [系统]
|
|
summary: 服务心跳
|
|
operationId: getRoot
|
|
responses:
|
|
"200":
|
|
description: 服务存活
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
code: { type: integer, example: 0 }
|
|
message: { type: string, example: success }
|
|
data: { nullable: true }
|
|
|
|
/healthz:
|
|
get:
|
|
tags: [系统]
|
|
summary: 存活探针
|
|
description: 供容器/反向代理探活,不校验访问密钥。
|
|
operationId: getHealthz
|
|
responses:
|
|
"200":
|
|
description: 服务存活
|
|
content:
|
|
text/plain:
|
|
schema: { type: string }
|
|
example: ok
|
|
|
|
/status:
|
|
get:
|
|
tags: [系统]
|
|
summary: 运行状态监控面板
|
|
description: HTML 状态页,包含运行时长、签名方法、数据库大小、设备密钥池、
|
|
请求统计与内置 API 文档;页面每 3 秒自动刷新。
|
|
operationId: getStatus
|
|
security:
|
|
- accessKeyQuery: []
|
|
- accessKeyHeader: []
|
|
parameters:
|
|
- $ref: "#/components/parameters/AccessKeyQuery"
|
|
responses:
|
|
"200":
|
|
description: HTML 状态页
|
|
content:
|
|
text/html:
|
|
schema: { type: string }
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/self-info:
|
|
get:
|
|
tags: [系统]
|
|
summary: 设备密钥池信息
|
|
description: 返回设备池启用状态与统计,包含设备密钥等敏感数据。
|
|
operationId: getSelfInfo
|
|
security:
|
|
- accessKeyQuery: []
|
|
- accessKeyHeader: []
|
|
parameters:
|
|
- $ref: "#/components/parameters/AccessKeyQuery"
|
|
responses:
|
|
"200":
|
|
description: 设备池信息
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
code: { type: integer, example: 0 }
|
|
message: { type: string, example: SUCCESS }
|
|
data:
|
|
type: object
|
|
properties:
|
|
device_pool_enabled: { type: boolean }
|
|
device_pool_stats:
|
|
type: object
|
|
properties:
|
|
total: { type: integer }
|
|
valid: { type: integer }
|
|
invalid: { type: integer }
|
|
current_index: { type: integer }
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/vacuum:
|
|
post:
|
|
tags: [系统]
|
|
summary: 压缩数据库
|
|
description: |
|
|
执行数据库压缩以释放磁盘空间。
|
|
- SQLite: 执行 VACUUM
|
|
- PostgreSQL: 由 autovacuum 自动管理,本操作为空操作
|
|
仅支持 POST,GET 请求会被拒绝。
|
|
operationId: vacuumDatabase
|
|
security:
|
|
- accessKeyQuery: []
|
|
- accessKeyHeader: []
|
|
parameters:
|
|
- $ref: "#/components/parameters/AccessKeyQuery"
|
|
responses:
|
|
"200":
|
|
description: 压缩结果
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success: { type: boolean }
|
|
message: { type: string, example: 数据库压缩完成 }
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/search:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 搜索书籍
|
|
operationId: searchBooks
|
|
parameters:
|
|
- name: query
|
|
in: query
|
|
required: true
|
|
description: 搜索关键字(别名 `key`)
|
|
schema: { type: string }
|
|
example: 斗罗大陆
|
|
- name: key
|
|
in: query
|
|
description: "`query` 的别名,`query` 为空时生效"
|
|
schema: { type: string }
|
|
- name: offset
|
|
in: query
|
|
description: 结果偏移量,默认 0
|
|
schema: { type: integer, default: 0, minimum: 0 }
|
|
example: 0
|
|
- name: tab_type
|
|
in: query
|
|
description: 搜索类型,默认 3
|
|
schema: { type: integer, default: 3 }
|
|
example: 3
|
|
responses:
|
|
"200":
|
|
description: 搜索结果(上游转发,结构以实际返回为准)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpstreamResponse"
|
|
example: { code: 0, msg: success, data: { search_book_data_list: [], has_more: 1 } }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/detail:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 获取书籍详情
|
|
operationId: getBookDetail
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
responses:
|
|
"200":
|
|
description: 书籍详情(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/info:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 获取书籍详情(/detail 别名)
|
|
operationId: getBookDetailAlias
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
responses:
|
|
"200":
|
|
description: 同 /detail
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/catalog:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 获取章节目录
|
|
operationId: getBookCatalog
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
responses:
|
|
"200":
|
|
description: 章节目录(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/directory/all_items:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 获取章节目录(/catalog 别名)
|
|
operationId: getBookCatalogAlias
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
responses:
|
|
"200":
|
|
description: 同 /catalog
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/multi-detail:
|
|
get:
|
|
tags: [书籍]
|
|
summary: 批量获取书籍详情
|
|
operationId: getMultiBookDetail
|
|
parameters:
|
|
- name: book_id
|
|
in: query
|
|
required: true
|
|
description: 书籍 ID 列表,逗号分隔
|
|
schema: { type: string }
|
|
example: "7123456789012345678,7234567890123456789"
|
|
responses:
|
|
"200":
|
|
description: 批量详情(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/content:
|
|
get:
|
|
tags: [内容]
|
|
summary: 获取章节正文
|
|
description: |
|
|
返回章节正文。启用段评(`review=1` 或配置 `fanqie.content.enable_review: true`)时,
|
|
正文 HTML 中会注入段评标记。
|
|
operationId: getItemContent
|
|
parameters:
|
|
- name: item_id
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
example: "7271032589123412345"
|
|
- name: text_mode
|
|
in: query
|
|
description: 纯文本模式,1/true=纯文本,0/false=HTML
|
|
schema: { type: string, enum: ["0", "1", "true", "false"] }
|
|
example: "0"
|
|
- name: image_mode
|
|
in: query
|
|
description: 图片处理,1/true=保留图片,0/false=移除图片
|
|
schema: { type: string, enum: ["0", "1", "true", "false"] }
|
|
example: "1"
|
|
- name: review
|
|
in: query
|
|
description: 段评开关,1=启用,0=禁用;缺省时使用配置文件
|
|
schema: { type: string, enum: ["0", "1"] }
|
|
responses:
|
|
"200":
|
|
description: 章节正文
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/UpstreamResponse"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: 正文内容(HTML 或纯文本,取决于 text_mode)
|
|
title: { type: string, description: 章节标题 }
|
|
novel_data:
|
|
type: object
|
|
properties:
|
|
book_id: { type: string, description: 所属书籍 ID }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/multi-content:
|
|
get:
|
|
tags: [内容]
|
|
summary: 批量获取章节正文
|
|
operationId: getMultiItemContent
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
- name: item_ids
|
|
in: query
|
|
required: true
|
|
description: 章节 ID 列表,逗号分隔
|
|
schema: { type: string }
|
|
example: "7271032589123412345,7271032589123412346"
|
|
- name: text_mode
|
|
in: query
|
|
description: 纯文本模式,1/true=纯文本
|
|
schema: { type: string, enum: ["0", "1", "true", "false"] }
|
|
- name: image_mode
|
|
in: query
|
|
description: 图片处理,1/true=保留
|
|
schema: { type: string, enum: ["0", "1", "true", "false"] }
|
|
- name: ignore_failed
|
|
in: query
|
|
description: 忽略失败章节,1/true=在结果中跳过而非报错
|
|
schema: { type: string, enum: ["0", "1", "true", "false"] }
|
|
responses:
|
|
"200":
|
|
description: 批量正文(并发拉取)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/item-summary:
|
|
get:
|
|
tags: [内容]
|
|
summary: 获取章节摘要
|
|
operationId: getItemSummary
|
|
parameters:
|
|
- $ref: "#/components/parameters/BookId"
|
|
- name: item_ids
|
|
in: query
|
|
required: true
|
|
description: 章节 ID 列表,逗号分隔
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: 章节摘要(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/chapter_review:
|
|
get:
|
|
tags: [评论]
|
|
summary: 获取章节段评
|
|
description: 获取整个章节的段落评论数据。
|
|
operationId: getChapterReview
|
|
parameters:
|
|
- name: item_id
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
- name: book_id
|
|
in: query
|
|
description: 书籍 ID
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: 章节段评数据
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/review:
|
|
get:
|
|
tags: [评论]
|
|
summary: 获取段落评论/回复
|
|
description: 获取指定章节某一段落的评论;携带 `comment_id` 时返回该评论的回复。
|
|
operationId: getReview
|
|
parameters:
|
|
- name: item_id
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
- name: para
|
|
in: query
|
|
description: 段落索引(从 0 开始)
|
|
schema: { type: integer, minimum: 0 }
|
|
example: 0
|
|
- name: book_id
|
|
in: query
|
|
description: 书籍 ID
|
|
schema: { type: string }
|
|
- name: comment_id
|
|
in: query
|
|
description: 评论 ID(获取回复时需要)
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: 评论/回复数据
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/review_html:
|
|
get:
|
|
tags: [评论]
|
|
summary: 获取段落评论 HTML 页面
|
|
description: 渲染好的评论 HTML 页面,可直接嵌入阅读器展示。
|
|
operationId: getReviewHtml
|
|
parameters:
|
|
- name: item_id
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
- name: para
|
|
in: query
|
|
required: true
|
|
description: 段落索引(从 0 开始)
|
|
schema: { type: integer, minimum: 0 }
|
|
example: 0
|
|
responses:
|
|
"200":
|
|
description: 评论 HTML 页面
|
|
content:
|
|
text/html:
|
|
schema: { type: string }
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/audio:
|
|
get:
|
|
tags: [多媒体]
|
|
summary: 获取音频/有声书信息
|
|
description: 返回章节的有声书播放信息,含本地音频代理地址。
|
|
operationId: getAudio
|
|
parameters:
|
|
- name: item_ids
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
- name: tone_id
|
|
in: query
|
|
description: 音色 ID,默认 1
|
|
schema: { type: string, default: "1" }
|
|
- name: output
|
|
in: query
|
|
description: 输出模式
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: 音频信息(上游转发,含代理 URL)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/audio/proxy/{key}:
|
|
get:
|
|
tags: [多媒体]
|
|
summary: 音频代理
|
|
description: |
|
|
通过 /audio 返回的代理 key 拉取音频内容。
|
|
key 有效期 2 小时(与 CDN 链接有效期一致),过期后需重新调用 /audio。
|
|
operationId: getAudioProxy
|
|
parameters:
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
description: /audio 接口生成的代理 key
|
|
schema: { type: string }
|
|
example: a3f8c92e1b7d4f60
|
|
responses:
|
|
"200":
|
|
description: 音频二进制内容
|
|
content:
|
|
audio/mpeg:
|
|
schema: { type: string, format: binary }
|
|
audio/aac:
|
|
schema: { type: string, format: binary }
|
|
"400":
|
|
description: key 无效、过期或下载失败
|
|
content:
|
|
text/plain:
|
|
schema: { type: string }
|
|
example: 音频信息已过期,请重新调用 /audio 接口
|
|
|
|
/video:
|
|
get:
|
|
tags: [多媒体]
|
|
summary: 获取视频信息
|
|
operationId: getVideo
|
|
parameters:
|
|
- name: item_ids
|
|
in: query
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
responses:
|
|
"200":
|
|
description: 视频信息(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/manga:
|
|
get:
|
|
tags: [多媒体]
|
|
summary: 获取漫画内容
|
|
description: |
|
|
返回漫画章节内容,图片经解密处理。
|
|
输出模式 `output` 优先于配置 `fanqie.manga.output_mode`:
|
|
- `url`: 返回原始图片 URL 数组
|
|
- `proxy`: 返回本地代理 URL(/manga/image/{item_id}/{index})
|
|
- `data_uri`: 返回 Base64 编码(data:image/jpeg;base64,...)
|
|
operationId: getManga
|
|
parameters:
|
|
- name: item_ids
|
|
in: query
|
|
required: true
|
|
description: 章节 ID(别名 `item_id`)
|
|
schema: { type: string }
|
|
- name: item_id
|
|
in: query
|
|
description: "`item_ids` 的别名"
|
|
schema: { type: string }
|
|
- name: show_html
|
|
in: query
|
|
description: 1=返回 HTML 阅读页
|
|
schema: { type: string, enum: ["0", "1"] }
|
|
- name: output
|
|
in: query
|
|
description: 输出模式,优先于配置文件
|
|
schema: { type: string, enum: [url, proxy, data_uri] }
|
|
responses:
|
|
"200":
|
|
description: 漫画内容
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
text/html:
|
|
schema: { type: string }
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/manga/image/{item_id}/{index}:
|
|
get:
|
|
tags: [多媒体]
|
|
summary: 漫画图片代理
|
|
description: 返回解密后的漫画图片,响应带 1 小时缓存头。
|
|
operationId: getMangaImage
|
|
parameters:
|
|
- name: item_id
|
|
in: path
|
|
required: true
|
|
description: 章节 ID
|
|
schema: { type: string }
|
|
- name: index
|
|
in: path
|
|
required: true
|
|
description: 图片序号(从 0 开始)
|
|
schema: { type: integer, minimum: 0 }
|
|
responses:
|
|
"200":
|
|
description: 图片二进制内容
|
|
headers:
|
|
Cache-Control:
|
|
schema: { type: string }
|
|
description: 固定为 public, max-age=3600
|
|
content:
|
|
image/jpeg:
|
|
schema: { type: string, format: binary }
|
|
image/png:
|
|
schema: { type: string, format: binary }
|
|
image/webp:
|
|
schema: { type: string, format: binary }
|
|
"400":
|
|
description: URL 格式或 index 参数错误
|
|
content:
|
|
text/plain:
|
|
schema: { type: string }
|
|
"500":
|
|
description: 图片获取/解密失败
|
|
content:
|
|
text/plain:
|
|
schema: { type: string }
|
|
|
|
/category-front:
|
|
get:
|
|
tags: [分类]
|
|
summary: 获取分类首页
|
|
operationId: getCategoryFront
|
|
parameters:
|
|
- name: tab
|
|
in: query
|
|
description: 标签页,默认 0
|
|
schema: { type: string, default: "0" }
|
|
responses:
|
|
"200":
|
|
description: 分类首页数据(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/category-landing:
|
|
get:
|
|
tags: [分类]
|
|
summary: 获取分类详情页
|
|
description: 支持按分类筛选、排序与分页的书籍列表。
|
|
operationId: getCategoryLanding
|
|
parameters:
|
|
- name: category_id
|
|
in: query
|
|
required: true
|
|
description: 分类 ID
|
|
schema: { type: string }
|
|
- name: offset
|
|
in: query
|
|
description: 偏移量,默认 0
|
|
schema: { type: integer, default: 0, minimum: 0 }
|
|
- name: limit
|
|
in: query
|
|
description: 每页数量,默认 20
|
|
schema: { type: integer, default: 20, minimum: 1 }
|
|
responses:
|
|
"200":
|
|
description: 分类书籍列表(上游转发)
|
|
content:
|
|
application/json:
|
|
schema: { $ref: "#/components/schemas/UpstreamResponse" }
|
|
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/ServerError"
|
|
/emoji/{file}:
|
|
get:
|
|
tags: [静态资源]
|
|
summary: 表情图片
|
|
description: 评论中使用的表情静态图片文件。
|
|
operationId: getEmoji
|
|
parameters:
|
|
- name: file
|
|
in: path
|
|
required: true
|
|
description: 表情文件名
|
|
schema: { type: string }
|
|
example: 1@2x.png
|
|
responses:
|
|
"200":
|
|
description: 图片文件
|
|
content:
|
|
image/png:
|
|
schema: { type: string, format: binary }
|
|
image/gif:
|
|
schema: { type: string, format: binary }
|
|
"404":
|
|
description: 文件不存在
|
|
content:
|
|
text/plain:
|
|
schema: { type: string }
|
|
|
|
|
|
components:
|
|
securitySchemes:
|
|
accessKeyQuery:
|
|
type: apiKey
|
|
in: query
|
|
name: key
|
|
description: 访问密钥,对应配置 server.access_key(或环境变量 FQ_SERVER_ACCESS_KEY)
|
|
accessKeyHeader:
|
|
type: apiKey
|
|
in: header
|
|
name: X-Access-Key
|
|
description: 访问密钥(与查询参数 key 等效)
|
|
|
|
parameters:
|
|
BookId:
|
|
name: book_id
|
|
in: query
|
|
required: true
|
|
description: 书籍 ID
|
|
schema: { type: string }
|
|
example: "7123456789012345678"
|
|
AccessKeyQuery:
|
|
name: key
|
|
in: query
|
|
description: 访问密钥(仅配置了 server.access_key 时需要)
|
|
schema: { type: string }
|
|
|
|
responses:
|
|
Unauthorized:
|
|
description: 访问密钥缺失或无效(业务接口仅在 server.protect_all 开启时需要)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example: { code: -1, msg: "访问被拒绝:无效的访问密钥" }
|
|
ServerError:
|
|
description: 服务端错误(上游请求失败等)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example: { code: -1, msg: "获取内容失败: 请求超时" }
|
|
BadRequest:
|
|
description: 参数缺失或格式错误
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
example: { code: -1, msg: "缺少 item_id 参数" }
|
|
|
|
schemas:
|
|
ErrorResponse:
|
|
type: object
|
|
required: [code, msg]
|
|
properties:
|
|
code:
|
|
type: integer
|
|
const: -1
|
|
msg:
|
|
type: string
|
|
description: 错误信息
|
|
|
|
UpstreamResponse:
|
|
type: object
|
|
description: |
|
|
通用响应包装。多数接口转发上游数据,`data` 的内部结构随上游接口变化,
|
|
以实际返回为准;此处仅约束外层字段。
|
|
required: [code, msg]
|
|
properties:
|
|
code:
|
|
type: integer
|
|
description: 0=成功,-1=失败
|
|
example: 0
|
|
msg:
|
|
type: string
|
|
example: success
|
|
data:
|
|
description: 业务数据,结构随接口不同
|