feat: 新增批量获取文件下载预签名 URL 接口

新增 POST /api/admin/storage/batch-download-urls,支持一次传入最多 50 个 file_key,返回对应预签名下载 URL 映射。解决列表页批量展示图片需多次请求的问题。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-18 10:08:45 +08:00
parent e52671eaed
commit 2413c2fe14
3 changed files with 127 additions and 0 deletions

View File

@@ -12,6 +12,86 @@ func registerStorageRoutes(router fiber.Router, handler *admin.StorageHandler, d
storage := router.Group("/storage")
groupPath := basePath + "/storage"
Register(storage, doc, groupPath, "POST", "/batch-download-urls", handler.GetBatchDownloadURLs, RouteSpec{
Summary: "批量获取文件下载预签名 URL",
Description: `## 文件展示流程
本接口用于批量获取对象存储文件的预签名下载 URL。由于存储桶为私有访问文件不能直接通过路径访问必须通过预签名 URL 才能展示。
### 为什么需要这个接口
对象存储使用私有 Bucket文件路径file_key本身无法直接访问。前端持有的 file_key 只是文件标识,需要通过本接口换取带签名的临时访问 URL才能在 ` + "`" + `<img>` + "`" + ` 或下载链接中使用。
### 完整使用流程
1. **业务接口返回 file_key**(存储在数据库中的文件路径标识)
2. **调用本接口** 传入所有需要展示的 file_key 列表
3. **使用返回的 URL** 直接赋值给 ` + "`" + `<img src>` + "`" + ` 或下载链接,图片/文件内容直接从对象存储加载,不经过后端
### 列表页批量展示示例
` + "```" + `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 生成会失败,整个请求返回错误
- 图片/文件内容直接从对象存储加载到浏览器,**不经过后端**,不消耗服务器带宽`,
Tags: []string{"对象存储"},
Input: new(dto.GetBatchDownloadURLsRequest),
Output: new(dto.GetBatchDownloadURLsResponse),
Auth: true,
})
Register(storage, doc, groupPath, "POST", "/upload-url", handler.GetUploadURL, RouteSpec{
Summary: "获取文件上传预签名 URL",
Description: `## 文件上传流程