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

452 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理后台 API
管理端接口:除 `POST /admin/login` 与初始化向导 `/setup` 外,一律要求
`Authorization: Bearer <token>`(见《认证与限流》),无效令牌 401。
## 初始化向导:GET /setup
未初始化时返回 HTML 配置页(站点名称、管理员密码、上传/限流/保存策略);
已初始化时 `303` 重定向到 `/`
```bash
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` |
```bash
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"}'
```
```json
{ "code": 200, "msg": "ok", "data": { "ok": true, "admin": "/#/admin" } }
```
**错误响应**(400,HTML 表单时内嵌错误提示):
```json
{ "code": 400, "msg": "管理员密码至少 8 位" }
```
## 登录:POST /admin/login
```bash
curl -s -X POST http://localhost:8466/admin/login \
-H 'Content-Type: application/json' \
-d '{"password":"admin12345"}'
```
**成功响应**200):
```json
{
"code": 200, "msg": "ok",
"data": {
"id": "admin", "username": "admin",
"token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_at": 1750000000,
"expires_in": 604800
}
}
```
**错误响应**
```json
{ "code": 401, "msg": "密码错误" }
```
```json
{ "code": 423, "msg": "请求次数过多,请稍后再试" }
```
## 校验会话:GET /admin/verify
```bash
curl -s http://localhost:8466/admin/verify -H "Authorization: Bearer $TOKEN"
```
```json
{ "code": 200, "msg": "ok", "data": { "id": "admin", "username": "admin", "token": "eyJ…", "token_type": "Bearer", "expires_at": 1750000000 } }
```
## 登出:POST /admin/logout
无状态 JWT,服务端仅返回确认(客户端应丢弃令牌):
```json
{ "code": 200, "msg": "ok", "data": { "ok": true } }
```
## 仪表盘:GET /admin/dashboard
```bash
curl -s http://localhost:8466/admin/dashboard -H "Authorization: Bearer $TOKEN"
```
```json
{
"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` |
```bash
curl -s "http://localhost:8466/admin/file/list?page=1&size=10&status=active&sortBy=size&sortOrder=desc" \
-H "Authorization: Bearer $TOKEN"
```
```json
{
"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=42``POST {"id":42}`。返回列表条目字段;文本分享额外含 `content`(全文)。
```json
{ "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 } }
```
```json
{ "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` 按时间/永久) |
```bash
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}'
```
```json
{ "code": 200, "msg": "ok", "data": "更新成功" }
```
```json
{ "code": 400, "msg": "code已存在" }
```
## 删除文件:DELETE /admin/file/delete
删除分享记录并连带删除存储文件(文本分享无存储文件)。`DELETE``POST`,请求体 `{"id":42}`
```bash
curl -s -X DELETE http://localhost:8466/admin/file/delete \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"id":42}'
```
```json
{ "code": 200, "msg": "ok", "data": null }
```
```json
{ "code": 400, "msg": "请选择要删除的文件" }
```
## 批量删除:POST /admin/file/batch-delete
`POST``DELETE`,请求体 `{"ids":[41,42,43]}`。返回逐条统计:
```json
{
"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
`PATCH``POST`。请求体:`ids[]` 必需;`expired_at`ISO 8601/`expired_count` 二选一;
`clearExpiredAt: true`(或 `clear_expired_at`= 清空过期时间并置 `expired_count=-1`(永久)。
```bash
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}'
```
```json
{
"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
对单个文件执行快捷策略。`PATCH``POST`。批量版为 `/admin/file/batch-policy-action``{"ids":[…]}`),响应结构同批量更新。
**请求体**
| 字段 | 说明 |
|---|---|
| `id` | 文件 ID |
| `action` | `extend_24h` / `extend_7d` / `make_permanent` / `reset_download_limit` |
| `downloadLimit` | 仅 `reset_download_limit` 用:新取件次数(默认 5,须 >0) |
```bash
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}'
```
```json
{ "code": 200, "msg": "ok", "data": { "id": 42, "action": "reset_download_limit" } }
```
```json
{ "code": 400, "msg": "不支持的策略动作" }
```
> `extend_24h`/`extend_7d` 在当前过期时间(未过期时)基础上顺延;`make_permanent` 清空过期时间并置次数 -1。
## 管理员下载:GET /admin/file/download?id=
下载原文件(**不消耗取件次数**);文件返回二进制流(支持 Range),文本分享返回 JSON(`data` 为文本内容)。
```bash
curl -s -OJ "http://localhost:8466/admin/file/download?id=42" -H "Authorization: Bearer $TOKEN"
```
## 文本预览:GET /admin/file/preview
仅文本分享可用(文件分享返回 400)。`maxChars` 截断长度(默认 40001~20000)。
```bash
curl -s "http://localhost:8466/admin/file/preview?id=41&maxChars=100" -H "Authorization: Bearer $TOKEN"
```
```json
{
"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"
}
}
```
```json
{ "code": 400, "msg": "仅文本分享支持预览" }
```
## 读取配置:GET /admin/config/get
返回运行时配置 KV(含默认值与管理端修改)。`admin_token` 恒返回空串(屏蔽);
`jwt_secret` 不下发;存储引擎为进程级单例,`_engine_hint` 提示引擎配置修改需重启。
26.9 新增键(需求 ①②③④⑩)一并返回:`background_url``footer_text``footer_beian`
`notify_enabled``max_save_count``max_file_size` 等。
```json
{
"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
运行时切换存储引擎,**无需重启**:
```bash
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|webdav`400 中文错误)。
- 切换流程:用最新 KV 参数构建新引擎 → 健康检查 → 通过才替换当前引擎并持久化 `storage_engine`
- 失败(构建/健康检查不过)返回 503,**原引擎与 KV 均保持不变**。
- 切到当前引擎为幂等操作(直接返回 200)。
- 旧文件按归属引擎(`file_codes.engine`)取回,切换后旧引擎文件仍可下载。
## 更新配置:PATCH /admin/config/update
部分更新(JSON 对象,未提供的键不变;表单亦可)。**PATCH 为主名,POST 为兼容别名**。
- 仅接受管理端可见键(见上响应键集合),未知键忽略。
- 数值型键自动转型:`openUpload``enableChunk``uploadSize``storageLimit`、限流四组、`max_save_seconds``adminSessionExpire``showAdminAddr`26.9 新增 `max_save_count``max_file_size``notify_enabled``opacity` 为浮点。
- **26.9**`site_domain`(站点对外域名)可经本端点设置,非法格式 400(仅 http/https、主机+端口、不带路径)。
- **26.9 引擎键**`storage_engine` 不经本端点修改(走 `POST /admin/storage/switch`);引擎参数键
`local_storage_path``webdav_url``webdav_root_path``webdav_username``webdav_password`
`s3_endpoint_url``s3_region_name``s3_bucket_name``s3_access_key_id``s3_secret_access_key`
`aws_session_token``s3_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` 1~10000、`uploadMinute` 1~1440 等;
- 字符串长度:`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`)需重启。
```bash
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}'
```
```json
{ "code": 200, "msg": "ok", "data": { "ok": true } }
```
```json
{ "code": 400, "msg": "adminSessionExpire 必须是 1 到 365 个整天" }
```
```json
{ "code": 400, "msg": "background_url 仅支持 http(s) 地址、data:image 图片或站内相对路径" }
```
## 修改管理员密码:PATCH /admin/settings/password
`PATCH``POST`。新密码 ≥8 位;成功后哈希保存并**轮换 `jwt_secret`,所有旧令牌失效(401)**,需重新登录。
```bash
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"}'
```
```json
{ "code": 200, "msg": "ok", "data": { "ok": true } }
```
```json
{ "code": 400, "msg": "新密码长度至少 8 位" }
```
```json
{ "code": 401, "msg": "旧密码错误" }
```
## 手动回收:POST /admin/recycle/run
**26.9**:手动触发一轮过期分享回收(定时循环之外的管理端入口)。回收范围:
时间已过期、次数已耗尽、创建时间超过 `retention_days` 的分享——删除记录并
连带删除存储对象(SHA512 去重开启时做引用计数,仍有其他分享引用的对象保留)。
```bash
curl -s -X POST "http://localhost:8466/admin/recycle/run" -H "Authorization: Bearer $TOKEN"
```
```json
{ "code": 200, "msg": "ok", "data": { "removed": 3 } }
```
相关配置键:`recycle_enabled`(定时开关)、`recycle_interval`(扫描间隔)、
`retention_days`(最长存储时长)、`dedup_enabled`(引用计数开关)。