Files
junhong_cmp_fiber/internal/routes/storage.go
break c7f9e005af
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 1h43m42s
feat(业务用户组): AUG26-003 业务用户组与店铺负责人分组导入
- 迁移 000221:新增 tb_business_user_group、tb_business_user_group_member、tb_shop_business_owner_import_task,成员一账号一行由部分唯一索引保证,店铺所属组按当前负责人实时推导,不回填历史分组。
- 用户组 CRUD、成员改组/清空归属、店铺批量交接(原子失败不部分写入)。
- 店铺负责人 CSV 导入任务:逐行独立事务、逐行明细、任务级与行级失败分离。
- 读侧推导与筛选:未分组、业务线、停用组可筛出并带停用标记。
- 补齐操作审计动作与资源、openapi 清单、发布门禁巡检表清单。
- 归档 add-shop-salesperson-groups 变更并同步 openspec/specs/business-user-group,补齐 AUG26-003 验证证据链。
2026-09-14 16:51:44 +08:00

155 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
func registerStorageRoutes(router fiber.Router, handler *admin.StorageHandler, doc *openapi.Generator, basePath string) {
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: `## 文件上传流程
本接口用于获取对象存储的预签名上传 URL实现前端直传文件到对象存储。
### 完整流程
1. **调用本接口** 获取预签名 URL 和 file_key
2. **使用预签名 URL 上传文件** 发起 PUT 请求直接上传到对象存储
3. **调用业务接口** 使用 file_key 调用相关业务接口(如 ICCID 导入)
### 前端上传示例
` + "```" + `javascript
// 1. 获取预签名 URL
const { data } = await api.post('/storage/upload-url', {
file_name: 'cards.xlsx',
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
purpose: 'iot_import'
});
// 2. 上传文件到对象存储
await fetch(data.upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' },
body: file
});
// 3. 调用业务接口
await api.post('/iot-cards/import', {
carrier_id: 1,
batch_no: 'BATCH-2025-01',
file_key: data.file_key
});
` + "```" + `
### purpose 可选值
| 值 | 说明 | 生成路径格式 |
|---|------|-------------|
| iot_import | ICCID/设备导入 (Excel) | imports/YYYY/MM/DD/uuid.xlsx |
| batch_purchase | 资产套餐批量订购 (CSV) | batch-purchases/YYYY/MM/DD/uuid.csv |
| device_batch_allocation | 设备批量分配、设置套餐系列或回收 (CSV) | device-batch-allocations/YYYY/MM/DD/uuid.csv |
| shop_import | 店铺负责人导入 (CSV) | shop-imports/YYYY/MM/DD/uuid.csv |
| export | 数据导出 | exports/YYYY/MM/DD/uuid.xlsx |
| attachment | 附件上传 | attachments/YYYY/MM/DD/uuid.ext |
### 注意事项
- 预签名 URL 有效期 **15 分钟**,请及时使用
- 上传时 Content-Type 需与请求时一致
- file_key 在上传成功后永久有效,用于后续业务接口调用
- 上传失败时可重新调用本接口获取新的 URL`,
Tags: []string{"对象存储"},
Input: new(dto.GetUploadURLRequest),
Output: new(dto.GetUploadURLResponse),
Auth: true,
})
}