# object-storage-js

面向浏览器端上传场景的对象存储适配层。它把服务端上传、主流云厂商 SDK 上传和 S3 协议上传统一到同一套初始化、上传、进度和取消接口中，业务代码只需要切换 `type` 和配置即可。

## 能力概览

- **统一接口**：所有 Provider 通过 `new ObjectStorage(config).upload(params)` 调用。
- **按需引入 SDK**：不内置云厂商 SDK，由业务方通过 `sdkObject` 传入，避免增加基础包体积。
- **上传进度**：统一输出 `{ loaded, total, percent }`。
- **取消上传**：通过 `onCancel` 暴露取消句柄；支持能力取决于底层 SDK 或 XHR。
- **大文件处理**：normal、OSS、OBS、S3/MinIO、七牛云、百度 BOS 等场景按各自 SDK 能力支持分片或并发参数。
- **扩展友好**：内部采用 Adapter 结构，新增 Provider 只需要增加对应上传实现和配置类型。

## 支持的 Provider

| Type     | Provider  | SDK / 上传方式         | 说明                            |
| -------- | --------- | ------------------ | ----------------------------- |
| `normal` | 业务服务端     | XHR + FormData     | 适合上传到自有后端，再由后端落对象存储。          |
| `oss`    | 阿里云 OSS   | `ali-oss`          | 支持 AK/SK、STS、multipartUpload。 |
| `obs`    | 华为云 OBS   | OBS Browser SDK    | 小文件走预签名 PUT，大文件走 SDK 分片上传。    |
| `minio`  | MinIO     | AWS SDK v2 S3      | S3 兼容协议，默认开启 path style。      |
| `aws`    | AWS S3    | AWS SDK v2 S3      | 兼容 AWS SDK v2 的 S3 上传能力。      |
| `qiniu`  | 七牛云 Kodo  | 七牛云 JS SDK         | 使用业务服务端签发的 uploadToken 上传。    |
| `bos`    | 百度智能云 BOS | 百度 BOS Browser SDK | 支持 AK/SK、服务端签名和分块上传。          |

## 安装

```bash
pnpm add object-storage-js
```


## 快速开始

```ts
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const storage = new ObjectStorage({
  type: ObjectStorageType.OSS,
  sdkObject: OSS,
  accessKey: 'your-access-key',
  secretKey: 'your-secret-key',
  securityToken: 'optional-sts-token',
  bucketName: 'example-bucket',
  endPoint: 'https://oss-cn-hangzhou.aliyuncs.com',
});

await storage.upload({
  file,
  fileName: `uploads/${file.name}`,
  maxSize: 100 * 1024 * 1024,
  partSize: 5 * 1024 * 1024,
  queueLength: 3,
  onUploadProgress(progress) {
    console.log(progress.percent);
  },
  onCancel(handle) {
    // handle.cancel();
  },
});
```

## UMD 用法

适合直接在浏览器中通过 `<script>` 标签引入。UMD 构建会暴露全局变量 `ObjectStorage`，其中包含默认导出的构造函数、命名导出的 `ObjectStorage` 和 `ObjectStorageType`。

```html
<input id="file" type="file" />
<script src="https://unpkg.com/object-storage-js/dist/index.umd.js"></script>
<script>
  const { ObjectStorage: StorageClient, ObjectStorageType } = window.ObjectStorage;

  document.querySelector('#file').addEventListener('change', async (event) => {
    const file = event.target.files[0];
    if (!file) return;

    const storage = new StorageClient({
      type: ObjectStorageType.NORMAL,
    });

    await storage.upload({
      file,
      fileName: file.name,
      uploadUrl: '/api/files/upload',
      mergeUrl: '/api/files/merge',
      maxSize: 100 * 1024 * 1024,
      onUploadProgress(progress) {
        console.log(`upload progress: ${progress.percent}%`);
      },
    });
  });
</script>
```

## API

### `new ObjectStorage(config)`

公共配置字段：

| 字段               | 类型                        | 说明                            |
| ---------------- | ------------------------- | ----------------------------- |
| `type`           | `ObjectStorageType`       | Provider 类型。                  |
| `sdkObject`      | `unknown`                 | 云厂商 SDK 构造函数或模块；`normal` 不需要。 |
| `sdkOtherParams` | `Record<string, unknown>` | 透传给 SDK 构造函数的额外参数。            |

Provider 配置字段：

| Provider        | 必填字段                                  | 可选字段                                                                              |
| --------------- | ------------------------------------- | --------------------------------------------------------------------------------- |
| `normal`        | 无                                     | 无                                                                                 |
| `oss`           | `sdkObject`, `bucketName`, `endPoint` | `accessKey`, `secretKey`, `securityToken`, `region`, `sdkOtherParams`             |
| `obs`           | `sdkObject`, `bucketName`, `endPoint` | `accessKey`, `secretKey`, `securityToken`, `sdkOtherParams`                       |
| `minio` / `aws` | `sdkObject`, `bucketName`             | `accessKey`, `secretKey`, `securityToken`, `endPoint`, `region`, `sdkOtherParams` |
| `qiniu`         | `sdkObject`                           | `uploadToken`, `sdkOtherParams`                                                   |
| `bos`           | `sdkObject`, `bucketName`, `endPoint` | `accessKey`, `secretKey`, `signatureUrl`, `sdkOtherParams`                        |

### `upload(params)`

| 字段                 | 类型                        | 说明                                  |
| ------------------ | ------------------------- | ----------------------------------- |
| `file`             | `File`                    | 要上传的文件。                             |
| `fileName`         | `string`                  | 目标对象 key；不传时使用 `file.name`。         |
| `maxSize`          | `number`                  | 超过该大小后启用分片/大文件策略，具体行为由 Provider 决定。 |
| `partSize`         | `number`                  | 分片大小，单位字节。                          |
| `queueLength`      | `number`                  | 分片上传并发数。                            |
| `uploadUrl`        | `string`                  | `normal` 上传接口地址。                    |
| `mergeUrl`         | `string`                  | `normal` 分片合并接口地址。                  |
| `uploadToken`      | `string`                  | 上传凭证，例如七牛云 token；会覆盖初始化配置里的 token。  |
| `contentType`      | `string`                  | 显式指定内容类型。                           |
| `sdkUploadParams`  | `Record<string, unknown>` | 透传给厂商上传方法的额外参数。                     |
| `onUploadProgress` | `(progress) => void`      | 上传进度回调。                             |
| `onCancel`         | `(handle) => void`        | 上传可取消时回调取消句柄。                       |

## Provider 示例

### normal：上传到业务服务端

```ts
const storage = new ObjectStorage({
  type: ObjectStorageType.NORMAL,
});

await storage.upload({
  file,
  uploadUrl: '/api/files/upload',
  mergeUrl: '/api/files/merge',
  maxSize: 100 * 1024 * 1024,
});
```

`normal` 分片上传会向 `uploadUrl` 提交 `file`、`uploadId`、`chunk`、`chunks`、`fileName`，最后请求 `mergeUrl?uploadId=xxx&fileName=xxx`。

### 阿里云 OSS

```ts
import OSS from 'ali-oss';
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const storage = new ObjectStorage({
  type: ObjectStorageType.OSS,
  sdkObject: OSS,
  bucketName: 'example-bucket',
  endPoint: 'https://oss-cn-hangzhou.aliyuncs.com',
  region: 'oss-cn-hangzhou',
  accessKey: 'your-sts-access-key-id',
  secretKey: 'your-sts-access-key-secret',
  securityToken: 'your-sts-token',
});

await storage.upload({
  file,
  fileName: `uploads/${file.name}`,
  maxSize: 100 * 1024 * 1024,
  partSize: 5 * 1024 * 1024,
  queueLength: 3,
  onUploadProgress: console.log,
});
```

OSS 浏览器端建议使用服务端签发的 STS 临时凭证，不建议把长期 AccessKey / SecretKey 写入前端代码。

### 华为云 OBS

```ts
import ObsClient from 'esdk-obs-browserjs';
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const storage = new ObjectStorage({
  type: ObjectStorageType.OBS,
  sdkObject: ObsClient,
  bucketName: 'example-bucket',
  endPoint: 'https://obs.cn-north-4.myhuaweicloud.com',
  accessKey: 'your-temporary-access-key-id',
  secretKey: 'your-temporary-secret-access-key',
  securityToken: 'your-security-token',
});

await storage.upload({
  file,
  fileName: `uploads/${file.name}`,
  maxSize: 100 * 1024 * 1024,
  partSize: 5 * 1024 * 1024,
  queueLength: 3,
  onUploadProgress: console.log,
});
```

OBS 小文件会通过 SDK 生成预签名 URL 后 PUT 上传，大文件会调用 OBS SDK 的 `uploadFile` 分片上传。

### MinIO / AWS S3

```ts
import AWS from 'aws-sdk';
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const minioStorage = new ObjectStorage({
  type: ObjectStorageType.MINIO,
  sdkObject: AWS,
  bucketName: 'example-bucket',
  endPoint: 'https://minio.example.com',
  region: 'us-east-1',
  accessKey: 'your-access-key',
  secretKey: 'your-secret-key',
});

await minioStorage.upload({
  file,
  fileName: `uploads/${file.name}`,
  maxSize: 100 * 1024 * 1024,
  partSize: 5 * 1024 * 1024,
  queueLength: 3,
  onUploadProgress: console.log,
});
```

AWS S3 使用方式一致，只需要把 `type` 改为 `ObjectStorageType.AWS`，并按实际服务配置 `region`、`endPoint` 和临时凭证。`minio` 会默认开启 S3 path style。

### 七牛云 Kodo

```ts
import * as qiniu from 'qiniu-js';
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const storage = new ObjectStorage({
  type: ObjectStorageType.QINIU,
  sdkObject: qiniu,
  uploadToken,
});

await storage.upload({
  file,
  fileName: `uploads/${file.name}`,
  onUploadProgress: console.log,
});
```

七牛云上传 token 应由服务端签发。浏览器端不应该持有 AccessKey / SecretKey。

### 百度 BOS

```ts
import baidubce from '@baiducloud/sdk';
import ObjectStorage, { ObjectStorageType } from 'object-storage-js';

const storage = new ObjectStorage({
  type: ObjectStorageType.BOS,
  sdkObject: baidubce.sdk,
  bucketName: 'example-bucket',
  endPoint: 'https://bj.bcebos.com',
  signatureUrl: '/api/bos/signature',
});

await storage.upload({
  file,
  fileName: `uploads/${file.name}`,
  contentType: file.type,
});
```
