From c23e7ac3d018a7e4425a7e7d9f4b970cf2759300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Tue, 24 Sep 2024 17:16:38 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91=E7=8E=B0=E5=B7=B2=E6=94=AF=E6=8C=81=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=96=87=E4=BB=B6=E7=9B=B4=E4=BC=A0=E5=88=B0OSS?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 5 +- package.json | 3 +- sheep/api/infra/file.js | 21 +++ .../s-uploader/choose-and-upload-file.js | 158 ++++++++++++++---- sheep/components/s-uploader/s-uploader.vue | 6 +- 5 files changed, 158 insertions(+), 35 deletions(-) diff --git a/.env b/.env index 6172fd55..a78c4d81 100644 --- a/.env +++ b/.env @@ -2,12 +2,15 @@ SHOPRO_VERSION = v1.8.3 # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) -SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn +SHOPRO_BASE_URL = http://123.183.21.179:48080 # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080 ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 +SHOPRO_UPLOAD_TYPE = client + # 后端接口前缀(一般不建议调整) SHOPRO_API_PATH = /app-api diff --git a/package.json b/package.json index d2e3bb5f..cf0e81dd 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "luch-request": "^3.0.8", "pinia": "^2.0.33", "pinia-plugin-persist-uni": "^1.2.0", - "weixin-js-sdk": "^1.6.0" + "weixin-js-sdk": "^1.6.0", + "crypto-js": "^4.2.0" }, "devDependencies": { "prettier": "^2.8.7", diff --git a/sheep/api/infra/file.js b/sheep/api/infra/file.js index f7f2a964..fdf59ac5 100644 --- a/sheep/api/infra/file.js +++ b/sheep/api/infra/file.js @@ -1,4 +1,5 @@ import { baseUrl, apiPath, tenantId } from '@/sheep/config'; +import request from '@/sheep/request'; const FileApi = { // 上传文件 @@ -40,6 +41,26 @@ const FileApi = { }); }); }, + + // 获取文件预签名地址 + getFilePresignedUrl: (path) => { + return request({ + url: '/infra/file/presigned-url', + method: 'GET', + params: { + path, + }, + }); + }, + + // 创建文件 + createFile: (data) => { + return request({ + url: '/infra/file/create', // 请求的 URL + method: 'POST', // 请求方法 + data: data, // 要发送的数据 + }); + }, }; export default FileApi; diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js index 604ff9d8..64175e4c 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -1,5 +1,6 @@ 'use strict'; import FileApi from '@/sheep/api/infra/file'; +import CryptoJS from 'crypto-js'; const ERR_MSG_OK = 'chooseAndUploadFile:ok'; const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; @@ -106,7 +107,7 @@ function normalizeChooseAndUploadFileRes(res, fileType) { item.name = item.path.substring(item.path.lastIndexOf('/') + 1); } if (fileType) { - item.fileType = fileType; + item.type = fileType; } item.cloudPath = Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); }); @@ -116,6 +117,28 @@ function normalizeChooseAndUploadFileRes(res, fileType) { return res; } +function convertToArrayBuffer(uniFile) { + return new Promise((resolve, reject) => { + const fs = uni.getFileSystemManager(); + + fs.readFile({ + filePath: uniFile.path, // 确保路径正确 + success: (fileRes) => { + try { + // 将读取的内容转换为 ArrayBuffer + const arrayBuffer = new Uint8Array(fileRes.data).buffer; + resolve(arrayBuffer); + } catch (error) { + reject(new Error(`转换为 ArrayBuffer 失败: ${error.message}`)); + } + }, + fail: (error) => { + reject(new Error(`读取文件失败: ${error.errMsg}`)); + }, + }); + }); +} + function uploadCloudFiles(files, max = 5, onUploadProgress) { files = JSON.parse(JSON.stringify(files)); const len = files.length; @@ -165,36 +188,61 @@ function uploadCloudFiles(files, max = 5, onUploadProgress) { }); } -function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { - return choosePromise - .then((res) => { - if (onChooseFile) { - const customChooseRes = onChooseFile(res); - if (typeof customChooseRes !== 'undefined') { - return Promise.resolve(customChooseRes).then((chooseRes) => - typeof chooseRes === 'undefined' ? res : chooseRes, - ); - } +async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { + // 获取选择的文件 + const res = await choosePromise; + // 处理文件选择回调 + let files = res.tempFiles || []; + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + files = await Promise.resolve(customChooseRes); + if (typeof files === 'undefined') { + files = res.tempFiles || []; // Fallback } - return res; - }) - .then((res) => { - if (res === false) { - return { - errMsg: ERR_MSG_OK, - tempFilePaths: [], - tempFiles: [], - }; - } - return res; - }) - .then(async (files) => { - for (let file of files.tempFiles) { - const { data } = await FileApi.uploadFile(file.path); - file.url = data; - } - return files; - }); + } + } + + // 如果是前端直连上传 + if (UPLOAD_TYPE.CLIENT === import.meta.env.SHOPRO_UPLOAD_TYPE) { + for (const file of files) { + // 获取二进制文件对象 + const fileBuffer = await convertToArrayBuffer(file); + // 1.1 生成文件名称 + const fileName = await generateFileName(fileBuffer, file.name); + // 1.2 获取文件预签名地址 + const { data: presignedInfo } = await FileApi.getFilePresignedUrl(file.name); + // 1.3 上传文件 + await uni.request({ + url: presignedInfo.uploadUrl, // 预签名的上传 URL + method: 'PUT', // 使用 PUT 方法 + header: { + 'Content-Type': file.type + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型 + }, + data: fileBuffer, // 文件的路径,适用于小程序 + success: (res) => { + // 1.4. 记录文件信息到后端(异步) + createFile(presignedInfo, fileName, file); + // 1.5. 重新赋值 + file.url = presignedInfo.url; + file.name = fileName; + console.log('上传成功:', res); + }, + fail: (err) => { + console.error('上传失败:', err); + }, + }); + } + return files; + } else { + // 后端上传 + for (let file of files) { + const { data } = await FileApi.uploadFile(file.path); + file.url = data; + } + + return files; + } } function chooseAndUploadFile( @@ -210,4 +258,54 @@ function chooseAndUploadFile( return uploadFiles(chooseAll(opts), opts); } +/** + * 生成文件名称(使用算法SHA256) + * @param arrayBuffer 二进制文件对象 + * @param fileName 文件名称 + */ +async function generateFileName(arrayBuffer, fileName) { + return new Promise((resolve, reject) => { + try { + // 创建 WordArray + const wordArray = CryptoJS.lib.WordArray.create(new Uint8Array(arrayBuffer)); + // 计算SHA256 + const sha256 = CryptoJS.SHA256(wordArray).toString(); + // 拼接后缀 + const ext = fileName.substring(fileName.lastIndexOf('.')); + resolve(`${sha256}${ext}`); + } catch (error) { + reject(new Error('计算SHA256失败: ' + error.message)); + } + }); +} + +/** + * 创建文件信息 + * @param vo 文件预签名信息 + * @param name 文件名称 + * @param file 文件 + */ +function createFile(vo, name, file) { + const fileVo = { + configId: vo.configId, + url: vo.url, + path: name, + name: file.name, + type: file.fileType, + size: file.size, + }; + FileApi.createFile(fileVo); + return fileVo; +} + +/** + * 上传类型 + */ +const UPLOAD_TYPE = { + // 客户端直接上传(只支持S3服务) + CLIENT: 'client', + // 客户端发送到后端上传 + SERVER: 'server', +}; + export { chooseAndUploadFile, uploadCloudFiles }; diff --git a/sheep/components/s-uploader/s-uploader.vue b/sheep/components/s-uploader/s-uploader.vue index 95cfb057..a459d84f 100644 --- a/sheep/components/s-uploader/s-uploader.vue +++ b/sheep/components/s-uploader/s-uploader.vue @@ -369,7 +369,7 @@ }, }) .then((result) => { - this.setSuccessAndError(result.tempFiles); + this.setSuccessAndError(result); }) .catch((err) => { console.log('选择失败', err); @@ -453,7 +453,7 @@ if (index === -1 || !this.files) break; if (item.errMsg === 'request:fail') { - this.files[index].url = item.path; + this.files[index].url = item.url; this.files[index].status = 'error'; this.files[index].errMsg = item.errMsg; // this.files[index].progress = -1 @@ -587,7 +587,7 @@ path: v.path, size: v.size, fileID: v.fileID, - url: v.url, + url: v.path, }); }); return newFilesData; From f6b2eb69252d46404cfdf2fe5214d3d04d3456d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Tue, 24 Sep 2024 17:17:18 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index a78c4d81..ef22147e 100644 --- a/.env +++ b/.env @@ -9,7 +9,7 @@ SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080 ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 -SHOPRO_UPLOAD_TYPE = client +SHOPRO_UPLOAD_TYPE = server # 后端接口前缀(一般不建议调整) SHOPRO_API_PATH = /app-api From 76e7ed8f250bc0ee658ef4d8c672d61a987bb613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Tue, 24 Sep 2024 19:14:52 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E6=96=87=E4=BB=B6=E7=9B=B4=E4=BC=A0=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sheep/components/s-uploader/choose-and-upload-file.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js index 64175e4c..a92e0ace 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -107,7 +107,7 @@ function normalizeChooseAndUploadFileRes(res, fileType) { item.name = item.path.substring(item.path.lastIndexOf('/') + 1); } if (fileType) { - item.type = fileType; + item.fileType = fileType; } item.cloudPath = Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); }); @@ -217,7 +217,7 @@ async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { url: presignedInfo.uploadUrl, // 预签名的上传 URL method: 'PUT', // 使用 PUT 方法 header: { - 'Content-Type': file.type + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型 + 'Content-Type': file.fileType + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型 }, data: fileBuffer, // 文件的路径,适用于小程序 success: (res) => { From 6c958914766b327ac0f90005d29e4a46abc0484d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Wed, 25 Sep 2024 08:25:20 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../s-uploader/choose-and-upload-file.js | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js index a92e0ace..18f0487a 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -206,12 +206,10 @@ async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { // 如果是前端直连上传 if (UPLOAD_TYPE.CLIENT === import.meta.env.SHOPRO_UPLOAD_TYPE) { for (const file of files) { - // 获取二进制文件对象 - const fileBuffer = await convertToArrayBuffer(file); - // 1.1 生成文件名称 - const fileName = await generateFileName(fileBuffer, file.name); - // 1.2 获取文件预签名地址 + // 1.1 获取文件预签名地址 const { data: presignedInfo } = await FileApi.getFilePresignedUrl(file.name); + // 1.2 获取二进制文件对象 + const fileBuffer = await convertToArrayBuffer(file); // 1.3 上传文件 await uni.request({ url: presignedInfo.uploadUrl, // 预签名的上传 URL @@ -222,10 +220,9 @@ async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) { data: fileBuffer, // 文件的路径,适用于小程序 success: (res) => { // 1.4. 记录文件信息到后端(异步) - createFile(presignedInfo, fileName, file); + createFile(presignedInfo, file); // 1.5. 重新赋值 file.url = presignedInfo.url; - file.name = fileName; console.log('上传成功:', res); }, fail: (err) => { @@ -258,38 +255,16 @@ function chooseAndUploadFile( return uploadFiles(chooseAll(opts), opts); } -/** - * 生成文件名称(使用算法SHA256) - * @param arrayBuffer 二进制文件对象 - * @param fileName 文件名称 - */ -async function generateFileName(arrayBuffer, fileName) { - return new Promise((resolve, reject) => { - try { - // 创建 WordArray - const wordArray = CryptoJS.lib.WordArray.create(new Uint8Array(arrayBuffer)); - // 计算SHA256 - const sha256 = CryptoJS.SHA256(wordArray).toString(); - // 拼接后缀 - const ext = fileName.substring(fileName.lastIndexOf('.')); - resolve(`${sha256}${ext}`); - } catch (error) { - reject(new Error('计算SHA256失败: ' + error.message)); - } - }); -} - /** * 创建文件信息 * @param vo 文件预签名信息 - * @param name 文件名称 * @param file 文件 */ -function createFile(vo, name, file) { +function createFile(vo, file) { const fileVo = { configId: vo.configId, url: vo.url, - path: name, + path: file.name, name: file.name, type: file.fileType, size: file.size, From 6f8e82e6cf0463920978215905c3bddc3e368e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Wed, 25 Sep 2024 23:16:50 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 20 +++++++++---------- .../s-uploader/choose-and-upload-file.js | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.env b/.env index ef22147e..1ccae9a0 100644 --- a/.env +++ b/.env @@ -1,31 +1,31 @@ # 版本号 -SHOPRO_VERSION = v1.8.3 +SHOPRO_VERSION=v1.8.3 # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) -SHOPRO_BASE_URL = http://123.183.21.179:48080 +SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) -SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080 +SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080 ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 -SHOPRO_UPLOAD_TYPE = server +SHOPRO_UPLOAD_TYPE=client # 后端接口前缀(一般不建议调整) -SHOPRO_API_PATH = /app-api +SHOPRO_API_PATH=/app-api # 后端 websocket 接口前缀 -SHOPRO_WEBSOCKET_PATH = /infra/ws +SHOPRO_WEBSOCKET_PATH=/infra/ws # 开发环境运行端口 -SHOPRO_DEV_PORT = 3000 +SHOPRO_DEV_PORT=3000 # 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀 -SHOPRO_STATIC_URL = http://test.yudao.iocoder.cn +SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn ### SHOPRO_STATIC_URL = https://file.sheepjs.com # 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启) -SHOPRO_MPLIVE_ON = 0 +SHOPRO_MPLIVE_ON=0 # 租户ID 默认 1 -SHOPRO_TENANT_ID = 1 +SHOPRO_TENANT_ID=1 diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js index 18f0487a..d3c5bccc 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -1,6 +1,5 @@ 'use strict'; import FileApi from '@/sheep/api/infra/file'; -import CryptoJS from 'crypto-js'; const ERR_MSG_OK = 'chooseAndUploadFile:ok'; const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; From f491e87d6afc9beb8983a9f5d4f29f7aaac1e4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E8=B6=8A?= <552369664@qq.com> Date: Thu, 26 Sep 2024 09:08:57 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=91=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index cf0e81dd..d2e3bb5f 100644 --- a/package.json +++ b/package.json @@ -94,8 +94,7 @@ "luch-request": "^3.0.8", "pinia": "^2.0.33", "pinia-plugin-persist-uni": "^1.2.0", - "weixin-js-sdk": "^1.6.0", - "crypto-js": "^4.2.0" + "weixin-js-sdk": "^1.6.0" }, "devDependencies": { "prettier": "^2.8.7",