Files
FileShare/docs/openapi.yaml
T
SKYMirror 7f060dd0e4 26.9(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务:

- 安全审计全部修复(docs/security-audit-2026-09-05.md):
  bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、
  全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、
  管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、
  会话默认 7 天、限流缓存故障降级、robots.txt 端点等
- 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固
- Redis 支持库号(FCB_REDIS_DB / redis://…/db URL)
- 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、
  提码 5 位起、chunk 32MiB 上限、admin 审计动作等)

验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
2026-09-05 04:22:41 +08:00

1921 lines
77 KiB
YAML
Raw 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.
openapi: 3.0.3
info:
title: 文件快传 Go 版 API
version: 2.5.6
description: |
文件快传 Go 重写版(Gin + GORM)完整 API 规范,与 `server/internal/api/` 实现逐一对齐。
约定:
- 业务路由挂根路径(/share /chunk /presign /admin),公共接口保留 /api/v1 前缀。
- 统一响应 `{"code":200,"msg":"ok","data":...}`HTTP 状态码与 code 一致,失败时 data 缺省。
- 管理接口(/admin/login 除外)需 `Authorization: Bearer <JWT>`。
- 未初始化时除 /setup 与 /api/v1/health 外一律 428;限流超限 423。
contact:
name: 文件快传 Rewrite Team
servers:
- url: /
description: 同源部署(默认 :8466
tags:
- name: 公共
- name: 初始化
- name: 分享
- name: 分片上传
- name: 预签名直传
- name: 管理后台
- name: 审计日志
paths:
/api/v1/health:
get:
tags: [公共]
summary: 健康检查
operationId: health
responses:
'200':
description: 服务状态
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
status: { type: string, example: ok }
version: { type: string, example: 2.5.6 }
storage: { type: string, example: local }
time: { type: string, example: '2025-06-01T12:00:00+08:00' }
/robots.txt:
get:
tags: [公共]
summary: robots.txt
operationId: robotsText
description: |
输出管理端可配置的 `robotsText` 内容(text/plain)。
内容可在管理后台「站点配置」中自定义,默认 `User-agent: *\nDisallow: /`。
responses:
'200':
description: robots.txt 内容
content:
text/plain:
schema: { type: string, example: 'User-agent: *\nDisallow: /' }
/api/v1/config:
get:
tags: [公共]
summary: 公共站点配置
operationId: publicConfig
responses:
'200':
description: 站点公开配置与功能开关
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
config:
type: object
description: 策略字段为上传页动态读取的当前快照(管理端改后即时反映);snake_case 与 camelCase 双份
properties:
name: { type: string }
description: { type: string }
explain: { type: string }
logo_url: { type: string }
favicon_url: { type: string }
background_url: { type: string, description: '需求 ① 背景图 URL/上传地址,空=主题默认' }
footer_text: { type: string, description: '需求 ② 页脚自定义内容' }
footer_beian: { type: string, description: '需求 ② 备案号' }
storage_engine: { type: string, description: 当前存储引擎名(仅名称,凭据不下发) }
site_domain: { type: string, description: 'v3.1 站点对外域名(空=用当前访问地址)' }
notify_enabled: { type: integer, enum: [0, 1], description: '需求 ③ 通知开关(1=右上角悬浮窗)' }
notify_title: { type: string }
notify_content: { type: string }
uploadSize: { type: integer, format: int64, description: '回落上限(max_file_size=0 时生效)' }
max_file_size: { type: integer, format: int64, description: '需求 ⑩ 存储策略-单文件上限字节(0=回落 uploadSize' }
maxFileSize: { type: integer, format: int64, description: 同 max_file_size }
allowedFileTypes: { type: array, items: { type: string } }
expireStyle: { type: array, items: { type: string } }
max_save_seconds: { type: integer, format: int64, description: '需求 ④ 最长保存秒数上限(0=默认 7 天兜底)' }
maxSaveSeconds: { type: integer, format: int64, description: 同 max_save_seconds }
max_save_count: { type: integer, description: '需求 ④ 单次分享最大可取次数上限(0=不限制)' }
maxSaveCount: { type: integer, description: 同 max_save_count }
uploadCount: { type: integer, description: 上传频率限制-窗口内次数 }
uploadMinute: { type: integer, description: 上传频率限制-窗口分钟 }
enableChunk: { type: boolean }
openUpload: { type: boolean }
meta:
type: object
properties:
version: { type: string }
features:
type: object
properties:
chunkUpload: { type: boolean }
guestUpload: { type: boolean }
/setup:
get:
tags: [初始化]
summary: 初始化向导页面
operationId: setupPage
description: 未初始化返回 HTML 向导;已初始化 303 重定向到 /。
responses:
'200':
description: HTML 向导页
content:
text/html:
schema: { type: string }
'303':
description: 已初始化,重定向到 /
post:
tags: [初始化]
summary: 提交初始化
operationId: setupSubmit
description: |
表单或 JSON。管理员密码 ≥8 位;`expireStyle` 支持多值(复选框或逗号分隔)。
成功写入站点配置、密码哈希并生成 jwt_secret。表单提交返回成功 HTMLJSON 提交返回 JSON。
requestBody:
content:
application/json:
schema:
type: object
properties:
admin_password: { type: string, minLength: 8 }
confirm_password: { type: string }
site_name: { type: string }
upload_size_value: { type: integer, default: 10 }
upload_size_unit: { type: string, enum: [KB, MB, GB], default: MB }
save_time_value: { type: integer, default: 0 }
save_time_unit: { type: string, enum: [second, minute, hour, day], default: day }
expireStyle:
oneOf:
- type: string
- type: array
items: { type: string }
code_generate_type: { type: string, enum: [number, secret], default: secret }
errorCount: { type: integer, default: 10 }
errorMinute: { type: integer, default: 1 }
loginCount: { type: integer, default: 5 }
loginMinute: { type: integer, default: 15 }
uploadCount: { type: integer, default: 10 }
uploadMinute: { type: integer, default: 1 }
allowed_file_types: { type: string, default: '*' }
openUpload: { type: boolean, default: true }
enableChunk: { type: boolean, default: false }
application/x-www-form-urlencoded:
schema:
type: object
properties:
admin_password: { type: string }
confirm_password: { type: string }
site_name: { type: string }
responses:
'200':
description: 初始化成功
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
ok: { type: boolean, example: true }
admin: { type: string, example: '/#/admin' }
text/html:
schema: { type: string }
'400':
$ref: '#/components/responses/BadRequest'
/share/text:
post:
tags: [分享]
summary: 创建文本分享
operationId: shareText
description: |
文本 ≤222KB(请求体全局上限 1MiBContent-Length>441KB 直接 403);上传类接口(成功计数 upload 限流,423 超限),经审计中间件落库。
保存策略(需求 ④):expire_style 须在 expireStyle 白名单(400);count 型受
max_save_count 约束(403「限制次数最多为 N 次」);时间型受 max_save_seconds 约束(403)。
v3.1:支持 JSON 提交(字段同名);空文本 400「分享内容不能为空」;
可选 code 自定义提取码(5-8 位字母或数字,占用 400「该提取码已被占用」)。
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [text]
properties:
text: { type: string, maxLength: 227328 }
expire_value: { type: integer, default: 1 }
expire_style: { type: string, enum: [day, hour, minute, count, forever], default: day }
code: { type: string, minLength: 5, maxLength: 8, pattern: '^[A-Za-z0-9]+$', description: 自定义提取码(5-8 位字母数字,可省略=随机) }
responses:
'200':
description: 取件码
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
code: { type: string, example: 8XQ2M }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/share/file:
post:
tags: [分享]
summary: 上传文件并创建分享
operationId: shareFile
description: |
multipart 上传;扩展名/MIME 白名单 + magic bytes 防伪;大小受动态策略上限限制
max_file_size>0 时为其,否则回落 uploadSize;403「大小超过限制,最大为10.00 MB」,
管理端改后立即生效);保存策略同 /share/text403/400);容量超限 507。
requestBody:
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
code: { type: string, minLength: 5, maxLength: 8, pattern: '^[A-Za-z0-9]+$', description: 自定义提取码(5-8 位字母数字,可省略=随机) }
file:
type: string
format: binary
expire_value: { type: integer, default: 1 }
expire_style: { type: string, enum: [day, hour, minute, count, forever], default: day }
responses:
'200':
description: 取件码与文件名
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
code: { type: string, example: K3P9W }
name: { type: string, example: report.pdf }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'423': { $ref: '#/components/responses/RateLimited' }
'503': { $ref: '#/components/responses/ServiceUnavailable' }
'507': { $ref: '#/components/responses/InsufficientStorage' }
'428': { $ref: '#/components/responses/NotInitialized' }
/share/metadata:
get:
tags: [分享]
summary: 查询分享元信息(GET
operationId: shareMetadataGet
description: 不消耗次数;每次访问计入 metadata 限流。
parameters:
- $ref: '#/components/parameters/CodeQuery'
responses:
'200':
description: 元信息
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareMetadata' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [分享]
summary: 查询分享元信息(POST
operationId: shareMetadataPost
requestBody:
content:
application/json:
schema:
type: object
required: [code]
properties:
code: { type: string }
responses:
'200':
description: 元信息(同 GET 版本)
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareMetadata' }
'400': { $ref: '#/components/responses/BadRequest' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/share/select:
get:
tags: [分享]
summary: 取件(消耗次数)
operationId: shareSelectGet
description: |
每调用一次消耗 1 次取件。文本分享返回 text/plain 正文;文件分享返回文件流
(200 全量 / 206 区间,支持 RangeAccept-Ranges: bytes)。失败计入 error 限流(423)。
parameters:
- $ref: '#/components/parameters/CodeQuery'
- name: Range
in: header
required: false
schema: { type: string, example: 'bytes=0-1023' }
description: 文件分享的区间请求(单区间)
responses:
'200':
description: 文本正文或文件流
content:
text/plain:
schema: { type: string }
application/octet-stream:
schema: { type: string, format: binary }
'206':
description: Range 区间文件流(Content-Range
content:
application/octet-stream:
schema: { type: string, format: binary }
'404': { $ref: '#/components/responses/NotFound' }
'416': { $ref: '#/components/responses/RangeNotSatisfiable' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [分享]
summary: 取件详情(JSON
operationId: shareSelectPost
description: |
返回元信息 + 内容/下载地址。文本分享含 text/content;文件分享含 download_url
(代理地址或 S3 预签名直链)。次数型分享返回代理地址且本次不消耗(消耗在代理下载时)。
requestBody:
content:
application/json:
schema:
type: object
required: [code]
properties:
code: { type: string }
responses:
'200':
description: 分享详情
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareDetail' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/share/download:
get:
tags: [分享]
summary: 代理下载(消耗次数)
operationId: shareDownload
description: key 为窗口令牌(双窗口校验,鉴权失败 403 且计入 error 限流);文件流 200/206 支持 Range。
parameters:
- name: key
in: query
required: true
schema: { type: string }
- $ref: '#/components/parameters/CodeQuery'
responses:
'200':
description: 文本 JSON 或文件流
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
application/octet-stream:
schema: { type: string, format: binary }
'206':
description: Range 区间文件流
content:
application/octet-stream:
schema: { type: string, format: binary }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'416': { $ref: '#/components/responses/RangeNotSatisfiable' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/chunk/upload/init:
post:
tags: [分片上传]
summary: 初始化分片上传会话
operationId: chunkInit
description: |
相同 file_hash+file_size+file_name 的未完成会话自动续传(uploaded_chunks 非空即续传)。
chunk_size 缺省 5MB、硬上限 32MiB(超出 400「chunk_size 过大」);需 enableChunk 开启(关闭时全部 /chunk 端点 403)。总大小按分片数上限受动态策略约束
max_file_size>0 时为其,否则 uploadSize403);类型白名单 403。
requestBody:
content:
application/json:
schema:
type: object
required: [file_name, file_size]
properties:
file_name: { type: string }
file_size: { type: integer, format: int64, minimum: 1 }
chunk_size: { type: integer, format: int64, default: 5242880, maximum: 33554432, description: 每片大小(字节),≤32MiB }
file_hash: { type: string, description: 整文件 SHA256,断点续传匹配键 }
responses:
'200':
description: 会话信息
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ChunkInitData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'423': { $ref: '#/components/responses/RateLimited' }
'507': { $ref: '#/components/responses/InsufficientStorage' }
'428': { $ref: '#/components/responses/NotInitialized' }
/chunk/upload/{uploadID}/{chunkIndex}:
post:
tags: [分片上传]
summary: 上传单个分片
operationId: chunkUpload
description: multipart 字段 chunk;单分片 ≤ min(chunk_size, 32MiB);重复上传幂等(skipped:true);首分片做 magic bytes 防伪。
parameters:
- $ref: '#/components/parameters/UploadID'
- name: chunkIndex
in: path
required: true
schema: { type: integer, minimum: 0 }
requestBody:
content:
multipart/form-data:
schema:
type: object
required: [chunk]
properties:
chunk: { type: string, format: binary }
responses:
'200':
description: 分片结果
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ChunkUploadData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/chunk/upload/status/{uploadID}:
get:
tags: [分片上传]
summary: 查询上传进度
operationId: chunkStatus
parameters:
- $ref: '#/components/parameters/UploadID'
responses:
'200':
description: 进度
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ChunkStatusData' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/chunk/upload/complete/{uploadID}:
post:
tags: [分片上传]
summary: 合并分片并创建分享
operationId: chunkComplete
description: 分片到齐后调用;按索引有序合并 + SHA256 校验(不完整 400 / 哈希不符 400);累计大小受动态策略上限(403,超限清理会话);保存策略同 /share/text403/400)。
parameters:
- $ref: '#/components/parameters/UploadID'
requestBody:
content:
application/json:
schema:
type: object
properties:
expire_value: { type: integer, default: 1 }
expire_style: { type: string, enum: [day, hour, minute, count, forever], default: day }
responses:
'200':
description: 取件码与文件名
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareFileData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/chunk/upload/{uploadID}:
delete:
tags: [分片上传]
summary: 取消上传
operationId: chunkCancel
parameters:
- $ref: '#/components/parameters/UploadID'
responses:
'200':
description: 已取消
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
message: { type: string, example: 上传已取消 }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/presign/upload/init:
post:
tags: [预签名直传]
summary: 初始化预签名上传
operationId: presignInit
description: |
S3 引擎返回 mode=directupload_url 为预签名 PUT URL);local/webdav 返回 mode=proxy
upload_url=/presign/upload/proxy/{uploadID})。会话 900 秒有效。
file_size 受动态策略上限(max_file_size>0 时为其,否则 uploadSize403);保存策略同 /share/text。
requestBody:
content:
application/json:
schema:
type: object
required: [file_name, file_size]
properties:
file_name: { type: string }
file_size: { type: integer, format: int64, minimum: 1 }
expire_value: { type: integer, default: 1 }
expire_style: { type: string, enum: [day, hour, minute, count, forever], default: day }
responses:
'200':
description: 上传会话
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/PresignInitData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'423': { $ref: '#/components/responses/RateLimited' }
'507': { $ref: '#/components/responses/InsufficientStorage' }
'428': { $ref: '#/components/responses/NotInitialized' }
/presign/upload/proxy/{uploadID}:
put:
tags: [预签名直传]
summary: 代理上传(proxy 模式)
operationId: presignProxy
description: multipart 字段 file;实际大小须与声明一致(±1KB);成功即建分享并删除会话。
parameters:
- $ref: '#/components/parameters/UploadID'
requestBody:
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file: { type: string, format: binary }
responses:
'200':
description: 取件码与文件名
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareFileData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/presign/upload/confirm/{uploadID}:
post:
tags: [预签名直传]
summary: 直传确认(direct 模式)
operationId: presignConfirm
description: |
客户端完成 S3 PUT 后调用。服务端 HeadObject 核对实际对象:
超过策略上限 → 删除对象、释放容量预留并 403;实际大小与声明差 >1KB → 400
前 64 字节做 magic bytes 校验(失败删除对象)。全部通过后创建分享记录。
parameters:
- $ref: '#/components/parameters/UploadID'
responses:
'200':
description: 取件码与文件名
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/ShareFileData' }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/presign/upload/status/{uploadID}:
get:
tags: [预签名直传]
summary: 查询预签名会话
operationId: presignStatus
parameters:
- $ref: '#/components/parameters/UploadID'
responses:
'200':
description: 会话状态
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
upload_id: { type: string }
file_name: { type: string }
file_size: { type: integer, format: int64 }
mode: { type: string, enum: [direct, proxy] }
created_at: { type: string }
expires_at: { type: string }
is_expired: { type: boolean }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/presign/upload/{uploadID}:
delete:
tags: [预签名直传]
summary: 取消预签名会话
operationId: presignCancel
description: direct 模式会尽力清理已直传的对象。
parameters:
- $ref: '#/components/parameters/UploadID'
responses:
'200':
description: 已取消
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
message: { type: string, example: 上传会话已取消 }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/login:
post:
tags: [管理后台]
summary: 管理员登录
operationId: adminLogin
description: 失败 401 并计入 login 限流(423 超限)。
requestBody:
content:
application/json:
schema:
type: object
required: [password]
properties:
password: { type: string }
responses:
'200':
description: JWT 会话
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/LoginData' }
'401': { $ref: '#/components/responses/Unauthorized' }
'423': { $ref: '#/components/responses/RateLimited' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/verify:
get:
tags: [管理后台]
summary: 校验管理员会话
operationId: adminVerify
security: [{ bearerAuth: [] }]
responses:
'200':
description: 会话信息
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/LoginData' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/logout:
post:
tags: [管理后台]
summary: 管理员登出
operationId: adminLogout
description: 无状态 JWT,客户端丢弃令牌即可。
security: [{ bearerAuth: [] }]
responses:
'200':
description: 确认
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/OkData' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/dashboard:
get:
tags: [管理后台]
summary: 管理端仪表盘统计
operationId: adminDashboard
security: [{ bearerAuth: [] }]
responses:
'200':
description: 计数/存储用量/最近文件等
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
totalFiles: { type: integer }
storageUsed: { type: string, example: '123456789' }
sysUptime: { type: integer, nullable: true, description: 启动时刻 Unix 毫秒 }
yesterdayCount: { type: integer }
yesterdaySize: { type: string }
todayCount: { type: integer }
todaySize: { type: string }
activeCount: { type: integer }
expiredCount: { type: integer }
textCount: { type: integer }
fileCount: { type: integer }
chunkedCount: { type: integer }
usedCount: { type: integer }
storageBackend: { type: string, enum: [local, s3, webdav] }
uploadSizeLimit: { type: integer, format: int64 }
openUpload: { type: integer }
enableChunk: { type: integer }
maxSaveSeconds: { type: integer, format: int64 }
topSuffixes:
type: array
items:
type: object
properties:
suffix: { type: string }
count: { type: integer }
recentFiles:
type: array
items: { $ref: '#/components/schemas/AdminFileItem' }
recentActivities: { type: array, items: { type: object } }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/list:
get:
tags: [管理后台]
summary: 分页查询分享列表
operationId: adminFileList
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, default: 1, minimum: 1 } }
- { name: size, in: query, schema: { type: integer, default: 10, minimum: 1, maximum: 100 } }
- { name: keyword, in: query, schema: { type: string } }
- { name: status, in: query, schema: { type: string, enum: [active, expired] } }
- { name: type, in: query, schema: { type: string, enum: [text, file, chunked] } }
- { name: sortBy, in: query, schema: { type: string, enum: [created_at, expired_at, name, size, used_count, code] } }
- { name: sortOrder, in: query, schema: { type: string, enum: [asc, desc], default: desc } }
responses:
'200':
description: 列表与统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
page: { type: integer }
size: { type: integer }
total: { type: integer }
summary:
type: object
properties:
totalFiles: { type: integer }
activeCount: { type: integer }
expiredCount: { type: integer }
textCount: { type: integer }
fileCount: { type: integer }
chunkedCount: { type: integer }
storageUsed: { type: integer, format: int64 }
usedCount: { type: integer, format: int64 }
data:
type: array
items: { $ref: '#/components/schemas/AdminFileItem' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/detail:
get:
tags: [管理后台]
summary: 文件详情(GET
operationId: adminFileDetailGet
security: [{ bearerAuth: [] }]
parameters:
- $ref: '#/components/parameters/FileIDQuery'
responses:
'200':
description: 文件条目(文本分享额外含 content 全文)
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/AdminFileItem' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 文件详情(POST
operationId: adminFileDetailPost
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [id]
properties:
id: { type: integer, format: int64 }
responses:
'200':
description: 同 GET 版本
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/AdminFileItem' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/update:
patch:
tags: [管理后台]
summary: 更新分享字段(主名)
operationId: adminFileUpdate
description: code 冲突 400POST /admin/file/update 为兼容别名。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema: { $ref: '#/components/schemas/FileUpdateInput' }
responses:
'200':
description: 更新成功(data 为字符串“更新成功”)
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 更新分享字段(POST 别名)
operationId: adminFileUpdateAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema: { $ref: '#/components/schemas/FileUpdateInput' }
responses:
'200':
description: 更新成功
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/delete:
delete:
tags: [管理后台]
summary: 删除单个分享(主名)
operationId: adminFileDelete
description: 连带删除存储文件;POST /admin/file/delete 为兼容别名。
security: [{ bearerAuth: [] }]
parameters:
- { name: id, in: query, schema: { type: integer, format: int64 }, description: 亦可放请求体 }
requestBody:
content:
application/json:
schema:
type: object
properties:
id: { type: integer, format: int64 }
responses:
'200':
description: 删除成功(data 为 null
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 删除单个分享(POST 别名)
operationId: adminFileDeleteAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
properties:
id: { type: integer, format: int64 }
responses:
'200':
description: 删除成功
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/batch-delete:
post:
tags: [管理后台]
summary: 批量删除
operationId: adminFileBatchDelete
description: DELETE 方法亦可。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
delete:
tags: [管理后台]
summary: 批量删除(DELETE 别名)
operationId: adminFileBatchDeleteAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/batch-update:
patch:
tags: [管理后台]
summary: 批量更新过期策略
operationId: adminFileBatchUpdate
description: expired_at / expired_count / clearExpiredAt 三选一;POST 亦可。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
expired_at: { type: string, description: ISO 8601 }
expired_count: { type: integer }
clearExpiredAt: { type: boolean, description: 清空过期时间并置 expired_count=-1(永久) }
clear_expired_at: { type: boolean, description: 同 clearExpiredAt }
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 批量更新(POST 别名)
operationId: adminFileBatchUpdateAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
expired_at: { type: string }
expired_count: { type: integer }
clearExpiredAt: { type: boolean }
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/policy-action:
patch:
tags: [管理后台]
summary: 单文件过期策略动作
operationId: adminFilePolicyAction
description: 动作:extend_24h / extend_7d / make_permanent / reset_download_limitPOST 亦可。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema: { $ref: '#/components/schemas/PolicyInput' }
responses:
'200':
description: 执行结果
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/PolicyResult' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 单文件策略动作(POST 别名)
operationId: adminFilePolicyActionAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema: { $ref: '#/components/schemas/PolicyInput' }
responses:
'200':
description: 执行结果
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/PolicyResult' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/batch-policy-action:
patch:
tags: [管理后台]
summary: 批量过期策略动作
operationId: adminFileBatchPolicyAction
description: 响应结构同批量更新;POST 亦可。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
allOf:
- type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
- $ref: '#/components/schemas/PolicyInput'
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 批量策略动作(POST 别名)
operationId: adminFileBatchPolicyActionAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
allOf:
- type: object
required: [ids]
properties:
ids: { type: array, items: { type: integer, format: int64 } }
- $ref: '#/components/schemas/PolicyInput'
responses:
'200':
description: 批量统计
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/BatchStats' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/download:
get:
tags: [管理后台]
summary: 管理员下载原文件
operationId: adminFileDownload
description: 不消耗取件次数;文件返回二进制流(支持 Range),文本分享返回 JSON。
security: [{ bearerAuth: [] }]
parameters:
- $ref: '#/components/parameters/FileIDQuery'
responses:
'200':
description: 文件流或文本 JSON
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
application/octet-stream:
schema: { type: string, format: binary }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/file/preview:
get:
tags: [管理后台]
summary: 文本预览
operationId: adminFilePreview
description: 仅文本分享可用(文件分享 400);maxChars 默认 40001~20000)。
security: [{ bearerAuth: [] }]
parameters:
- $ref: '#/components/parameters/FileIDQuery'
- { name: maxChars, in: query, schema: { type: integer, default: 4000, minimum: 1, maximum: 20000 } }
responses:
'200':
description: 预览内容
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
id: { type: integer, format: int64 }
code: { type: string }
name: { type: string }
type: { type: string, example: text }
content: { type: string }
length: { type: integer }
previewLength: { type: integer }
truncated: { type: boolean }
maxChars: { type: integer }
createdAt: { type: string }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/config/get:
get:
tags: [管理后台]
summary: 读取运行时配置
operationId: adminConfigGet
description: admin_token 屏蔽(恒空串)、jwt_secret 不下发;_engine_hint 提示引擎配置需重启。
security: [{ bearerAuth: [] }]
responses:
'200':
description: 配置 KV
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
additionalProperties: true
properties:
site_name: { type: string }
logo_url: { type: string }
favicon_url: { type: string }
background_url: { type: string }
footer_text: { type: string }
footer_beian: { type: string }
notify_enabled: { type: integer, enum: [0, 1] }
openUpload: { type: integer }
uploadSize: { type: integer, format: int64 }
max_file_size: { type: integer, format: int64, description: '0=回落 uploadSize' }
max_save_count: { type: integer, description: '0=不限制' }
max_save_seconds: { type: integer, format: int64 }
allowed_file_types: { type: array, items: { type: string } }
expireStyle: { type: array, items: { type: string } }
storageLimit: { type: integer, format: int64 }
uploadCount: { type: integer }
uploadMinute: { type: integer }
admin_token: { type: string, example: '' }
_engine_hint:
type: object
properties:
storage_backend: { type: string }
note: { type: string }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/config/update:
patch:
tags: [管理后台]
summary: 部分更新配置(主名)
operationId: adminConfigUpdate
description: |
JSON 对象部分更新,未知键忽略;数值键自动转型;v2 起新键按 settings.KVSchema 校验
(整型边界/字符串长度/列表非空,越界 400 中文错误);background_url 协议白名单
http(s)/data:image/站内相对路径);admin_token 明文自动哈希并轮换 jwt_secret
(全部令牌立即失效);adminSessionExpire 须 1~365 整天(默认 604800 即 7 天);notify_content 服务端白名单净化(仅保留文本与 <a href=http(s)|/|#>);策略与限流修改立即生效,引擎配置需重启。
POST /admin/config/update 为兼容别名。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
additionalProperties: true
properties:
site_name: { type: string }
logo_url: { type: string }
favicon_url: { type: string }
background_url:
type: string
maxLength: 2048
description: '需求 ① 背景图;仅 http(s)、data:image/* 与站内相对路径(/开头),空串清除;非法协议 400'
footer_text: { type: string, maxLength: 2000, description: '需求 ② 页脚自定义内容' }
footer_beian: { type: string, maxLength: 128, description: '需求 ② 备案号' }
notify_enabled: { type: integer, enum: [0, 1], description: '需求 ③ 通知开关' }
notify_title: { type: string, maxLength: 128 }
notify_content: { type: string, maxLength: 2000 }
openUpload: { type: integer, enum: [0, 1] }
uploadSize: { type: integer, format: int64, minimum: 1024, maximum: 10737418240 }
max_file_size: { type: integer, format: int64, minimum: 0, maximum: 10737418240, description: '需求 ⑩ 存储策略-单文件上限字节;0=回落 uploadSize' }
max_save_count: { type: integer, minimum: 0, maximum: 100000, description: '需求 ④ 单次分享最大可取次数上限;0=不限制' }
max_save_seconds: { type: integer, format: int64, minimum: 0, maximum: 31536000, description: '需求 ④ 最长保存秒数上限;0=默认 7 天兜底' }
allowed_file_types:
oneOf:
- type: array
items: { type: string }
minItems: 1
- type: string
description: 逗号分隔亦可;"*" 不限制
expireStyle:
oneOf:
- type: array
items: { type: string }
minItems: 1
- type: string
description: 允许的过期方式白名单(day/hour/minute/forever/count 子集)
storageLimit: { type: integer, format: int64, minimum: 0 }
uploadCount: { type: integer, minimum: 1, maximum: 10000 }
uploadMinute: { type: integer, minimum: 1, maximum: 1440 }
errorCount: { type: integer }
errorMinute: { type: integer }
loginCount: { type: integer }
loginMinute: { type: integer }
admin_token: { type: string, description: 明文密码自动哈希并轮换 jwt_secret;空串忽略 }
adminSessionExpire: { type: integer, default: 604800, description: 秒,须 86400 的 1~365 整数倍(默认 7 天) }
responses:
'200':
description: 保存成功
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/OkData' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 部分更新配置(POST 别名)
operationId: adminConfigUpdateAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
'200':
description: 保存成功
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/OkData' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/storage/switch:
post:
tags: [admin]
summary: 存储引擎热切换(v3
description: |
运行时切换存储引擎(local|s3|webdav)。构建新引擎并做健康检查,
通过才生效;失败返回 503 且保持原引擎。成功后持久化 storage_engine KV
重启后沿用。
operationId: adminStorageSwitch
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [engine]
properties:
engine:
type: string
enum: [local, s3, webdav]
responses:
"200":
description: 切换成功
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Envelope"
- type: object
properties:
data:
type: object
properties:
ok: { type: boolean }
engine: { type: string }
"400": { $ref: "#/components/responses/BadRequest" }
"401": { $ref: "#/components/responses/Unauthorized" }
"503": { $ref: "#/components/responses/ServiceUnavailable" }
/admin/settings/password:
patch:
tags: [管理后台]
summary: 修改管理员密码(主名)
operationId: adminChangePassword
description: 新密码 ≥8 位;成功后轮换 jwt_secret,全部旧令牌失效(401)。POST 亦可。
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [old_password, new_password]
properties:
old_password: { type: string }
new_password: { type: string, minLength: 8 }
responses:
'200':
description: 修改成功
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/OkData' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
post:
tags: [管理后台]
summary: 修改管理员密码(POST 别名)
operationId: adminChangePasswordAlias
deprecated: true
security: [{ bearerAuth: [] }]
requestBody:
content:
application/json:
schema:
type: object
required: [old_password, new_password]
properties:
old_password: { type: string }
new_password: { type: string, minLength: 8 }
responses:
'200':
description: 修改成功
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: { $ref: '#/components/schemas/OkData' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/audit/list:
get:
tags: [审计日志]
summary: 分页查询审计日志(主名)
operationId: adminAuditList
description: |
需求 ③ 审计查询。行字段 snake_case 为主(camelCase 兼容双份)。
action: upload|downloadresult: success|denied|failed;时间参数 ISO 8601。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, default: 1, minimum: 1 } }
- { name: size, in: query, schema: { type: integer, default: 20, minimum: 1, maximum: 200 }, description: 兼容 pageSize }
- { name: action, in: query, schema: { type: string, enum: [upload, download] } }
- { name: result, in: query, schema: { type: string, enum: [success, denied, failed] } }
- { name: ip, in: query, schema: { type: string } }
- { name: start_time, in: query, schema: { type: string, example: '2025-06-01T00:00:00+08:00' } }
- { name: end_time, in: query, schema: { type: string, example: '2025-06-02T00:00:00+08:00' } }
responses:
'200':
description: 审计日志分页
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
data:
type: array
items: { $ref: '#/components/schemas/AuditItem' }
total: { type: integer }
page: { type: integer }
size: { type: integer }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
/admin/audit/logs:
get:
tags: [审计日志]
summary: 分页查询审计日志(别名)
operationId: adminAuditLogs
deprecated: true
description: 与 /admin/audit/list 完全相同(同一 handler 的兼容别名)。
security: [{ bearerAuth: [] }]
parameters:
- { name: page, in: query, schema: { type: integer, default: 1 } }
- { name: size, in: query, schema: { type: integer, default: 20 } }
- { name: action, in: query, schema: { type: string, enum: [upload, download] } }
- { name: result, in: query, schema: { type: string, enum: [success, denied, failed] } }
- { name: ip, in: query, schema: { type: string } }
- { name: start_time, in: query, schema: { type: string } }
- { name: end_time, in: query, schema: { type: string } }
responses:
'200':
description: 审计日志分页
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
type: object
properties:
data:
type: array
items: { $ref: '#/components/schemas/AuditItem' }
total: { type: integer }
page: { type: integer }
size: { type: integer }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'428': { $ref: '#/components/responses/NotInitialized' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: POST /admin/login 获取
parameters:
CodeQuery:
name: code
in: query
required: true
schema: { type: string }
description: 取件码
UploadID:
name: uploadID
in: path
required: true
schema: { type: string }
description: 上传会话 ID
FileIDQuery:
name: id
in: query
required: true
schema: { type: integer, format: int64 }
description: 文件记录 ID
responses:
BadRequest:
description: 参数/格式错误
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 400, msg: '过期时间类型错误' }
Unauthorized:
description: 未认证
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 401, msg: '密码错误' }
Forbidden:
description: 拒绝(类型/鉴权/上限)
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 403, msg: '不允许上传该类型文件' }
NotFound:
description: 不存在/已过期
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 404, msg: '文件不存在' }
RangeNotSatisfiable:
description: Range 越界
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 416, msg: '请求范围超出文件大小' }
RateLimited:
description: 限流超限
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 423, msg: '请求次数过多,请稍后再试' }
NotInitialized:
description: 系统未初始化(除 /setup 与 /api/v1/health 外)
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 428, msg: '系统未初始化,请先完成初始化' }
NotImplemented:
description: 引擎不支持
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 501, msg: '当前存储引擎不支持该操作' }
ServiceUnavailable:
description: 存储不可用
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 503, msg: '存储服务不可用,请稍后再试' }
InsufficientStorage:
description: 容量超限
content:
application/json:
schema: { $ref: '#/components/schemas/Envelope' }
example: { code: 507, msg: '存储空间已达到管理员设置的容量上限' }
schemas:
Envelope:
type: object
description: 统一响应封装(HTTP 状态码 = code;失败时 data 缺省)
required: [code, msg]
properties:
code: { type: integer, example: 200 }
msg: { type: string, example: ok }
data: { description: 业务数据,失败时缺省 }
OkData:
type: object
properties:
ok: { type: boolean, example: true }
ShareMetadata:
type: object
properties:
code: { type: string }
name: { type: string }
size: { type: integer, format: int64 }
type: { type: string, enum: [text, file] }
is_text: { type: boolean }
created_at: { type: string }
expired_at: { type: string, nullable: true }
expires_at: { type: string, nullable: true }
expired_count: { type: integer, description: '-1 按时间/永久;>0 次数型限额' }
used_count: { type: integer }
remaining_downloads: { type: integer, nullable: true }
ShareDetail:
allOf:
- $ref: '#/components/schemas/ShareMetadata'
- type: object
properties:
text: { type: string, description: 文本内容或下载地址 }
content: { type: string, description: 文本内容(仅文本分享) }
download_url: { type: string, nullable: true, description: 代理地址或 S3 预签名直链 }
ShareFileData:
type: object
properties:
code: { type: string }
name: { type: string }
ChunkInitData:
type: object
properties:
existed: { type: boolean, example: false }
upload_id: { type: string }
chunk_size: { type: integer, format: int64 }
total_chunks: { type: integer }
uploaded_chunks: { type: array, items: { type: integer } }
ChunkUploadData:
type: object
properties:
chunk_hash: { type: string }
chunk_index: { type: integer }
skipped: { type: boolean, description: 幂等命中已传分片时为 true }
ChunkStatusData:
type: object
properties:
upload_id: { type: string }
file_name: { type: string }
file_size: { type: integer, format: int64 }
chunk_size: { type: integer, format: int64 }
total_chunks: { type: integer }
uploaded_chunks: { type: array, items: { type: integer } }
progress: { type: number }
PresignInitData:
type: object
properties:
upload_id: { type: string }
upload_url: { type: string, description: 'direct=S3 预签名 PUT URLproxy=/presign/upload/proxy/{id}' }
mode: { type: string, enum: [direct, proxy] }
expires_in: { type: integer, example: 900 }
file_path: { type: string }
proxy_upload_url: { type: string, description: '仅 proxy 模式,与 upload_url 等价' }
legacy_proxy_upload_url: { type: string, description: '仅 proxy 模式;/api 前缀兼容别名,已废弃' }
LoginData:
type: object
properties:
id: { type: string, example: admin }
username: { type: string, example: admin }
token: { type: string }
token_type: { type: string, example: Bearer }
expires_at: { type: integer, format: int64, description: Unix 秒 }
expires_in: { type: integer, description: 秒 }
AdminFileItem:
type: object
description: snake_case 与 camelCase 双份输出(文档以 snake_case 为准)
properties:
id: { type: integer, format: int64 }
code: { type: string }
name: { type: string }
prefix: { type: string }
suffix: { type: string }
size: { type: integer, format: int64 }
is_text: { type: boolean }
is_chunked: { type: boolean }
is_expired: { type: boolean }
expired_at: { type: string, nullable: true }
expired_count: { type: integer }
used_count: { type: integer }
created_at: { type: string }
has_download_limit: { type: boolean }
is_permanent: { type: boolean }
remaining_downloads: { type: integer, nullable: true }
file_hash: { type: string, nullable: true }
text: { type: boolean, description: true 表示为文本分享 }
FileUpdateInput:
type: object
required: [id]
properties:
id: { type: integer, format: int64 }
code: { type: string }
prefix: { type: string }
suffix: { type: string }
expired_at: { type: string, description: ISO 8601 }
expired_count: { type: integer }
PolicyInput:
type: object
required: [action]
properties:
id: { type: integer, format: int64 }
action: { type: string, enum: [extend_24h, extend_7d, make_permanent, reset_download_limit] }
downloadLimit: { type: integer, minimum: 1, description: 仅 reset_download_limit;默认 5 }
PolicyResult:
type: object
properties:
id: { type: integer, format: int64 }
action: { type: string }
BatchStats:
type: object
description: 请求/成功/缺失/失败统计(snake_case 与 camelCase 双份)
properties:
requestedCount: { type: integer }
deletedCount: { type: integer, description: 仅批量删除 }
updatedCount: { type: integer, description: 仅批量更新 }
missingCount: { type: integer }
failedCount: { type: integer }
deleted: { type: array, items: { type: integer, format: int64 } }
missing: { type: array, items: { type: integer, format: int64 } }
failed:
type: array
items:
type: object
properties:
id: { type: integer, format: int64 }
reason: { type: string }
AuditItem:
type: object
description: 需求 ③ 审计行(snake_case 与 camelCase 双份)
properties:
id: { type: integer, format: int64 }
action: { type: string, enum: [upload, download] }
file_code: { type: string }
file_name: { type: string }
size_bytes: { type: integer, format: int64 }
transferred_bytes: { type: integer, format: int64 }
ip: { type: string }
user_agent: { type: string }
device_os: { type: string }
device_browser: { type: string }
device_type: { type: string }
actor: { type: string, enum: [admin, guest] }
result: { type: string, enum: [success, denied, failed] }
error_msg: { type: string }
duration_ms: { type: integer, format: int64 }
created_at: { type: string }
security: []