UNPKG

qiniu-js

Version:

Javascript SDK for Qiniu Resource (Cloud) Storage AP

236 lines (200 loc) 6.01 kB
import * as utils from '../utils' import { getUploadUrl, UploadCompleteData } from '../api' import StatisticsLogger from '../statisticsLog' import { region } from '../config' export const DEFAULT_CHUNK_SIZE = 4 // 单位 MB /** 上传文件的资源信息配置 */ export interface Extra { /** 文件原文件名 */ fname: string /** 用来放置自定义变量 */ customVars?: { [key: string]: string } /** 自定义元信息 */ metadata?: { [key: string]: string } /** 文件类型设置 */ mimeType?: string // } /** 上传任务的配置信息 */ export interface Config { /** 是否开启 cdn 加速 */ useCdnDomain: boolean /** 是否对分片进行 md5校验 */ checkByMD5: boolean /** 强制直传 */ forceDirect: boolean /** 上传失败后重试次数 */ retryCount: number /** 自定义上传域名 */ uphost: string /** 自定义分片上传并发请求量 */ concurrentRequestLimit: number /** 是否禁止静态日志上报 */ disableStatisticsReport: boolean /** 分片大小,单位为 MB */ chunkSize: number /** 上传区域 */ region?: typeof region[keyof typeof region] } export interface UploadOptions { file: File key: string token: string putExtra?: Partial<Extra> config?: Partial<Config> } export interface UploadInfo { id: string url: string } /** 传递给外部的上传进度信息 */ export interface UploadProgress { total: ProgressCompose uploadInfo?: UploadInfo chunks?: ProgressCompose[] } export interface UploadHandler { onData: (data: UploadProgress) => void onError: (err: utils.CustomError) => void onComplete: (res: any) => void } export interface Progress { loaded: number total: number } export interface ProgressCompose { loaded: number size: number percent: number } export type XHRHandler = (xhr: XMLHttpRequest) => void const GB = 1024 ** 3 export default abstract class Base { protected config: Config protected putExtra: Extra protected xhrList: XMLHttpRequest[] = [] protected file: File protected key: string protected aborted = false protected retryCount = 0 protected token: string protected uploadUrl: string protected bucket: string protected uploadAt: number protected progress: UploadProgress protected onData: (data: UploadProgress) => void protected onError: (err: utils.CustomError) => void protected onComplete: (res: any) => void protected abstract run(): utils.Response<any> constructor(options: UploadOptions, handlers: UploadHandler, private statisticsLogger: StatisticsLogger) { this.config = { useCdnDomain: true, disableStatisticsReport: false, retryCount: 3, checkByMD5: false, uphost: '', forceDirect: false, chunkSize: DEFAULT_CHUNK_SIZE, concurrentRequestLimit: 3, ...options.config } this.putExtra = { fname: '', ...options.putExtra } this.file = options.file this.key = options.key this.token = options.token this.onData = handlers.onData this.onError = handlers.onError this.onComplete = handlers.onComplete try { this.bucket = utils.getPutPolicy(this.token).bucket } catch (e) { this.onError(e) } } public async putFile(): Promise<utils.ResponseSuccess<UploadCompleteData>> { this.aborted = false if (!this.putExtra.fname) { this.putExtra.fname = this.file.name } if (this.file.size > 10000 * GB) { const err = new Error('file size exceed maximum value 10000G') this.onError(err) throw err } if (this.putExtra.customVars) { if (!utils.isCustomVarsValid(this.putExtra.customVars)) { const err = new Error('customVars key should start width x:') this.onError(err) throw err } } if (this.putExtra.metadata) { if (!utils.isMetaDataValid(this.putExtra.metadata)) { const err = new Error('metadata key should start with x-qn-meta-') this.onError(err) throw err } } try { this.uploadUrl = await getUploadUrl(this.config, this.token) this.uploadAt = new Date().getTime() const result = await this.run() this.onComplete(result.data) if (!this.config.disableStatisticsReport) { this.sendLog(result.reqId, 200) } return result } catch (err) { this.clear() if (err.isRequestError && !this.config.disableStatisticsReport) { const reqId = this.aborted ? '' : err.reqId const code = this.aborted ? -2 : err.code this.sendLog(reqId, code) } const needRetry = err.isRequestError && err.code === 0 && !this.aborted const notReachRetryCount = ++this.retryCount <= this.config.retryCount // 以下条件满足其中之一则会进行重新上传: // 1. 满足 needRetry 的条件且 retryCount 不为 0 // 2. uploadId 无效时在 resume 里会清除本地数据,并且这里触发重新上传 if (needRetry && notReachRetryCount || err.code === 612) { return this.putFile() } this.onError(err) throw err } } private clear() { this.xhrList.forEach(xhr => xhr.abort()) this.xhrList = [] } public stop() { this.clear() this.aborted = true } public addXhr(xhr: XMLHttpRequest) { this.xhrList.push(xhr) } private sendLog(reqId: string, code: number) { this.statisticsLogger.log({ code, reqId, host: utils.getDomainFromUrl(this.uploadUrl), remoteIp: '', port: utils.getPortFromUrl(this.uploadUrl), duration: (new Date().getTime() - this.uploadAt) / 1000, time: Math.floor(this.uploadAt / 1000), bytesSent: this.progress ? this.progress.total.loaded : 0, upType: 'jssdk-h5', size: this.file.size }, this.token) } public getProgressInfoItem(loaded: number, size: number) { return { loaded, size, percent: loaded / size * 100 } } }