@realsee/universal-uploader
v0.1.1
Published
universal-uploader
Keywords
Readme
UNIVERSAL UPLOADER
如视 realsee.cn 的多云部署存储上传工具。
安装
npm i @realsee/universal-uploaderbrowser example
import { Uploader, ProviderName, ProviderAdaptor } from '@realsee/universal-uploader';
const form = document.querySelector('form')!;
const status = document.querySelector('#status')!;
const ASYNC_ADAPTORS: Record<ProviderName, ProviderAdaptor> = {
'cos': () => import('@realsee/universal-uploader/adaptors/cos'),
'oss': () => import('@realsee/universal-uploader/adaptors/oss'),
'aws': () => import('@realsee/universal-uploader/adaptors/aws'),
};
function log(...text: (string | number)[]) {
status.innerHTML = text.join('<br/>');
}
async function submit(formData: FormData) {
const provider = formData.get('provider') as ProviderName;
const token = formData.get('token') as string;
const key = formData.get('key') as string;
const file = formData.get('file') as File;
const parallel = formData.get('parallel') as string;
const partSize = formData.get('part-size') as string;
const retry = formData.get('retry') as string;
if (!key || !file || !file.size || !token) {
alert('Form Invalid');
return;
}
const uploader = new Uploader(ASYNC_ADAPTORS[provider], {
getToken: () => {
const tokenObject = JSON.parse(token);
console.log(tokenObject);
return Promise.resolve(tokenObject);
},
});
const result = await uploader.upload(key, file, {
parallel: parseInt(parallel, 10) || Uploader.defaultUploadHandler.parallel,
partSize: partSize || Uploader.defaultUploadHandler.partSize,
retry: parseInt(retry, 10) || Uploader.defaultUploadHandler.retry,
onProgress(event) {
log(
'progress',
`event.percentage(${event.transferredParts}/${event.totalParts})`
);
},
onSuccess(event) {
log(
'success',
`download_url: ${event.download_url}`,
`download_url_not_cached: ${event.download_url_not_cached}`,
`etag: ${event.etag}`,
);
},
onError(error) {
console.log(error);
log(
'error',
error.message,
);
}
});
console.log('result', result);
}
form.addEventListener('submit', event => {
event.preventDefault();
const formData = new FormData(form);
submit(formData);
}, false);node example
需要 node 支持 fetch API
import { Uploader, ProviderName, ProviderAdaptor } from '@realsee/universal-uploader';
import { openAsBlob } from '@realsee/universal-uploader/node';
const ASYNC_ADAPTORS: Record<ProviderName, ProviderAdaptor> = {
'cos': () => import('@realsee/universal-uploader/adaptors/cos'),
'oss': () => import('@realsee/universal-uploader/adaptors/oss'),
'aws': () => import('@realsee/universal-uploader/adaptors/aws'),
};
async function main() {
const provider: ProviderName = 'cos';
const token = '<token>';
const key = 'a-test-file-from-universal-uploader.txt';
const file = await openAsBlob('./test.txt');
let parallel: number | undefined;
let partSize: number | undefined;
let retry: number | undefined;
const uploader = new Uploader(ASYNC_ADAPTORS[provider], {
getToken: () => {
const tokenObject = JSON.parse(token);
console.log(provider, 'token', tokenObject);
return Promise.resolve(tokenObject);
},
});
const result = await uploader.upload(key, file, {
parallel: parallel ?? Uploader.defaultUploadHandler.parallel,
partSize: partSize ?? Uploader.defaultUploadHandler.partSize,
retry: retry ?? Uploader.defaultUploadHandler.retry,
onProgress(event) {
console.log(
provider,
'progress',
`event.percentage(${event.transferredParts}/${event.totalParts})`
);
},
onSuccess(event) {
console.log(
provider,
'success',
`download_url: ${event.download_url}`,
`download_url_not_cached: ${event.download_url_not_cached}`,
`etag: ${event.etag}`,
);
},
onError(error) {
console.log(
provider,
'error',
error.message,
);
}
});
console.log(provider, 'result', result);
}
main();Node 本地大文件上传
uploadFile() 从文件系统按分片读取,不会把整个文件载入内存。AWS 与腾讯云 COS Node 均可使用;COS 通过官方 S3 兼容协议传输。
先安装 Node 上传 adapter 的可选 peer dependency:
npm i @aws-sdk/client-s3 @realsee/universal-uploaderimport { Uploader } from '@realsee/universal-uploader';
import AwsAdaptor from '@realsee/universal-uploader/adaptors/aws';
const uploader = new Uploader(AwsAdaptor, {
// 必须返回新鲜票据,不能固定返回启动时取得的票据。
getToken: () => fetchUploadToken(),
});
const controller = new AbortController();
const receipt = await uploader.uploadFile('input.zip', '/absolute/path/input.zip', {
signal: controller.signal,
onProgress(progress) {
console.log(progress.transferredBytes, progress.totalBytes);
},
});
console.log(receipt.objectPath, receipt.bytes, receipt.etag);上传票据的 bucket + region + prefix 组成一次上传租约的 locator。续期票据 locator 不变时继续分片;locator 变化时会中止当前 multipart 并从 0 重传,最多自动重启 2 次。UploadReceipt 和可枚举的 UploadError 字段不会包含临时密钥。
为保证长传可续租,uploadFile() 会拒绝在 Uploader 构造参数中传入静态 token 或 Promise,必须使用可重复调用的 getToken 函数。AbortSignal 和 requestTimeoutMs 同时覆盖票据获取与对象存储请求。
UploadError.code 是稳定分类:INVALID_ARGUMENT、LOCAL_IO_ERROR、MISSING_DEPENDENCY、INVALID_TOKEN、TOKEN_ERROR、REQUEST_TIMEOUT、STORAGE_ERROR、LEASE_RESTART_LIMIT 或 ABORTED;结合 stage 与 retryable 决定是否重试。
所有云 SDK 都是可选 peer dependency:AWS/COS Node(包括 adaptors/cos-node)统一安装 @aws-sdk/client-s3;直接导入浏览器 COS 或 OSS adaptor 时,再显式安装对应 SDK。
如何获取 token
CLI
安装后可通过命令行直接上传文件,无需编写代码。
# 基本用法
realsee-universal-uploader upload -p cos -t ./token.json -k "images/photo.jpg" --file ./photo.jpg
# 自定义分片参数
realsee-universal-uploader upload -p aws -t ./token.json -k "data/file.bin" --file ./large.bin --part-size 5mb --parallel 4 --retry 3
# JSON 输出(方便 CI/CD 集成)
realsee-universal-uploader upload -p oss -t ./token.json -k "assets/doc.pdf" --file ./doc.pdf --json长时间上传需要续期时,CLI 会重新读取 --token 指向的 JSON 文件;票据提供方应以原子替换方式更新该文件,避免读到半写入内容。
参数说明
| 参数 | 必填 | 说明 | 默认值 |
|------|------|------|--------|
| -p, --provider | ✅ | 云供应商: cos, oss, aws | - |
| -t, --token | ✅ | 上传凭证 JSON 文件路径 | - |
| -k, --key | ✅ | 上传目标路径 (key) | - |
| --file | ✅ | 要上传的本地文件路径 | - |
| --parallel | | 分片并发数 | 2 |
| --part-size | | 分片大小,如 10mb, 5mb | 10mb |
| --retry | | 失败重试次数 | 2 |
| --json | | 以 JSON 格式输出结果 | false |
其他命令
# 查看版本(两个命令名等价)
universal-uploader --version
realsee-universal-uploader --version
# 查看支持的云供应商
universal-uploader providers如何获取 token
参考 https://wiki.lianjia.com/pages/viewpage.action?pageId=542219308 中 “获取 cos token” 接口。
- 数据字典的 idenname,ak,从数据云平台获取 http://vrdata-cloud.lianjia.com/#MyResourcePage
- 将获取到的数据中的
data字段作为 token 内容作为getToken回调函数的返回值。 - 为何
getToken是回调函数? 数据分片上传,可能出现 token 过期的情况,此时会自动调用 getToken 获取最新token,继续上传行为。
如何获取上传的数据
- 从数据云平台获取 http://vrdata-cloud.lianjia.com/#DownloadDataPage
- 如果上传公用桶,可以直接在返回中的 download_url, download_url_not_cached 字段得到。
