From 4b8784d5e7d34d9089326cdc3b1e731b27609fa8 Mon Sep 17 00:00:00 2001 From: huang Date: Sat, 18 Apr 2026 10:08:53 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20OpenAPI=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=20URL=20=E6=8E=A5=E5=8F=A3=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- docs/admin-openapi.yaml | 160 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index d9b9a7b..04a7908 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -4268,6 +4268,30 @@ components: description: 总订单数 type: integer type: object + DtoGetBatchDownloadURLsRequest: + properties: + file_keys: + description: 文件路径标识列表,最多 50 个 + items: + type: string + minLength: 1 + nullable: true + type: array + required: + - file_keys + type: object + DtoGetBatchDownloadURLsResponse: + properties: + expires_in: + description: URL 有效期(秒) + type: integer + urls: + additionalProperties: + type: string + description: file_key 到预签名下载 URL 的映射 + nullable: true + type: object + type: object DtoGetUploadURLRequest: properties: content_type: @@ -21150,6 +21174,142 @@ paths: summary: 代理商资金概况 tags: - 代理商资金管理 + /api/admin/storage/batch-download-urls: + post: + description: |- + ## 文件展示流程 + + 本接口用于批量获取对象存储文件的预签名下载 URL。由于存储桶为私有访问,文件不能直接通过路径访问,必须通过预签名 URL 才能展示。 + + ### 为什么需要这个接口 + + 对象存储使用私有 Bucket,文件路径(file_key)本身无法直接访问。前端持有的 file_key 只是文件标识,需要通过本接口换取带签名的临时访问 URL,才能在 `` 或下载链接中使用。 + + ### 完整使用流程 + + 1. **业务接口返回 file_key**(存储在数据库中的文件路径标识) + 2. **调用本接口** 传入所有需要展示的 file_key 列表 + 3. **使用返回的 URL** 直接赋值给 `` 或下载链接,图片/文件内容直接从对象存储加载,不经过后端 + + ### 列表页批量展示示例 + + ```javascript + // 假设列表数据中每条记录有 avatar_key 字段 + const listData = await api.get('/customers'); + + // 收集所有 file_key(过滤掉空值) + const fileKeys = listData.map(item => item.avatar_key).filter(Boolean); + + // 一次性换取所有下载 URL + const { data } = await api.post('/storage/batch-download-urls', { + file_keys: fileKeys + }); + + // 渲染时通过 map 取对应 URL + listData.forEach(item => { + item.avatarUrl = data.urls[item.avatar_key] ?? ''; + }); + ``` + + ### 单个文件展示示例 + + ```javascript + // 详情页只有一个文件时,也走批量接口(保持一致) + const { data } = await api.post('/storage/batch-download-urls', { + file_keys: [record.license_key] + }); + + const licenseUrl = data.urls[record.license_key]; + ``` + + ### 前端缓存建议 + + 预签名 URL 有效期 **24 小时**,建议在内存中缓存,避免同一页面重复请求: + + ```javascript + const urlCache = new Map(); // key: file_key, value: { url, expireAt } + + async function getFileUrl(fileKey) { + const cached = urlCache.get(fileKey); + if (cached && cached.expireAt > Date.now()) { + return cached.url; + } + const { data } = await api.post('/storage/batch-download-urls', { + file_keys: [fileKey] + }); + const url = data.urls[fileKey]; + urlCache.set(fileKey, { url, expireAt: Date.now() + data.expires_in * 1000 * 0.9 }); + return url; + } + ``` + + ### 注意事项 + + - 单次请求最多 **50 个** file_key + - 预签名 URL 有效期 **24 小时**,过期后需重新获取 + - file_key 不存在时对应的 URL 生成会失败,整个请求返回错误 + - 图片/文件内容直接从对象存储加载到浏览器,**不经过后端**,不消耗服务器带宽 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DtoGetBatchDownloadURLsRequest' + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoGetBatchDownloadURLsResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 批量获取文件下载预签名 URL + tags: + - 对象存储 /api/admin/storage/upload-url: post: description: |-