Skip to content

Commit 8a04a2d

Browse files
committed
feat: 完善合集目录作品与AI批量候选管理
新增逻辑作品分页接口并统一合集中的散本、目录作品及权限过滤。 将标签和分类的AI候选筛选移至服务端,同时补充测试并同步API文档。
1 parent 560fd17 commit 8a04a2d

29 files changed

Lines changed: 2140 additions & 406 deletions

docs/API.md

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Content-Type: application/json
9898
| 方法 | 路径 | 说明 |
9999
|:---|:---|:---|
100100
| GET | `/api/comics` | 列表(按用户可访问书库过滤,搜索/筛选/分页/排序/FTS5 全文搜索)🔒 |
101+
| GET | `/api/catalog/items` | 合集选择器使用的逻辑作品列表(目录作品与散本统一分页)🔒 |
101102
| GET | `/api/comics/:id` | 详情(无权限返回 403)🔒 |
102103
| PUT | `/api/comics/:id/favorite` | 切换收藏 🔒 |
103104
| PUT | `/api/comics/:id/rating` | 更新评分 🔒 |
@@ -142,8 +143,64 @@ Authorization: Bearer <token>
142143
- 普通用户传入无权限书库 ID 时会被过滤掉;交集为空时返回空列表。
143144
- 没有任何书库访问权限的普通用户返回空列表,不会退化成全库查询。
144145
- `sortBy=title` 时会按服务端维护的 `titleSortKey` 排序,效果上 `第2卷``第10卷` 前,常见中文标题按拼音顺序排列。
145-
- `seriesView=true` 专供统一书架展示:服务端先执行当前列表的权限和筛选条件,再把命中的目录作品成员折叠为一个虚拟条目。虚拟条目的 ID 为 `series-<seriesId>`,不是真实漫画 ID;客户端应从该条目进入 `/api/series/:id`,不要把虚拟 ID 传给漫画详情、阅读或下载接口。
146-
- 折叠后的响应会重新计算 `total`,并统一返回 `page=1``pageSize=折叠后条目数``totalPages=1`。需要稳定分页或逐本数据的 API 客户端不应启用该参数。
146+
- `seriesView=true` 专供统一书架展示:服务端先执行当前列表的权限和筛选条件,再把命中的目录作品成员折叠为一个虚拟条目。虚拟条目的 ID 为 `series-<seriesId>`,不是真实漫画 ID;`comicCount` 表示目录作品包含的阅读单元数。客户端应从该条目进入 `/api/series/:id`,不要把虚拟 ID 传给漫画详情、阅读或下载接口。
147+
- 折叠在分页之前完成,响应中的 `total``pageSize``totalPages` 均按折叠后的逻辑作品计算。需要明确区分散本和目录作品的选择器应优先使用 `/api/catalog/items`,避免解析虚拟 ID。
148+
149+
### 合集可选作品列表
150+
151+
```http
152+
GET /api/catalog/items?contentType=comic&search=作品名&page=1&pageSize=12&sortBy=title&sortOrder=asc
153+
Authorization: Bearer <token>
154+
```
155+
156+
该接口用于合集创建等“选择作品”场景,在数据库中先生成逻辑作品,再执行计数、排序和分页:
157+
158+
- 至少包含两个有效漫画阅读单元的目录作品返回一条 `kind=series` 记录,成员不会重复作为散本返回。
159+
- 单成员目录关系退回普通 `kind=comic` 记录,不会导致作品消失。
160+
- 小说始终逐本返回 `kind=comic`,不接入目录作品。
161+
- 搜索目录作品标题或任一成员标题/文件名时,返回对应目录作品。
162+
- 普通用户始终按当前 `canView` 权限过滤;传入的 `libraryIds` 会与可访问书库取交集,交集为空时返回空列表。
163+
164+
| 查询参数 | 类型 | 必填 | 说明 |
165+
|:---|:---|:---:|:---|
166+
| `contentType` | string || `comic` / `novel`,默认 `comic` |
167+
| `search` | string || 按逻辑作品标题、成员标题或文件名搜索 |
168+
| `libraryIds` | string || 逗号分隔的书库 ID,普通用户不能借此扩大权限范围 |
169+
| `page` | int || 页码,从 `1` 开始,默认 `1` |
170+
| `pageSize` | int || 每页数量,默认 `24`,最大 `100` |
171+
| `sortBy` | string || 当前仅支持 `title`,默认 `title` |
172+
| `sortOrder` | string || `asc` / `desc`,默认 `asc` |
173+
174+
响应示例:
175+
176+
```json
177+
{
178+
"items": [
179+
{
180+
"id": "ser_xxx",
181+
"kind": "series",
182+
"title": "目录作品",
183+
"coverUrl": "/api/comics/comic_xxx/thumbnail",
184+
"itemCount": 15,
185+
"libraryId": "lib_xxx"
186+
},
187+
{
188+
"id": "comic_xxx",
189+
"kind": "comic",
190+
"title": "散本漫画",
191+
"coverUrl": "/api/comics/comic_xxx/thumbnail",
192+
"itemCount": 1,
193+
"libraryId": "lib_xxx"
194+
}
195+
],
196+
"page": 1,
197+
"pageSize": 12,
198+
"total": 2,
199+
"totalPages": 1
200+
}
201+
```
202+
203+
`id` 是对应实体的真实 ID。创建合集时,应根据 `kind` 分别提交到 `comicIds``seriesIds`,不要添加或解析虚拟前缀。
147204

148205
### 设置阅读状态
149206

@@ -321,16 +378,73 @@ Content-Type: application/json
321378

322379
| 方法 | 路径 | 说明 |
323380
|:---|:---|:---|
324-
| GET | `/api/groups` | 分组列表(支持 contentType/category/tags/favoritesOnly/libraryIds 过滤)🔒 |
381+
| GET | `/api/groups` | 分组列表(目录作品成员参与 contentType/category/tags/favoritesOnly/libraryIds 过滤与计数)🔒 |
325382
| GET | `/api/groups/comic-map` | 漫画-分组映射关系 |
326-
| GET | `/api/groups/:id` | 分组详情 |
327-
| POST | `/api/groups` | 创建分组 🔒管理员 |
383+
| GET | `/api/groups/:id` | 分组详情(分别返回 `seriesList``comics`)🔒 |
384+
| POST | `/api/groups` | 创建分组,可提交 `comicIds``seriesIds` 🔒管理员 |
328385
| PUT | `/api/groups/:id` | 更新分组 🔒管理员 |
329386
| DELETE | `/api/groups/:id` | 删除分组 🔒管理员 |
330387
| POST | `/api/groups/:id/comics` | 添加漫画到分组 🔒管理员 |
331388
| DELETE | `/api/groups/:id/comics/:comicId` | 从分组移除漫画 🔒管理员 |
389+
| POST | `/api/groups/:id/series` | 添加目录作品到分组 🔒管理员 |
390+
| DELETE | `/api/groups/:id/series/:seriesId` | 从分组移除目录作品 🔒管理员 |
332391
| PUT | `/api/groups/:id/reorder` | 分组内漫画排序 🔒管理员 |
333392

393+
### 合集与目录作品
394+
395+
创建合集时可同时提交散本和目录作品,整个创建过程在同一个数据库事务中完成:
396+
397+
```http
398+
POST /api/groups
399+
Content-Type: application/json
400+
401+
{
402+
"name": "合集名称",
403+
"comicIds": ["comic_xxx"],
404+
"seriesIds": ["ser_xxx"]
405+
}
406+
```
407+
408+
`comicIds``seriesIds` 均可省略或传空数组。目录作品也可以在合集创建后单独添加或移除:
409+
410+
```http
411+
POST /api/groups/:id/series
412+
Content-Type: application/json
413+
414+
{"seriesIds": ["ser_xxx"]}
415+
```
416+
417+
```http
418+
DELETE /api/groups/:id/series/:seriesId
419+
```
420+
421+
`GET /api/groups/:id` 将散本和目录作品分开返回:
422+
423+
```json
424+
{
425+
"id": 1,
426+
"name": "合集名称",
427+
"comicCount": 3,
428+
"seriesList": [
429+
{
430+
"id": "ser_xxx",
431+
"title": "目录作品",
432+
"rootRelativePath": "目录作品",
433+
"coverComicId": "comic_001",
434+
"coverUrl": "/api/comics/comic_001/thumbnail",
435+
"sortIndex": 0,
436+
"comics": []
437+
}
438+
],
439+
"comics": []
440+
}
441+
```
442+
443+
- `comicCount` 是当前用户可见的散本与目录作品成员去重后的阅读单元总数。
444+
- 普通用户的 `seriesList``seriesList[].comics``comics` 均按可查看书库过滤。
445+
- 普通用户无法访问合集内任何阅读单元时返回 `403`;管理员请求不存在的合集时返回 `404`
446+
- `contentType=comic|novel` 可继续筛选详情;目录作品只参与漫画结果,小说仍以散本返回。
447+
334448
### 分组元数据管理 🔒管理员
335449

336450
| 方法 | 路径 | 说明 |
@@ -440,6 +554,38 @@ Content-Type: application/json
440554
| POST | `/api/ai/verify-duplicates` | AI 重复验证 |
441555
| POST | `/api/ai/recommend-goal` | AI 推荐阅读目标 |
442556

557+
### AI 批量标签与分类候选选择
558+
559+
`/api/ai/batch-suggest-tags``/api/ai/batch-suggest-category` 均返回 SSE 流,每次最多处理 30 本作品。请求体支持以下两种互斥模式:
560+
561+
```json
562+
{
563+
"comicIds": ["comic_xxx"],
564+
"targetLang": "zh",
565+
"apply": false
566+
}
567+
```
568+
569+
```json
570+
{
571+
"selector": {
572+
"scope": "missing",
573+
"contentType": "comic",
574+
"libraryIds": ["lib_xxx"],
575+
"limit": 30
576+
},
577+
"targetLang": "zh",
578+
"apply": false
579+
}
580+
```
581+
582+
- `comicIds` 保留用于显式选择具体作品,超过 30 个时只处理前 30 个。
583+
- `selector.scope`:标签接口支持 `missing` / `all`;分类接口支持 `uncategorized` / `missing` / `all`。默认分别为 `missing``uncategorized`
584+
- `selector.contentType`:可选值为 `comic` / `novel`;省略时处理两种内容。
585+
- `selector.libraryIds`:可选书库范围,始终与当前用户可查看书库取交集,不能扩大权限。
586+
- `selector.limit`:默认和最大值均为 `30`
587+
- `selector` 模式先发送 `selection` SSE 数据,其中包含 `eligible` 候选总数和 `selected` 本次处理数;随后发送逐本结果及最终 `done` 数据。
588+
443589
### AI 漫画级功能
444590

445591
| 方法 | 路径 | 说明 |

0 commit comments

Comments
 (0)