# 通用对象存储 - 技术设计 ## Context ### 背景 当前系统的 ICCID 导入功能采用传统的文件上传方式:前端上传 CSV 文件到后端,后端解析后处理。这种方式存在以下问题: 1. **性能瓶颈**:大文件上传占用后端带宽和内存 2. **扩展性差**:未来导出功能也需要文件存储能力 3. **安全风险**:文件处理在内存中进行,存在 OOM 风险 ### 现状 - 联通云对象存储(CUCloud OSS)已开通,Bucket `cmp` 已创建 - 联通云 OSS 兼容 AWS S3 API,支持预签名 URL(已验证) - 项目使用 `github.com/aws/aws-sdk-go` v1 版本 ### 约束 - 本项目作为公司后端模板,设计需要通用化 - 联通云 OSS 的 Endpoint 格式:`http://obs-{region}.cucloud.cn` - 同一时刻只使用一个云存储提供商(不需要多云并存) ## Goals / Non-Goals ### Goals 1. **通用对象存储能力**:提供可复用的对象存储包 `pkg/storage/` 2. **预签名 URL 支持**:前端直传,不经过后端 3. **ICCID 导入改造**:集成对象存储,提升性能 4. **配置驱动**:通过配置文件切换不同云存储 ### Non-Goals 1. **不实现导出功能**:只准备能力,导出功能后续单独开发 2. **不实现多云并存**:同一时刻只用一个云 3. **不删除对象存储文件**:导入完成后只删除本地临时文件 4. **不实现断点续传**:小文件(CSV)不需要 ## Decisions ### Decision 1: 使用 AWS SDK v1 而非 v2 **选择**:`github.com/aws/aws-sdk-go`(v1) **理由**: - 联通云官方文档推荐使用 v1 - 已验证 v1 在联通云上的预签名功能正常工作 - v1 的 API 更简洁,学习成本低 **备选方案**: - AWS SDK v2:API 更现代,但联通云文档无示例,兼容性未知 - MinIO Go Client:功能更丰富,但增加额外依赖 ### Decision 2: Provider 接口设计 **选择**:定义简洁的 `Provider` 接口 ```go type Provider interface { // 上传文件 Upload(ctx context.Context, key string, reader io.Reader, contentType string) error // 下载文件到 io.Writer Download(ctx context.Context, key string, writer io.Writer) error // 下载文件到本地临时文件 DownloadToTemp(ctx context.Context, key string) (localPath string, cleanup func(), err error) // 删除文件 Delete(ctx context.Context, key string) error // 检查文件是否存在 Exists(ctx context.Context, key string) (bool, error) // 生成上传预签名 URL GetUploadURL(ctx context.Context, key string, contentType string, expires time.Duration) (string, error) // 生成下载预签名 URL GetDownloadURL(ctx context.Context, key string, expires time.Duration) (string, error) } ``` **理由**: - 接口方法覆盖导入导出所需的全部操作 - `DownloadToTemp` 封装临时文件管理,调用者无需关心清理 - 不暴露 Bucket 参数,由实现内部管理(配置驱动) ### Decision 3: 文件路径规范 **选择**:`{purpose}/{year}/{month}/{day}/{uuid}.{ext}` ``` imports/2025/01/24/550e8400-e29b-41d4.csv exports/2025/01/24/123456-cards.xlsx attachments/2025/01/24/license.pdf ``` **理由**: - 按日期组织便于管理和清理 - UUID 保证唯一性 - purpose 前缀区分业务场景 ### Decision 4: 配置结构 **选择**:嵌套配置,支持多种预签名有效期 ```yaml storage: provider: "s3" s3: endpoint: "http://obs-helf.cucloud.cn" region: "cn-langfang-2" bucket: "cmp" access_key_id: "${OSS_ACCESS_KEY_ID}" secret_access_key: "${OSS_SECRET_ACCESS_KEY}" use_ssl: false path_style: true presign: upload_expires: "15m" download_expires: "24h" temp_dir: "/tmp/junhong-storage" ``` **理由**: - 凭证通过环境变量注入,不硬编码 - 预签名有效期可配置,适应不同场景 - `path_style: true` 确保联通云兼容性 ### Decision 5: Service 层封装 **选择**:创建 `StorageService` 封装业务逻辑 ```go type StorageService struct { provider Provider config *config.StorageConfig } // 获取上传 URL(自动生成 file_key) func (s *StorageService) GetUploadURL(ctx context.Context, purpose, fileName string) (*PresignResult, error) // 下载到临时文件(自动清理) func (s *StorageService) DownloadToTemp(ctx context.Context, fileKey string) (string, func(), error) ``` **理由**: - 封装 file_key 生成逻辑(日期 + UUID) - 统一管理临时文件清理 - Handler 层只关心业务参数 ### Decision 6: 导入接口改造 **选择**:移除文件上传,改为传递 file_key **Before**: ``` POST /api/admin/iot-cards/import Content-Type: multipart/form-data carrier_id, batch_no, file ``` **After**: ``` 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" } ``` **理由**: - JSON 接口更简洁 - 文件已在对象存储,只需传路径 - Worker 从对象存储下载处理 ## Risks / Trade-offs | 风险 | 影响 | 缓解措施 | |------|------|----------| | 联通云服务不可用 | 无法上传/下载文件 | 1) 配置超时和重试 2) 监控告警 | | 预签名 URL 泄露 | 文件可能被非法访问 | 1) 短有效期(15分钟) 2) 使用 HTTPS | | 临时文件未清理 | 磁盘空间占用 | 1) defer cleanup() 2) 定期清理任务 | | **BREAKING** 接口变更 | 前端需要适配 | 1) 与前端团队同步 2) 提供迁移文档 | ## 包结构 ``` pkg/storage/ ├── storage.go # Provider 接口定义 ├── types.go # 公共类型(PresignResult, Config) ├── s3.go # S3 兼容实现 └── service.go # StorageService 封装 internal/service/storage/ └── service.go # 业务层 Service(可选,如需更多业务逻辑) internal/handler/admin/ └── storage.go # StorageHandler(获取上传 URL) ``` ## Migration Plan ### 部署步骤 1. **配置准备**: - 在各环境配置文件中添加 `storage` 配置块 - 设置环境变量 `OSS_ACCESS_KEY_ID` 和 `OSS_SECRET_ACCESS_KEY` 2. **数据库迁移**: - 执行迁移添加 `storage_bucket`、`storage_key` 字段 3. **代码部署**: - 部署新版本后端代码 4. **前端适配**: - 前端发布新版本,使用新的上传流程 ### 回滚策略 - 数据库字段为可空,不影响回滚 - 旧版前端可继续使用(需保留旧接口一段时间,或不回滚) ## Open Questions 1. **是否需要文件大小限制?** - 建议:CSV 文件限制 10MB - 待确认:具体限制值 2. **是否需要文件类型校验?** - 建议:只允许 `.csv` 文件 - 待确认:是否需要更严格的校验 3. **旧接口保留多久?** - 建议:不保留,直接切换 - 待确认:与前端团队协调