Atsumeru 1.1 API 使用详解
Atsumeru 1.1 REST API 全端点实测详解:HTTP Basic 认证与首次调用、server/users/settings/importer/books/categories 六域逐端点请求示例与真实回显、分页过滤排序通用约定、categories/set JSON 500 与 cover/download 空 200 两则排障实录(2026-11-01 实测)。
Atsumeru 1.1 API 使用详解
目标:把 Atsumeru 1.1 的 REST API 逐域讲透——认证方式、六大端点域(server / users / settings / importer / books / categories)每个 endpoint 的方法、参数、请求示例与真实响应回显,外加分页/过滤/排序通用约定与两则实测排障实录。全部请求在 2026-11-01 对本专栏部署篇拉起的
atsumeru-11-web服务(宿主端口 8138)实测;部署过程见 《Atsumeru Docker Compose 部署指南》,配置项语义见 《Atsumeru 1.1 配置参数详解》,组件背景见 《Atsumeru 介绍与选型指南》。
一、环境信息
- 被测服务:Atsumeru v1.1(
/api/server/info实测"version":"1.1","version_name":"Bohrium"),监听 31337、宿主映射http://localhost:8138 - 认证形态:纯 HTTP Basic(源码
WebSecurityConfiguration仅启用httpBasic(),1.1 无 token/登录端点,详见部署篇 4.3 排障实录) - 演示账号:管理员
Admin / q1w2e3r4、普通用户root / q1w2e3r4(普通用户用于 403 越权实测) - 测试数据:书库内 1 个系列
atsumeru-serieb71204461360651e64a359ad591269ac(标题 "Volume 1")与 1 个档案atsumeru36fae69422e431620cadd1b555d248c9(3 页 CBZ,1384 字节),由部署篇导入流程产生 - 取证方式:
curl逐条实测(响应回显逐行照抄,过长 JSON 标注节选),端点与参数语义用 jsDelivr 镜像源码Atsumeru-xyz/Atsumeru@1.1的 6 个 Controller 交叉核对;1.1 镜像无 Swagger/OpenAPI 可导出,端点图完全来自源码 + 实测 - 查询日期:2026-11-01
二、前置条件
- 服务已按 部署篇 拉起且健康,
curl -s -o /dev/null -w '%{http_code}' -u Admin:q1w2e3r4 http://localhost:8138/api/server/ping返回200; - 已掌握管理员凭据(本环境为
Admin / q1w2e3r4);除/api/v1/users/me与 books 读取类端点外,绝大多数管理端点要求ADMIN角色; - 客户端工具仅需
curl;jq 可选(本篇响应回显均为原始输出,未过 jq)。
三、API 总览
| 维度 | 约定 | 依据 |
|---|---|---|
| 协议/格式 | HTTP/1.1,请求与响应均为 application/json;charset=UTF-8(图片/档案端点返回二进制流) | 实测响应头 |
| 版本前缀 | 业务端点在 /api/v1/ 下;server 域例外,为 /api/server(无版本号) | 源码 @RequestMapping |
| 认证 | HTTP Basic,401 时响应头带 WWW-Authenticate: Basic realm="Realm" | 实测 |
| 授权 | 类/方法级 @PreAuthorize:hasRole('ADMIN')、hasRole('ADMIN') or hasAuthority('IMPORTER') 等 | 源码 |
| 限流 | 无(Spring Security 未配置限流;1.1 无 API Key 机制) | 源码 + 实测 |
| 错误信封 | 管理写操作统一返回 {"code":<int>,"message":"..."};未捕获异常返回 Spring Boot 默认错误 JSON(timestamp/status/error/path) | 实测 |
| 文档 | 1.1 无 Swagger UI/OpenAPI(WebUI 与 Swagger 属 2.x jar 发行版) | A1 版本线说明 |
端点域速查(全部实测或源码取证):
| 域 | 前缀 | 端点 |
|---|---|---|
| server | /api/server | GET /ping、GET /info、GET /clear_cover_cache |
| users | /api/v1/users | GET /me、GET /list、GET /constants、PUT /create、PATCH /update、DELETE /delete?user_id= |
| settings | /api/v1/settings | GET /get、POST /update |
| importer | /api/v1/importer | GET /status、GET /list、POST /add、DELETE /remove/{hash}、GET /scan、GET /rescan、GET /rescan/{hash} |
| books | /api/v1/books | GET ""、GET /filters、`GET |
| categories | /api/v1/books/categories | GET ""、PUT /create?name=、PATCH /edit?id=&name=、POST /set、POST /order、DELETE /delete?id=;另有 GET /api/v1/books/metacategories |
四、认证与首次调用
4.1 无凭据与错凭据(401 行为)
不带凭据访问任意 API,返回 401 且响应头携带 Basic 质询:
curl -s -i http://localhost:8138/api/v1/users/me
HTTP/1.1 401
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Set-Cookie: JSESSIONID=9ADBC636D9C922BEF3F27C2A4DE54CE3; Path=/; HttpOnly
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Sun, 01 Nov 2026 11:46:50 GMT
密码错误同样只有裸 401(无错误 JSON 信封):
curl -s -o /dev/null -w '%{http_code}\n' -u Admin:wrong123 http://localhost:8138/api/v1/users/me
401
4.2 存活探测与版本确认
curl -s -i -u Admin:q1w2e3r4 http://localhost:8138/api/server/ping
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Sun, 01 Nov 2026 11:46:51 GMT
ping 成功即 200 空体(源码 ResponseEntity.ok().build())。取版本与统计:
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/server/info
{"name":"Atsumeru","version":"1.1","version_name":"Bohrium","has_password":true,"debug_mode":false,"stats":{"total_series":1,"total_archives":1,"total_chapters":0,"total_categories":1}}
五、各 API 域详解
5.1 server 域(/api/server)
| 端点 | 方法 | 权限 | 说明 |
|---|---|---|---|
/ping | GET | Basic | 200 空体存活探测 |
/info | GET | Basic | 名称/版本/统计 |
/clear_cover_cache | GET | ADMIN | 清空封面缓存目录并后台重建 |
注意 clear_cover_cache 是 GET(源码 @GetMapping),且会删除整个 /app/cache 目录后异步重存封面:
curl -s -i -u Admin:q1w2e3r4 http://localhost:8138/api/server/clear_cover_cache
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 01 Nov 2026 11:48:24 GMT
{"code":200,"message":"Cache cleared"}
5.2 users 域(/api/v1/users)
类级 @PreAuthorize("hasRole('ADMIN')"),但 /me 对任何已认证用户可用(返回调用者自身)。
查自己 / 查全员(password 字段返回的是 BCrypt 散列,非明文):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/users/me
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/users/list
{"id":1,"user_name":"Admin","password":"$2a$10$ne7i/OgxDu6S0HnSUmVlBuZX/XE.l5L93adFeGMOLIPMFEPV.mc26","roles":["ADMIN"],"authorities":["ROLE_ADMIN"]}
[{"id":1,"user_name":"Admin","password":"$2a$10$ne7i/OgxDu6S0HnSUmVlBuZX/XE.l5L93adFeGMOLIPMFEPV.mc26","roles":["ADMIN"],"authorities":["ROLE_ADMIN"]},{"id":2,"user_name":"root","password":"$2a$10$acVoMoHHYm6ft0h6FnHrRenRVgKlTIbtNZinu2BOTMYGVwuDi04.u","roles":["USER"],"authorities":["ROLE_USER"]}]
常量枚举 /constants——roles/authorities/分类/流派/标签全集,建用户前先取合法值(输出较长,节选至流派枚举中段):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/users/constants
{"roles":["ADMIN","USER"],"authorities":["IMPORTER","UPLOADER","METADATA_UPDATER","DOWNLOAD_FILES"],"categories":[{"id":"atsumeru-category966faeabbc679ba4457e2d75e031d487","name":"Unsorted","content_type":"UNKNOWN","order":0}],"genres":[{"name":"Action","id":0},{"name":"Adult","id":1},{"name":"Adventure","id":2},{"name":"Comedy","id":3},{"name":"Doujinshi","id":4},{"name":"Drama","id":5},{"name":"Ecchi","id":6},{"name":"Fantasy","id":7},{"name":"Gender Bender","id":8},{"name":"Harem","id":9},{"name":"Historical","id":10},{"name":"Horror","id":11},{"name":"Josei","id":12},{"name":"Magic","id":13},{"name":"Martial Arts","id":14},{"name":"Mecha","id":15},{"name":"Mystery","id":16},{"name":"One Shot","id":17},{"name":"Psychological","id":18},{"name":"Romance","id":19},{"name":"School Life","id":20},{"name":"Sci-fi","id":21},{"name":"Seinen","id":22},{"name":"Shoujo","id":23},{"name":"Shoujo Ai","id":24},{"name":"Shounen","id":25},{"name":"Shounen Ai","id":26},{"name":"Slice of Life","id":27},{"name":"Sports","id":28},{"name":"Supernatural","id":29},{"name":"Tragedy","id":30},{"name":"Yaoi","id":31},{"name":"Yuri","id":32}],"tags":[]}
创建用户 PUT /create(注意是 PUT 不是 POST)。先测非法短密码——HTTP 状态仍是 200,业务码在信封里:
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X PUT -d '{"user_name":"d5reader","password":"abc","roles":["USER"]}' http://localhost:8138/api/v1/users/create
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 01 Nov 2026 11:48:23 GMT
{"code":400,"message":"Password must be at least 6 characters long!"}
合法创建(message 返回新用户 ID):
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X PUT -d '{"user_name":"d5reader","password":"q1w2e3r4","roles":["USER"],"authorities":["ROLE_USER"]}' http://localhost:8138/api/v1/users/create
HTTP/1.1 200
...(响应头同上,省略)...
{"code":201,"message":"3"}
更新用户 PATCH /update(别名 /edit):password 传空串保留原密码(源码 StringUtils.isEmpty(password) 分支);下例给 d5reader 追加 DOWNLOAD_FILES 细粒度权限并屏蔽 Adult 流派:
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X PATCH -d '{"id":3,"user_name":"d5reader","password":"","roles":["USER"],"authorities":["ROLE_USER","DOWNLOAD_FILES"],"disallowed_genres":["Adult"]}' http://localhost:8138/api/v1/users/update
HTTP/1.1 200
...(响应头同上,省略)...
{"code":201,"message":"User successfully created"}
回显两处 API 怪癖(均为 1.1 真实行为):更新成功的 message 竟写作 "User successfully created"、业务码 201——语义与文案不符,脚本判成败应以 HTTP 200 + code 字段为准。复查列表确认更新生效:
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/users/list
[{"id":1,"user_name":"Admin","password":"$2a$10$ne7i/OgxDu6S0HnSUmVlBuZX/XE.l5L93adFeGMOLIPMFEPV.mc26","roles":["ADMIN"],"authorities":["ROLE_ADMIN"]},{"id":2,"user_name":"root","password":"$2a$10$acVoMoHHYm6ft0h6FnHrRenRVgKlTIbtNZinu2BOTMYGVwuDi04.u","roles":["USER"],"authorities":["ROLE_USER"]},{"id":3,"user_name":"d5reader","password":"$2a$10$qNBMLHB9EUhLj06WHTegi.DcmeGgPYM0yQBXRKr0N4Z8hx/W1aq.i","roles":["USER"],"authorities":["ROLE_USER","DOWNLOAD_FILES"],"disallowed_genres":["Adult"]}]
权限边界实测:普通用户 d5reader 访问 ADMIN 专属端点被 403 拦截,但读书库正常:
curl -s -o /dev/null -w '%{http_code}\n' -u d5reader:q1w2e3r4 http://localhost:8138/api/v1/users/list
curl -s -o /dev/null -w '%{http_code}\n' -u d5reader:q1w2e3r4 'http://localhost:8138/api/v1/books?limit=1'
curl -s -o /dev/null -w '%{http_code}\n' -u root:q1w2e3r4 http://localhost:8138/api/v1/settings/get
403
200
403
删除用户 DELETE /delete?user_id=(查询参数不是路径段):
curl -s -i -u Admin:q1w2e3r4 -X DELETE 'http://localhost:8138/api/v1/users/delete?user_id=3'
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"User successfully deleted"}
5.3 settings 域(/api/v1/settings)
类级 ADMIN。GET /get 返回六键当前值(键语义详解见 配置篇第三章):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/settings/get
{"allow_loading_list_with_volumes":true,"allow_loading_list_with_chapters":false,"disable_request_logging_into_console":true,"disable_file_watcher":false,"disable_watch_for_modified_files":false,"disable_chapters":true}
POST /update 必须整包提交六键(@RequestBody ServerSettings,缺键按 false 处理)。下例把控制台请求日志关掉(disable_request_logging_into_console true→false),并立即 cat 宿主侧属性文件验证落盘:
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X POST -d '{"allow_loading_list_with_volumes":true,"allow_loading_list_with_chapters":false,"disable_request_logging_into_console":false,"disable_file_watcher":false,"disable_watch_for_modified_files":false,"disable_chapters":true}' http://localhost:8138/api/v1/settings/update
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"Settings updated successfully"}
cat /opt/data/quzuo/tmp/atsumeru/config/atsumeru.properties
#Auto save properties: Sun Nov 01 11:48:23 UTC 2026
#Sun Nov 01 11:48:23 UTC 2026
allow_loading_list_with_volumes=true
allow_loading_list_with_chapters=false
disable_chapters=true
disable_file_watcher=false
disable_watch_for_modified_files=false
disable_request_logging_into_console=false
改回 true 后再验证一次(回显 Settings updated successfully,属性文件末行恢复 disable_request_logging_into_console=true,时间戳同为 11:48:23,此处不再重复贴出)。两点源码级警告:
disable_chapters值发生变化时,服务端会延迟自动重启(源码restartServerDelayed()等待导入/元数据任务空闲后AtsumeruApplication.restart()),API 调用方应预期短暂不可用;- 导入/元数据刷新/封面保存任一进行中时,
GET /get与POST /update返回 406{"message":"Unable to edit server settings"}(源码serverLockedSupplier,本篇实测时段无导入任务,未触发)。
5.4 importer 域(/api/v1/importer)
类级 hasRole('ADMIN') or hasAuthority('IMPORTER')。
状态与目录注册表:
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/importer/status
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/importer/list
{"import_active":false,"last_start_time":1793531877754,"running_ms":0,"imported":0,"total":0,"percent":0.0}
[{"hash":"63fe8498b2a1b0edec7c2efdc8fd3296","path":"/library/manga","singles":false,"singles_in_root":false,"singles_if_in_root_with_folders":false,"ignore_volume_numbers_detection":false,"series_count":1,"singles_count":0,"archives_count":1,"chapters_count":0}]
hash 即 md5(path)(源码 ImportFolder.createHash()),remove 用它定位。
注册目录 POST /add:请求体是 ImportFolder JSON(最少 path,其余布尔可选)。先用不存在的路径测失败分支——404:
curl -s -o /dev/null -w '%{http_code}\n' -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X POST -d '{"path":"/library/nonexistent"}' http://localhost:8138/api/v1/importer/add
404
宿主先建目录 mkdir -p /opt/data/quzuo/tmp/atsumeru/library/d5tmp(bind 挂载映射为容器 /library/d5tmp),真实注册成功:
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X POST -d '{"path":"/library/d5tmp","singles":true}' http://localhost:8138/api/v1/importer/add
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"Folder added"}
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/importer/list
[{"hash":"63fe8498b2a1b0edec7c2efdc8fd3296","path":"/library/manga","singles":false,"singles_in_root":false,"singles_if_in_root_with_folders":false,"ignore_volume_numbers_detection":false,"series_count":1,"singles_count":0,"archives_count":1,"chapters_count":0},{"hash":"dbcbda0ce560cd26226a7754d5972396","path":"/library/d5tmp","singles":true,"singles_in_root":false,"singles_if_in_root_with_folders":false,"ignore_volume_numbers_detection":false,"series_count":0,"singles_count":0,"archives_count":0,"chapters_count":0}]
新目录 hash dbcbda0ce560cd26226a7754d5972396 与宿主侧 python3 -c "import hashlib;print(hashlib.md5(b'/library/d5tmp').hexdigest())" 计算值一致,印证 md5 规则。
移除目录 DELETE /remove/{hash}(同时把该目录内容从索引摘除);对同一 hash 再删一次得到 404:
curl -s -i -u Admin:q1w2e3r4 -X DELETE http://localhost:8138/api/v1/importer/remove/dbcbda0ce560cd26226a7754d5972396
curl -s -o /dev/null -w '%{http_code}\n' -u Admin:q1w2e3r4 -X DELETE http://localhost:8138/api/v1/importer/remove/dbcbda0ce560cd26226a7754d5972396
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"Folder removed from index"}
404
触发扫描——注意 scan 与 rescan 都是 GET(有副作用的 GET,写脚本时别习惯性用 POST):
curl -s -i -u Admin:q1w2e3r4 http://localhost:8138/api/v1/importer/scan
curl -s -i -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/importer/rescan?fully=false'
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/importer/status
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"Scan started"}
HTTP/1.1 200
...(响应头同上,省略)...
{"code":200,"message":"Scan started"}
{"import_active":true,"last_start_time":1793533762221,"running_ms":64,"imported":0,"total":1,"percent":0.0}
rescan 不带参数默认 fully=true(回显 "Rescan started"),fully=false 退化为增量扫描(回显 "Scan started");/rescan/{hash} 可只重扫某个目录或系列。add/remove/scan/rescan 均带 @CacheEvict,扫描后 books/filters 缓存自动失效。
5.5 books 域(/api/v1/books)
读取类端点无角色限制(任何已认证用户可读,受用户 allowed_categories/disallowed_genres 数据过滤)。
列表 GET "",11 个查询参数(源码 getBooks 形参):
| 参数 | 默认 | 说明 |
|---|---|---|
type | 空 | ContentType(UNKNOWN 等) |
category | 空 | 分类真实 ID |
presentation | series | series/archives/singles/issues 等展示形态 |
search | 空 | 标题关键词(非空时内部转走 filtered 查询) |
sort | created_at | 排序字段(合法值见 6.2) |
asc | false | 升/降序 |
page | 1 | 页码,从 1 起 |
limit | 30 | 每页条数 |
with_volumes | false | 内嵌卷列表,受 allow_loading_list_with_volumes 开关约束 |
with_chapters | false | 内嵌章节,受 allow_loading_list_with_chapters 开关约束 |
all | false | true 时不分页返回全部 |
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books?limit=30'
[{"id":"atsumeru-serieb71204461360651e64a359ad591269ac","title":"Volume 1","is_single":false,"folder":"/library/manga/AtsumeruTestSeries","cover":"atsumeru36fae69422e431620cadd1b555d248c9?t\u003d1793531877744","authors":["quzuo"],"genres":["Action"],"content_type":"UNKNOWN","is_mature":false,"is_adult":false,"pagesCount":3,"status":"ONGOING","translation_status":"UNKNOWN","plot_type":"UNKNOWN","censorship":"UNKNOWN","color":"UNKNOWN","rating":0,"created_at":1793531877744,"updated_at":1793531877744}]
搜索按标题匹配——本系列文件夹名是 AtsumeruTestSeries 但标题为 "Volume 1",因此 search=Atsumeru 空、search=Volume 命中:
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books?search=Atsumeru'
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books?search=Volume'
[]
[{"id":"atsumeru-serieb71204461360651e64a359ad591269ac","title":"Volume 1","is_single":false,"folder":"/library/manga/AtsumeruTestSeries","cover":"atsumeru36fae69422e431620cadd1b555d248c9?t\u003d1793531877744","authors":["quzuo"],"genres":["Action"],"content_type":"UNKNOWN","is_mature":false,"is_adult":false,"pagesCount":3,"status":"ONGOING","translation_status":"UNKNOWN","plot_type":"UNKNOWN","censorship":"UNKNOWN","color":"UNKNOWN","rating":0,"created_at":1793531877744,"updated_at":1793531877744}]
with_volumes 内嵌卷(allow_loading_list_with_volumes=true 才生效,否则参数被静默忽略):
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books?with_volumes=true&limit=3'
[{"id":"atsumeru-serieb71204461360651e64a359ad591269ac","title":"Volume 1","is_single":false,"folder":"/library/manga/AtsumeruTestSeries","cover":"atsumeru36fae69422e431620cadd1b555d248c9","authors":["quzuo"],"genres":["Action"],"content_type":"UNKNOWN","is_mature":false,"is_adult":false,"pagesCount":3,"status":"ONGOING","translation_status":"UNKNOWN","plot_type":"UNKNOWN","censorship":"UNKNOWN","color":"UNKNOWN","rating":0,"created_at":1793531877744,"updated_at":1793531877744,"volumes":[{"id":"atsumeru36fae69422e431620cadd1b555d248c9","title":"Volume 01","additional_title":"Volume 1","cover_accent":"#1c1d23","file_name":"AtsumeruTestSeries v001.cbz","file_path":"/library/manga/AtsumeruTestSeries/AtsumeruTestSeries v001.cbz","volume":1.0,"pages_count":3,"is_book":false,"created_at":1793531877744}]}]
详情/卷列表 GET /{hash}、GET /{hash}/volumes(系列与单本档案共用同一 ID 空间):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/atsumeru-serieb71204461360651e64a359ad591269ac/volumes
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/atsumeru36fae69422e431620cadd1b555d248c9
[{"id":"atsumeru36fae69422e431620cadd1b555d248c9","title":"Volume 01","additional_title":"Volume 1","cover_accent":"#1c1d23","file_name":"AtsumeruTestSeries v001.cbz","file_path":"/library/manga/AtsumeruTestSeries/AtsumeruTestSeries v001.cbz","volume":1.0,"pages_count":3,"is_book":false,"created_at":1793531877744}]
{"id":"atsumeru36fae69422e431620cadd1b555d248c9","title":"Volume 1","volume":1.0,"isBook":false,"fileSize":1384,"folder":"/library/manga/AtsumeruTestSeries/AtsumeruTestSeries v001.cbz","cover":"atsumeru36fae69422e431620cadd1b555d248c9?t\u003d1793531877744","authors":["quzuo"],"genres":["Action"],"content_type":"UNKNOWN","is_mature":false,"is_adult":false,"pagesCount":3,"status":"UNKNOWN","translation_status":"UNKNOWN","plot_type":"UNKNOWN","censorship":"UNKNOWN","color":"UNKNOWN","rating":0,"cover_accent":"#1c1d23","created_at":1793531877744,"updated_at":1793531877744}
取页 GET /{archive_hash}/page/{n}(n 从 1 起;?convert=true 可强制转 PNG):
curl -s -D - -u Admin:q1w2e3r4 -o /opt/data/quzuo/tmp/atsumeru/d5_page1.png http://localhost:8138/api/v1/books/atsumeru36fae69422e431620cadd1b555d248c9/page/1
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: image/png
Content-Length: 222
Date: Sun, 01 Nov 2026 11:48:24 GMT
python3 -c "d=open('/opt/data/quzuo/tmp/atsumeru/d5_page1.png','rb').read();print('bytes:',len(d),'magic:',d[:8].hex())"
bytes: 222 magic: 89504e470d0a1a0a
封面与下载的正确路径(详见本章末排障实录):GET /cover/{image_hash}(image_hash 就是列表 cover 字段 ?t= 前的部分)与 GET /download/{archive_hash}——注意都是前缀式路径,不是 /{hash}/cover:
curl -s -D - -u Admin:q1w2e3r4 -o /opt/data/quzuo/tmp/atsumeru/d5_cover.png http://localhost:8138/api/v1/books/cover/atsumeru36fae69422e431620cadd1b555d248c9
curl -s -D - -u Admin:q1w2e3r4 -o /opt/data/quzuo/tmp/atsumeru/d5_dl.cbz http://localhost:8138/api/v1/books/download/atsumeru36fae69422e431620cadd1b555d248c9
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: image/png
Content-Length: 222
Date: Sun, 01 Nov 2026 11:49:21 GMT
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Disposition: attachment; filename=AtsumeruTestSeries+v001.cbz
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 1384
Date: Sun, 01 Nov 2026 11:49:21 GMT
python3 -c "d=open('/opt/data/quzuo/tmp/atsumeru/d5_cover.png','rb').read();print('bytes:',len(d),'magic:',d[:8].hex())"
python3 -c "d=open('/opt/data/quzuo/tmp/atsumeru/d5_dl.cbz','rb').read();print('bytes:',len(d),'magic:',d[:4].hex())"
bytes: 222 magic: 89504e470d0a1a0a
bytes: 1384 magic: 504b0304
下载体 504b0304(PK ZIP 魔数)与源 CBZ 大小 1384 字节一致。/download 受 DOWNLOAD_FILES 权限约束(源码 FilesHelper.downloadFile 读取 Authentication),管理员默认放行。
过滤元数据 GET /filters(当前库可筛维度与取值,输出较长,节选前 900 字符)与 GET /filtered(GET 单值过滤 / POST 表单多值过滤):
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books/filters?presentation=series'
curl -s -u Admin:q1w2e3r4 'http://localhost:8138/api/v1/books/filtered?search=Volume'
[{"id":"sort","name":"Sort","has_and_mode":false,"single_mode":true,"values":["CREATED_AT","UPDATED_AT","TITLE","POPULARITY","YEAR","COUNTRY","LANGUAGE","PUBLISHER","SERIE","PARODY","VOLUMES_COUNT","CHAPTERS_COUNT","SCORE","LAST_READ"]},{"id":"type","name":"Content type","has_and_mode":false,"single_mode":true,"values":["UNKNOWN"]},{"id":"status","name":"Status","has_and_mode":false,"single_mode":true,"values":["ONGOING"]},{"id":"translation_status","name":"Translation status","has_and_mode":false,"single_mode":true,"values":["UNKNOWN"]},{"id":"plot_type","name":"Plot type","has_and_mode":false,"single_mode":true,"values":["UNKNOWN"]},{"id":"censorship","name":"Censorship","has_and_mode":false,"single_mode":true,"values":["UNKNOWN"]},{"id":"color","name":"Color","has_and_mode":false,"single_mode":true,"values":["UNKNOWN"]},{"id":"age_rating","name":"Age rating","has_and_mode":false,"sing
...(输出过长,节选至此)...
[{"id":"atsumeru-serieb71204461360651e64a359ad591269ac","title":"Volume 1","is_single":false,"folder":"/library/manga/AtsumeruTestSeries","cover":"atsumeru36fae69422e431620cadd1b555d248c9?t\u003d1793531877744","authors":["quzuo"],"genres":["Action"],"content_type":"UNKNOWN","is_mature":false,"is_adult":false,"pagesCount":3,"status":"ONGOING","translation_status":"UNKNOWN","plot_type":"UNKNOWN","censorship":"UNKNOWN","color":"UNKNOWN","rating":0,"created_at":1793531877744,"updated_at":1793531877744}]
聚合元分类 GET /metacategories(作者/流派等维度计数):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/metacategories
[{"id":"authors","name":"Authors","count":1},{"id":"censorship","name":"Censorship","count":1},{"id":"color","name":"Colors","count":1},{"id":"genres","name":"Genres","count":1}]
另有 DELETE /delete/{book_hash}(删除内容索引,源码取证;本篇测试库为演示数据,未实测删除以免破坏前文回显依赖)。
排障实录:
/{hash}/cover、/{hash}/download返回 200 空体 直觉写法GET /api/v1/books/{archive_hash}/cover与.../download不报 404,而是 HTTP 200 + Content-Length: 0,极易误判为"封面丢失/文件为空":curl -s -o /dev/null -w '%{http_code} size=%{size_download}\n' -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/atsumeru36fae69422e431620cadd1b555d248c9/cover实测回显
200 size=0。真因:这两个端点实际注册为前缀式/cover/{image_hash}、/download/{archive_hash}(源码@GetMapping),错误路径落入兜底静态资源处理返回空 200。改正确路径后 222 字节 PNG / 1384 字节 CBZ 正常返回(见上文实测)。
5.6 categories 域(/api/v1/books/categories)
除 GET "" 外全部 ADMIN(@PreAuthorize("hasRole('ADMIN')"))。普通用户越权实测:
curl -s -i -u root:q1w2e3r4 -X PUT 'http://localhost:8138/api/v1/books/categories/create?name=X' | head -1
HTTP/1.1 403
列表(注意可见性规则:源码 getCategoryList 对带 content_type 的分类要求"至少被 1 个内容引用"才返回):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/categories
[{"id":"atsumeru-category966faeabbc679ba4457e2d75e031d487","name":"Unsorted","content_type":"UNKNOWN","order":0}]
创建 PUT /create?name=(重名返回 409 "Category already exists"):
curl -s -i -u Admin:q1w2e3r4 -X PUT 'http://localhost:8138/api/v1/books/categories/create?name=D5Test'
HTTP/1.1 201
...(响应头同 5.1,省略)...
{"code":201,"message":"Category created"}
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/categories
[{"id":"atsumeru-category966faeabbc679ba4457e2d75e031d487","name":"Unsorted","content_type":"UNKNOWN","order":0},{"id":"atsumeru-category91fc5d43a0ea621bdac306045436e310","name":"D5Test","order":1}]
新分类 ID 形如 atsumeru-category<md5>。
把内容挂到分类 POST /set——本域最大的坑,见下方排障实录:请求体必须是 application/x-www-form-urlencoded,参数名=内容 ID,参数值=分类真实 ID(多个用逗号分隔)。
先复现 JSON 写法失败:
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X POST -d '{"atsumeru-serieb71204461360651e64a359ad591269ac":["atsumeru-category91fc5d43a0ea621bdac306045436e310"]}' http://localhost:8138/api/v1/books/categories/set
HTTP/1.1 500
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 01 Nov 2026 11:49:21 GMT
Connection: close
{"timestamp":"Nov 1, 2026, 11:49:21 AM","status":500,"error":"Internal Server Error","path":"/api/v1/books/categories/set"}
改表单编码成功:
curl -s -i -u Admin:q1w2e3r4 -X POST --data-urlencode 'atsumeru-serieb71204461360651e64a359ad591269ac=atsumeru-category91fc5d43a0ea621bdac306045436e310' http://localhost:8138/api/v1/books/categories/set
HTTP/1.1 200
...(响应头同 5.1,省略)...
{"code":200,"message":"Categories set for provided content list"}
排障实录:categories/set 用 JSON 请求体必 500 端点形参是
@RequestBody MultiValueMap<String, String>。发application/json时,Spring 的 Gson 转换器把对象解析成LinkedTreeMap,向MultiValueMap绑定时值类型不匹配,服务端日志抛IllegalArgumentException: argument type mismatch,客户端只见裸 500 错误信封(实测回显见上)。解法:用--data-urlencode '内容ID=分类ID'表单编码提交(成功回显 "Categories set for provided content list");清空分类挂值则参数值留空('内容ID=',本篇实测同样 200)。
改名 PATCH /edit?id=&name=、排序 POST /order(JSON 数组,元素为含 order 字段的分类对象——此端点反而是 JSON 请求体):
curl -s -i -u Admin:q1w2e3r4 -X PATCH 'http://localhost:8138/api/v1/books/categories/edit?id=atsumeru-category91fc5d43a0ea621bdac306045436e310&name=D5TestRenamed'
curl -s -i -u Admin:q1w2e3r4 -H 'Content-Type: application/json' -X POST -d '[{"id":"atsumeru-category966faeabbc679ba4457e2d75e031d487","name":"Unsorted","content_type":"UNKNOWN","order":1},{"id":"atsumeru-category91fc5d43a0ea621bdac306045436e310","name":"D5TestRenamed","order":0}]' http://localhost:8138/api/v1/books/categories/order
HTTP/1.1 200
...(响应头同 5.1,省略)...
{"code":200,"message":"Category edited"}
HTTP/1.1 200
...(响应头同 5.1,省略)...
{"code":200,"message":"Categories ordered"}
排序后列表(Unsorted 因内容已改挂 D5TestRenamed、自身引用数归零而被可见性规则隐藏):
curl -s -u Admin:q1w2e3r4 http://localhost:8138/api/v1/books/categories
[{"id":"atsumeru-category91fc5d43a0ea621bdac306045436e310","name":"D5TestRenamed","order":0}]
解除挂载 → 删除 → 重复删除收尾:
curl -s -i -u Admin:q1w2e3r4 -X POST --data-urlencode 'atsumeru-serieb71204461360651e64a359ad591269ac=' http://localhost:8138/api/v1/books/categories/set
curl -s -i -u Admin:q1w2e3r4 -X DELETE 'http://localhost:8138/api/v1/books/categories/delete?id=atsumeru-category91fc5d43a0ea621bdac306045436e310'
curl -s -i -u Admin:q1w2e3r4 -X DELETE 'http://localhost:8138/api/v1/books/categories/delete?id=atsumeru-category91fc5d43a0ea621bdac306045436e310' | tail -1
HTTP/1.1 200
...(响应头同 5.1,省略)...
{"code":200,"message":"Categories set for provided content list"}
HTTP/1.1 200
...(响应头同 5.1,省略)...
{"code":200,"message":"Category deleted"}
{"code":404,"message":"Category not exist"}
5.7 请求审计日志
所有 API 调用自动落 /app/logs/requests.log(INFO:[时间] [客户端IP@用户名] Requested 路径),是 1.1 唯一的调用审计面。本篇取证尾段实测回显:
tail -n 12 /opt/data/quzuo/tmp/atsumeru/logs/requests.log
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/set
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/set
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/edit?id=atsumeru-category91fc5d43a0ea621bdac306045436e310&name=D5TestRenamed
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/order
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories
INFO:[Sun Nov 01 11:49:21 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/set
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/delete?id=atsumeru-category91fc5d43a0ea621bdac306045436e310
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/books/categories/delete?id=atsumeru-category91fc5d43a0ea621bdac306045436e310
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/importer/scan
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/importer/rescan?fully=false
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/users/delete?user_id=3
INFO:[Sun Nov 01 11:49:22 UTC 2026] [10.89.57.1@Admin] Requested /api/v1/users/list
六、分页/过滤/排序通用约定
6.1 分页
page 从 1 起、limit 默认 30;响应是裸 JSON 数组,没有 total 字段——判断翻页结束只能"取到空数组即到底",或 all=true 一次取全量(大库慎用)。
6.2 排序
sort 默认 created_at、asc 默认 false(降序)。合法值以 /books/filters 返回的 sort.values 为准:CREATED_AT、UPDATED_AT、TITLE、POPULARITY、YEAR、COUNTRY、LANGUAGE、PUBLISHER、SERIE、PARODY、VOLUMES_COUNT、CHAPTERS_COUNT、SCORE、LAST_READ(本篇实测 sort=title&asc=true 正常返回)。传枚举外的值会得到 400/500 绑定错误,无容错。
6.3 过滤与缓存
- 单值过滤走
GET /filtered?status=...&genres=...;多值/AND 模式走POST /filtered+ 表单体(与 categories/set 同款 MultiValueMap 约定,用表单编码别用 JSON); books与filters响应带 Spring Cache(源码@Cacheable,key 含用户名+全部查询参数);importer/add|remove|scan|rescan与categories/set|order会@CacheEvict全量失效——改了文件但看不到变化,先触发一次 scan 再查;- 用户级数据过滤:
allowed_categories白名单与disallowed_genres/disallowed_tags黑名单在 SQL 层生效,同一端点不同账号返回集不同(见 5.2 权限实测与配置篇 5.2)。
七、常见问题
Q1:所有端点都 401,是不是要先登录换 token?
不是。1.1 是纯 HTTP Basic(无 /auth/login,见部署篇 4.3 排障实录),每个请求直接带 -u 用户名:密码 即可。
Q2:为什么找不到 Swagger? 官方镜像线停在 1.1,无 WebUI/Swagger;OpenAPI 属 2.x jar 发行版(A1 版本线说明)。端点图只能靠源码 + 实测,即本篇方法。
Q3:categories/set 为什么 JSON 提交必 500?
该端点形参是 MultiValueMap,只吃表单编码;JSON 会触发 Gson 类型绑定异常。解法与完整复现见 5.6 排障实录。
Q4:cover/download 返回 200 但文件是 0 字节?
路径写反了。正确为 /api/v1/books/cover/{image_hash}、/api/v1/books/download/{archive_hash}(前缀式),错误路径落兜底返回空 200。见 5.5 排障实录。
Q5:写操作(建用户/挂分类)HTTP 都是 200,怎么判断业务成败?
看响应信封的 code 字段(如 {"code":400,"message":"Password must be at least 6 characters long!"} 时 HTTP 仍是 200)。仅 categories/create 成功时 HTTP 才是 201。脚本建议同时校验 HTTP 与 code。
Q6:settings/update 少传几个键会怎样?
六键之外的缺失键按默认 false 整包覆盖并立即落盘 atsumeru.properties,务必先 GET /get 取全量再改后回传(5.3 实测流程)。
八、参考链接
- Atsumeru 源码(1.1 控制器取证):https://github.com/Atsumeru-xyz/Atsumeru (
src/main/java/xyz/atsumeru/web/controller/rest/下ServerApiController、UsersApiController、SettingsApiController、ImporterApiController、BooksApiController、CategoriesApiController,jsDelivr 镜像cdn.jsdelivr.net/gh/Atsumeru-xyz/Atsumeru@1.1/...取回,查询日期 2026-11-01) - 官方 Docker 镜像:https://hub.docker.com/r/atsumerudev/atsumeru (tags 实测见部署篇第二章)
- 站内姊妹篇:《Atsumeru 介绍与选型指南》、《Atsumeru Docker Compose 部署指南》、《Atsumeru 1.1 配置参数详解》