【功能新增】现已支持前端文件直传到OSS服务器

pull/103/head
卢越 2024-09-24 17:16:38 +08:00
parent 56182dfbe9
commit c23e7ac3d0
5 changed files with 158 additions and 35 deletions

5
.env
View File

@ -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

View File

@ -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",

View File

@ -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;

View File

@ -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 };

View File

@ -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;