POST
/api/v2/uploadREST{ }Bodyapplication/json
{
"novel": {
"platformId": 1,
"authorId": 2,
"title": "示例小说",
"summary": "小说简介",
"coverUrl": null,
"status": "ONGOING"
},
"volumes": [
{ "order": 1, "title": "第一卷 启程" },
{ "order": 2, "title": "第二卷 远方" }
],
"chapters": [
{
"order": 1,
"volumeOrder": null,
"title": "序章",
"content": "未分卷的序章正文。"
},
{
"order": 2,
"volumeOrder": 1,
"title": "第一章 出发",
"content": "第一段。\n\n第二段。"
},
{
"order": 3,
"volumeOrder": 2,
"title": "第二章 远行",
"content": "第二卷中的章节正文。"
}
]
}Request Body
application/json这是推荐的小说导入接口。上传前先取得平台 ID,可选取得作者 ID。 每次请求只能选择以下一种模式:
- 新建小说:提供
novel对象,不提供novelId。成功返回201。 - 已有小说续传:提供
novelId,不提供novel。成功返回200。
不带查询参数。新建模式允许只建元信息;续传模式至少包含一个卷或一个章节。
8.1 新建小说并上传卷、章节
{
"novel": {
"platformId": 1,
"authorId": 2,
"title": "示例小说",
"summary": "小说简介",
"coverUrl": null,
"status": "ONGOING"
},
"volumes": [
{ "order": 1, "title": "第一卷 启程" },
{ "order": 2, "title": "第二卷 远方" }
],
"chapters": [
{
"order": 1,
"volumeOrder": null,
"title": "序章",
"content": "未分卷的序章正文。"
},
{
"order": 2,
"volumeOrder": 1,
"title": "第一章 出发",
"content": "第一段。\n\n第二段。"
},
{
"order": 3,
"volumeOrder": 2,
"title": "第二章 远行",
"content": "第二卷中的章节正文。"
}
]
}8.2 给已有小说分批续传
{
"novelId": 10,
"volumes": [
{ "order": 3, "title": "第三卷 归途" }
],
"chapters": [
{
"order": 4,
"volumeOrder": 3,
"title": "第三章 归来",
"content": "新章节正文。"
},
{
"order": 5,
"volumeOrder": 1,
"title": "第一卷补记",
"content": "引用此前已经存在的第一卷。"
}
]
}续传只修改本批次列出的卷、章节,不清空其他数据,不修改小说元信息。
要修改小说元信息,另行使用 PUT /api/v2/novelinfo?id=10。
8.3 顶层字段
| 请求键 | 类型 | 必填 | 说明 |
|---|---|---|---|
novel | object / null | 新建模式必填 | 第 5.4 节定义的小说写入对象;与非空 novelId 互斥。 |
novelId | integer / null | 续传模式必填 | 已存在的小说 ID;不存在返回 404。与非空 novel 互斥。 |
volumes | array / null | 否 | 本批次要新增/覆盖的卷,最多 200 项;省略/null/空数组均表示本批不写入卷。 |
chapters | array / null | 否 | 本批次要新增/覆盖的章节,最多 1000 项;省略/null/空数组均表示本批不写入章节。 |
8.4 volumes 数组每项的所有键
| 请求键 | 类型 | 必填 | 说明 |
|---|---|---|---|
order | integer | 是 | 小说内卷序号;同一批次不能重复,不要求数组按序排列。 |
title | string | 是 | 卷名,去除首尾空白后不能为空。 |
这里不提交 novelId、id 或时间字段;小说由顶层确定。
卷按 (novelId, order) 新增或覆盖,已有卷保留 ID、创建时间和章节关联。
8.5 chapters 数组每项的所有键
| 请求键 | 类型 | 必填 | 默认/约束 |
|---|---|---|---|
order | integer | 是 | 全书章节序号;同一批次不能重复,跨卷不重置,不要求连续或数组排序。 |
volumeOrder | integer / null | 否 | 卷序号,不是卷 ID;引用本批次创建的卷,或同一本小说此前已经存在的卷。省略/null 会将章节设为未分卷。 |
title | string | 是 | 章节标题,去除首尾空白后不能为空。 |
content | string | 是 | 非全空白的纯文本正文,原始换行和空白保留。 |
批量上传不接受单章接口的 volumeId,用 volumeOrder 避免新卷尚无数据库 ID 的问题。
卷先写入,章节后写入;数组顺序不影响关联。卷引用找不到返回 400,整个事务回滚。
同一小说允许未分卷章节与分卷章节混用,卷可以暂时没有章节。
8.6 成功响应的所有键
新建返回 201,同时带 Location: /api/v2/novelinfo?id=10;续传返回 200。
{
"novel": {
"id": 10,
"platformId": 1,
"authorId": 2,
"title": "示例小说",
"summary": "小说简介",
"coverUrl": null,
"status": "ONGOING",
"createdAt": "2026-10-29T04:00:00Z",
"updatedAt": "2026-10-29T04:00:00Z"
},
"volumes": [
{
"id": 20,
"novelId": 10,
"order": 1,
"title": "第一卷 启程",
"createdAt": "2026-10-29T04:00:00Z",
"updatedAt": "2026-10-29T04:00:00Z"
}
],
"chapters": [
{
"id": 30,
"order": 2,
"volumeId": 20,
"updatedAt": "2026-10-29T04:00:00Z"
}
],
"chapterCount": 1
}以上用一卷一章展示响应结构,并非 8.1 完整请求的逐项响应。
| 响应键 | 类型 | 说明 |
|---|---|---|
novel | object | 完整小说元信息,所有键见第 5 节。 |
volumes | array | 本批次显式写入的卷,结构见第 6 节,按请求数组顺序返回;未写卷时为 []。仅被章节引用的旧卷不会额外出现在这里。 |
chapters | array | 本批次章节回执,按请求数组顺序返回;未写章节时为 []。不重复返回正文,完整章节用 GET /api/v2/content 获取。 |
chapters[].id | integer | 章节数据库 ID。 |
chapters[].order | integer | 全书章节序号。 |
chapters[].volumeId | integer / null | 解析后的数据库卷 ID,未分卷为 null。 |
chapters[].updatedAt | string | 本次写入的服务端更新时间。 |
chapterCount | integer | 本批次成功写入的章节条数,不是全书章节总数。新增和覆盖都计数。 |
8.7 事务、覆盖与重试规则
- 每批次一个数据库事务,任何关联或数据库错误都会撤销本批新小说、卷和章节的全部修改,不会部分成功。
- 卷按
(novelId, order)、章节按(novelId, order)覆盖。既有章节保留 ID,替换标题、正文与卷关联。 - 卷序号和章节序号是两套独立编号。序号是定位键,提交新序号不会移动/重编号旧记录,而是创建另一条。
- 同一批内部的重复卷序号或重复章节序号直接返回 400,避免隐式“最后一条覆盖”。
- 首次创建不按标题去重,也没有幂等键。重复发送带
novel的请求会创建多部小说。 - 成功取得
novel.id后,后续请求统一改用novelId。相同续传数据可以重试,记录 ID 保持不变,更新时间会更新。 - 如首次请求超时或连接中断,提交结果可能不明;先查询并核对,不要盲目再次用
novel新建。 - 上传卷/章节不刷新小说的
updatedAt,该字段仅反映小说元信息写入时间。 - 多批之间不是同一个事务:某批失败不会撤销此前成功的批次。8 MiB/1000 章以内按需拆分,分批进度由客户端记录。
正文与 JSON 示例读取自
docs/content/athub/API.md,与项目文档保持同源。发现文档有问题?在 GitHub 提出建议 ↗