@@ -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