# OSS直传优化方案使用说明

## 概述

本方案实现了阿里云OSS直传功能，支持：
- ✅ 前端直接上传到OSS（不经过后端服务器）
- ✅ 分片上传（支持大文件）
- ✅ 断点续传
- ✅ 上传进度查询
- ✅ 上传回调验证

## 核心优势

1. **性能提升**：文件直接上传到OSS，不经过后端服务器，减少服务器带宽和IO压力
2. **支持大文件**：通过分片上传，支持超大文件上传
3. **断点续传**：上传中断后可以继续上传，无需重新开始
4. **安全性**：使用签名验证，确保上传安全

## API接口说明

### 1. 生成OSS直传签名

**接口地址**：`POST /oss/direct/signature`

**请求参数**：
- `dir`（可选）：上传目录，默认 `video/`
- `fileName`（可选）：文件名，不传则自动生成UUID
- `maxSize`（可选）：最大文件大小（字节），默认100MB
- `expireTime`（可选）：过期时间（毫秒），默认1小时

**响应示例**：
```json
{
  "code": 200,
  "success": true,
  "data": {
    "accessKeyId": "LTAI5t...",
    "policy": "eyJleHBpcmF0aW9uIjoi...",
    "signature": "abc123...",
    "dir": "video/",
    "host": "https://alien-volume.oss-cn-beijing.aliyuncs.com",
    "expire": "1704067200",
    "ossKey": "video/uuid-filename.mp4",
    "callbackUrl": "",
    "callbackBody": ""
  }
}
```

### 2. 初始化分片上传

**接口地址**：`POST /oss/direct/multipart/init`

**请求参数**：
- `ossKey`（必需）：OSS文件路径，如 `video/filename.mp4`

**响应示例**：
```json
{
  "code": 200,
  "success": true,
  "data": {
    "uploadId": "abc123...",
    "ossKey": "video/filename.mp4"
  }
}
```

### 3. 上传分片

**接口地址**：`POST /oss/direct/multipart/upload`

**请求参数**：
- `ossKey`（必需）：OSS文件路径
- `uploadId`（必需）：上传ID
- `partNumber`（必需）：分片序号（从1开始）
- `partData`（必需）：分片数据（二进制）

**响应示例**：
```json
{
  "code": 200,
  "success": true,
  "data": {
    "partNumber": 1,
    "eTag": "\"abc123...\""
  }
}
```

### 4. 完成分片上传（合并）

**接口地址**：`POST /oss/direct/multipart/complete`

**请求参数**：
- `ossKey`（必需）：OSS文件路径
- `uploadId`（必需）：上传ID
- `partETags`（必需）：所有分片的ETag列表（JSON数组）

**请求Body示例**：
```json
[
  {"partNumber": 1, "eTag": "\"abc123...\""},
  {"partNumber": 2, "eTag": "\"def456...\""},
  {"partNumber": 3, "eTag": "\"ghi789...\""}
]
```

**响应示例**：
```json
{
  "code": 200,
  "success": true,
  "data": {
    "fileUrl": "https://alien-volume.oss-cn-beijing.aliyuncs.com/video/filename.mp4",
    "ossKey": "video/filename.mp4"
  }
}
```

### 5. 取消分片上传

**接口地址**：`POST /oss/direct/multipart/abort`

**请求参数**：
- `ossKey`（必需）：OSS文件路径
- `uploadId`（必需）：上传ID

### 6. 查询已上传的分片（断点续传）

**接口地址**：`GET /oss/direct/multipart/list`

**请求参数**：
- `ossKey`（必需）：OSS文件路径
- `uploadId`（必需）：上传ID

**响应示例**：
```json
{
  "code": 200,
  "success": true,
  "data": [
    {
      "partNumber": 1,
      "eTag": "\"abc123...\"",
      "size": 5242880
    },
    {
      "partNumber": 2,
      "eTag": "\"def456...\"",
      "size": 5242880
    }
  ]
}
```

### 7. OSS上传回调验证

**接口地址**：`POST /oss/direct/callback`

**请求头**：
- `Authorization`：OSS回调的Authorization头

**请求参数**：
- `pubKeyUrl`：公钥URL
- `callbackBody`：回调Body（JSON格式）

## 使用流程

### 方式一：简单直传（小文件，<100MB）

1. 调用 `/oss/direct/signature` 获取签名
2. 前端使用签名直接POST到OSS
3. 上传成功后，OSS会返回文件URL

### 方式二：分片上传（大文件，>100MB）

1. 调用 `/oss/direct/multipart/init` 初始化分片上传，获取 `uploadId`
2. 将文件分成多个分片（建议每个分片5-10MB）
3. 对每个分片调用 `/oss/direct/multipart/upload` 上传
4. 所有分片上传完成后，调用 `/oss/direct/multipart/complete` 合并分片
5. 获取最终的文件URL

### 方式三：断点续传

1. 调用 `/oss/direct/multipart/list` 查询已上传的分片
2. 只上传未完成的分片
3. 所有分片完成后，调用 `/oss/direct/multipart/complete` 合并

## 前端集成示例

### JavaScript示例（简单直传）

```javascript
// 1. 获取签名
const response = await fetch('/oss/direct/signature?dir=video/&fileName=test.mp4', {
  method: 'POST'
});
const { data } = await response.json();

// 2. 构建FormData
const formData = new FormData();
formData.append('key', data.ossKey);
formData.append('policy', data.policy);
formData.append('OSSAccessKeyId', data.accessKeyId);
formData.append('signature', data.signature);
formData.append('file', file); // 文件对象

// 3. 直接上传到OSS
const uploadResponse = await fetch(data.host, {
  method: 'POST',
  body: formData
});

// 4. 上传成功，获取文件URL
const fileUrl = data.host + '/' + data.ossKey;
```

### JavaScript示例（分片上传）

```javascript
// 1. 初始化分片上传
const initResponse = await fetch('/oss/direct/multipart/init', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: `ossKey=video/test.mp4`
});
const { data: initData } = await initResponse.json();
const { uploadId, ossKey } = initData;

// 2. 分片上传
const chunkSize = 5 * 1024 * 1024; // 5MB
const chunks = Math.ceil(file.size / chunkSize);
const partETags = [];

for (let i = 0; i < chunks; i++) {
  const start = i * chunkSize;
  const end = Math.min(start + chunkSize, file.size);
  const chunk = file.slice(start, end);
  
  const chunkResponse = await fetch('/oss/direct/multipart/upload', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      ossKey,
      uploadId,
      partNumber: i + 1,
      partData: await chunk.arrayBuffer()
    })
  });
  
  const { data: partData } = await chunkResponse.json();
  partETags.push({
    partNumber: partData.partNumber,
    eTag: partData.eTag
  });
}

// 3. 完成分片上传
const completeResponse = await fetch('/oss/direct/multipart/complete', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    ossKey,
    uploadId,
    partETags
  })
});

const { data: completeData } = await completeResponse.json();
const fileUrl = completeData.fileUrl;
```

## 注意事项

1. **文件大小限制**：建议单个分片5-10MB，最多支持10000个分片
2. **签名过期**：签名默认1小时过期，过期后需要重新获取
3. **错误处理**：上传失败时，可以调用 `/oss/direct/multipart/abort` 清理未完成的分片
4. **安全性**：生产环境建议配置OSS回调，验证上传结果
5. **性能优化**：大文件建议使用分片上传，可以并行上传多个分片提升速度

## 配置说明

确保在 `application.yml` 或 Nacos 中配置了以下OSS参数：

```yaml
ali:
  oss:
    accessKeyId: your-access-key-id
    accessKeySecret: your-access-key-secret
    endPoint: oss-cn-beijing.aliyuncs.com
    bucketName: your-bucket-name
```

## 故障排查

1. **签名验证失败**：检查AccessKey和Secret是否正确
2. **分片上传失败**：检查分片大小是否合理（建议5-10MB）
3. **合并失败**：确保所有分片都已成功上传，且ETag正确
4. **回调验证失败**：检查公钥URL是否可访问
