导入图片引发出的对图片、视频、文档等上传的思考
来源:SegmentFault
时间:2023-02-16 15:38:39 177浏览 收藏
哈喽!今天心血来潮给大家带来了《导入图片引发出的对图片、视频、文档等上传的思考》,想必大家应该对数据库都不陌生吧,那么阅读本文就都不会很困难,以下内容主要涉及到MySQL、拦截器、windows-server、java-web、poi,若是你正在学习数据库,千万别错过这篇文章~希望能帮助到你!
导读
我们在开发的过程中,经常会遇到导入和导出:
- 从哪里导入到哪里?我们在客户端选择上传Excel文件,同时调用服务端的某个接口。服务端通过HttpServletRequest获取Excel的数据流,通过poi的相关操作获取单元格的数值,并填充到相应的javabean的实例化对象中。再调用事务的保存方法,利用hibernate框架或mybatis框架,将对象数据保存到数据库中。
- 从哪里导出到哪里?从服务器上的数据库的数据导出到本地,并以Excel文件的方式存储。我们在客户端选择导出Excel文件,同时调用服务端的某个接口。服务端通过HttpServletResponse响应客户端的请求,同时,调用事务层的查询方法,拿到待导出的数据源,过滤我们想要的数据。调用poi的相关操作,将数据填充到Excel表中。
导入
这里以导入材料为主,材料中存有图片,如图所示:
你会发现,Excel表中存储的是图片在服务器上的路径,为什么存储的是图片在服务器的路径,而不是图片的字节码数据?
我们都知道任何文件都可以按照字节码的方式存储,比如视频文件、音乐文件、图片文件、GIF文件、文本框文件等。但是字节码的存储和读取都占用内存,如果在大批量的导入和导出的情况下,势必会占用JVM内存,造成资源阻塞。
因而,我们存储的是图片的路径,这还不是随随便便的路径,而是其所在服务器的路径。为什么选择路径。从上图中的图片路径来看,路径的字符比较短。占用的内存比较少,存储和读取相对来说快。因而,我们读入的图片的路径。比如上图中ENGINE_PLATFORM1TENANTTHUMBNAILTENANT-LOGO_1_1534415695498_1.jpg的图片:
其所对应的服务器的图片地址是http://cw.rosunn.com/upload/i...。因而,我们只要在数据库中存储/ENGINE_PLATFORM/1/TENANT/THUMBNAIL/TENANT-LOGO_1_1534415695498_1.jpg这部分路径就可以的。我们的域名前缀http://cw.rosunn.com/是固定的,其所对应的图片的文件夹upload是固定的,该文件夹下有很多的图片文件夹。每个图片的文件夹都是不一样的。如图所示:
因为我们是Windows服务器,所以服务器是Windows界面化操作。其实,一台电脑就是一台服务器,要不然,怎么说本地服务器呢?在该文件夹下,有三个子文件夹。
- attach文件夹,存储与附件相关的文件夹。
- image文件夹,存储图片的文件夹
- uEditor文件夹,前端会使用uEditor框架,这是多文本编辑器,可以上传图片、视频等。
以后,可能会有视频文件夹,如果做教学软件的话。不管是存储图片文件和附件文件,还是视频文件,我们在数据库中都只存储该文件路径。当我们从数据库中读出图片到前台页面,我门只要拿到其存储的路径,并在前端做如下配置即可:
明白这一点,我们就好往下进行,当我们点击前端代码的导入按钮时,如图所示:
其首先会进入到拦截器,然后再进入到服务器的三层架构中。
服务器的三层架构
我们常说服务器有三层架构,即dao层,service层,controller层。实际上这是个通俗的概念,然而,在真正的开发过程中,并非只有三层架构,其中还会有拦截器的概念。如果你用servlet开发,会涉及到过滤器。拦截器和过滤器的功能是一样的,只不过用法是不一样的。它俩到底有什么区别,我想网上的博客非常多。这里就不在细说了。也许,你可以参考这篇博客:拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别
一般项目启动后,首先进入的不是controller层,而是拦截器,controller层只是针对接口而言的。
拦截器,听名知其意,主要做数据的过滤和拦截。对于数据库中不时常改变的数据,比如系统变量和数据字典等,我们可以放到拦截器的缓存中,当我们加载数据字典时,不必再从数据库中读取,而是读取缓存的数据字典。这样,减少了与数据库的连接,从而提升了效率。
曾经在实习时,有个老大教我,说影响服务端的效率一般是db操作、网络调用操作、线程、JVM优化等。至于,我们是用++i,还是i++,哪个效率高一点。当然,是++i效率高一点。i++内部会有一个临时变量,其存储的i改变前值,然后再执行i = i + 1,返回的也是临时变量。++i直接执行的是i = i + 1,并返回改变后的值。但是,不会考虑到这个问题,因为它的影响微乎其微。
同时,我们每打开一个页面,都要经过拦截器,有些页面需要登录才能看,有些页面可以不用登录。这就是拦截器的作用。
我们这个项目使用的是Apache Shiro框架。其是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理的拦截器框架。
除了,我们登录后台需要身份验证,需要shiro的拦截。或者,我们调用第三方支付接口,其要回调我们的接口。但是,我们对每个接口,都要进行拦截,防止其恶意攻击。此时,我们需要忽略第三方回调的我们的接口,也就是说,这个接口不在我们的拦截范围之内,如下代码:
备注,因为涉及到隐私,部分代码省略,或以 ** 代替,望请见谅。
http://域名/upload//image/sys...。如果其在服务器中存在,我们就创建图片的对象,图片的属性remoteRelativeUrl,存储该路径的后半部分,也就是相对路径,并保存到数据库中,返回一个图片对象。并把图片对象放到集合中,然后保存到材料对象中。材料对象再保存到数据中,这就完成一次导入。但是jsonObjectList可能有多个对象,再遍历一次,直到遍历所有的对象。如下是材料的java类:
@Data @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "zq_material") public class Material extends BaseObj { /** * 材料名称 */ @Column(name = "material_name") private String materialName; /** * 单位 */ @ManyToOne @JoinColumn(name = "unit_code") private DataDict unit; /** * 零售价 */ @Column(name = "retail_price", precision = 12, scale = 2) private BigDecimal retailPrice; /** * 状态 */ @Enumerated(EnumType.STRING) private MaterialStateEnum status; /** * 浏览量 */ @Column(name = "page_view") private Long pageView; /** * 图片 */ @ManyToMany(fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) @JoinTable( name = "zq_material_pictures", joinColumns = {@JoinColumn(name = "zq_material_id")}, inverseJoinColumns = @JoinColumn(name = "core_picture_id") ) @JSONField(serialize = false) private List
为何向上遍历三次
我们看一下字节码文件在服务器的位置,如图所示:
由图可知。一次次的向上遍历,只为找到根路径,也就是http://域名。这是Tomcat的配置。然后再配置upload文件夹,即http://域名/upload
导入的执行效率
以上导入可以分为两种方式。一种是如果导入的数据中,但凡有一条数据不成功,所有的数据都无法导入。这就涉及到了事务一致性的问题。因而,我们需要放在事务层,也就是service层。为什么spring直到service层是事务层,这和我们的框架配置有关,把service层定义为事务层。如果某一条数据导入失败,并不影响其他数据的导入,我们可以放在controller层。
但是,如果处理的不当,便影响导入的执行效率。为什么这么说?比如,我们现在导入的是材料,材料有单位。单位放置在数据字典中,假设单位有16条数据,如图所示:
假如jsonObjectList的集合有1000条数据。我们每次遍历jsonObjectList集合,都要创建一次查询,也就是与数据库创建一次连接,保存之后再关掉连接,势必会减低导入效率:
我们从数据查找出当前单位的行数据,封装成我们想要的数据字典的对象。此时与数据库建立连接和释放数据库的连接,最多需要16000次,这势必会会增加服务器的资源,降低导入的执行效率。最少也需要1000次。 同时,系列也是来源于数据字典,然而,系列是以逗号分割的字符串,也就是说,我们需要将字符串分割成数组,再遍历这个数组,获取数据字典的对象,此时,最少语句数据库的连接数为16000,最多就不大清楚了。因而,严重降低导入的效率。 我们为什么不采用最少的呢?因而,我们在遍历jsonObjectList 之前,就从数据库中的加载出所有的单位的数据字典的集合,同时,也加载出系列的集合。放置在map的键值对当中,根据key值来取value值,如代码所示: 根据数据字典的父code值加载出所有的子code对象。 对于上面的一串代码,我们省略其他的代码,只加载和数据字典相关的代码,于是乎,得到: 这就是数据库导入优化,但是导入图片,和我们上传图片、视频、文档有关系吗? 我们在做java开发时,势必会涉及到文件操作。我们一般会上传图片、文件、视频等,但它们以什么样的格式存储。正如上面提到的,我们上传图片、视频、附件等,会在服务器上创建一个文件夹,他们存储在该文件夹中。我们只要获取文件夹的相对路径即可,就能将其加载出来,这样比较节省数据库的资源。如图所示: //单位
String unitValue = json.getString("I");
if (isNotNull(unitValue)) {
List
/**
* Created By zby on 14:12 2019/3/24
* 将dict封装成map
*/
private Map
/**
* Created By zby on 17:35 2019/2/20
* 导入
*/
@RequestMapping(value = "/import", method = RequestMethod.POST)
public Result importMaterials(HttpServletRequest request) {
JSONObject body = new JSONObject();
int totalNum = 0, successNum = 0;
// 单位
Map
图片、视频、附件上传
这一般都是异步上传,先将文件的路径以对象的保存到数据库中,再返回文件被保存后的带有主键id的对象。我们拿到持久态的文件对象后,在前端页面展示出来。因而,我们在保存材料时,前端只要向后端传输文件的id,或者是文件.id即可,比如 logo.id。spring会自动创建该文件对象,并将id到注入文件对象中。
总结
我们在开发过程中,要知其然,知其所以然。
以上就是《导入图片引发出的对图片、视频、文档等上传的思考》的详细内容,更多关于mysql的资料请关注golang学习网公众号!
-
499 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
101 收藏
-
368 收藏
-
475 收藏
-
266 收藏
-
273 收藏
-
283 收藏
-
210 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习