Files
SKYMirror 7a9015aad0
CI 测试 / go vet + go test (push) Failing after 5s
26.9:直链下载 + 过期回收 + SHA512 去重 + 防盗链 + 媒体预览 + 文件夹上传提示
- 对象存储直链:S3 引擎 302 到限时预签名 URL(有效期钳位分享剩余时效),失败自动回落代理
- 过期回收:janitor 定时扫描 + 取件惰性回收 + 管理端手动触发(POST /admin/recycle/run),
  retention_days 最长存储时长;删除走引用计数(去重对象安全)
- SHA512 内容去重:三条上传链路落库后计算哈希,命中即复用旧对象并删除本次副本
- 下载防盗链:Referer 白名单(同源/空 Referer/通配域名放行),挂 /share/download
- 取件页图片/音频内联预览(下载地址直连,加载失败回退下载按钮)
- 文件夹上传:拖拽目录明确提示"建议压缩后上传"(webkitGetAsEntry 探测)
- 管理端设置卡「回收与下载安全」8 个新配置键(KVSchema + configKeys + UI + i18n)
- 前端产物重建并同步 server/web/dist 与 web-embed
- 文档:10-config 配置表、03-file-share 直链/防盗链/文件夹章节、07-admin 回收端点、openapi
2026-09-08 04:54:05 +08:00

18 KiB
Raw Permalink Blame History

管理后台 API

管理端接口:除 POST /admin/login 与初始化向导 /setup 外,一律要求 Authorization: Bearer <token>(见《认证与限流》),无效令牌 401。

初始化向导:GET /setup

未初始化时返回 HTML 配置页(站点名称、管理员密码、上传/限流/保存策略); 已初始化时 303 重定向到 /

curl -i http://localhost:8466/setup

初始化提交:POST /setup

表单(浏览器向导)或 JSON 均可;成功后写入库配置 KV 并生成密码哈希与 jwt_secret。 表单提交返回成功 HTML 页;JSON 提交返回 JSON。

主要字段

字段 必需 默认 说明
admin_password - 管理员密码(≥8 位)
confirm_password - 确认密码(须一致)
site_name 文件快传 站点名称
upload_size_value / upload_size_unit 10 / MB 单文件大小限制(单位 KB/MB/GB)
save_time_value / save_time_unit 0 / day 最长保存秒数(0=不限)
expireStyle day,hour,minute,forever,count 过期方式(可多值/逗号分隔)
code_generate_type secret 取件码类型 number/secret
errorCount / errorMinute 10 / 1 取件错误限流
loginCount / loginMinute 5 / 15 登录失败限流
uploadCount / uploadMinute 10 / 1 上传限流
allowed_file_types * 逗号分隔白名单
openUpload / enableChunk 1 / 0 游客上传 / 分片开关(1/true/on/yes
curl -s -X POST http://localhost:8466/setup \
  -H 'Content-Type: application/json' \
  -d '{"admin_password":"admin12345","confirm_password":"admin12345","site_name":"我的文件柜","upload_size_value":10,"upload_size_unit":"MB"}'
{ "code": 200, "msg": "ok", "data": { "ok": true, "admin": "/#/admin" } }

错误响应(400,HTML 表单时内嵌错误提示):

{ "code": 400, "msg": "管理员密码至少 8 位" }

登录:POST /admin/login

curl -s -X POST http://localhost:8466/admin/login \
  -H 'Content-Type: application/json' \
  -d '{"password":"admin12345"}'

成功响应200):

{
  "code": 200, "msg": "ok",
  "data": {
    "id": "admin", "username": "admin",
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "token_type": "Bearer",
    "expires_at": 1750000000,
    "expires_in": 604800
  }
}

错误响应

{ "code": 401, "msg": "密码错误" }
{ "code": 423, "msg": "请求次数过多,请稍后再试" }

校验会话:GET /admin/verify

curl -s http://localhost:8466/admin/verify -H "Authorization: Bearer $TOKEN"
{ "code": 200, "msg": "ok", "data": { "id": "admin", "username": "admin", "token": "eyJ…", "token_type": "Bearer", "expires_at": 1750000000 } }

登出:POST /admin/logout

无状态 JWT,服务端仅返回确认(客户端应丢弃令牌):

{ "code": 200, "msg": "ok", "data": { "ok": true } }

仪表盘:GET /admin/dashboard

curl -s http://localhost:8466/admin/dashboard -H "Authorization: Bearer $TOKEN"
{
  "code": 200, "msg": "ok",
  "data": {
    "totalFiles": 42,
    "storageUsed": "123456789",
    "sysUptime": 1750000000000,
    "yesterdayCount": 5, "yesterdaySize": "1048576",
    "todayCount": 12, "todaySize": "5242880",
    "activeCount": 40, "expiredCount": 2,
    "textCount": 10, "fileCount": 32, "chunkedCount": 3,
    "usedCount": 156,
    "storageBackend": "local",
    "uploadSizeLimit": 10485760,
    "openUpload": 1, "enableChunk": 1,
    "maxSaveSeconds": 0,
    "topSuffixes": [ { "suffix": ".pdf", "count": 12 }, { "suffix": "Text", "count": 10 } ],
    "recentFiles": [ { "id": 42, "code": "K3P9W", "name": "report.pdf", "size": 1048576, "created_at": "2025-06-01T12:00:00+08:00", "expired_at": "2025-06-08T12:00:00+08:00", "is_expired": false, "expired_count": -1, "used_count": 3, "is_text": false, "is_chunked": false, "is_permanent": false, "has_download_limit": false, "remaining_downloads": null, "file_hash": null, "prefix": "report", "suffix": ".pdf", "text": false, "createdAt": "2025-06-01T12:00:00+08:00", "expiredAt": "2025-06-08T12:00:00+08:00", "isExpired": false, "expiredCount": -1, "usedCount": 3, "isText": false, "isChunked": false, "isPermanent": false, "hasDownloadLimit": false, "remainingDownloads": null, "fileHash": null } ],
    "recentActivities": []
  }
}

storageUsed/todaySize/yesterdaySize 为字符串字节数;sysUptime 为服务启动时刻的 Unix 毫秒。

文件列表:GET /admin/file/list

参数

参数 默认 说明
page / size 1 / 10 分页(size 1~100
keyword - 模糊匹配取件码/文件名/哈希/文本内容
status - active / expired
type - text / file / chunked
sortBy created_at created_at/expired_at/name/size/used_count/code
sortOrder desc asc / desc
curl -s "http://localhost:8466/admin/file/list?page=1&size=10&status=active&sortBy=size&sortOrder=desc" \
  -H "Authorization: Bearer $TOKEN"
{
  "code": 200, "msg": "ok",
  "data": {
    "page": 1, "size": 10, "total": 40,
    "summary": { "totalFiles": 42, "activeCount": 40, "expiredCount": 2, "textCount": 10, "fileCount": 32, "chunkedCount": 3, "storageUsed": 123456789, "usedCount": 156 },
    "data": [
      { "id": 42, "code": "K3P9W", "name": "report.pdf", "prefix": "report", "suffix": ".pdf", "size": 1048576, "is_text": false, "is_chunked": false, "is_expired": false, "expired_at": "2025-06-08T12:00:00+08:00", "expired_count": -1, "used_count": 3, "created_at": "2025-06-01T12:00:00+08:00", "has_download_limit": false, "is_permanent": false, "remaining_downloads": null, "file_hash": null, "text": false, "isText": false, "isChunked": false, "isExpired": false, "expiredAt": "2025-06-08T12:00:00+08:00", "expiredCount": -1, "usedCount": 3, "createdAt": "2025-06-01T12:00:00+08:00", "hasDownloadLimit": false, "isPermanent": false, "remainingDownloads": null, "fileHash": null }
    ]
  }
}

文件详情:GET /admin/file/detail

GET ?id=42POST {"id":42}。返回列表条目字段;文本分享额外含 content(全文)。

{ "code": 200, "msg": "ok", "data": { "id": 42, "code": "K3P9W", "name": "report.pdf", "size": 1048576, "is_text": false, "expired_at": "2025-06-08T12:00:00+08:00", "expired_count": -1, "used_count": 3, "created_at": "2025-06-01T12:00:00+08:00", "file_hash": null } }
{ "code": 404, "msg": "文件不存在" }

更新文件:PATCH /admin/file/update

更新取件码/文件名(前后缀)/过期策略。PATCH 为主名,POST 为兼容别名

请求体

字段 类型 说明
id int 必需
code string 新取件码(冲突 400「code已存在」)
prefix / suffix string 文件名前后缀
expired_at string 过期时间(ISO 8601,如 2025-07-01T00:00:00+08:00
expired_count int 取件次数上限(-1 按时间/永久)
curl -s -X PATCH http://localhost:8466/admin/file/update \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"id":42,"expired_at":"2025-07-01T00:00:00+08:00","expired_count":10}'
{ "code": 200, "msg": "ok", "data": "更新成功" }
{ "code": 400, "msg": "code已存在" }

删除文件:DELETE /admin/file/delete

删除分享记录并连带删除存储文件(文本分享无存储文件)。DELETEPOST,请求体 {"id":42}

curl -s -X DELETE http://localhost:8466/admin/file/delete \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"id":42}'
{ "code": 200, "msg": "ok", "data": null }
{ "code": 400, "msg": "请选择要删除的文件" }

批量删除:POST /admin/file/batch-delete

POSTDELETE,请求体 {"ids":[41,42,43]}。返回逐条统计:

{
  "code": 200, "msg": "ok",
  "data": {
    "requestedCount": 3, "deletedCount": 2, "missingCount": 1, "failedCount": 0,
    "deleted": [41, 42], "missing": [43], "failed": [],
    "requested_count": 3, "deleted_count": 2, "missing_count": 1, "failed_count": 0
  }
}

批量更新:PATCH /admin/file/batch-update

PATCHPOST。请求体:ids[] 必需;expired_atISO 8601/expired_count 二选一; clearExpiredAt: true(或 clear_expired_at= 清空过期时间并置 expired_count=-1(永久)。

curl -s -X PATCH http://localhost:8466/admin/file/batch-update \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"ids":[41,42],"clearExpiredAt":true}'
{
  "code": 200, "msg": "ok",
  "data": { "requestedCount": 2, "updatedCount": 2, "missingCount": 0, "failedCount": 0, "updated": 2, "missing": [], "failed": [], "requested_count": 2, "updated_count": 2, "missing_count": 0, "failed_count": 0 }
}

过期策略动作:PATCH /admin/file/policy-action

对单个文件执行快捷策略。PATCHPOST。批量版为 /admin/file/batch-policy-action{"ids":[…]}),响应结构同批量更新。

请求体

字段 说明
id 文件 ID
action extend_24h / extend_7d / make_permanent / reset_download_limit
downloadLimit reset_download_limit 用:新取件次数(默认 5,须 >0)
curl -s -X PATCH http://localhost:8466/admin/file/policy-action \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"id":42,"action":"reset_download_limit","downloadLimit":3}'
{ "code": 200, "msg": "ok", "data": { "id": 42, "action": "reset_download_limit" } }
{ "code": 400, "msg": "不支持的策略动作" }

extend_24h/extend_7d 在当前过期时间(未过期时)基础上顺延;make_permanent 清空过期时间并置次数 -1。

管理员下载:GET /admin/file/download?id=

下载原文件(不消耗取件次数);文件返回二进制流(支持 Range),文本分享返回 JSON(data 为文本内容)。

curl -s -OJ "http://localhost:8466/admin/file/download?id=42" -H "Authorization: Bearer $TOKEN"

文本预览:GET /admin/file/preview

仅文本分享可用(文件分享返回 400)。maxChars 截断长度(默认 40001~20000)。

curl -s "http://localhost:8466/admin/file/preview?id=41&maxChars=100" -H "Authorization: Bearer $TOKEN"
{
  "code": 200, "msg": "ok",
  "data": {
    "id": 41, "code": "8XQ2M", "name": "Text.txt", "type": "text",
    "content": "你好,文件快传",
    "length": 24, "previewLength": 24, "truncated": false,
    "maxChars": 100, "max_chars": 100,
    "created_at": "2025-06-01T12:00:00+08:00", "createdAt": "2025-06-01T12:00:00+08:00"
  }
}
{ "code": 400, "msg": "仅文本分享支持预览" }

读取配置:GET /admin/config/get

返回运行时配置 KV(含默认值与管理端修改)。admin_token 恒返回空串(屏蔽); jwt_secret 不下发;存储引擎为进程级单例,_engine_hint 提示引擎配置修改需重启。 26.9 新增键(需求 ①②③④⑩)一并返回:background_urlfooter_textfooter_beiannotify_enabledmax_save_countmax_file_size 等。

{
  "code": 200, "msg": "ok",
  "data": {
    "site_name": "文件快传",
    "name": "文件快传",
    "description": "开箱即用的文件快传系统",
    "page_explain": "…", "keywords": "…",
    "notify_title": "系统通知", "notify_content": "…", "notify_enabled": 1,
    "logo_url": "",
    "favicon_url": "",
    "background_url": "", "footer_text": "", "footer_beian": "",
    "openUpload": 1, "uploadSize": 10485760,
    "max_file_size": 0, "max_save_count": 0,
    "allowed_file_types": ["*"], "expireStyle": ["day","hour","minute","forever","count"],
    "code_generate_type": "secret", "enableChunk": 1,
    "uploadMinute": 1, "uploadCount": 10,
    "errorMinute": 1, "errorCount": 10,
    "loginCount": 5, "loginMinute": 15,
    "max_save_seconds": 0, "storageLimit": 0,
    "opacity": 0.9, "background": "", "showAdminAddr": 0, "robotsText": "User-agent: *\nDisallow: /",
    "adminSessionExpire": 604800,
    "storage_path": "", "local_storage_path": "/app/data",
    "file_storage": "local",
    "admin_token": "",
    "_engine_hint": { "storage_backend": "local", "note": "存储引擎为进程级单例,修改存储引擎相关配置后需重启服务生效" }
  }
}

存储引擎热切换:POST /admin/storage/switch26.9

运行时切换存储引擎,无需重启

curl -s -X POST http://localhost:8466/admin/storage/switch \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"engine":"s3"}'
# 成功:{"code":200,"msg":"ok","data":{"ok":true,"engine":"s3"}}
# 失败:503 {"code":503,"msg":"存储引擎切换失败,已保持原引擎: …"}
  • engine 仅接受 local|s3|webdav400 中文错误)。
  • 切换流程:用最新 KV 参数构建新引擎 → 健康检查 → 通过才替换当前引擎并持久化 storage_engine
  • 失败(构建/健康检查不过)返回 503,原引擎与 KV 均保持不变
  • 切到当前引擎为幂等操作(直接返回 200)。
  • 旧文件按归属引擎(file_codes.engine)取回,切换后旧引擎文件仍可下载。

更新配置:PATCH /admin/config/update

部分更新(JSON 对象,未提供的键不变;表单亦可)。PATCH 为主名,POST 为兼容别名

  • 仅接受管理端可见键(见上响应键集合),未知键忽略。
  • 数值型键自动转型:openUploadenableChunkuploadSizestorageLimit、限流四组、max_save_secondsadminSessionExpireshowAdminAddr26.9 新增 max_save_countmax_file_sizenotify_enabledopacity 为浮点。
  • 26.9site_domain(站点对外域名)可经本端点设置,非法格式 400(仅 http/https、主机+端口、不带路径)。
  • 26.9 引擎键storage_engine 不经本端点修改(走 POST /admin/storage/switch);引擎参数键 local_storage_pathwebdav_urlwebdav_root_pathwebdav_usernamewebdav_passwords3_endpoint_urls3_region_names3_bucket_names3_access_key_ids3_secret_access_keyaws_session_tokens3_addressing_style)可经本端点保存——保存后对应引擎实例缓存失效, 下次切换/构建生效;敏感键空串或 ****** 表示不修改。
  • 26.9 schema 校验settings.KVSchema,越界一律 400,中文错误信息):
    • 整型边界:max_file_size ≤ 10GiB10737418240)、max_save_count ≤ 100000、max_save_seconds ≤ 31536000365 天)、notify_enabled ∈ {0,1}、uploadCount 110000、uploadMinute 11440 等;
    • 字符串长度:background_url ≤ 2048、footer_text ≤ 2000、footer_beian ≤ 128、notify_title ≤ 128、notify_content ≤ 2000 字符;
    • 列表键 expireStyle / allowed_file_types:须为字符串数组(或逗号分隔串)且至少保留一项;
    • 错误示例:{"code":400,"msg":"max_file_size 必须是整数"}{"code":400,"msg":"max_file_size 不能大于 10737418240"}{"code":400,"msg":"footer_beian 长度不能超过 128 字符"}{"code":400,"msg":"notify_enabled 不能大于 1"}
  • background_url 协议白名单(需求 ①,防 javascript: 注入):仅 http(s)://data:image/* 与站内相对路径(/开头);空串=清除背景。非法值 400: {"code":400,"msg":"background_url 仅支持 http(s) 地址、data:image 图片或站内相对路径"}
  • admin_token:明文密码自动哈希,并轮换 jwt_secret(全部管理员令牌立即失效);空串忽略;已是哈希格式则原样保存。
  • adminSessionExpire 须为 1~365 的整天秒数(86400 的整数倍),否则 400。
  • storageLimit 不能小于 0。
  • 修改限流/策略配置立即生效(无需重启:限流规则运行时同步,策略由上传链路每次实时读取);引擎相关(file_storage/s3_*/webdav_*/storage_path/local_storage_path)需重启。
curl -s -X PATCH http://localhost:8466/admin/config/update \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"site_name":"我的文件柜","uploadSize":52428800,"openUpload":1,"footer_beian":"京ICP备20240001号","max_file_size":10485760,"max_save_count":5}'
{ "code": 200, "msg": "ok", "data": { "ok": true } }
{ "code": 400, "msg": "adminSessionExpire 必须是 1 到 365 个整天" }
{ "code": 400, "msg": "background_url 仅支持 http(s) 地址、data:image 图片或站内相对路径" }

修改管理员密码:PATCH /admin/settings/password

PATCHPOST。新密码 ≥8 位;成功后哈希保存并轮换 jwt_secret,所有旧令牌失效(401,需重新登录。

curl -s -X PATCH http://localhost:8466/admin/settings/password \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"old_password":"admin12345","new_password":"new-pass-6789"}'
{ "code": 200, "msg": "ok", "data": { "ok": true } }
{ "code": 400, "msg": "新密码长度至少 8 位" }
{ "code": 401, "msg": "旧密码错误" }

手动回收:POST /admin/recycle/run

26.9:手动触发一轮过期分享回收(定时循环之外的管理端入口)。回收范围: 时间已过期、次数已耗尽、创建时间超过 retention_days 的分享——删除记录并 连带删除存储对象(SHA512 去重开启时做引用计数,仍有其他分享引用的对象保留)。

curl -s -X POST "http://localhost:8466/admin/recycle/run" -H "Authorization: Bearer $TOKEN"
{ "code": 200, "msg": "ok", "data": { "removed": 3 } }

相关配置键:recycle_enabled(定时开关)、recycle_interval(扫描间隔)、 retention_days(最长存储时长)、dedup_enabled(引用计数开关)。