Skip to content

Commit

Permalink
Merge pull request #210 from ifanrx/release/3.23.3
Browse files Browse the repository at this point in the history
Release/3.23.3
  • Loading branch information
jiajun-ifanr authored Sep 21, 2023
2 parents 7020a87 + 02d649a commit 9c99ac9
Show file tree
Hide file tree
Showing 19 changed files with 1,713 additions and 39 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 3.23.3 (2023-08-21)
- [A] 小程序各端 SDK 添加断点续传方法

## 3.23.2 (2023-08-1)
- [F] 修复 React Native 第三方登录字段取值错误

Expand Down
13 changes: 13 additions & 0 deletions core/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,18 @@ function compareBaseLibraryVersion(v1, v2) {
return 0
}

const promisify = func => {
return (args = {}) =>
new Promise((resolve, reject) => {
func(
Object.assign(args, {
success: resolve,
fail: reject,
})
)
})
}

module.exports = {
mergeRequestHeader,
log: log.log,
Expand Down Expand Up @@ -479,4 +491,5 @@ module.exports = {
withRetry: require('./withRetry'),
getBytedanceAppName: require('./getBytedanceAppName'),
compareBaseLibraryVersion,
promisify,
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minapp-sdk",
"version": "3.23.2",
"version": "3.23.3",
"main": "./lib/index.js",
"browser": "./lib/web.js",
"miniprogram": "lib",
Expand Down
2 changes: 2 additions & 0 deletions sdk-file/src/alipay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const request = require('./request')
const baasRequest = require('./baasRequest')
const polyfill = require('./polyfill')
const uploadFile = require('./uploadFile')
const multipartUploadFile = require('./multipartUploadFile')
const pay = require('./pay')
const alipayQRCode = require('./alipayQRCode')
const reportTicket = require('./reportTicket')
Expand All @@ -19,6 +20,7 @@ BaaS.use(request)
BaaS.use(baasRequest)
BaaS.use(polyfill)
BaaS.use(uploadFile)
BaaS.use(multipartUploadFile)
BaaS.use(pay)
BaaS.use(alipayQRCode)
BaaS.use(reportTicket)
Expand Down
251 changes: 251 additions & 0 deletions sdk-file/src/alipay/multipartUploadFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
const constants = require('core-module/constants')
const HError = require('core-module/HError')
const utils = require('core-module/utils')
const storage = require('core-module/storage')
const dayjs = require('dayjs')
const {multipartUpload} = require('core-module/upload')
const SparkMD5 = require('spark-md5')

const storageKey = constants.STORAGE_KEY.MULTIPART_UPLOAD
const { getAuthorization, init, complete } = multipartUpload

class UploadError extends HError {
mapErrorMessage(code) {
switch (code) {
case 11:
return '文件不存在'
case 12:
return '上传文件失败'
case 13:
return '没有权限'
default:
return '未知错误'
}
}
}

const createFileChunks = async fileParams => {
const chunkSize = 1 * 1024 * 1024 // 又拍云限制每次只能上传 1MB
const readFileAsync = utils.promisify(my.getFileSystemManager().readFile)

let current = 0
let chunks = [] // 保存与返回所有切片的参数

while (current < fileParams.fileSize) {
// 文件进行切片
const length = Math.min(fileParams.fileSize - current, chunkSize)

const chunk = await readFileAsync({
filePath: fileParams.filePath,
position: current,
length,
})

chunks.push({ data: chunk.data, length })
current = current + chunkSize
}

return chunks
}

const multipartStorage = {
get: key => {
const currentValue = storage.get(storageKey) || {}
return currentValue[key]
},
set: (key, value) => {
const currentValue = storage.get(storageKey) || {}
storage.set(storageKey, {
...currentValue,
[key]: {
...(currentValue[key] ? currentValue[key] : {}),
...value,
},
})
},
delete: key => {
const currentValue = storage.get(storageKey) || {}
delete currentValue[key]
storage.set(storageKey, currentValue)
},
}

/**
* 上传大文件。
* @memberof BaaS
* @param {FileParams} fileParams 文件参数
* @param {FileMeta} metaData 文件元信息
* @param {string} type 文件类型
* @return {Promise<any>}
*/
const multipartUploadFile = async (fileParams, metaData) => {
if (
!fileParams ||
typeof fileParams !== 'object' ||
!fileParams.filePath ||
!fileParams.fileSize
) {
throw new HError(605)
}

if (
fileParams.fileName !== undefined &&
typeof fileParams.fileName !== 'string'
) {
throw new HError(605)
}

if (!metaData) {
metaData = {}
} else if (typeof metaData !== 'object') {
throw new HError(605)
}

const wxRequest = utils.promisify(my.request)
const chunks = await createFileChunks(fileParams)
const md5 = SparkMD5.ArrayBuffer.hash(chunks)

const getUploadRecord = () => {
const now = dayjs()
const uploadRecord = multipartStorage.get(md5) || {}

if (!uploadRecord.init_time) return null

if (now.diff(dayjs(uploadRecord.init_time), 'hour') < 24) {
return uploadRecord
}

return null
}

const initMultipartUpload = async () => {
const uploadRecord = getUploadRecord()

// 有上传记录,则续传
if (uploadRecord) {
const res = await getAuthorization(uploadRecord.id)
const initConfig = {...res.data, ...uploadRecord}
return initConfig
}

const filename =
fileParams.fileName || utils.getFileNameFromPath(fileParams.filePath)
const data = {file_size: fileParams.fileSize, filename}
const res = await init(data, utils.replaceQueryParams(metaData))
const initConfig = res.data

// 超时或者初始上传,都重新设置新值
multipartStorage.set(md5, {
init_time: new Date().getTime(),
multi_part_id: +initConfig.multi_part_id,
multi_uuid: initConfig.multi_uuid,
upload_url: initConfig.upload_url,
id: initConfig.id,
})

return initConfig
}

const multipartUpload = async data => {
const _chunks = chunks.slice(data.multi_part_id)
let uuid = data.multi_uuid
let nextPartId = data.multi_part_id

const uploadChunk = async chunk => {
const res = await wxRequest({
url: data.upload_url,
method: 'PUT',
data: chunk.data,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': chunk.length,
Authorization: data.authorization,
'x-date': data.date,
'x-upyun-multi-stage': 'upload',
'x-upyun-multi-uuid': uuid,
'x-upyun-part-id': nextPartId,
},
})

const stringifiedStatusCode = res.statusCode + ''

if (!stringifiedStatusCode.startsWith('2')) {
// 如果是 authorization 超时 30 分钟,则需重新获取
if (res && res.data && res.data.code === 40100002) {
const uploadRecord = getUploadRecord()
const res = await getAuthorization(uploadRecord.id)
data.authorization = res.data.authorization
data.date = res.data.date
return await uploadChunk(chunk)
}

throw res
}

return res
}

for (let chunk of _chunks) {
const res = await uploadChunk(chunk)

uuid = res.header['x-upyun-multi-uuid']
nextPartId = res.header['x-upyun-next-part-id']

multipartStorage.set(md5, {
multi_part_id: +nextPartId,
multi_uuid: uuid,
}) // 保存当前上传记录

if (nextPartId === -1) break
}

return { file: data, multi_uuid: uuid }
}

const completeMultipartUpload = async data => {
const uploadRecord = getUploadRecord()

return complete(uploadRecord.id, data.multi_uuid).then(res => {
if (res.data.upload_status !== 'success') {
throw new HError(617)
}

multipartStorage.delete(md5) // 上传成功,删除上传记录
return {
data: {
status: 'ok',
path: data.file.path,
file: {
id: data.file.id,
path: data.file.path,
name: data.file.name,
created_at: data.file.created_at,
// mime_type: fileObj.type, // 又拍云没有提供
cdn_path: data.file.cdn_path,
size: fileParams.fileSize,
},
},
}
})
}

try {
const initConfig = await initMultipartUpload()
const data = await multipartUpload(initConfig)
return await completeMultipartUpload(data)
} catch (error) {
// 没有 statusCode 返回,一般是网络问题,不做删除处理
if (!error.statusCode) {
throw new HError(600)
}

multipartStorage.delete(md5) // 上传成功,删除上传记录
throw error
}
}

module.exports = function (BaaS) {
BaaS.multipartUploadFile = multipartUploadFile
}

module.exports.UploadError = UploadError
1 change: 1 addition & 0 deletions sdk-file/src/baidu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ BaaS.use(reportTemplateMsgAnalytics)
BaaS.request = require('./request')
BaaS._baasRequest = require('./baasRequest')
BaaS.uploadFile = require('./uploadFile')
BaaS.multipartUploadFile = require('./multipartUploadFile')
BaaS._createRequestMethod()
// 暴露 BaaS 到小程序环境
if (typeof swan !== 'undefined') {
Expand Down
Loading

0 comments on commit 9c99ac9

Please sign in to comment.