解决的问题:
1、使用view的<Upload>组件实现图片文件的上传。
2、<Upload>组件action请求地址无法到自己写的后台。
3、前台base64的图片展示。
4、文件伪造。(修改文件的后缀为图片格式的后缀)。
需求很简单:
将某一模块的编辑页面的"XX图片"字段由文本框输入改成上传图片。或者新增一个图片上传的属性。
要求:
1、 前端图片展示用BASE64格式的形式展示。
一、完成后的效果图:
1、上传前
2、点击相机,选择图片文件进行上传。
2.1 文件格式的校验
2.2 文件大小的限制
3、上传后
4、点击图片中的眼睛图标,可以对图片进行放大查看。
5、点击图片中的垃圾筒的图标,可以对图片进行删除。回到上传前。
二、前端vue组件代码。
官网地址:
https://www.iviewui.com/components/upload
<template> <div class="demo-upload-list" v-if="formData.stationPic"> <template> <img :src="formData.stationPic"> <div class="demo-upload-list-cover"> <Icon type="ios-eye-outline" @click.native="handleView()"></Icon> <Icon type="ios-trash-outline" @click.native="handleRemove()"></Icon> </div> </template> </div> <Upload ref="upload" :show-upload-list="false" :on-success="handleSuccess" :format="['jpg','jpeg']" :max-size="2048" :on-format-error="handleFormatError" :on-exceeded-size="handleMaxSize" :before-upload="handleBeforeUpload" :headers="headers" type="drag" action="/api/zclanes/upload" style="display: inline-block;width:58px;"> <div style="width: 58px;height:58px;line-height: 58px;"> <Icon type="ios-camera" size="20"></Icon> </div> </Upload> <Modal title="View Image" v-model="visible"> <img :src="formData.stationPic" v-if="visible" style="width: 100%"> </Modal> </template>
data() { return { headers: {'Sonep-Token': CacheUtil.getSession('access-token')}, visible: false, },
methods: { handleView() { this.visible = true; }, handleRemove() { this.formData.stationPic = null; }, handleSuccess(res, file) { if (res.status === 200) {//上传成功 this.$Message.success('上传成功'); this.formData.stationPic = res.data; } else { this.$Message.error('上传失败'); } }, handleFormatError(file) { this.$Notice.warning({ title: '文件格式不正确', desc: file.name + '的文件格式不正确, 请选择jpg或者jpeg格式的图片' }); }, handleMaxSize(file) { this.$Notice.warning({ title: '超出文件大小限制', desc: file.name + '文件太大,不能超过2M.' }); }, handleBeforeUpload() {//上传文件之前的钩子,参数为上传的文件,若返回 false 或者 Promise 则停止上传 } },
三、后端代码
3.1 controller
/** * 图片上传 * @param file * @return */ @RequestMapping("/upload") public ResponsePayload upload(MultipartFile file){ return service.upload(file); }
3.2 service
@Override public ResponsePayload upload(MultipartFile file) { try { //判断文件是不是图片 BufferedImage image = ImageIO.read(file.getInputStream()); if (image == null || image.getWidth() <= 0 || image.getHeight() <= 0) { return ResponseUtil.getFailResponse(HttpStatus.SC_BAD_REQUEST, "你上传的不是图片文件"); } } catch (IOException e) { e.printStackTrace(); return ResponseUtil.getFailResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "上传异常, 请稍后再试..."); } //获取文件的路径 String filePath = IOHelper.checkPath(System.getProperty("java.io.tmpdir")) + "\\img"; // String filePath = FileUtils.getDataFilePath("d:/img"); //1. 获取文件的原始名称 String originalFilename = file.getOriginalFilename();//timg (1).jpg //1.1 获取最后一个.的位置 int lastIndexOf = file.getOriginalFilename().lastIndexOf("."); //1.2 获取文件的后缀名 .jpg String suffix = originalFilename.substring(lastIndexOf); //2. 重命名文件名称 String fileName = UUID.randomUUID().toString() + suffix; File savedFile = new File(filePath, fileName); try { file.transferTo(savedFile); //转BASE64 String path = savedFile.getPath(); //返回BASE64图片字符串 return ResponseUtil.success(ImageBase64Utils.toBase64(path)); } catch (IOException e) { e.printStackTrace(); return ResponseUtil.getFailResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR,"上传异常, 请稍后再试..."); } finally { //删除本地保存的图片 savedFile.delete(); } }
四、问题回顾及解决
我们依次看下文章开头提到的4个问题
4.1 使用view的<Upload>组件实现图片文件的上传。
可以看下本文的第二部分:前端vue组件代码。
同时也给出官网地址:
https://www.iviewui.com/components/upload
4.2 <Upload>组件action请求地址无法到自己写的后台。
这个问题很恶心。
4.2.1 action地址的写法
官网用的是自己写的请求地址:action="//jsonplaceholder.typicode.com/posts/"。仔细一看是什么鬼,怎么有两个斜杠。
运行下官网的例子:
我们看到发送的请求是:https://jsonplaceholder.typicode.com/posts/。
仔细分析下:发现地址前面是带域名的:jsonplaceholder.typicode.com
这里有几种写法:
写法一:如果域名是不变的。
action="//localhost:8080/api/zclanes/upload" ---> http://localhost:8080/api/zclanes/upload
写法二:不加域名的写法。(本文用的是这种)
action="/api/zclanes/upload" ---> http://localhost:8080/zclanes/api/upload
4.2.2 请求的发送
填写完正确的地址后,后台代码也写好了。重启项目后,请求发送不出去。
我这里最后查出来是少加了个请求头。猜测:项目有auth功能,每个请求都必须加鉴权的请求头进行鉴权。如果有跟我一样的问题,找到要加的请求头,把请求头加上去就ok了。部分代码如下,详细代码上文有。其实想说明的是,<Upload>组件请求头如何添加。 其实还可以添加请求参数,具体参考官网API。
:headers="headers"
headers: {'Sonep-Token': CacheUtil.getSession('access-token')},
4.2.3 前台base64的图片展示。
参考地址:https://www.cnblogs.com/hjw-zq/p/8821898.html
其实就一句代码:
formData.stationPic: 就是后台返回的BASE64的字符串。
<img :src="formData.stationPic">
base图片展示原理: https://www.cnblogs.com/zdz8207/p/web-image-base64.html
个人理解:将图片文件转成BASE64格式的字符串。由页面自动会解析BASE64格式的图片文件。跟图片文件所在路径的没有关系。
4.2.4 文件伪造。(修改文件的后缀为图片格式的后缀)。
其实解决完上面三个问题,这个功能完成的就八九不离十了。 当我在测文件大小限制的时候,我电脑里没找到2M的图片,于是我就找了个2M的excel文件,把它的后缀名改成了图片格式进行上传。测试通过。于是我就想到了,如果小于2M的文件改了会上传成功么。 我的答案肯定是不允许的,因为它本质就不是图片。测试也能上传成功,但就是显示不出图案来。这肯定是不对的。
于是我就想到了还少个判断文件是否是图片的逻辑。
Java判断文件是否是图片:
参考地址:https://blog.csdn.net/itjauser/article/details/97395034
五、小结:
图片上传看似简单的一个动作,其实过程很复杂。
流程大致如下:
1、选择上传的文件,发送请求。(这里请求的业务逻辑有很多种解决方案,可以把图片上传到服务器(七牛云、阿里云、本地的数据库等)。或者转成BASE64格式的字符串。不管哪种实现方式,目的都是为了把图片以某种形式存放起来,并将图片地址存放的地址返回,在前台进行显示)。
2、 将图片以BASE64的形式存储。
3、 返回BASE64字符串。
4、 拿到后台响应的图片地址,进行展示。