【功能新增】现已支持前端文件直传到OSS服务器
parent
56182dfbe9
commit
c23e7ac3d0
5
.env
5
.env
|
@ -2,12 +2,15 @@
|
||||||
SHOPRO_VERSION = v1.8.3
|
SHOPRO_VERSION = v1.8.3
|
||||||
|
|
||||||
# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development)
|
# 后端接口 - 正式环境(通过 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)
|
# 后端接口 - 测试环境(通过 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
|
### 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
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,8 @@
|
||||||
"luch-request": "^3.0.8",
|
"luch-request": "^3.0.8",
|
||||||
"pinia": "^2.0.33",
|
"pinia": "^2.0.33",
|
||||||
"pinia-plugin-persist-uni": "^1.2.0",
|
"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": {
|
"devDependencies": {
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
|
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
|
||||||
|
import request from '@/sheep/request';
|
||||||
|
|
||||||
const FileApi = {
|
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;
|
export default FileApi;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import FileApi from '@/sheep/api/infra/file';
|
import FileApi from '@/sheep/api/infra/file';
|
||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
|
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
|
||||||
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
|
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
|
||||||
|
@ -106,7 +107,7 @@ function normalizeChooseAndUploadFileRes(res, fileType) {
|
||||||
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
|
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
|
||||||
}
|
}
|
||||||
if (fileType) {
|
if (fileType) {
|
||||||
item.fileType = fileType;
|
item.type = fileType;
|
||||||
}
|
}
|
||||||
item.cloudPath = Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
|
item.cloudPath = Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
|
||||||
});
|
});
|
||||||
|
@ -116,6 +117,28 @@ function normalizeChooseAndUploadFileRes(res, fileType) {
|
||||||
return res;
|
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) {
|
function uploadCloudFiles(files, max = 5, onUploadProgress) {
|
||||||
files = JSON.parse(JSON.stringify(files));
|
files = JSON.parse(JSON.stringify(files));
|
||||||
const len = files.length;
|
const len = files.length;
|
||||||
|
@ -165,36 +188,61 @@ function uploadCloudFiles(files, max = 5, onUploadProgress) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) {
|
async function uploadFiles(choosePromise, { onChooseFile, onUploadProgress }) {
|
||||||
return choosePromise
|
// 获取选择的文件
|
||||||
.then((res) => {
|
const res = await choosePromise;
|
||||||
if (onChooseFile) {
|
// 处理文件选择回调
|
||||||
const customChooseRes = onChooseFile(res);
|
let files = res.tempFiles || [];
|
||||||
if (typeof customChooseRes !== 'undefined') {
|
if (onChooseFile) {
|
||||||
return Promise.resolve(customChooseRes).then((chooseRes) =>
|
const customChooseRes = onChooseFile(res);
|
||||||
typeof chooseRes === 'undefined' ? res : chooseRes,
|
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 {
|
if (UPLOAD_TYPE.CLIENT === import.meta.env.SHOPRO_UPLOAD_TYPE) {
|
||||||
errMsg: ERR_MSG_OK,
|
for (const file of files) {
|
||||||
tempFilePaths: [],
|
// 获取二进制文件对象
|
||||||
tempFiles: [],
|
const fileBuffer = await convertToArrayBuffer(file);
|
||||||
};
|
// 1.1 生成文件名称
|
||||||
}
|
const fileName = await generateFileName(fileBuffer, file.name);
|
||||||
return res;
|
// 1.2 获取文件预签名地址
|
||||||
})
|
const { data: presignedInfo } = await FileApi.getFilePresignedUrl(file.name);
|
||||||
.then(async (files) => {
|
// 1.3 上传文件
|
||||||
for (let file of files.tempFiles) {
|
await uni.request({
|
||||||
const { data } = await FileApi.uploadFile(file.path);
|
url: presignedInfo.uploadUrl, // 预签名的上传 URL
|
||||||
file.url = data;
|
method: 'PUT', // 使用 PUT 方法
|
||||||
}
|
header: {
|
||||||
return files;
|
'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(
|
function chooseAndUploadFile(
|
||||||
|
@ -210,4 +258,54 @@ function chooseAndUploadFile(
|
||||||
return uploadFiles(chooseAll(opts), opts);
|
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 };
|
export { chooseAndUploadFile, uploadCloudFiles };
|
||||||
|
|
|
@ -369,7 +369,7 @@
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.setSuccessAndError(result.tempFiles);
|
this.setSuccessAndError(result);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log('选择失败', err);
|
console.log('选择失败', err);
|
||||||
|
@ -453,7 +453,7 @@
|
||||||
|
|
||||||
if (index === -1 || !this.files) break;
|
if (index === -1 || !this.files) break;
|
||||||
if (item.errMsg === 'request:fail') {
|
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].status = 'error';
|
||||||
this.files[index].errMsg = item.errMsg;
|
this.files[index].errMsg = item.errMsg;
|
||||||
// this.files[index].progress = -1
|
// this.files[index].progress = -1
|
||||||
|
@ -587,7 +587,7 @@
|
||||||
path: v.path,
|
path: v.path,
|
||||||
size: v.size,
|
size: v.size,
|
||||||
fileID: v.fileID,
|
fileID: v.fileID,
|
||||||
url: v.url,
|
url: v.path,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return newFilesData;
|
return newFilesData;
|
||||||
|
|
Loading…
Reference in New Issue