reactor:【INFRA】文件上传 api,增加 directory 参数,去除 path 参数,并支持按照日期分目录、文件名不再使用 sha256 而是时间戳
							parent
							
								
									368f7c753f
								
							
						
					
					
						commit
						91d70b41cb
					
				|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ export namespace BpmCategoryApi { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** 模型分类信息 */ |   /** 模型分类信息 */ | ||||||
|  |   // TODO @jason:这个应该非 api 的,可以考虑抽到页面里哈。
 | ||||||
|   export interface ModelCategoryInfo { |   export interface ModelCategoryInfo { | ||||||
|     id: number; |     id: number; | ||||||
|     name: string; |     name: string; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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 }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|     // 上传文件 |     // 上传文件 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|     // 上传文件 |     // 上传文件 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV