Sharry 1.17.0 API 使用详解

Sharry 1.17.0 REST API 实战:/api/v2 挂载前缀、Sharry-Auth 认证头、四大权限域 44 端点全景、分享全生命周期、tus 断点续传、别名收件与管理域,附九条排障实录。

SharrySharryREST API文件分享2026/11/01

Sharry 1.17.0 API 使用详解

目标:面向需要脚本化/自动化对接 Sharry 的读者,讲清 Sharry 1.17.0 REST API 的真实调用方式——服务地址与挂载前缀、认证头约定、四大权限域端点全景、分享全生命周期、tus 断点续传、别名收件与管理域,并把实测中踩到的坑(路径前缀、认证头名称、必填字段、单位毫秒等)沉淀为排障实录。全部请求与响应均在 Ubuntu 22.04 + Docker 部署环境实测通过。

一、环境信息

本文 API 实测在以下环境进行:

  • 操作系统:Ubuntu 22.04(x86_64)
  • Sharry 版本:1.17.0(官方镜像 eikek0/sharry:v1.17.0,构建时间 2026-10-05,/open/info/version 实查)
  • 部署形态:Docker Compose 双服务(Sharry + PostgreSQL 18.6),部署细节见 《Sharry 1.17.0 Docker Compose 部署指南》
  • 宿主机端口:8131(映射容器 9090)
  • API 挂载前缀:/api/v2(实测发现,官方 OpenAPI 文档中的路径不含此前缀,详见第十一章排障实录)
  • 演示账号:admin(fixed 模块内置管理员,密码 q1w2e3r4)
  • 客户端工具:curl 7.81.0

二、前置条件

  1. 一套可用的 Sharry 服务:本文所有示例基于 Docker Compose 部署的 1.17.0 实例;如尚未部署,先按本专栏部署篇完成安装。
  2. 一个可登录的账号:内置 admin,或通过开放注册自建(见 9.1 节)。
  3. 确认服务可达:
curl -s http://localhost:8131/api/v2/open/info/version
{"version":"1.17.0","builtAtMillis":1791184276879,"builtAtString":"2026-10-05 09:11:16.879+0200","gitCommit":"31154b4ebab6e0c623a9ccb7b54691d7b8d6eb80","gitVersion":"1.17.0"}
  1. 三个自定义请求头先认识(Sharry 与常见 Bearer Token 风格差异很大,全部实测确认):
请求头用途对应端点域
Sharry-Auth用户认证令牌(登录接口返回的 token 原文,不加 Bearer 前缀)/sec/*、/admin/*
Sharry-Alias别名页上传凭证(别名 ID)/alias/*
Sharry-Password分享密码(需 percent-encode)密码保护的公开分享

三、API 总览

3.1 服务地址与挂载前缀

REST API 全部挂在 /api/v2 前缀之下。官方 OpenAPI 3.0 规范文件中写的路径(如 /open/auth/login)是不含前缀的规范路径,实际调用要补上前缀:

实际请求地址 = http://<主机>:<端口>/api/v2 + <规范路径>

两个可用的文档入口(实测):

  • 在线交互文档(Swagger UI):http://<主机>:8131/api/doc——服务器内置 Swagger UI 5.20.0,可直接在浏览器里试调(注意其 "Try it out" 生成的请求同样带 /api/v2 前缀);
  • 静态规范文件:https://eikek.github.io/sharry/openapi/sharry-openapi.yml(openapi 3.0,license GPLv3,info.version 1.17.0)。

3.2 四大权限域与端点全景

官方把路由按权限分为四类域(OpenAPI 规范与实测归纳)。1.17.0 共 44 条路径、约 61 个操作,全景如下({id} 为分享 ID、{pid} 为发布 ID、{fid} 为文件 ID):

开放域 /open(无需认证,10 条路径)

方法路径用途
GET/open/info/version版本与构建信息
GET/open/info/appconfig前端配置(注册模式、大小上限等)
POST/open/auth/login账号密码登录换取 token
GET/open/auth/oauth/{id}OAuth2 提供方跳转
POST/open/auth/oauth/{id}/resumeOAuth2 回调续接
POST/open/auth/proxy反向代理头认证
POST/open/signup/register开放注册新账号
GET/open/share/{pid}匿名读取已发布分享详情
GET/open/share/{pid}/file/{fid}匿名下载单个文件
GET/open/share/{pid}/zip匿名下载整包 ZIP

认证域 /sec(需 Sharry-Auth 头,25 条路径)

方法路径用途
POST/sec/auth/session续期会话(换新 token)
POST/sec/auth/logout登出(见 11.6 陷阱)
GET, POST, DELETE/sec/settings/email当前账号邮箱查询/设置/删除
GET, POST/sec/settings/password当前账号改密
GET, POST/sec/alias别名列表 / 创建别名
GET, POST, DELETE/sec/alias/{id}别名详情 / 修改 / 删除
GET/sec/alias-member可加为别名成员的账号搜索
POST/sec/uploadmultipart 一步建分享并传文件
POST/sec/upload/new建空分享(JSON 元数据)
POST/sec/upload/{id}/files/add向已有分享追加文件
OPTIONS, POST/sec/upload/{id}/files/tustus 协议探测 / 创建上传会话
PATCH, HEAD/sec/upload/{id}/files/tus/{fid}tus 分片写入 / 进度查询
GET/sec/share/search搜索自己的分享(分页)
GET, DELETE/sec/share/{id}分享详情 / 删除分享
POST, DELETE/sec/share/{id}/name设置 / 清除分享名
POST/sec/share/{id}/validity修改有效期
POST/sec/share/{id}/description修改描述
POST/sec/share/{id}/maxviews修改最大查看次数
POST, DELETE/sec/share/{id}/password设置 / 移除密码
POST, DELETE/sec/share/{id}/publish发布 / 下线
GET, DELETE/sec/share/{id}/file/{fid}下载 / 删除单个文件
GET/sec/share/{id}/zip所有者打包下载
GET/sec/mail/template/share/{id}预览「分享就绪」邮件模板
GET/sec/mail/template/alias/{aid}预览别名页链接邮件模板
POST/sec/mail/send发送通知邮件

别名域 /alias(需 Sharry-Alias 头,6 条路径)

/alias/upload、/alias/upload/new、/alias/upload/{id}/files/add、/alias/upload/{id}/files/tus、/alias/upload/{id}/files/tus/{fid} 五条路径在规范中以 $ref 方式完全镜像同名 /sec/upload* 端点(方法一致),区别仅是认证方式从 Sharry-Auth 换成 Sharry-Alias;另有 POST /alias/mail/notify/{id} 供匿名上传者请求邮件通知。

管理域 /admin(需 Sharry-Auth 头且账号为管理员,3 条路径)

方法路径用途
POST/admin/signup/newinvite生成注册邀请码(invite 模式)
GET, POST/admin/account账号列表 / 创建账号
GET, POST, DELETE/admin/account/{accountId}账号详情 / 修改 / 删除

3.3 通用请求与响应约定

  • 请求体:一律 application/json(文件上传用 multipart/form-data);JSON 字段缺失或拼错返回 400,报文为 Internal Error: Invalid message body: Could not decode JSON: ...(原文照录,见 11.3);
  • 操作结果:多数写操作返回 BasicResult/IdResult 结构——{"success":布尔,"message":"...","id":"..."},id 仅在 success 为 true 时有效;
  • 时间字段:全部为 Unix 毫秒时间戳(如 created:1793502793912);
  • 时长字段:validity、maxValidity 等一律为毫秒整数(如 7 天 = 604800000),见 11.5;
  • 大小字段:字节整数(maxSize:1610612736 即 1.5 GiB)。

💡 技术拓展:/sec/share/{id} 返回里的 publishInfo.id 与路径参数 {pid} 是同一个东西——分享创建后的 ID({id})与发布后的公开 ID({pid})是两套标识。公开访问域只认 {pid},管理域只认 {id};reuseId 参数传 true 时重复发布会沿用原 {pid}(下线再上线链接不变),传 false 则换新链接。

四、认证与首次调用

4.1 登录获取令牌

POST /api/v2/open/auth/login,请求体字段为 account 与 password:

curl -s -X POST http://localhost:8131/api/v2/open/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"account":"admin","password":"q1w2e3r4"}'
{"id":"G3xsRFTpFqK-pDbvZ8HdWsP-cdR4sooakgi-RN2CRvjCaHq","user":"admin","admin":true,"success":true,"message":"Login successful","token":"1793502747122-RzN4c1JGVHBGcUstcERidlo4SGRXc1AtY2RSNHNvb2FrZ2ktUk4yQ1J2akNhSHEvYWRtaW4vdHJ1ZQ==-$2a$10$CWLjrfvTL9xEfxhv1pAE6.-ygfRuUZQBCNU/Vj2lQN32hWpFfc=","validMs":480000}

关键字段:token 是后续所有认证请求的凭证原文;validMs:480000 表示令牌 480 秒(8 分钟)后过期——对应配置 backend.auth.session-valid = "8 minutes";admin:true 标识管理员。

💡 技术拓展:token 是「时间戳-负载-签名」三段式,中段是 Base64:把上面 token 的中段解码得到 G3xsRFTpFqK-pDbvZ8HdWsP-cdR4sooakgi-RN2CRvjCaHq/admin/true——即「账号 ID/用户名/是否管理员」。它不是 JWT(无 header.payload.sig 三段 JSON 结构),而是服务端用 server-secret HMAC 签名的自定义格式,因此改密码不会使已签发 token 失效,只能等过期。

4.2 会话续期与登出

续期(拿旧 token 换新 token,重新计时 8 分钟):

curl -s -X POST http://localhost:8131/api/v2/sec/auth/session \
  -H "Sharry-Auth: $TOKEN"
{"id":"G3xsRFTpFqK-pDbvZ8HdWsP-cdR4sooakgi-RN2CRvjCaHq","user":"admin","admin":true,"success":true,"message":"Login successful","token":"1793502771807-RzN4c1JGVHBGcUstcERidlo4SGRXc1AtY2RSNHNvb2FrZ2ktUk4yQ1J2akNhSHEvYWRtaW4vdHJ1ZQ==-$2a$10$t7gW0.cDh4NF76EDXxRRcO-9cYvHuhXzrDewElA5/rsCWcXMiM=","validMs":480000}

登出:

curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:8131/api/v2/sec/auth/logout -H "Sharry-Auth: $TOKEN2"
200

脚本轮询场景的正确姿势:每收到 401 就重新登录,或在每次调用前用 /sec/auth/session 换新 token(Web 客户端按 auth-renewal = 4 minutes 周期自动续期)。

4.3 认证头实战:Bearer 为什么不行

Sharry 的认证头叫 Sharry-Auth,值就是 token 原文。若按 REST 惯例用 Authorization: Bearer,服务端会当未认证处理:

curl -s -X POST http://localhost:8131/api/v2/sec/auth/session \
  -H "Authorization: Bearer $TOKEN"
Authentication failed.

(HTTP 状态码 401,响应体原文如上。)换成正确的头即成功:

curl -s -X POST http://localhost:8131/api/v2/sec/auth/session \
  -H "Sharry-Auth: $TOKEN"
{"id":"G3xsRFTpFqK-pDbvZ8HdWsP-cdR4sooakgi-RN2CRvjCaHq","user":"admin","admin":true,"success":true,"message":"Login successful","token":"...","validMs":480000}

4.4 服务信息端点

GET /api/v2/open/info/appconfig 返回服务器生效配置的只读视图(写脚本前先读它,能省去硬编码):

curl -s http://localhost:8131/api/v2/open/info/appconfig | head -c 800
{"appName":"Sharry","baseUrl":"","logoUrl":"/app/assets/sharry-webapp/1.17.0/img/logo.png","logoUrlDark":"/app/assets/sharry-webapp/1.17.0/img/logo-dark.png","iconUrl":"/app/assets/sharry-webapp/1.17.0/img/icon.svg","iconUrlDark":"/app/assets/sharry-webapp/1.17.0/img/icon-dark.svg","footerText":"","footerVisible":true,"signupMode":"open","oauthConfig":[],"chunkSize":10485760,"retryDelays":[0,3000,6000,12000,24000,48000],"maxValidity":2592000000,"maxSize":1610612736,"mailEnabled":false,"welcomeMessage":"","defaultLanguage":"gb","authRefreshTime":240000,"initialPage":"home","oauthOnly":false,"aliasMemberEnabled":true,"defaultValidity":604800000,"initialTheme":"light","oauthAutoRedirect":true,"proxyAuthEnabled":false,"proxyOnly":false,"hideLoginForm":false,"zipMaxSize":1610612736,"defaultTime

(超过 800 字节部分截断,完整响应含 maxPageSize:100、defaultTimezone 等字段。)脚本常取的字段:signupMode(注册模式)、maxSize(单分享大小上限 1.5 GiB)、maxValidity(有效期上限 30 天 = 2592000000 毫秒)、chunkSize(tus 分块建议 10 MiB)。

五、分享管理域(/sec/share 与 /sec/upload)

5.1 创建分享与必填陷阱

POST /api/v2/sec/upload/new 创建一个空分享,请求体是 ShareProperties 结构。validity 与 maxViews 是必填字段,只传其一会被拒绝(实测):

curl -s -X POST http://localhost:8131/api/v2/sec/upload/new \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"validity": 604800000}'
Internal Error: Invalid message body: Could not decode JSON: {
  "validity" : 604800000
}

补全 maxViews 后成功:

curl -s -X POST http://localhost:8131/api/v2/sec/upload/new \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"api-demo","validity":604800000,"maxViews":10}'
{"success":true,"message":"Share created.","id":"AnKj8P4WYPJ-bsxwxmYQMAW-vpv7HTknBpr-P4WgUg7k71c"}

ShareProperties 全部字段:name(可选,分享名)、validity(必填,毫秒)、maxViews(必填,最大查看次数)、description(可选,Markdown 文本)、password(可选,创建时直接带密码)。

5.2 multipart 上传文件

向已有分享追加文件用 POST /api/v2/sec/upload/{id}/files/add,multipart 部件名任意(规范声明全部部件按文件处理):

echo 'sharry api test file content' > api-note.txt
curl -s -X POST "http://localhost:8131/api/v2/sec/upload/$SID/files/add" \
  -H "Sharry-Auth: $TOKEN" -F "file=@api-note.txt"
{"success":true,"message":"File(s) added"}

另有一步到位的 POST /api/v2/sec/upload(multipart):除名为 meta 的部件按 JSON 元数据解析外,其余部件全部作为文件入库,适合「建分享 + 传文件」一次完成。

5.3 发布、下线与删除(完整生命周期)

发布(reuseId:true 表示下线后重新发布沿用同一公开链接):

curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/publish" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"reuseId": true}'
{"success":true,"message":"Share published."}

查看详情(注意 publishInfo.id 就是公开访问用的 {pid}):

curl -s "http://localhost:8131/api/v2/sec/share/$SID" -H "Sharry-Auth: $TOKEN"
{"id":"AnKj8P4WYPJ-bsxwxmYQMAW-vpv7HTknBpr-P4WgUg7k71c","name":"api-demo","aliasId":null,"aliasName":null,"validity":604800000,"maxViews":10,"password":false,"descriptionRaw":null,"description":null,"created":1793502793912,"publishInfo":{"id":"Awzs1vkjsm2-rqiykNG4dGm-CmTorBc4qG1-D5UsynFCY16","enabled":true,"views":0,"publishDate":1793502800841,"publishUntil":1794107600841,"expired":false,"lastAccess":null},"files":[{"id":"8BmFHJPU9Tj-JBRoqjy5oEB-XTXVyATSVp6-Y4skL3ZCRP","filename":"api-note.txt","size":29,"mimetype":"text/plain","checksum":"","storedSize":29,"created":1793502800812}]}

下线(DELETE 方法,不是 POST):

curl -s -X DELETE "http://localhost:8131/api/v2/sec/share/$SID/publish" \
  -H "Sharry-Auth: $TOKEN"
{"success":true,"message":"Share unpublished."}

下线后详情里 publishInfo.enabled 变为 false,views 保留(实测 {"id":"Awzs1vkjsm2-...","enabled":false,"views":2,...}),公开链接立即 404,重新 publish(reuseId:true)可复活原链接。

删除分享(不可恢复):

curl -s -X DELETE "http://localhost:8131/api/v2/sec/share/$SID" -H "Sharry-Auth: $TOKEN"
{"success":true,"message":"Share deleted."}

删除后再查详情返回 404:

curl -s -o /dev/null -w '%{http_code}\n' \
  "http://localhost:8131/api/v2/sec/share/$SID" -H "Sharry-Auth: $TOKEN"
404

5.4 查询与搜索

GET /api/v2/sec/share/search?q=<关键词>&p=<页码>:

curl -s "http://localhost:8131/api/v2/sec/share/search?q=api&p=1" -H "Sharry-Auth: $TOKEN"
{"items":[{"id":"AnKj8P4WYPJ-bsxwxmYQMAW-vpv7HTknBpr-P4WgUg7k71c","name":"api-demo","aliasInfo":null,"validity":604800000,"maxViews":10,"password":false,"created":1793502793912,"files":1,"size":29,"published":true}]}

列表项是精简版分享元数据(files 为文件数、size 为总字节、published 为发布状态)。分页参数见第十章。

5.5 属性更新(名称/有效期/次数/描述)

四个属性端点都是 POST(不是 PUT,PUT 会 404,见 11.4),请求体统一为 {"value": ...}:

curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/name" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":"api-demo-renamed"}'
{"success":true,"message":"Name updated."}
curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/maxviews" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":25}'
{"success":true,"message":"Max. views updated."}
curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/validity" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":1209600000}'
{"success":true,"message":"Validity updated."}
curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/description" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":"# 更新后的描述\n\n支持 **Markdown**。"}'
{"success":true,"message":"Description updated."}

其中 name 与 password 另有 DELETE 方法用于清除(validity/maxviews/description 没有清除语义,只能改值)。

5.6 分享密码的设置与移除

设置密码(POST /api/v2/sec/share/{id}/password,体为 SingleString):

curl -s -X POST "http://localhost:8131/api/v2/sec/share/$SID/password" \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"value":"sharepass123"}'
{"success":true,"message":"Password updated."}

移除密码用 DELETE /api/v2/sec/share/{id}/password。注意创建分享时若在 ShareProperties 里传 "password":""(空字符串),会把分享标记为「有密码」但密码内容为空,匿名访问会一直要密码——不想设密码就省略该字段。

5.7 ZIP 打包下载

所有者视角 GET /api/v2/sec/share/{id}/zip,或公开视角(见 6.2 节后实测)。实测公开视角带密码头下载:

curl -s -o api.zip -w '%{http_code} %{size_download}\n' \
  "http://localhost:8131/api/v2/open/share/$PID/zip" \
  -H 'Sharry-Password: sharepass123'
200 843
file api.zip
api.zip: Zip archive data, at least v2.0 to extract, compression method=deflate

ZIP 打包走流式压缩,受 zip-max-size 配置限制(实测实例为 1.5 GiB)。

六、公开访问域(/open/share)

6.1 匿名访问详情与文件

已发布分享无需任何认证即可读取(密码分享除外):

curl -s "http://localhost:8131/api/v2/open/share/$PID"
{"id":"AnKj8P4WYPJ-bsxwxmYQMAW-vpv7HTknBpr-P4WgUg7k71c","name":"api-demo","aliasId":null,"aliasName":null,"validity":604800000,"maxViews":10,"password":false,"descriptionRaw":null,"description":null,"created":1793502793912,"publishInfo":{"id":"Awzs1vkjsm2-rqiykNG4dGm-CmTorBc4qG1-D5UsynFCY16","enabled":true,"views":0,"publishDate":1793502800841,"publishUntil":1794107600841,"expired":false,"lastAccess":null},"files":[{"id":"8BmFHJPU9Tj-JBRoqjy5oEB-XTXVyATSVp6-Y4skL3ZCRP","filename":"api-note.txt","size":29,"mimetype":"text/plain","checksum":"","storedSize":29,"created":1793502800812}]}

匿名下载单个文件(内容直接回显):

curl -s "http://localhost:8131/api/v2/open/share/$PID/file/8BmFHJPU9Tj-JBRoqjy5oEB-XTXVyATSVp6-Y4skL3ZCRP"
sharry api test file content

6.2 密码三级控制实测

对设置了密码的分享(上文 sharepass123),匿名访问实测三级响应:

curl -s -o /dev/null -w '%{http_code}\n' "http://localhost:8131/api/v2/open/share/$PID"
401
curl -s -o /dev/null -w '%{http_code}\n' \
  "http://localhost:8131/api/v2/open/share/$PID" -H 'Sharry-Password: wrongpass'
403
curl -s -o /dev/null -w '%{http_code}\n' \
  "http://localhost:8131/api/v2/open/share/$PID" -H 'Sharry-Password: sharepass123'
200

即:不带密码头 401、密码错误 403、密码正确 200。Sharry-Password 头的值需 percent-encode(含中文/空格/特殊字符的密码先编码再放头里)。同一密码头同样适用于 /open/share/{pid}/file/{fid} 与 /open/share/{pid}/zip。

七、断点续传域(tus 协议)

Sharry 的 Web 界面上传默认走 tus 协议(开放断点续传标准),脚本同样可以使用。

7.1 探测与创建上传会话

先 OPTIONS 探测(可选):OPTIONS /api/v2/sec/upload/{id}/files/tus 返回 Tus-Version: 1.0.0 与 Tus-Extension 支持列表。创建会话用 POST,带 Upload-Length(文件总字节数)与 Upload-Metadata(自定义元数据,键值对用 Base64 编码、空格分隔):

curl -s -i -X POST "http://localhost:8131/api/v2/sec/upload/$SID/files/tus" \
  -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' \
  -H 'Upload-Length: 512' \
  -H 'Upload-Metadata: filename YXBpLWJpZy5iaW4=' | head -8
HTTP/1.1 201 Created
Date: Sun, 01 Nov 2026 03:14:32 GMT
Connection: keep-alive
Content-Length: 0
Tus-Resumable: 1.0.0
Location: http://localhost:9090/api/v2/sec/upload/AnKj8P4WYPJ-bsxwxmYQMAW-vpv7HTknBpr-P4WgUg7k71c/files/tus/HBbGLq4WpUY-46aNBZ1uViQ-equLaAj5mTF-Afyn3UxY8Ci

Location 指向新文件资源(末段即 {fid})。注意:Location 的主机端口来自服务器 base-url 配置(本实例为 http://localhost:9090),从外部调用时应只取其路径部分拼接自己的可达地址(见 11.7)。

7.2 HEAD 查询进度

curl -s -I -X HEAD "$TUS" -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' | grep -Ei 'HTTP|Upload-Offset|Tus'
HTTP/1.1 200 OK
Tus-Resumable: 1.0.0
Upload-Offset: 0
Tus-Max-Size: 1610612736

Upload-Offset 是已接收字节数(断点续传的续传点),Tus-Max-Size 即实例的 max-size(1610612736 字节 = 1.5 GiB)。

7.3 PATCH 分片写入与断点续传

先传前 29 字节:

curl -s -i -X PATCH "$TUS" \
  -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' \
  -H 'Upload-Offset: 0' -H 'Content-Type: application/offset+octet-stream' \
  --data-binary @part1.bin | grep -Ei 'HTTP|Upload-Offset'
HTTP/1.1 204 No Content
Upload-Offset: 29

再次 HEAD 确认进度停在 29:

curl -s -I -X HEAD "$TUS" -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' | grep -Ei 'Upload-Offset'
Upload-Offset: 29

模拟断点恢复:以 Upload-Offset: 29 续传剩余 483 字节:

curl -s -i -X PATCH "$TUS" \
  -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' \
  -H 'Upload-Offset: 29' -H 'Content-Type: application/offset+octet-stream' \
  --data-binary @part2.bin | grep -Ei 'HTTP|Upload-Offset'
HTTP/1.1 204 No Content
Upload-Offset: 512

Upload-Offset 到达 Upload-Length 即传输完成,文件自动落入分享。若客户端传的 Upload-Offset 与服务器进度不符,tus 规范要求返回 409,客户端应先 HEAD 校准。

7.4 容量上限(413)

创建会话时 Upload-Length 超过 Tus-Max-Size 直接拒绝(实测):

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  "http://localhost:8131/api/v2/sec/upload/$SID/files/tus" \
  -H "Sharry-Auth: $TOKEN" -H 'Tus-Resumable: 1.0.0' \
  -H 'Upload-Length: 2147483648'
413

⚠️ 注意(实测差异):tus 路径在会话创建时就强制 max-size;而 multipart 的 /files/add 端点不做预检——超限文件在 multipart 路径下可能以非 413 的方式失败或落库异常。脚本化大文件传输务必走 tus 路径,让 413 在第一时间暴露。

八、别名收件域(/alias)

别名(alias)是 Sharry 的「对外收件入口」:把别名 ID 给对方,对方无需账号即可向你上传文件。

8.1 创建别名

POST /api/v2/sec/alias,AliasChange 结构四个必填字段:name、validity(经此别名上传的分享默认有效期,毫秒)、enabled、members(成员账号 ID 数组,可为空):

curl -s -X POST http://localhost:8131/api/v2/sec/alias \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"apialias","validity":604800000,"enabled":true,"members":[]}'
{"success":true,"message":"Alias successfully created.","id":"9YmUsu5agsy-YF9S3injsrm-fvkRaSkPFSX-vhFCwPkzTHw"}

只传 name 会报 decode 错误(实测回显同 11.3 模式)。别名列表:

curl -s http://localhost:8131/api/v2/sec/alias -H "Sharry-Auth: $TOKEN"
{"items":[{"id":"FY31msiqnpa-WaFz96zhNT2-tVY1vLh6BMy-jnw7cX8h5qX","name":"客户投递入口","validity":604800000,"owner":"admin","enabled":true,"members":{"items":[]},"created":1793501785272}]}

(返回的是调用前已有别名;新建别名同结构追加在 items 中。)

8.2 别名收件全链路

以 Sharry-Alias 头替代 Sharry-Auth,即可在 /alias 域完成「建分享 → 传文件」:

curl -s -X POST http://localhost:8131/api/v2/alias/upload/new \
  -H "Sharry-Alias: $AID" -H 'Content-Type: application/json' \
  -d '{"name":"via-alias","validity":604800000,"maxViews":5}'
{"success":true,"message":"Share created.","id":"xqGXHhxLX1H-cMgYFuHqEc4-ytd8WjWLdhU-1KQphbFsTX"}
curl -s -X POST "http://localhost:8131/api/v2/alias/upload/$ASID/files/add" \
  -H "Sharry-Alias: $AID" -F "file=@demo-note.txt"
{"success":true,"message":"File(s) added"}

分享归属别名所有者(admin),所有者搜索即可看到,aliasInfo 标注来源别名:

curl -s "http://localhost:8131/api/v2/sec/share/search?q=via-alias&p=1" -H "Sharry-Auth: $TOKEN"
{"items":[{"id":"xqGXHhxLX1H-cMgYFuHqEc4-ytd8WjWLdhU-1KQphbFsTX","name":"via-alias","aliasInfo":{"id":"9YmUsu5agsy-YF9S3injsrm-fvkRaSkPFSX-vhFCwPkzTHw","name":"apialias"},"validity":604800000,"maxViews":5,"password":false,"created":1793502928966,"files":1,"size":41,"published":null}]}

之后所有者按 5.3 节正常发布即可把匿名投递的文件转成对外分享。匿名上传者还可调 POST /api/v2/alias/mail/notify/{id} 请求邮件通知(需服务器已配置 SMTP,本实例 mailEnabled:false 时该链路不生效)。

九、账号与管理域

9.1 注册与邀请

开放注册(signupMode:"open" 时可用),请求体字段是 login 与 password(不是 name/account,实测确认):

curl -s -X POST http://localhost:8131/api/v2/open/signup/register \
  -H 'Content-Type: application/json' \
  -d '{"login":"apitester","password":"q1w2e3r4"}'
{"success":true,"message":"Signup successful"}

invite 模式下管理员生成邀请码(POST /api/v2/admin/signup/newinvite,体为 {"password":"<配置文件中的邀请口令>"})。本实例为 open 模式,实测返回:

curl -s -X POST http://localhost:8131/api/v2/admin/signup/newinvite \
  -H "Sharry-Auth: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"password":"q1w2e3r4"}'
{"success":false,"message":"Signing up is not enabled for invitations.","key":null}

(不传 body 会得到 Internal Error: Malformed message body: Invalid JSON: empty body——空体也算 decode 失败。)

9.2 管理员账号管理

GET /api/v2/admin/account?q=<关键词> 检索账号(支持分页):

curl -s "http://localhost:8131/api/v2/admin/account?q=apitester" -H "Sharry-Auth: $TOKEN"
{"items":[{"id":"AaQ3tPUr73Y-AdaoWx5ramf-Dn6iXnDPdnR-mbDcu21cN6H","login":"apitester","source":"intern","state":"Active","admin":false,"email":null,"loginCount":0,"shares":0,"lastLogin":null,"created":1793502947917}]}

source:"intern" 表示 internal 模块创建(相对于 fixed/oauth 等外部来源)。配套操作:POST /admin/account 建号、POST /admin/account/{accountId} 改状态(Active/Disabled)与权限、DELETE /admin/account/{accountId} 删号。非管理员 token 调用返回 403。

9.3 邮件模板与通知

即便 SMTP 未启用,也可预览模板渲染结果(占位符已替换,实测):

curl -s "http://localhost:8131/api/v2/sec/mail/template/share/$SID" -H "Sharry-Auth: $TOKEN"
{"subject":"Download ready.","body":"Hello,\n\nthere are some files for you to download. Visit this link:\n\nhttp://localhost:9090/app/open/Awzs1vkjsm2-rqiykNG4dGm-CmTorBc4qG1-D5UsynFCY16\n\nThe required password will be sent by other means.\n\nGreetings,\nadmin via Sharry\n"}

邮件正文里的链接同样由 base-url 拼出(本实例为 localhost:9090,生产环境必须把 base-url 配成外部真实地址,详见配置篇)。POST /api/v2/sec/mail/send 按 recipients 数组实际发送,mailEnabled:false 时不可用。

十、分页、过滤与通用约定

  • 分页参数:列表类端点统一支持 page(页码,从 1 起)与 pageSize(页大小)查询参数(如 /sec/share/search、/admin/account);服务器硬上限 max-page-size = 100(配置项,超出按 100 截断);
  • 过滤参数:q 为关键词过滤(分享按名称/描述匹配、账号按登录名匹配、别名按名称匹配);
  • 响应结构:列表统一为 {"items":[...]};
  • 时间戳:created/publishDate/lastLogin 等全部为 Unix 毫秒;
  • 时长:validity/maxValidity/session-valid 相关回显一律毫秒整数;
  • ID 体系:账号 ID(account/{accountId})、分享 ID({id})、发布 ID({pid})、文件 ID({fid})、别名 ID(Sharry-Alias 头的值)五套相互独立,均由服务端生成 43 字符左右的随机串,不可自定义;
  • 编码:Sharry-Password 头需 percent-encode;Upload-Metadata 的值需 Base64;其余 JSON 体一律 UTF-8(中文可直接写)。

十一、常见问题(排障实录)

以下全部为本机实测复现的问题与结论。

11.1 调官方文档路径返回 404 "Not found"

现象:按 OpenAPI 规范路径直调,返回 HTTP 404 与纯文本 Not found:

curl -si -X POST http://localhost:8131/open/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"account":"admin","password":"q1w2e3r4"}' | head -3
HTTP/1.1 404 Not Found
Date: Sun, 01 Nov 2026 03:12:06 GMT
Connection: keep-alive

原因:1.x 的 REST 服务实际挂载在 /api/v2 前缀下(容器日志可见成功请求形如 GET /api/v2/open/info/version),规范文件与 Swagger 页面展示的路径都不含该前缀。解决:所有规范路径前补 /api/v2。

11.2 用 Authorization: Bearer 返回 401 Authentication failed

现象与解决:见 4.3 节实测。认证头名为 Sharry-Auth,值直接放 token,不加 Bearer 前缀;同理别名上传用 Sharry-Alias、密码访问用 Sharry-Password。

11.3 请求体报 Invalid message body: Could not decode JSON

现象(原文照录,缺 maxViews 时):

Internal Error: Invalid message body: Could not decode JSON: {
  "validity" : 604800000
}

原因:Circe 解码要求必填字段齐全。高频必填清单:建分享 ShareProperties 需 validity+maxViews;建别名 AliasChange 需 name+validity+enabled+members;发布 {"reuseId":布尔};注册 {"login","password"};邀请码 {"password":...};属性更新统一 {"value":...}。解决:对照本文各节请求体逐字段补全;空 body 也会报 Malformed message body: Invalid JSON: empty body。

11.4 属性更新用 PUT 返回 404

现象:PUT /api/v2/sec/share/{id}/name 等 → Not found。原因:这些端点只注册了 POST(清除用 DELETE),没有 PUT 路由。解决:一律改 POST。

11.5 validity 传「7 days」或秒数导致行为异常

现象:ShareProperties.validity 是 integer format duration——传字符串直接 decode 错;传秒数(如 604800)会被解释为 604800 毫秒(约 10 分钟),实测创建后详情回显 validity:604800。解决:统一传毫秒,7 天 = 604800000,且不得超过 appconfig 里的 maxValidity(本实例 2592000000)。

11.6 登出后 token 仍然有效

现象:POST /sec/auth/logout 返回 200(空响应体),但同一 token 再调 /sec/auth/session 依旧 200。原因:token 是无状态签名串,登出只对 Web 客户端语义有效(前端丢弃),服务端没有黑名单。解决:脚本侧把「登出」视为清理动作而非权限回收;确需权限回收时改 server-secret 重启(全部 token 立即失效)或禁用账号。

11.7 tus 的 Location 指向 localhost:9090 不可达

现象:创建 tus 会话返回 Location: http://localhost:9090/api/v2/...,外部机器直接 PUT/PATCH 该地址失败。原因:Location 由 base-url 配置拼接(本实例容器内默认 http://localhost:9090)。解决:外部调用取 Location 的路径部分(/api/v2/sec/upload/.../tus/{fid})拼自己的可达地址;根治则把 base-url 配成外部真实地址(Docker Compose 部署篇已演示 base-url = "http://localhost:8131")。

11.8 multipart 传大文件不报 413

现象:/files/add 传超过 max-size 的文件没有 413;tus 路径创建会话则立刻 413。结论:容量预检只在 tus 路径实现。解决:脚本大文件传输一律走 tus(7.4 节),并以 Tus-Max-Size 响应头为上限依据。

11.9 表名与数据库直查的坑

现象:PostgreSQL 直查 pg_dump -t account 报 no matching tables。原因:Sharry 数据库表名带尾下划线(account_ 等 10 张表)。解决:先 \dt 确认表名再操作;日常备份用 pg_dump 整库导出(部署篇持久化章有完整命令)。

十二、参考链接

Sharry 1.17.0 API 使用详解 - Quzuo