# 腾讯云 COS 上传集成说明

本文档记录本项目在前端（Expo/React Native）侧对腾讯云 COS 的接入方式与使用规范，包含配置、接口约定、上传用法、进度/取消/重试、URL 构建、权限与安全、常见问题等。

## 概览
- 依赖：`cos-js-sdk-v5`
- 临时密钥接口：`GET /api/users/cos-token`
- 统一封装：
  - 配置：`constants/Cos.ts`
  - 上传服务：`services/cos.ts`
  - Hook：`hooks/useCosUpload.ts`

## 安装与依赖
已在 `package.json` 中添加：

```json
{
  "dependencies": {
    "cos-js-sdk-v5": "^1.6.0"
  }
}
```

## 配置
编辑 `constants/Cos.ts`，填入实际 COS 参数：

```ts
export const COS_BUCKET = 'your-bucket-125xxxxxxx';
export const COS_REGION = 'ap-shanghai';
export const COS_PUBLIC_BASE = 'https://your-bucket-125xxxxxxx.cos.ap-shanghai.myqcloud.com';
export const COS_KEY_PREFIX = 'uploads/';
```

说明：
- COS_PUBLIC_BASE：若桶或 CDN 具有公网访问，使用对应域名（建议 HTTPS）。若为私有桶，可忽略该项（前端不能直接拼公开 URL）。
- `buildCosKey` 会依据日期和随机串生成不重复 Key，可通过 `prefix`/`userId` 定制目录。

## 后端接口约定：/api/users/cos-token
- 方法：`GET`
- 鉴权：建议要求登录态，后端根据用户权限签发最小权限临时凭证。
- 响应体示例（关键字段）：

```json
{
  "credentials": {
    "tmpSecretId": "TMPID...",
    "tmpSecretKey": "TMPKEY...",
    "sessionToken": "SESSION_TOKEN..."
  },
  "startTime": 1730000000,
  "expiredTime": 1730001800
}
```

- 最小权限策略建议（示例，后端侧）：

```json
{
  "version": "2.0",
  "statement": [
    {
      "action": [
        "name/cos:PutObject",
        "name/cos:InitiateMultipartUpload",
        "name/cos:UploadPart",
        "name/cos:CompleteMultipartUpload"
      ],
      "effect": "allow",
      "resource": [
        "qcs::cos:ap-shanghai:uid/125xxxxxxx:your-bucket-125xxxxxxx/*"
      ]
    }
  ]
}
```

注意：对上传路径进行前缀限制（如 `uploads/*`）能进一步收敛权限范围。

## 前端用法一：Hook（推荐）
在页面/组件内使用 `useCosUpload`，支持进度、取消与重试。

```tsx
import { useCosUpload } from '@/hooks/useCosUpload';
import * as ImagePicker from 'expo-image-picker';

export default function Uploader() {
  const { upload, progress, uploading, cancel } = useCosUpload({ prefix: 'images/', userId: 'uid-123' });

  const pickAndUpload = async () => {
    const res = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images });
    if (res.canceled || !res.assets?.[0]) return;
    const asset = res.assets[0];
    const result = await upload({ uri: asset.uri, name: asset.fileName, type: asset.mimeType });
    console.log('uploaded:', result); // { key, url }
  };

  return null;
}
```

说明：
- `progress` 范围 0-1；`uploading` 指示进行中；可随时 `cancel()`。
- 未开启公网访问时，`url` 可能为空（需服务端签名下载）。

## 前端用法二：直接调用服务
`services/cos.ts` 提供基础方法：

```ts
import { uploadWithRetry } from '@/services/cos';

const { key, etag } = await uploadWithRetry({
  key: 'uploads/demo.png',
  body: blob,
  contentType: 'image/png'
});
```

- 进度回调：`onProgress: ({ percent }) => { ... }`
- 取消：传入 `signal: AbortSignal`
- 重试：`maxRetries`/`backoffMs` 配置指数退避

## URL 构建
若为公网可访问场景，使用：

```ts
import { buildPublicUrl } from '@/constants/Cos';
const url = buildPublicUrl(key);
```

若为私有桶，请改走服务端生成临时下载链接或代理下载。

## 大文件与分片上传
当前默认使用 `putObject`。若需大文件/断点续传，建议切换 SDK 的分片接口 `sliceUploadFile`：

```ts
const taskId = cos.sliceUploadFile({
  Bucket: COS_BUCKET,
  Region: COS_REGION,
  Key: key,
  Body: fileOrBlob,
  onProgress(progress) { /* ... */ }
}, (err, data) => { /* ... */ });
```

如需切换，可在 `services/cos.ts` 中将 `putObject` 替换为 `sliceUploadFile`，并保留同样的进度、取消与重试包装。

## CORS 与浏览器（如使用 Web）
- COS 控制台需配置 CORS，允许来源域名、方法（PUT/POST/OPTIONS）、请求头（含 `Authorization`、`x-cos-*` 等）、暴露头。
- 若通过自有域名/网关代理，也需在代理层正确透传 CORS 与签名头。

## 安全与最佳实践
- 永久密钥仅存在服务端，通过 `/api/users/cos-token` 颁发短期凭证。
- 严控临时凭证权限与有效期，并限制上传前缀。
- 前端仅持有短期凭证；错误重试次数受限，避免暴力重放。

## 常见问题排查
- 403/签名失败：确认服务端时间同步、`region/bucket`/`resource` 配置正确；临时密钥未过期。
- CORS 失败：检查 COS 跨域规则；确认需要的请求头（含 `Authorization`）均已放行。
- URL 访问 403：私有桶下请改用服务端签名下载；或为指定前缀开启公共读（谨慎）。
- MIME/ContentType：从 ImagePicker 取 `mimeType`，或按扩展名兜底设置。

## 测试清单
- 小图上传成功且进度递增、可取消。
- 取消后不再继续发片段；重试如期生效。
- 返回的 `key` 能通过 `buildPublicUrl` 拼出可访问地址（公网桶）。
- 临时密钥过期后，能够重新获取并继续上传。

## 变更点速记
- 依赖：`cos-js-sdk-v5`
- 新增：`constants/Cos.ts`、`services/cos.ts`、`hooks/useCosUpload.ts`
- 接口：`GET /api/users/cos-token`（返回临时凭证）

---
如需我将某页面接入示例按钮或改造为分片上传，请在需求中指明目标文件与交互期望。
