From 0b18c15347dc9b4bae848a74d60bda2c81156dfd Mon Sep 17 00:00:00 2001 From: luk Date: Thu, 9 May 2024 20:04:02 +0800 Subject: [PATCH] check file size before uploading --- unitool.js | 134 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 52 deletions(-) diff --git a/unitool.js b/unitool.js index 048944b..28fcb5c 100644 --- a/unitool.js +++ b/unitool.js @@ -19,22 +19,22 @@ export default { RED: 'error', GREEN: 'success', BLUE: 'primary', - YELLOW: 'warning', + YELLOW: 'warn', GREY: 'info', - uAlertTips: { YELLOW: 'warning', BLUE: 'primary', RED: 'error', GREY: 'info', GREEN: 'success' }, - uBadge: { RED: 'error', BLUE: 'primary', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, - uButton: { WHITE: 'default', BLUE: 'primary', YELLOW: 'warning', RED: 'error', GREY: 'info', GREEN: 'success' }, - uNoticeBar: { YELLOW: 'warning', BLUE: 'primary', RED: 'error', GREY: 'info', GREEN: 'success', TRANS: 'none' }, - uTag: { BLUE: 'primary', RED: 'error', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, - uToast: { BLACK: 'default', BLUE: 'primary', YELLOW: 'warning', RED: 'error', GREY: 'info', GREEN: 'success' }, - uTopTips: { BLUE: 'primary', RED: 'error', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, + // uAlertTips: { YELLOW: 'warning', BLUE: 'primary', RED: 'error', GREY: 'info', GREEN: 'success' }, + // uBadge: { RED: 'error', BLUE: 'primary', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, + // uButton: { WHITE: 'default', BLUE: 'primary', YELLOW: 'warning', RED: 'error', GREY: 'info', GREEN: 'success' }, + // uNoticeBar: { YELLOW: 'warning', BLUE: 'primary', RED: 'error', GREY: 'info', GREEN: 'success', TRANS: 'none' }, + // uTag: { BLUE: 'primary', RED: 'error', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, + // uToast: { BLACK: 'default', BLUE: 'primary', YELLOW: 'warning', RED: 'error', GREY: 'info', GREEN: 'success' }, + // uTopTips: { BLUE: 'primary', RED: 'error', YELLOW: 'warning', GREY: 'info', GREEN: 'success' }, - uniBadge: { GREY: 'default', YELLOW: 'warning', BLUE: 'primary', GREEN: 'success', RED: 'error' }, - uniButton: { GREY: 'default', RED: 'warn', RGB: 'primary' }, - uniPopupDialog: { GREEN: 'success', YELLOW: 'warn', RED: 'error', GREY: 'info' }, - uniPopupMessage: { GREEN: 'success', YELLOW: 'warn', RED: 'error', GREY: 'info' }, - uniTag: { GREY: 'default', YELLOW: 'warning', BLUE: 'primary', GREEN: 'success', RED: 'error', PURPLE: 'royal' }, + // uniBadge: { GREY: 'default', YELLOW: 'warning', BLUE: 'primary', GREEN: 'success', RED: 'error' }, + // uniButton: { GREY: 'default', RED: 'warn', RGB: 'primary' }, + // uniPopupDialog: { GREEN: 'success', YELLOW: 'warn', RED: 'error', GREY: 'info' }, + // uniPopupMessage: { GREEN: 'success', YELLOW: 'warn', RED: 'error', GREY: 'info' }, + // uniTag: { GREY: 'default', YELLOW: 'warning', BLUE: 'primary', GREEN: 'success', RED: 'error', PURPLE: 'royal' }, }, thisPage () { @@ -282,7 +282,7 @@ export default { }, async pickupFile2Server ({ - mediaType = 'image', + mediaType = 'image', // could be: image, video, array of supported extensions, anything else count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], @@ -293,23 +293,38 @@ export default { } = {}) { // 有的管理后台不需要登录就允许上传,例如 cmctoy。因此不要在这里依赖登录状态。 - let filePath + let filePath, fileSize if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) + fileSize = tempFiles?.[0]?.size filePath = tempFilePaths?.[0] } else if (mediaType === 'video') { - let [errorChoose, { tempFilePath }] = await uni.chooseVideo({ sourceType }) + let [errorChoose, { tempFilePath, size, duration }] = await uni.chooseVideo({ sourceType }) + fileSize = size filePath = tempFilePath } else { // #ifdef WEB - let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseFile({ count, type: 'all' }) + // https://uniapp.dcloud.net.cn/api/media/file.html + let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseFile({ count, extension: Array.isArray(mediaType) ? mediaType : undefined, type: Array.isArray(mediaType) ? undefined : 'all' }) // 20240429 但是测试下来 extension 参数无效 + fileSize = tempFiles?.[0]?.size filePath = tempFilePaths?.[0] + let fileExt = path.extname(tempFiles?.[0]?.name?.toLowerCase?.() || '') + if (Array.isArray(mediaType) && !mediaType?.includes?.(fileExt)) { + console.log('pickupFile2Server: UNSUPPORTED_FILETYPE ' + fileExt) + this.showToast({ type: this.c2t.RED, title: { zhCN: '不支持的文件后缀\n' + fileExt, enUS: 'Unsupported file extension\n' + fileExt } }) + return { _state: 'UNSUPPORTED_FILETYPE' } + } // #endif // #ifndef WEB - return { _state: 'UNKNOWN_MEDIATYPE' } + return { _state: 'UNSUPPORTED_FILETYPE' } // #endif } + if (!fileSize) { + this.showToast({ type: this.c2t.YELLOW, title: this.localizeText({ zhCN: '文件为空,无法上传', enUS: 'Empty files cannot be uploaded' }) }) + return { _state: 'CER_EMPTY_FILE' } + } + if (filePath) { for (let key in formData) { // multer 不会自动处理 JSON 数据,必须前后端配合处理 @@ -334,7 +349,7 @@ export default { } if (data?._state === 'SUCCESS' && data?.path) { - // 后台送来的 baseUrl 在开发环境下,不一定符合前端实际,因为后台只知道预设的 servUrl,而前端会再根据 location.origin 来调整,所以再设置一个 fileUrl。 + // 后台送来的 baseUrl 在开发环境下,不一定符合前端实际,因为后台只知道预设的 servUrl 例如 https://pexserver.test.tic.cc:7739/... ,而开发环境下实际上是 http://localhost:7739/... 所以再设置一个 fileUrl 来根据 location.origin 调整。// todo: 改名叫 clientUrl 或 userUrl 或 baseUrl4Client,与 baseUrl 对应 return { _state: 'SUCCESS', fileUrl: this.make_server_url(data.path), ...data } // { path, destination, filename, fileUrl, cid?, ipfsUrl?, baseUrl?, ...file } 注意,data.path 不包含起头的 '/' } else { return { _state: 'CLIENT_FAIL_UPLOAD_FILE', error: errorUpload } @@ -350,52 +365,64 @@ export default { // return { _state: 'USER_OFFLINE', errMsg: 'offline user cannot upload files' } // } - let filePath, cloudPath + let filePath, cloudPath, fileSize if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) - if (!errorChoose) { - // uni.showModal({ title: 'tempFilePaths[0]=' + tempFilePaths[0] }) - filePath = tempFilePaths?.[0] // 在 H5 上并不是文件路径名,而是类似 "blob:http://localhost:8080/f0d3e54d-0694-4803-8097-641d76a10b0d“。// 在 iOS 上是 "_doc/uniapp_temp_1598593902955/compressed/1598593925815.png", 有时还包含从 file:/// 开始的完整路径名 - let random = crypto.randomBytes(16).toString('hex') - // #ifndef WEB - // let [errorGetImageInfo, { path, width, height, orientation, type }] = await uni.getImageInfo({ src: filePath }) - // cloudPath = path // 完整路径,包含后缀名。形如 file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/D064A425A8BEC13F9D8F741B98B37BC5/doc/uniapp_temp_1598593902955/compressed/1598593925815.png - cloudPath = `APP_${wo.envar.clientInfo.osName}_${random}${path.extname(filePath || '')}` - // #endif - // #ifdef WEB - cloudPath = `WEB_${wo.envar.clientInfo.osName}_${random}${path.extname(tempFiles?.[0]?.name || '')}` // name is available in H5 only. 只包含文件名和后缀名,不包含路径。 - // #endif - if (!path.extname(cloudPath)) cloudPath += '.jpg' + if (errorChoose) { + return { _state: 'CER_FAIL_CHOOSE' } } + fileSize = tempFiles?.[0]?.size + filePath = tempFilePaths?.[0] // 在 H5 上并不是文件路径名,而是类似 "blob:http://localhost:8080/f0d3e54d-0694-4803-8097-641d76a10b0d“。// 在 iOS 上是 "_doc/uniapp_temp_1598593902955/compressed/1598593925815.png", 有时还包含从 file:/// 开始的完整路径名 + let random = crypto.randomBytes(16).toString('hex') + // #ifndef WEB + // let [errorGetImageInfo, { path, width, height, orientation, type }] = await uni.getImageInfo({ src: filePath }) + // cloudPath = path // 完整路径,包含后缀名。形如 file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/D064A425A8BEC13F9D8F741B98B37BC5/doc/uniapp_temp_1598593902955/compressed/1598593925815.png + cloudPath = `APP_${wo.envar.clientInfo.osName}_${random}${path.extname(filePath || '')}` + // #endif + // #ifdef WEB + cloudPath = `WEB_${wo.envar.clientInfo.osName}_${random}${path.extname(tempFiles?.[0]?.name || '')}` // name is available in H5 only. 只包含文件名和后缀名,不包含路径。 + // #endif + if (!path.extname(cloudPath)) cloudPath += '.jpg' + } else if (mediaType === 'video') { let [errorChoose, { tempFilePath, tempFile, duration, size, width, height, name }] = await uni.chooseVideo({ sourceType, maxDuration }) - if (!errorChoose) { - // uni.showModal({ title: 'tempFilePath=' + tempFilePath }) - filePath = tempFilePath // 在 iOS 上形如 "file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/26B43CD2F587D37FC6799108434A6F84/doc/uniapp_temp_1598596171580/gallery/IMG_3082.MOV" - let random = crypto.randomBytes(16).toString('hex') - // #ifndef WEB - cloudPath = `APP_${wo.envar.clientInfo.osName}_${random}_dur${duration}${path.extname(filePath || '')}` - // #endif - // #ifdef WEB - cloudPath = `WEB_${wo.envar.clientInfo.osName}_${random}_dur${duration}${path.extname(name || '')}` // tempFile and name are H5 only - // #endif - // iOS 上测试,filePath 为 *.MOV,而阿里云只允许 *.mp4, 所以默认添加 .mp4 后缀。参见 https://uniapp.dcloud.net.cn/uniCloud/storage?id=clouduploadfile - // 20200915测试,阿里云支持上传 *.mov 了。 - if (!path.extname(cloudPath)) cloudPath += '.mp4' + if (errorChoose) { + return { _state: 'CER_FAIL_CHOOSE' } } + fileSize = size + filePath = tempFilePath // 在 iOS 上形如 "file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/26B43CD2F587D37FC6799108434A6F84/doc/uniapp_temp_1598596171580/gallery/IMG_3082.MOV" + let random = crypto.randomBytes(16).toString('hex') + // #ifndef WEB + cloudPath = `APP_${wo.envar.clientInfo.osName}_${random}_dur${duration}${path.extname(filePath || '')}` + // #endif + // #ifdef WEB + cloudPath = `WEB_${wo.envar.clientInfo.osName}_${random}_dur${duration}${path.extname(name || '')}` // tempFile and name are H5 only + // #endif + // iOS 上测试,filePath 为 *.MOV,而阿里云只允许 *.mp4, 所以默认添加 .mp4 后缀。参见 https://uniapp.dcloud.net.cn/uniCloud/storage?id=clouduploadfile + // 20200915测试,阿里云支持上传 *.mov 了。 + if (!path.extname(cloudPath)) cloudPath += '.mp4' } else { + // https://uniapp.dcloud.net.cn/uniCloud/storage.html#uploadfile let { errMsg, tempFilePaths, tempFiles } = await uniCloud.chooseAndUploadFile({ - type: 'all', // valid for H5 only: https://uniapp.dcloud.net.cn/uniCloud/storage.html#uploadfile + type: Array.isArray(mediaType) ? undefined : 'all', // valid for H5 only + extension: Array.isArray(mediaType) ? mediaType : undefined, count: 1, // extention: [], // onChooseFile: ({ errMsg, tempFilePaths, tempFiles }) => { }, // onUploadProgress: ({ index, loaded, total, tempFilePath, tempFile }) => { } }) - if (errMsg === 'chooseAndUploadFile:ok') { - return { _state: 'SUCCESS', fileUrl: tempFiles?.[0]?.url } - } else { - return { _state: 'CLIENT_FAIL_chooseAndUploadFile' } + if (errMsg !== 'chooseAndUploadFile:ok') { + return { _state: 'CER_FAIL_CHOOSE' } } + fileSize = tempFiles?.[0]?.size + if (fileSize) { + return { _state: 'SUCCESS', fileUrl: tempFiles?.[0]?.url } + } + } + + if (!fileSize) { + this.showToast({ type: this.c2t.YELLOW, title: this.localizeText({ zhCN: '文件为空,无法上传', enUS: 'Empty files cannot be uploaded' }) }) + return { _state: 'CER_EMPTY_FILE' } } if (filePath) { @@ -421,7 +448,7 @@ export default { async pickupFile ({ baseType = globalThis.wo?.envar?.baseTypeDefault || BASE_TYPE_DEFAULT, - mediaType = 'image', + mediaType = 'image', // could be image, video, array of supported extensions, anything else // 20240502 todo: rename to pickupFileType count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], @@ -442,6 +469,9 @@ export default { open_url ({ url, title, inWebview } = {}) { url = this.localizeText?.(url) || url + if (!url) { + return + } if (wo.envar.inPc) { window.open(url, '_blank') } else if (inWebview) {