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 `。 - 未初始化时除 /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: '26.9 站点对外域名(空=用当前访问地址)' } 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 } upload_rate: { type: integer, format: int64, default: 0, description: '26.9 上行带宽字节/秒;0=不限速' } download_rate: { type: integer, format: int64, default: 0, description: '26.9 下行带宽字节/秒;0=不限速' } 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)。 26.9:支持 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 } upload_rate: { type: integer, format: int64, description: '26.9 上行带宽字节/秒;0=不限速' } download_rate: { type: integer, format: int64, description: '26.9 下行带宽字节/秒;0=不限速' } 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 服务端白名单净化(仅保留文本与 );策略与限流修改立即生效,引擎配置需重启。 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: []