diff --git a/.env b/.env index 6172fd55..1ccae9a0 100644 --- a/.env +++ b/.env @@ -1,28 +1,31 @@ # 版本号 -SHOPRO_VERSION = v1.8.3 +SHOPRO_VERSION=v1.8.3 # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) -SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn +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=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/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..d3c5bccc 100644 --- a/sheep/components/s-uploader/choose-and-upload-file.js +++ b/sheep/components/s-uploader/choose-and-upload-file.js @@ -116,6 +116,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 +187,58 @@ 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) { + // 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 + method: 'PUT', // 使用 PUT 方法 + header: { + 'Content-Type': file.fileType + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型 + }, + data: fileBuffer, // 文件的路径,适用于小程序 + success: (res) => { + // 1.4. 记录文件信息到后端(异步) + createFile(presignedInfo, file); + // 1.5. 重新赋值 + file.url = presignedInfo.url; + 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 +254,32 @@ function chooseAndUploadFile( return uploadFiles(chooseAll(opts), opts); } +/** + * 创建文件信息 + * @param vo 文件预签名信息 + * @param file 文件 + */ +function createFile(vo, file) { + const fileVo = { + configId: vo.configId, + url: vo.url, + path: file.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;