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 全绿;二进制端到端冒烟通过
1921 lines
77 KiB
YAML
1921 lines
77 KiB
YAML
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。表单提交返回成功 HTML,JSON 提交返回 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(请求体全局上限 1MiB,Content-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/text(403/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 区间,支持 Range,Accept-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 时为其,否则 uploadSize;403);类型白名单 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/text(403/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=direct(upload_url 为预签名 PUT URL);local/webdav 返回 mode=proxy
|
||
(upload_url=/presign/upload/proxy/{uploadID})。会话 900 秒有效。
|
||
file_size 受动态策略上限(max_file_size>0 时为其,否则 uploadSize;403);保存策略同 /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 冲突 400;POST /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_limit;POST 亦可。
|
||
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 默认 4000(1~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|download;result: 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 URL;proxy=/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: []
|