【功能新增】现已支持前端文件直传到OSS服务器
parent
56182dfbe9
commit
c23e7ac3d0
5
.env
5
.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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue