reactor:【INFRA】文件上传 api,增加 directory 参数,去除 path 参数,并支持按照日期分目录、文件名不再使用 sha256 而是时间戳

pull/87/MERGE
YunaiV 2025-05-02 19:59:05 +08:00
parent 368f7c753f
commit 91d70b41cb
8 changed files with 41 additions and 34 deletions

View File

@ -259,6 +259,7 @@ setupVbenVxeTable({
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add // vxeUI.formats.add
// add by 星语:数量格式化,例如说:金额
vxeUI.formats.add('formatAmount', { vxeUI.formats.add('formatAmount', {
cellFormatMethod({ cellValue }, digits = 2) { cellFormatMethod({ cellValue }, digits = 2) {
if (cellValue === null || cellValue === undefined) { if (cellValue === null || cellValue === undefined) {

View File

@ -15,6 +15,7 @@ export namespace BpmCategoryApi {
} }
/** 模型分类信息 */ /** 模型分类信息 */
// TODO @jason这个应该非 api 的,可以考虑抽到页面里哈。
export interface ModelCategoryInfo { export interface ModelCategoryInfo {
id: number; id: number;
name: string; name: string;

View File

@ -2,6 +2,7 @@ import { requestClient } from '#/api/request';
export namespace BpmModelApi { export namespace BpmModelApi {
/** 用户信息 TODO 这个是不是可以抽取出来定义在公共模块 */ /** 用户信息 TODO 这个是不是可以抽取出来定义在公共模块 */
// TODO @芋艿:一起看看。
export interface UserInfo { export interface UserInfo {
id: number; id: number;
nickname: string; nickname: string;
@ -9,6 +10,7 @@ export namespace BpmModelApi {
deptId?: number; deptId?: number;
deptName?: string; deptName?: string;
} }
/** 流程定义 VO */ /** 流程定义 VO */
export interface ProcessDefinitionVO { export interface ProcessDefinitionVO {
id: string; id: string;

View File

@ -23,12 +23,13 @@ export namespace InfraFileApi {
configId: number; // 文件配置编号 configId: number; // 文件配置编号
uploadUrl: string; // 文件上传 URL uploadUrl: string; // 文件上传 URL
url: string; // 文件 URL url: string; // 文件 URL
path: string; // 文件路径
} }
/** 上传文件 */ /** 上传文件 */
export interface FileUploadReqVO { export interface FileUploadReqVO {
file: globalThis.File; file: globalThis.File;
path?: string; directory?: string;
} }
} }
@ -45,11 +46,11 @@ export function deleteFile(id: number) {
} }
/** 获取文件预签名地址 */ /** 获取文件预签名地址 */
export function getFilePresignedUrl(path: string) { export function getFilePresignedUrl(name: string, directory?: string) {
return requestClient.get<InfraFileApi.FilePresignedUrlRespVO>( return requestClient.get<InfraFileApi.FilePresignedUrlRespVO>(
'/infra/file/presigned-url', '/infra/file/presigned-url',
{ {
params: { path }, params: { name, directory },
}, },
); );
} }
@ -64,5 +65,9 @@ export function uploadFile(
data: InfraFileApi.FileUploadReqVO, data: InfraFileApi.FileUploadReqVO,
onUploadProgress?: AxiosProgressEvent, onUploadProgress?: AxiosProgressEvent,
) { ) {
// 特殊:由于 upload 内部封装,即使 directory 为 undefined也会传递给后端
if (!data.directory) {
delete data.directory;
}
return requestClient.upload('/infra/file/upload', data, { onUploadProgress }); return requestClient.upload('/infra/file/upload', data, { onUploadProgress });
} }

View File

@ -28,6 +28,8 @@ const props = withDefaults(
file: File, file: File,
onUploadProgress?: AxiosProgressEvent, onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>; ) => Promise<AxiosResponse<any>>;
//
directory?: string;
disabled?: boolean; disabled?: boolean;
helpText?: string; helpText?: string;
// Infinity // Infinity
@ -44,13 +46,14 @@ const props = withDefaults(
}>(), }>(),
{ {
value: () => [], value: () => [],
directory: undefined,
disabled: false, disabled: false,
helpText: '', helpText: '',
maxSize: 2, maxSize: 2,
maxNumber: 1, maxNumber: 1,
accept: () => [], accept: () => [],
multiple: false, multiple: false,
api: useUpload().httpRequest, api: undefined,
resultField: '', resultField: '',
showDescription: false, showDescription: false,
}, },
@ -141,10 +144,9 @@ const beforeUpload = async (file: File) => {
}; };
async function customRequest(info: UploadRequestOption<any>) { async function customRequest(info: UploadRequestOption<any>) {
const { api } = props; let { api } = props;
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
console.warn('upload api must exist and be a function'); api = useUpload(props.directory).httpRequest;
return;
} }
try { try {
// //

View File

@ -30,6 +30,8 @@ const props = withDefaults(
file: File, file: File,
onUploadProgress?: AxiosProgressEvent, onUploadProgress?: AxiosProgressEvent,
) => Promise<AxiosResponse<any>>; ) => Promise<AxiosResponse<any>>;
//
directory?: string;
disabled?: boolean; disabled?: boolean;
helpText?: string; helpText?: string;
listType?: UploadListType; listType?: UploadListType;
@ -47,6 +49,7 @@ const props = withDefaults(
}>(), }>(),
{ {
value: () => [], value: () => [],
directory: undefined,
disabled: false, disabled: false,
listType: 'picture-card', listType: 'picture-card',
helpText: '', helpText: '',
@ -54,7 +57,7 @@ const props = withDefaults(
maxNumber: 1, maxNumber: 1,
accept: () => defaultImageAccepts, accept: () => defaultImageAccepts,
multiple: false, multiple: false,
api: useUpload().httpRequest, api: undefined,
resultField: '', resultField: '',
showDescription: true, showDescription: true,
}, },
@ -177,10 +180,9 @@ const beforeUpload = async (file: File) => {
}; };
async function customRequest(info: UploadRequestOption<any>) { async function customRequest(info: UploadRequestOption<any>) {
const { api } = props; let { api } = props;
if (!api || !isFunction(api)) { if (!api || !isFunction(api)) {
console.warn('upload api must exist and be a function'); api = useUpload(props.directory).httpRequest;
return;
} }
try { try {
// //

View File

@ -7,8 +7,7 @@ import { computed, unref } from 'vue';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import CryptoJS from 'crypto-js'; // import CryptoJS from 'crypto-js';
import { createFile, getFilePresignedUrl, uploadFile } from '#/api/infra/file'; import { createFile, getFilePresignedUrl, uploadFile } from '#/api/infra/file';
import { baseRequestClient } from '#/api/request'; import { baseRequestClient } from '#/api/request';
@ -81,7 +80,7 @@ export function useUploadType({
} }
// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构 // TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构
export const useUpload = () => { export const useUpload = (directory?: string) => {
// 后端上传地址 // 后端上传地址
const uploadUrl = getUploadUrl(); const uploadUrl = getUploadUrl();
// 是否使用前端直连上传 // 是否使用前端直连上传
@ -97,7 +96,7 @@ export const useUpload = () => {
// 1.1 生成文件名称 // 1.1 生成文件名称
const fileName = await generateFileName(file); const fileName = await generateFileName(file);
// 1.2 获取文件预签名地址 // 1.2 获取文件预签名地址
const presignedInfo = await getFilePresignedUrl(fileName); const presignedInfo = await getFilePresignedUrl(fileName, directory);
// 1.3 上传文件 // 1.3 上传文件
return baseRequestClient return baseRequestClient
.put(presignedInfo.uploadUrl, file, { .put(presignedInfo.uploadUrl, file, {
@ -107,13 +106,13 @@ export const useUpload = () => {
}) })
.then(() => { .then(() => {
// 1.4. 记录文件信息到后端(异步) // 1.4. 记录文件信息到后端(异步)
createFile0(presignedInfo, fileName, file); createFile0(presignedInfo, file);
// 通知成功,数据格式保持与后端上传的返回结果一致 // 通知成功,数据格式保持与后端上传的返回结果一致
return { data: presignedInfo.url }; return { data: presignedInfo.url };
}); });
} else { } else {
// 模式二:后端上传 // 模式二:后端上传
return uploadFile({ file }, onUploadProgress); return uploadFile({ file, directory }, onUploadProgress);
} }
}; };
@ -134,18 +133,13 @@ export const getUploadUrl = (): string => {
* *
* *
* @param vo * @param vo
* @param name
* @param file * @param file
*/ */
function createFile0( function createFile0(vo: InfraFileApi.FilePresignedUrlRespVO, file: File) {
vo: InfraFileApi.FilePresignedUrlRespVO,
name: string,
file: File,
) {
const fileVO = { const fileVO = {
configId: vo.configId, configId: vo.configId,
url: vo.url, url: vo.url,
path: name, path: vo.path,
name: file.name, name: file.name,
type: file.type, type: file.type,
size: file.size, size: file.size,
@ -160,12 +154,13 @@ function createFile0(
* @param file * @param file
*/ */
async function generateFileName(file: File) { async function generateFileName(file: File) {
// 读取文件内容 // // 读取文件内容
const data = await file.arrayBuffer(); // const data = await file.arrayBuffer();
const wordArray = CryptoJS.lib.WordArray.create(data); // const wordArray = CryptoJS.lib.WordArray.create(data);
// 计算SHA256 // // 计算SHA256
const sha256 = CryptoJS.SHA256(wordArray).toString(); // const sha256 = CryptoJS.SHA256(wordArray).toString();
// 拼接后缀 // // 拼接后缀
const ext = file.name.slice(Math.max(0, file.name.lastIndexOf('.'))); // const ext = file.name.slice(Math.max(0, file.name.lastIndexOf('.')));
return `${sha256}${ext}`; // return `${sha256}${ext}`;
return file.name;
} }

View File

@ -48,11 +48,10 @@ export function useFormSchema(): VbenFormSchema[] {
}, },
rules: 'required', rules: 'required',
}, },
// TODO @芋艿:图片上传
{ {
fieldName: 'logo', fieldName: 'logo',
label: '应用图标', label: '应用图标',
component: 'UploadImage', component: 'ImageUpload',
componentProps: { componentProps: {
limit: 1, limit: 1,
}, },