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

Merge pull request !103 from 卢越/master
pull/104/head
芋道源码 2024-09-26 01:24:22 +00:00 committed by Gitee
commit 2ca7f48131
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
4 changed files with 137 additions and 41 deletions

21
.env
View File

@ -1,28 +1,31 @@
# 版本号 # 版本号
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://api-dashboard.yudao.iocoder.cn
# 后端接口 - 测试环境(通过 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
# 后端 websocket 接口前缀 # 后端 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=自定义静态资源地址前缀 # 客户端静态资源地址 空=默认使用服务端指定的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 ### SHOPRO_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启) # 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
SHOPRO_MPLIVE_ON = 0 SHOPRO_MPLIVE_ON=0
# 租户ID 默认 1 # 租户ID 默认 1
SHOPRO_TENANT_ID = 1 SHOPRO_TENANT_ID=1

View File

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

View File

@ -116,6 +116,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 +187,58 @@ 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: [], // 1.1 获取文件预签名地址
tempFiles: [], const { data: presignedInfo } = await FileApi.getFilePresignedUrl(file.name);
}; // 1.2 获取二进制文件对象
} const fileBuffer = await convertToArrayBuffer(file);
return res; // 1.3 上传文件
}) await uni.request({
.then(async (files) => { url: presignedInfo.uploadUrl, // 预签名的上传 URL
for (let file of files.tempFiles) { method: 'PUT', // 使用 PUT 方法
const { data } = await FileApi.uploadFile(file.path); header: {
file.url = data; 'Content-Type': file.fileType + '/' + file.name.substring(file.name.lastIndexOf('.') + 1), // 设置内容类型
} },
return files; 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( function chooseAndUploadFile(
@ -210,4 +254,32 @@ function chooseAndUploadFile(
return uploadFiles(chooseAll(opts), opts); 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 }; export { chooseAndUploadFile, uploadCloudFiles };

View File

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