一二三应用开发平台文件处理设计与实现系列之3——后端统一封装设计与实现

背景

前面介绍了前端通过集成vue-simple-uploader实现了文件的上传,今天重点说一下后端的设计与实现。

功能需求梳理

从功能角度而言,实际主要就两项,一是上传,二是下载。其中上传在文件体积较大的情况下,为了加快上传速度,提升用户体验,在具体实现上进行了文件分块,以及文件块的合并操作。
从业务场景而言,主要分为两类:
一是表单相关的附件;二是通知公告等场景,使用富文本编辑器时上传的图片。
在这两类场景中,文件实际并不是主体,而是实体的附属品。
平台对文件上传下载的支撑功能,重点还在于表单相关附件,并支持图片的上传与展示。

注:以文件为主体的业务场景也有,主要是文档库、云盘、网盘等,该场景通常会作为独立的业务应用来实现,进行专门的设计与实现,不在平台当前设计考虑范围之内。

系统设计与实现

整体设计

基于功能需求梳理,平台将文件作为实体的附属来处理。平台进行全局的统一封装与处理,避免各实体各自创建和维护自己的附件信息。具体来说,就是增加“附件”实体,将文件的主要信息,如文件名、大小、类型、存放路径等信息存放到附件库表中,并关联实体的唯一性标识。从职责上,将附件实体放到业务支撑(support)模块中进行管理。

整体处理逻辑如下:
上传文件块,如文件体积较小,没有触发分块,则该文件块就是一个完整的文件,将该文件直接存储到磁盘,并生成附件记录,插入到库表(库表中存放文件路径)。
若文件体积较大,触发了分块,则只将分块存到磁盘临时目录下,不生成附件记录;待前端检测到所有文件块均已上传完成,调用合并文件块操作,依据全局唯一的文件标识,去临时目录下找到所有的文件块,进行文件合并操作,生成附件记录。

对于富文本编辑器中上传的图片,同样使用附件功能来进行统一封装,与普通文件不同的是,图片上传不分块,存放到预置的统一目录(image/)下,生成一个虚拟的实体标识,不对应具体的实体,该实体标识来存储图片及读取图片用来展示。

实体

通过平台实体配置功能,实现附件实体的属性配置,如下图所示:
image.png
示例数据如下:
image.png
name存放原始文件名;realName存放的是最终落盘文件名,为防止同名文件覆盖,落盘时会附加文件唯一性标识前缀。
length存放的是文件原始长度,长整型,单位是B;size则是将文件长度进行友好化转换,根据体积显示G或M或**K。
path是存储文件的相对路径,包含文件自身,是读取文件的重要关联关系。
entity存放附件对应的实体的标识。
type存放文件类型。

前端

前端api定义如下:

// 附件
export const attachment = Object.assign({}, COMMON_METHOD, {serveUrl: '/' + moduleName + '/' + 'attachment' + '/',// 上传操作内置于vue-simple-uploader中// 下载download(id) {return request.download({ url: this.serveUrl + id + '/download' })},// 合并文件块mergeChunks(param) {return request.post({ url: this.serveUrl + 'mergeChunks', data: param })},// 上传图片uploadImage(param) {return request.upload({ url: this.serveUrl + 'uploadImage', data: param })}
})

涉及到组件集成,部分后端服务地址没有体现在统一的api定义中,涉及到以下两处:
上传文件块操作,内置于vue-simple-uploader组件的配置选项options的targt属性中
image.png
图片读取操作,内置于富文本编辑器wangeditor的自定义上传操作中
image.png
合并文件块的核心操作,vue-simple-uploader的文件上传成功事件中,将文件关键信息整合后传到后端来,如下所示:

fileSuccess(rootFile, file) {if (file.chunks.length > 1) {//分块上传const param = {identifier: file.uniqueIdentifier,filename: file.name,moduleCode: this.moduleCode,entityType: this.entityType,entityId: this.entityId,type: file.fileType,totalSize: file.size}// 合并文件块this.$api.support.attachment.mergeChunks(param).then(() => {// 移除已上传成功的文件this.$refs.uploader.uploader.removeFile(file)})} else {// 不分块,移除已上传成功的文件this.$refs.uploader.uploader.removeFile(file)}}

对象视图

有两个辅助的对象视图,一个是文件块的定义,用于分块上传;另外一个是文件信息,用于合并文件块。

/*** 文件块对象模型,匹配前端vue-simple-uploader控件** @author wqliu* @date 2023-03-08*/
@Data
public class FileChunkVO extends BaseVO {/*** 当前文件块编号,从1开始*/private Integer chunkNumber;/*** 分块大小*/private Long chunkSize;/*** 当前分块大小*/private Long currentChunkSize;/*** 总大小*/private Long totalSize;/*** 文件标识*/private String identifier;/*** 文件名*/private String filename;/*** 相对路径*/private String relativePath;/*** 总块数*/private Integer totalChunks;/*** 文件类型*/private String type;/*** 文件块内容*/private MultipartFile file;/*** 业务分类*/private String entityType;/*** 业务实体标识*/private String entityId;/*** 模块编码*/private String moduleCode;}
/*** 文件 实体* 匹配前端simple-uploader控件** @author wqliu* @date 2023-11-27*/
@Data
public class FileInfo  {/*** 文件标识*/private String identifier;/*** 文件名*/private String filename;/*** 模块编码*/private String moduleCode;/*** 实体类型*/private String entityType;/*** 实体标识*/private String entityId;/*** 文件类型*/private String type;/*** 总大小*/private Long totalSize;}

控制器

在标准控制器的基础上,扩展几个方法
image.png

  • uploadChunk:上传文件块
  • mergeChunks:合并文件块
  • downloadFile:通过附件的唯一性标识来找到文件并返回文件流
  • list:根据实体标识查找其附件数据,返回列表
  • uploadImage:为图片设置的专门上传方法,接收参数是MultipartFile,而不是uploadChunk方法中的FileChunkVO
  • getImage:为图片设置的专门读取方法,与downloadFile实际调用的是同一个服务层方法getFile,差别在于downloadFile方法需要为response响应设置header,即response.setHeader(“Content-disposition”, “attachment;filename=” + encodeFileName(fileName));以便触发下载;对于图片,直接返回流即可。

服务

服务接口只有四个,分别是上传文件块、合并文件块、上传图片和获取文件流(包括图片流)。

/*** 上传文件块** @param fileChunk* @return 如是最后一块, 返回附件实体实体标识, 否则返回null*/String uploadChunk(FileChunk fileChunk);/*** 合并文件块* @param fileInfo 文件信息* @return {@link String} 文件标识*/String mergeChunks(FileInfo fileInfo);/*** 上传图片** @param image* @return 附件实体实体标识*/String uploadImage(MultipartFile image);/*** 获取文件流** @param id* @return 文件流*/InputStream getFile(String id);

对应的服务实现代码如下:

    @Override@Transactional(rollbackFor = Exception.class)public String uploadChunk(FileChunk fileChunk) {// 附件上传比较特殊,传输的数据是文件块,先根据文件块处理文件,然后生成附件实体数据// 上传文件块objectStoreService.uploadChunk(fileChunk);// 如只有一块,直接生成附件if (fileChunk.getTotalChunks() == 1) {// 生成附件信息return create(fileChunk);}return null;}@Override@Transactional(rollbackFor = Exception.class)public String mergeChunks(FileInfo fileInfo) {// 合并文件objectStoreService.mergeChunks(fileInfo);// 生成附件信息return create(fileInfo);}@Overridepublic String uploadImage(MultipartFile image) {//生成唯一性标识String entityId = IdWorker.getIdStr();// 存储文件objectStoreService.uploadImage(image, entityId);String realName = entityId + image.getOriginalFilename();// 生成附件信息Attachment entity = new Attachment();entity.setName(image.getOriginalFilename());// 设置友好显示大小entity.setSize(FileUtil.getFileSize(image.getSize()));entity.setLength(image.getSize());// 设置存储相对路径entity.setPath(FileConstant.IMAGE_PATH+realName);entity.setType(image.getContentType());entity.setRealName(realName);entity.setEntity(entityId);add(entity);return entity.getId();}@Overridepublic InputStream getFile(String id) {Attachment entity = query(id);return objectStoreService.getFile(entity.getPath());}/*** 创建附件——依据文件信息* @param fileInfo 文件* @return {@link String} 附件标识*/private String create(FileInfo fileInfo) {//实际存储文件名String realName = fileInfo.getIdentifier() + fileInfo.getFilename();// 存储相对路径String relativePath = objectStoreService.generateRelativePath(fileInfo.getModuleCode(),fileInfo.getEntityType());Attachment entity = new Attachment();entity.setName(fileInfo.getFilename());// 设置友好显示大小if (fileInfo.getTotalSize() != null) {entity.setSize(FileUtil.getFileSize(fileInfo.getTotalSize()));entity.setLength(fileInfo.getTotalSize());}// 设置存储相对路径entity.setPath(FilenameUtils.concat(relativePath, realName));entity.setType(fileInfo.getType());entity.setRealName(realName);entity.setEntity(fileInfo.getEntityId());add(entity);return entity.getId();}/*** 创建附件——依据文件块信息* @param fileChunk 文件块* @return {@link String} 附件标识*/private String create(FileChunk fileChunk) {String realName = fileChunk.getIdentifier() + fileChunk.getFilename();// 存储相对路径String relativePath = objectStoreService.generateRelativePath(fileChunk.getModuleCode(),fileChunk.getEntityType());Attachment entity = new Attachment();entity.setName(fileChunk.getFilename());// 设置友好显示大小if (fileChunk.getTotalSize() != null) {entity.setSize(FileUtil.getFileSize(fileChunk.getTotalSize()));entity.setLength(fileChunk.getTotalSize());}// 设置存储相对路径entity.setPath(FilenameUtils.concat(relativePath, realName));entity.setType(fileChunk.getFile().getContentType());entity.setRealName(realName);entity.setEntity(fileChunk.getEntityId());add(entity);return entity.getId();}

开源平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
欢迎收藏、点赞、评论,你的支持是我前行的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/588800.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop安装笔记1单机/伪分布式配置_Hadoop3.1.3——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

将下发的ds_db01.sql数据库文件放置mysql中 12、编写Scala代码,使用Spark将MySQL的ds_db01库中表user_info的全量数据抽取到Hive的ods库中表user_info。字段名称、类型不变,同时添加静态分区,分区字段为etl_date,类型为String&am…

年度总结 | 回味2023不平凡的一年

目录 前言1. 平台成就2. 自我提升3. Bug连连4. 个人展望 前言 每年CSDN的总结都不能落下,回顾去年:年度总结 | 回味2022不平凡的一年,在回忆今年,展望下年 1. 平台成就 平台造就我(我也造就平台哈哈) 每…

MATLAB中./和/,.*和*,.^和^的区别

MATLAB中./和/,.*和*,.^ 和^ 的区别 MATLAB中./和/,.*和*,.^ 和^ 的区别./ 和 / 的区别.//实验实验结果 .* 和 * 的区别.**实验实验结果 .^ 和^ 的区别.^n^n实验运行结果 MATLAB中./和/,.和,.^ 和^ 的区别 …

Plantuml之JSON数据语法介绍(二十五)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

设计模式(4)--对象行为(11)--访问者

1. 意图 表示一个作用于某对象结构中的各元素的操作。 使你可以在不改变各元素的类的前提下定义于作用于这些元素的新操作。 2. 五种角色 抽象访问者(Visitor)、具体访问者(Concrete Visitor)、抽象元素(Element)、 具体元素(Concrete Element)、对象结构(ObjectStructure) 3…

学习笔记15——前端和http协议

学习笔记系列开头惯例发布一些寻亲消息,感谢关注! 链接:https://baobeihuijia.com/bbhj/ 关系 客户端:对连接访问到的前端代码进行解析和渲染,就是浏览器的内核服务器端:按照规则编写前端界面代码 解析标准…

Mysql 高级语句

目录 高阶查询select语句: 显示表格中一个或数个字段的所有数据记录: 不显示重复的数据记录:distinct and且,or或 显示已知的值的数据记录:in 显示两个值范围内的数据记录:between 通配符&#xff1…

UE蓝图 RPG动作游戏(一) day15

角色状态制作 制作角色动画混合空间 创建一个动混合空间 添加动作在混合空间 动画蓝图 创建一个动画蓝图 先使用混合空间进行移动,后续优化后再使用状态机 编写垂直水平速度逻辑初始化,获取到此动画的角色组件 获取Horizontal与Vertical的速度逻辑 …

CDH 6.3.2集成flink 1.18 zookeeper版本不匹配Flink-yarn启动失败

CDH 6.3.2集成flink 1.18 zookeeper版本不匹配Flink-yarn不能正常启动,而在CHD Web页面,flink日志报错提示不明确,不能定位具体错误。CM WEB启动失败错误日志如下图所示: CM查看完成错误日志 [31/Dec/2023 10:45:09 0000] 26000…

osg::DrawElements*系列函数及GL_QUAD_STRIP、GL_QUADS绘制四边形效率对比

目录 1. 前言 2. osg::DrawElements*系列函数用法说明 3. GL_QUADS、GL_QUAD_STRIP用法及不同点 4. 效率对比 5. 总结 6. 参考资料 1. 前言 利用osg绘制图元,如:三角形、四边形等,一般用osg::PrimitiveSet类。其派生出了很多子类&#…

使用docker build构建image

文章目录 环境步骤准备例1:基本用法例2:缓存layer例3:Multi-stage例4:Mountcache mountbind mount 例5:参数例6:Export文件例7:测试 参考 环境 RHEL 9.3Docker Community 24.0.7 步骤 在Dock…

Cookie、Session

一、会话管理 1、什么是会话? 会话是客户端和服务端之间进行多次的请求和响应。 相当于两个人聊天,进行了多次的问答。 对多次问答的管理叫做会话管理,管理的东西是通信状态。 2、什么是状态? 举例: 小明去校园食堂…

常用设计模式全面总结版(JavaKotlin)

这篇文章主要是针对之前博客的下列文章的总结版本: 《设计模式系列学习笔记》《Kotlin核心编程》笔记:设计模式【Android知识笔记】FrameWork中的设计模式主要为了在学习了 Kotlin 之后,将 Java 的设计模式实现与 Kotin 的实现放在一起做一个对比。 一、创建型模式 单例模…

以太网二层交换机实验

实验目的: (1)理解二层交换机的原理及工作方式; (2)利用交换机组建小型交换式局域网。 实验器材: Cisco packet 实验内容: 本实验可用一台主机去ping另一台主机,并…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的图像剪切(ROI)功能(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的图像剪切(ROI)功能(C) Baumer工业相机Baumer工业相机的图像剪切(ROI)功能的技术背景CameraExplorer如何使用图像剪切(ROI)功…

Python武器库开发-武器库篇之Git的分支使用(三十九)

武器库篇之Git的分支使用(三十九) Git分支是一种用于在项目中并行开发和管理代码的功能。分支允许开发人员在不干扰主要代码的情况下创建新的代码版本,以便尝试新功能、修复错误或独立开发功能。一般正常情况下,开发人员开发一个软件,会有两…

HarmonyOS4.0系统性深入开发07创建一个ArkTS卡片

创建一个ArkTS卡片 在已有的应用工程中,创建ArkTS卡片,具体操作方式如下。 创建卡片。 根据实际业务场景,选择一个卡片模板。 在选择卡片的开发语言类型(Language)时,选择ArkTS选项,然后单…

nodejs+vue+微信小程序+python+PHP技术的健康信息网站-计算机毕业设计推荐

3.2 功能性需求分析 健康信息网站为会员提供健康信息服务的系统,管理员通过登录系统,管理会员信息、健康咨询、健康知识、健康档案、健康养生、健康信息的搜索、健康资讯等。需要学习的会员浏览健康信息网站,查询所有的健康信息,可…

【Java EE初阶三 】线程的状态与安全(下)

3. 线程安全 线程安全:某个代码,不管它是单个线程执行,还是多个线程执行,都不会产生bug,这个情况就成为“线程安全”。 线程不安全:某个代码,它单个线程执行,不会产生bug&#xff0c…

七:Day01_Java9—16新特性

第一章 JDK9 新特性 jdk9是新特性最多的,因为jdk8是一个稳定版本。 1、JDK9新特性概述 模块系统 (Module System) Java9最大特性。它提供了类似于OSGI框架的功能,模块之间存在相互的依赖关系,可以导出一个公共的API…