# 对象存储使用指南

本文档介绍如何在后端代码中使用对象存储服务。

## 配置

通过环境变量配置对象存储：

```bash
# 存储提供商
export JUNHONG_STORAGE_PROVIDER="s3"

# S3 配置
export JUNHONG_STORAGE_S3_ENDPOINT="http://obs-helf.cucloud.cn"
export JUNHONG_STORAGE_S3_REGION="cn-langfang-2"
export JUNHONG_STORAGE_S3_BUCKET="cmp"
export JUNHONG_STORAGE_S3_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export JUNHONG_STORAGE_S3_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export JUNHONG_STORAGE_S3_USE_SSL="false"
export JUNHONG_STORAGE_S3_PATH_STYLE="true"

# 预签名 URL 配置
export JUNHONG_STORAGE_PRESIGN_UPLOAD_EXPIRES="15m"
export JUNHONG_STORAGE_PRESIGN_DOWNLOAD_EXPIRES="24h"

# 临时文件目录
export JUNHONG_STORAGE_TEMP_DIR="/tmp/junhong-storage"
```

详细配置说明见 [环境变量配置文档](../environment-variables.md)

## StorageService 使用

### 获取预签名上传 URL

```go
result, err := storageService.GetUploadURL(ctx, "iot_import", "cards.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
if err != nil {
    return err
}

// result.URL - 预签名上传 URL
// result.FileKey - 文件路径（用于后续业务接口）
// result.ExpiresIn - URL 有效期（秒）
```

### 下载文件到临时目录

```go
localPath, cleanup, err := storageService.DownloadToTemp(ctx, fileKey)
if err != nil {
    return err
}
defer cleanup() // 处理完成后自动删除临时文件

// 使用 localPath 读取文件内容
f, _ := os.Open(localPath)
defer f.Close()
```

### 直接上传文件

```go
reader := bytes.NewReader(content)
err := storageService.Provider().Upload(ctx, fileKey, reader, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
```

### 检查文件是否存在

```go
exists, err := storageService.Provider().Exists(ctx, fileKey)
```

### 删除文件

```go
err := storageService.Provider().Delete(ctx, fileKey)
```

## Purpose 类型

| Purpose | 说明 | 生成路径 | ContentType |
|---------|------|---------|-------------|
| iot_import | ICCID 导入 (Excel) | imports/YYYY/MM/DD/uuid.xlsx | application/vnd.openxmlformats... |
| export | 数据导出 | exports/YYYY/MM/DD/uuid.xlsx | application/vnd.openxmlformats... |
| attachment | 附件上传 | attachments/YYYY/MM/DD/uuid.ext | 自动检测 |

## 错误处理

存储相关错误码定义在 `pkg/errors/codes.go`：

| 错误码 | 说明 |
|-------|------|
| 1090 | 对象存储服务未配置 |
| 1091 | 文件上传失败 |
| 1092 | 文件下载失败 |
| 1093 | 文件不存在 |
| 1094 | 不支持的文件用途 |
| 1095 | 不支持的文件类型 |

## 在 Handler 中使用

```go
type MyHandler struct {
    storageService *storage.Service
}

func (h *MyHandler) Upload(c *fiber.Ctx) error {
    var req dto.GetUploadURLRequest
    if err := c.BodyParser(&req); err != nil {
        return errors.New(errors.CodeInvalidParam, "参数解析失败")
    }

    result, err := h.storageService.GetUploadURL(
        c.UserContext(),
        req.Purpose,
        req.FileName,
        req.ContentType,
    )
    if err != nil {
        return errors.New(errors.CodeStorageUploadFailed, err.Error())
    }

    return response.Success(c, result)
}
```

## 在 Worker 中使用

```go
func (h *TaskHandler) HandleTask(ctx context.Context, task *asynq.Task) error {
    // 从任务记录获取文件路径
    fileKey := importTask.StorageKey

    // 下载到临时文件
    localPath, cleanup, err := h.storageService.DownloadToTemp(ctx, fileKey)
    if err != nil {
        return err
    }
    defer cleanup()

    // 解析文件
    f, _ := os.Open(localPath)
    defer f.Close()
    
    // 处理文件内容...
}
```

## 测试验证

运行对象存储功能测试：

```bash
go run scripts/test_storage.go
```

测试内容包括：
1. 生成预签名上传 URL
2. 上传测试文件
3. 检查文件是否存在
4. 下载到临时文件
5. 删除测试文件
