From 22d79ffc069763ce1baf76696e73cd120621ae23 Mon Sep 17 00:00:00 2001 From: Luk Date: Thu, 8 Aug 2024 11:30:29 +0800 Subject: [PATCH] test uploaded file types by file extension --- unitool.js | 58 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/unitool.js b/unitool.js index 5c853ae..f9c005a 100644 --- a/unitool.js +++ b/unitool.js @@ -1,4 +1,7 @@ -// uniapp people side tools +// uniapp client tools + +const crypto = require('crypto') +const path = require('path') //import './ican-H5Api.js' // 对齐H5Api: https://ext.dcloud.net.cn/plugin?id=415 // 注意要取消默认自带的 showToast https://uniapp.dcloud.io/api/system/clipboard?id=%e6%b3%a8%e6%84%8f const BASE_TYPE_DEFAULT = 'SERVER' // one of { SERVER: 服务器, UNICLOUD_FUNC: 云函数, UNICLOUD_OBJECT: 云对象 } @@ -7,11 +10,17 @@ const my = { // getCurrentPages() 在 topWindow/App.vue 里有可能为空,所以用 getApp().$store 更安全. 20230513: 发现在微信小程序模拟器里,getApp().$store.state 未定义,所以还是用 globalThis.wo?.ss return globalThis.wo?.ss?.i18n?.mylang || getApp()?.$store?.state?.i18n?.mylang }, + isTextFile (ext = '') { + return (wo.envar.textExtensionList || ['txt', 'text']).includes(ext.replace(/^\./, '').toLowerCase()) + }, + isImageFile (ext = '') { + return (wo.envar.imageExtensionList || ['jpg', 'jpeg', 'png', 'gif', 'webp', 'image']).includes(ext.replace(/^\./, '').toLowerCase()) + }, + isVideoFile (ext = '') { + return (wo.envar.videoExtensionList || ['avi', 'mp4', 'mov', 'wmv', 'video']).includes(ext.replace(/^\./, '').toLowerCase()) + } } -const crypto = require('crypto') -const path = require('path') - module.exports = { // 用直观的色彩,代替语义化的类型: // uView 中大量组件都使用 type 来设置颜色,而 uni-ui 中只有少量组件用到。 @@ -293,20 +302,22 @@ module.exports = { } = {}) { // 有的管理后台不需要登录就允许上传,例如 cmctoy。因此不要在这里依赖登录状态。 - let filePath, fileSize + let filePath, fileSize, filePicked if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) if (errorChoose) { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '图像选择失败。请稍后再试,或向客服投诉。', enUS: 'Image choose failed. Please try again later, or report to customer service.' } } } fileSize = tempFiles?.[0]?.size + filePicked = tempFiles?.[0] filePath = tempFilePaths?.[0] } else if (mediaType === 'video') { - let [errorChoose, { tempFilePath, size, duration }] = await uni.chooseVideo({ sourceType }) + let [errorChoose, { tempFilePath, tempFile, size, duration, }] = await uni.chooseVideo({ sourceType }) if (errorChoose) { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '视频选择失败。请稍后再试,或向客服投诉。', enUS: 'Video choose failed. Please try again later, or report to customer service.' } } } fileSize = size + filePicked = tempFile filePath = tempFilePath } else { // #ifdef WEB @@ -316,11 +327,8 @@ module.exports = { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '文件选择失败。请稍后再试,或向客服投诉。', enUS: 'File choose failed. Please try again later, or report to customer service.' } } } fileSize = tempFiles?.[0]?.size + filePicked = tempFiles?.[0] filePath = tempFilePaths?.[0] - let fileExt = path.extname(tempFiles?.[0]?.name?.toLowerCase?.() || '') - if (Array.isArray(mediaType) && !mediaType?.includes?.(fileExt)) { - return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '不支持的文件后缀\n' + fileExt, enUS: 'Unsupported file extension\n' + fileExt } } - } // #endif // #ifndef WEB return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '请切换到网页端上传文件!', enUS: 'Please switch to WebApp to upload files.' } } @@ -334,6 +342,13 @@ module.exports = { return { _state: 'CER_FILE_TOO_LARGE', _msg: { zhCN: `文件太大了,无法上传。最大允许 ${sizeLimitMB}`, enUS: `File too large to upload. Maximum allowed is ${sizeLimitMB}` } } } + let fileExt = filePicked?.name?.split?.('.')?.pop?.()?.toLowerCase?.() + if (mediaType === 'image' && !my.isImageFile(fileExt) + || mediaType === 'video' && !my.isVideoFile(fileExt) + || Array.isArray(mediaType) && !mediaType?.includes?.('.' + fileExt)) { + return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '不支持的文件:\n' + filePicked?.name, enUS: 'Unsupported file:\n' + filePicked?.name } } + } + if (filePath) { for (let key in formData) { // multer 不会自动处理 JSON 数据,必须前后端配合处理 @@ -374,13 +389,14 @@ module.exports = { // return { _state: 'USER_OFFLINE', errMsg: 'offline user cannot upload files' } // } - let filePath, cloudPath, fileSize + let filePath, cloudPath, fileSize, filePicked if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) if (errorChoose) { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '图像选择失败。请稍后再试,或向客服投诉。', enUS: 'Image choose failed. Please try again later, or report to customer service.' } } } fileSize = tempFiles?.[0]?.size + filePicked = tempFiles?.[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 @@ -392,13 +408,13 @@ module.exports = { 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) { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '视频选择失败。请稍后再试,或向客服投诉。', enUS: 'Video choose failed. Please try again later, or report to customer service.' } } } fileSize = size + filePicked = tempFile 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 @@ -425,11 +441,16 @@ module.exports = { return { _state: 'CER_FAIL_CHOOSE', _msg: { zhCN: '文件选择失败。请稍后再试,或向客服投诉。', enUS: 'File choose failed. Please try again later, or report to customer service.' } } } fileSize = tempFiles?.[0]?.size - if (fileSize) { - return { _state: 'SUCCESS', fileUrl: tempFiles?.[0]?.url } - } else { + filePicked = tempFiles?.[0] + + let fileExt = filePicked?.name?.split?.('.')?.pop?.()?.toLowerCase?.() + if (Array.isArray(mediaType) && !mediaType?.includes?.('.' + fileExt)) { + return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '不支持的文件:\n' + filePicked?.name, enUS: 'Unsupported file:\n' + filePicked?.name } } + } + if (!fileSize) { return { _state: 'CER_EMPTY_FILE', _msg: { zhCN: '文件为空,无法上传。', enUS: 'Empty files cannot be uploaded.' } } } + return { _state: 'SUCCESS', fileUrl: tempFiles?.[0]?.url } // #endif // #ifndef WEB return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '请切换到网页端上传文件!', enUS: 'Please switch to WebApp to upload files.' } } @@ -443,6 +464,13 @@ module.exports = { return { _state: 'CER_FILE_TOO_LARGE', _msg: { zhCN: `文件太大了,无法上传。最大允许 ${sizeLimitMB}`, enUS: `File too large to upload. Maximum allowed is ${sizeLimitMB}` } } } + let fileExt = filePicked?.name?.split?.('.')?.pop?.()?.toLowerCase?.() + if (mediaType === 'image' && !my.isImageFile(fileExt) + || mediaType === 'video' && !my.isVideoFile(fileExt) + || Array.isArray(mediaType) && !mediaType?.includes?.('.' + fileExt)) { + return { _state: 'UNSUPPORTED_FILETYPE', _msg: { zhCN: '不支持的文件:\n' + filePicked?.name, enUS: 'Unsupported file:\n' + filePicked?.name } } + } + if (filePath) { this.showLoading() const { fileID, requestId } = await uniCloud.uploadFile({