Files
huang 45aa7deb87
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
feat: 添加环境变量管理工具和部署配置改版
主要改动:
- 新增交互式环境配置脚本 (scripts/setup-env.sh)
- 新增本地启动快捷脚本 (scripts/run-local.sh)
- 新增环境变量模板文件 (.env.example)
- 部署模式改版:使用嵌入式配置 + 环境变量覆盖
- 添加对象存储功能支持
- 改进 IoT 卡片导入任务
- 优化 OpenAPI 文档生成
- 删除旧的配置文件,改用嵌入式默认配置
2026-01-26 10:28:29 +08:00

8.1 KiB
Raw Permalink Blame History

对象存储集成 - 任务清单

1. 基础设施

  • 1.1 在 pkg/config/config.go 添加 Storage 配置结构体
  • 1.2 在 configs/config.yaml 添加 storage 配置块(含环境变量占位符)
  • 1.3 创建 pkg/storage/ 目录结构

2. Provider 实现

  • 2.1 创建 pkg/storage/storage.go - Provider 接口定义
  • 2.2 创建 pkg/storage/types.go - 公共类型PresignResult、Config
  • 2.3 创建 pkg/storage/s3.go - S3Provider 实现
    • 2.3.1 实现 NewS3Provider 构造函数
    • 2.3.2 实现 Upload 方法
    • 2.3.3 实现 Download 方法
    • 2.3.4 实现 DownloadToTemp 方法(含 cleanup 函数)
    • 2.3.5 实现 Delete 方法
    • 2.3.6 实现 Exists 方法
    • 2.3.7 实现 GetUploadURL 方法
    • 2.3.8 实现 GetDownloadURL 方法
  • 2.4 创建 pkg/storage/service.go - StorageService 封装
    • 2.4.1 实现 GenerateFileKey 方法purpose + 日期 + UUID
    • 2.4.2 实现 GetUploadURL 方法(生成 key + 获取预签名)
    • 2.4.3 实现 DownloadToTemp 方法(透传 + 日志)

3. Bootstrap 集成

  • 3.1 在 internal/bootstrap/ 添加 storage 初始化逻辑
  • 3.2 在 cmd/api/main.go 集成 StorageService可选配置缺失时跳过
  • 3.3 在 cmd/worker/main.go 集成 StorageService

4. API 接口

  • 4.1 创建 internal/model/dto/storage.go - 请求/响应 DTO
    • 4.1.1 GetUploadURLRequestfile_name, content_type, purpose
    • 4.1.2 GetUploadURLResponseupload_url, file_key, expires_in
  • 4.2 创建 internal/handler/admin/storage.go - StorageHandler
    • 4.2.1 实现 GetUploadURL 方法
  • 4.3 注册路由 POST /api/admin/storage/upload-url
    • 4.3.1 在 RouteSpec.Description 添加前端使用流程说明Markdown 格式)
  • 4.4 更新 cmd/api/docs.gocmd/gendocs/main.go 添加 StorageHandler

5. ICCID 导入改造

  • 5.1 修改 internal/model/iot_card_import_task.go
    • 5.1.1 添加 StorageBucket 字段
    • 5.1.2 添加 StorageKey 字段
  • 5.2 创建数据库迁移文件添加新字段
  • 5.3 修改 internal/model/dto/iot_card_import.go
    • 5.3.1 将 CreateImportTaskRequest 从 multipart 改为 JSON
    • 5.3.2 添加 FileKey 字段,移除 File 字段
  • 5.4 修改 internal/handler/admin/iot_card_import.go
    • 5.4.1 移除 c.FormFile() 逻辑
    • 5.4.2 改为接收 JSON body 解析 file_key
    • 5.4.3 保存 storage_bucket 和 storage_key 到任务记录
  • 5.6 更新导入接口的 RouteSpec.Description
    • 5.6.1 说明接口变更BREAKING: multipart → JSON
    • 5.6.2 说明完整导入流程(先获取上传 URL → 上传文件 → 调用导入接口)
  • 5.5 修改 internal/task/iot_card_import.go
    • 5.5.1 从任务记录获取 storage_key
    • 5.5.2 调用 StorageService.DownloadToTemp 下载文件
    • 5.5.3 处理完成后调用 cleanup() 删除临时文件
    • 5.5.4 保留原有 CSV 解析逻辑

6. 错误码

  • 6.1 在 pkg/errors/codes.go 添加存储相关错误码
    • 6.1.1 ErrStorageUploadFailed
    • 6.1.2 ErrStorageDownloadFailed
    • 6.1.3 ErrStorageFileNotFound
    • 6.1.4 ErrStorageInvalidPurpose

7. 测试

  • 7.1 创建 scripts/test_storage.go - 对象存储功能验证脚本
  • 7.2 联通云后台验证文件上传成功
  • 7.3 现有 Worker 测试通过

8. 文档

  • 8.1 创建 docs/object-storage/使用指南.md - 后端开发指南
    • 8.1.1 StorageService 使用示例
    • 8.1.2 配置说明
    • 8.1.3 错误处理
  • 8.2 创建 docs/object-storage/前端接入指南.md - 前端接入说明
    • 8.2.1 文件上传完整流程(时序图)
    • 8.2.2 获取预签名 URL 接口说明
    • 8.2.3 使用预签名 URL 上传文件(含代码示例)
    • 8.2.4 ICCID 导入接口变更说明BREAKING CHANGE
    • 8.2.5 错误处理和重试策略
  • 8.3 更新 README.md 添加对象存储功能说明

附录:前端接入指南内容大纲

A. 文件上传流程(时序图)

前端                      后端 API                    对象存储
  │                          │                          │
  │ 1. POST /storage/upload-url                         │
  │    {file_name, content_type, purpose}               │
  │ ─────────────────────────►                          │
  │                          │                          │
  │ 2. 返回 {upload_url, file_key, expires_in}          │
  │ ◄─────────────────────────                          │
  │                          │                          │
  │ 3. PUT upload_url (文件内容)                        │
  │ ─────────────────────────────────────────────────► │
  │                          │                          │
  │ 4. 上传成功 (200 OK)                                │
  │ ◄───────────────────────────────────────────────── │
  │                          │                          │
  │ 5. POST /iot-cards/import                           │
  │    {carrier_id, batch_no, file_key}                 │
  │ ─────────────────────────►                          │
  │                          │                          │
  │ 6. 返回任务创建成功                                  │
  │ ◄─────────────────────────                          │

B. 接口说明RouteSpec.Description 内容参考)

获取上传 URL 接口

## 文件上传流程

### 第一步:获取预签名 URL
调用本接口获取上传 URL 和 file_key。

### 第二步:直接上传到对象存储
使用返回的 `upload_url` 发起 PUT 请求上传文件:
\`\`\`javascript
const response = await fetch(upload_url, {
  method: 'PUT',
  headers: { 'Content-Type': content_type },
  body: file
});
\`\`\`

### 第三步:使用 file_key 调用业务接口
上传成功后,使用 `file_key` 调用相关业务接口(如 ICCID 导入)。

### 注意事项
- 预签名 URL 有效期 15 分钟,请及时使用
- 上传失败时可重新获取 URL 重试
- file_key 在上传成功后永久有效

### purpose 可选值
| 值 | 说明 | 生成路径 |
|---|------|---------|
| iot_import | ICCID 导入 | imports/YYYY/MM/DD/uuid.csv |
| export | 数据导出 | exports/YYYY/MM/DD/uuid.xlsx |
| attachment | 附件上传 | attachments/YYYY/MM/DD/uuid.ext |

ICCID 导入接口

## ⚠️ 接口变更说明BREAKING CHANGE

本接口已从 `multipart/form-data` 改为 `application/json`### 变更前
\`\`\`
POST /api/admin/iot-cards/import
Content-Type: multipart/form-data
carrier_id, batch_no, file (文件)
\`\`\`

### 变更后
\`\`\`
POST /api/admin/iot-cards/import
Content-Type: application/json
{
  "carrier_id": 1,
  "batch_no": "BATCH-2025-01",
  "file_key": "imports/2025/01/24/abc123.csv"
}
\`\`\`

### 完整导入流程
1. 调用 `POST /api/admin/storage/upload-url` 获取上传 URL
2. 使用预签名 URL 上传 CSV 文件
3. 使用返回的 `file_key` 调用本接口

C. 前端代码示例TypeScript

// 完整的文件上传流程
async function uploadAndImport(file: File, carrierId: number, batchNo: string) {
  // 1. 获取预签名 URL
  const { data } = await api.post('/storage/upload-url', {
    file_name: file.name,
    content_type: file.type || 'text/csv',
    purpose: 'iot_import'
  });
  
  const { upload_url, file_key } = data;
  
  // 2. 上传文件到对象存储
  await fetch(upload_url, {
    method: 'PUT',
    headers: { 'Content-Type': file.type || 'text/csv' },
    body: file
  });
  
  // 3. 调用导入接口
  return api.post('/iot-cards/import', {
    carrier_id: carrierId,
    batch_no: batchNo,
    file_key: file_key
  });
}