商务网站建设详细步骤,浩森宇特北京网站建设,建站开发软件,常州市住房建设局网站1.写在之前
本文采用Spring Boot MinIO MySQLMybatis Plus技术栈#xff0c;参考ruoyi-vue-pro项目。
前端实现请看本篇文章el-form与el-upload结合上传带附件的表单数据#xff08;前端篇#xff09;-CSDN博客。
2.需求描述
在OA办公系统中#xff0c;流程表单申请人…1.写在之前
本文采用Spring Boot MinIO MySQLMybatis Plus技术栈参考ruoyi-vue-pro项目。
前端实现请看本篇文章el-form与el-upload结合上传带附件的表单数据前端篇-CSDN博客。
2.需求描述
在OA办公系统中流程表单申请人填写表单数据上传所需附件供流程后续审核人员下载查看。如下图所示生产单位经办人填写表单数据保存后提交流程审批任务到下一节点下一节点人员审核时下载查看初始节点人员上传的附件。 图注表单数据填写页面 图注流程节点审批信息页面
3.设计思路
文件存储放到MinIO中封装一个MinIO客户端给出上传下载删除文件方法代码如下所示。
Configuration
public class MinIOFileClient {Resourceprivate FileClientProperties fileClientProperties;Resourceprivate MinioClient client;public String upload(byte[] content, String name, String bucket, String type) throws Exception {// 执行上传client.putObject(PutObjectArgs.builder().bucket(bucket) // bucket 必须传递.contentType(type).object(name) // 相对路径作为 key.stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容.build());// 拼接返回路径return String.format(%s/%s/%s, fileClientProperties.getUrl(), bucket, name);}public void delete(String name, String bucket) throws Exception {client.removeObject(RemoveObjectArgs.builder().bucket(bucket) // bucket 必须传递.object(name) // 相对路径作为 key.build());}public byte[] getContent(String name, String bucket) throws Exception {GetObjectResponse response client.getObject(GetObjectArgs.builder().bucket(bucket) // bucket 必须传递.object(name) // 相对路径作为 key.build());return IoUtil.readBytes(response);}}
文件上传到MinIO服务器上返回一个文件下载的URL在具体的表单业务中存储一个List转换的String每个list包含文件的名称文件的唯一编码文件查看的URL。
Data
public class FileObject implements Serializable {/*** 文件名*/private String name;/*** 文件的唯一标识编码 因为要和前端el-upload联合使用 所以使用response这个字段*/private String response;/*** 文件存储的地址*/private String url;
}
初期设计思路为表单数据与文件数据一同传输给后端后端存储表单数据进入数据库之前单独调用文件传输接口上传文件获取文件的URL利用MyBatis Plus的type handler把List转为String存储进入业务表单数据。这样设计的好处在于文件上传下载的时候相当于与表单数据的存储合为一个事务能保证附件的上传成功率表单附件修改尤其删除已经存储的附件很方便。坏处就是表单数据是使用JSON数据格式传输的想要传输文件附件有一点不好实现。经过网上的探索最终这种方案被放弃来自一个网上的评论用JSON没办法使用流上传使用base64还会增加文件大小转换消耗浏览器资源图片一大或者变成了视频、大文件之类的这个就用不了了加上服务器需要对请求体大小专门放开不能这样惯着后端不然到时候代码会成祖传。最后这种方法被否了。选择了另外一种方案。
另外一种方案为文件上传删除与表单数据上传分开即开启el-upload的自动上传通过el-upload组件的on-success回调函数获取文件上传的信息FileObject,获取到对应的信息后通过Vue的组件通信返回给表单对应的数据表单数据保存时直接传入对应的文件信息数组后端直接转换的String存储到业务数据表中。这种方案的问题在于
第一在填写表单时点击了附件也上传成功后表单没有点击保存此时重新填写表单时上传的附件不会出现在表单中重新上传附件会导致文件重复存储进入磁盘造成资源的浪费
第二在表单附件删除时已经点击了附件删除但此时表单没有保存重新刷新打开表单时此时业务表单存储的附件信息没有改变但对应的有的文件已经被删除本来应该存在的文件不存在了。
4.文件的上传
为了解决第三节上传遇到的问题存储文件的信息引入一个数据库表存储信息如下
public class FileDO extends BaseDO {/*** 编号数据库自增*/private Long id;/*** 原文件名*/private String name;/*** 存储的bucket*/private String bucket;/*** 访问地址*/private String url;/*** 文件的 MIME 类型例如 application/octet-stream*/private String type;/*** 文件大小*/private Integer size;/*** 文件的唯一编码 因为要和前端el-upload联合使用 所以使用response这个字段*/private String response;}
在每次存储文件时根据文件内容与文件名生成唯一的文件编码也既是存储进入MinIO服务磁盘的文件名这样保证相同的文件会被替换减少空间的浪费。用此唯一编码查看FileDo所对应数据数据库是否有本数据如果有本数据删除本数据重新生成新的数据并插入。数据上传成功后返回对应的文件信息供前端使用。
public FileObject createFile(String name, String bucket, byte[] content) {// 计算默认的 path 名String type FileTypeUtils.getMineType(content, name);// 文件名称不能为空 FILE_NAME_IS_EMPTYif (StrUtil.isEmpty(name)) {throw exception(FILE_NAME_IS_EMPTY);}//生成唯一id存储 防止名称相同的文件被顶替String response FileUtils.generateCode(content, name);if (Boolean.TRUE.equals(validateFileExists(response, bucket))){// 说明该文件已经存储过 minIO存储的文件会被覆盖 不需要做任何事情// FileDO 删除 然后新保存fileMapper.deleteByResponseAndBucket(response, bucket);}String url minIOFileClient.upload(content, response, bucket, type);// 保存到数据库FileDO file new FileDO();file.setName(name);file.setResponse(response);file.setUrl(url);file.setType(type);file.setSize(content.length);file.setBucket(bucket);fileMapper.insert(file);// 构造返回数据FileObject fileObject new FileObject();fileObject.setName(name);fileObject.setResponse(response);fileObject.setUrl(url);return fileObject;}
[{name:1.jpg,response:5eb1dfe0f288445a49260074041508d932f6ad190a898ff0500e052d8ecf5a88.jpg,url:http://192.168.16.58:9000/operation/5eb1dfe0f288445a49260074041508d932f6ad190a898ff0500e052d8ecf5a88.jpg},{name:2.jpg,response:7d85e8fb46db1259089b025e44c09c9fa1f696db05437f21879d035b6f04e331.jpg,url:http://192.168.16.58:9000/operation/7d85e8fb46db1259089b025e44c09c9fa1f696db05437f21879d035b6f04e331.jpg}]
上面代码为业务数据表存储的具体数据。下图为FileDO对应的数据存储。 5.文件的删除
为解决第三节第二个删除的问题。在删除时el-upload组件删除文件不调用后端删除文件接口只做一个假删除真正的删除在表单修改点击保存调用后端的update接口时由后端做删除操作。
删除的逻辑为接口调用到达后端时比较数据库中已经存储的的附件数据data1与本次上传的附件数据data2计算单差集即只在data1中有而在data2中没有的附件信息data3data3即本次需要删除的附件信息。
fileApi.deleteFile(bidMapper.selectById(bidDO.getId()).getFiles(), bidDO.getFiles());
public void deleteFile(String response, String bucket){try{// 校验存在if (Boolean.FALSE.equals(validateFileExists(response, bucket))){throw exception(FILE_NOT_EXISTS);}//删除数据minIOFileClient.delete(response, bucket);fileMapper.deleteByResponseAndBucket(response, bucket);}catch (Exception e){log.error(e.getMessage());throw exception(FILE_DELETE_FAILED);}}public void deleteFile(ListFileObject oldFileList, ListFileObject newFileList){//计算集合的单差集即只返回【集合1】中有但是【集合2】中没有的元素例如// subtract([1,2,3,4],[2,3,4,5]) -》 [1]ListFileObject subtract CollUtil.subtractToList(JSON.parseArray(String.valueOf(oldFileList), FileObject.class), newFileList);subtract.forEach( s - {String response s.getResponse();FileDO fileDO fileMapper.selectOne(response, response);this.deleteFile(response, fileDO.getBucket());});}
6.文件的下载
文件的下载没有什么特殊的情况直接上代码就行。
public byte[] getFileContent(String response) {try{FileDO fileDO fileMapper.selectOne(response, response);return minIOFileClient.getContent(response, fileDO.getBucket());}catch (Exception e){log.error(e.getMessage());throw exception(FILE_DOWNLOAD_FAILED);}}public void downloadFile(HttpServletRequest request, HttpServletResponse response, String code) {try{byte[] file getFileContent(code);FileDO fileDO fileMapper.selectOne(response, code);ServletUtils.writeAttachment(response, fileDO.getName(), file);}catch (Exception e){log.error(e.getMessage());throw exception(FILE_DOWNLOAD_FAILED);}}
7.没有解决的问题
有这样一种情况在初始填写表单时上传了5个附件都上传成功了但发现上传错误删除了其中的两个附件此时点击保存表单表单中只存储了3个附件的信息被删除的两个附件不会再表单中体现也不会在磁盘上被删除因为前端没有调用实际的删除接口后端在差集比较时存储的数据为空造成了资源的浪费。
8.写在最后
本文很笼统的介绍了一下在附件与表单数据分开上传时自己遇到的一些问题以及自己探索的解决方法中间的描述有一些可能不是很清楚也还有遗留问题后续还会慢慢解决。看到这篇文章的你如果有任何指教欢迎私信探讨