在做商品管理模块时,需要实现一个图片上传的功能,用作商品图片的上传。在后续的计划中,商品的图片需要生成缩略图用于展示。基于这个原因,前端采用了element-ui的upload组件的照片墙模式,这种模式有小图预览功能。后端采用了thumbnailator的组件,可支持一些图片的处理,比如缩放、旋转、裁剪之类的功能,正好保存两张图片,一张原图,一张经过处理之后的图片。
前端代码:
<el-upload ref="imageUpload" action="/attachmentInfo/uploadImageFile" name="fileNames" list-type="picture-card" multiple accept="image/png,image/jpg,image/jpeg" :file-list="previewList" :on-preview="handlePictureCardPreview" :auto-upload="false" :on-remove="handleRemove" :before-remove="beforeRemove" :on-success="uploadSuccess" :before-upload="beforeUpload" :http-request="uploadFile" > <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过10MB</div> <i class="el-icon-plus"></i> </el-upload> validateData() { let validFlag = false // 获取待上传的文件列表 const uploadFiles = this.$refs.imageUpload.uploadFiles // 提交校验 this.$refs.commodityInfo.validate(valid => { if (valid) { validFlag = valid } if (uploadFiles && uploadFiles.length) { uploadFiles.forEach((item, index) => { if (validFlag && item.size > 10 * 1024 * 1024) { validFlag = false this.$message.error('第' + (index + 1) + '张图片大小不可超过10MB') } }) } }) return validFlag }, saveCommodityData() { const _this = this const saveDataFun = (callback) => { let imageIdArray = [] // console.log(_this.previewList) // console.log(_this.uploadFileList) if (_this.previewList && _this.previewList.length) { _this.previewList.forEach(item => imageIdArray.push(item.id)) } // 获取待上传的文件列表 const uploadFiles = _this.$refs.imageUpload.uploadFiles // 获取到新添加的文件 let newUploadFiles = uploadFiles.filter(item => item.response) newUploadFiles.forEach((item2, index) => { item2.index = index item2 = {...item2, ...item2.response.data[index]} newUploadFiles[index] = item2 imageIdArray.push(item2.id) }) // console.log(newUploadFiles) // 设置图片ids _this.commodityInfo.commodityImgIds = imageIdArray.join(',') // 设置商品分类名称 const commodityTypeIndex = _this.commodityTypeList.findIndex(item => String(item.id) === String(_this.commodityInfo.commodityTypeId)) _this.commodityInfo.commodityTypeName = commodityTypeIndex !== -1 ? _this.commodityTypeList[commodityTypeIndex].typeName : '' // 设置商品信息 _this.commodityInfoSaveDTO.commodityInfo = JSON.parse(JSON.stringify(_this.commodityInfo)) // 设置商品明细数据 _this.commodityInfoSaveDTO.commodityDetailRecordList = JSON.parse(JSON.stringify(_this.detailRecord)) // 设置图片列表 _this.commodityInfoSaveDTO.imageList = _this.commodityInfoSaveDTO.imageList && _this.commodityInfoSaveDTO.imageList.length ? _this.commodityInfoSaveDTO.imageList : [] $.ajax({ url: getContext() + "/commodityInfo/saveCommodityInfo", type: "POST", dataType: "json", contentType: "application/json", data: JSON.stringify(_this.commodityInfoSaveDTO), success: function (data) { if (data) { if (data.isSuccess) { window.top.ZXW_VUE.$notify.success({ message: '保存成功', duration: '1000' }) // 关闭窗口 _this.closeDialog() } else { window.top.ZXW_VUE.$notify.error({ message: data.msg, duration: '1000' }) } } }, error: function (msg) { } }); if (callback) { callback() } } if (this.validateData()) { // 获取待上传的文件列表 const uploadFiles = this.$refs.imageUpload.uploadFiles if (uploadFiles && uploadFiles.length) { this.fileData = new FormData(); // new formData对象 // 调用上传方法 this.$refs.imageUpload.submit() this.uploadFileList.forEach((file) => {// 因为要上传多个文件,所以需要遍历 this.fileData.append('fileNames', file.file) }) // 设置额外参数 this.fileData.append("attachmentType", "commodityImg") $.ajax({ url: getContext() + "/attachmentInfo/uploadImageFile", type: "POST", contentType: false, processData: false, data: this.fileData, success: function (data) { if (data.code === 0) { _this.uploadFileList.forEach(item => { item.onSuccess(data) }) // 调用保存数据方法 saveDataFun(() => { // 清空待上传文件列表 _this.uploadFileList = [] }) } }, error: function (msg) { _this.fileObj.onError(data) } }); } else { // 调用保存数据方法 saveDataFun() } } }, uploadFile(file) { this.uploadFileList.push(file); }, closeDialog() { // 关闭弹窗 window.top.ZXW_VUE.closeFirstDialog() }, handlePictureCardPreview(file) { // 预览图片 this.dialogImageUrl = file.url; window.top.ZXW_VUE.$alert('<img style="width: 60vw;height: 70vh" src="' + this.dialogImageUrl + '" alt="">', '预览图', { dangerouslyUseHTMLString: true, customClass: 'commodityInfoClass', showConfirmButton: false }); }, // 图片上传尺寸大小检验 beforeUpload(file) { let _this = this const isOver10M = file.size / 1024 / 1024 > 10; // 限制小于10M if (isOver10M) { _this.$message.error('图片大小不可超过10MB') } return !isOver10M }, beforeRemove(file, fileList) { const index = fileList.findIndex(item => item.uid === file.uid) // 移除前 return window.top.ZXW_VUE.$confirm(`确定移除第${index + 1}张图片, ${file.name}吗?`).then(() => { }) }
解析:
action是请求地址,默认单图上传时是必须的,name属性的值是对应上传接口中的multipartFile对应的名称,同样是单图上传时需要的值。multiple值表示当前支持多选。accept表示支持的格式。file-list是对应回显的图片列表数据。auto-upload是自动上传属性。http-request覆盖默认上传的功能。
由于elementui的upload组件是默认自动上传的,且为一张一张上传,而我们实际的需求是多张图片选择完成之后,点击保存时再一并上传,上传之前进行校验文件大小,删除时进行提醒,所以就需要做一些特殊的处理。
1、关闭自动上传属性。
2、开启multiple多选属性。
3、http-request对应uploadFile方法,将file对象添加到缓存集合中
4、添加beforeRemove函数,提醒图片删除
5、在保存提交方法中,根据_this.$refs.imageUpload.uploadFiles验证上传图片的大小
6、创建FormData对象,再调用this.$refs.imageUpload.submit(),再从缓存对象中遍历数据,使用append方法添加到formData对象中,key为fileNames
7、通过append方法,添加其他参数
8、调用ajax上传请求,设置contentType: false,processData: false属性
后端代码:
pom依赖 <!-- 图片缩略图 --> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency>
上传代码: /** * 上传图片附件 * * @return 新增结果 */ @ApiOperation(value = "上传图片附件", notes = "上传图片附件") @PostMapping("/uploadImageFile") public R<List<AttachmentInfo>> uploadImageFile(@RequestParam("fileNames") MultipartFile[] files,@RequestParam("attachmentType") String attachmentType) { List<AttachmentInfo> attachmentInfoList = attachmentInfoService.uploadImageFile(files,attachmentType); return success(attachmentInfoList); } public class AttachmentInfoServiceImpl extends ServiceImpl<AttachmentInfoMapper, AttachmentInfo> implements AttachmentInfoService { @Value("${com.zjh.uploadPath}") private String uploadPath; @Override public List<AttachmentInfo> uploadImageFile(MultipartFile[] files, String attachmentType) { // 图片路径 String imageFilePath = uploadPath + "attachment" + File.separator + "images"; File path = new File(imageFilePath); if (!path.exists()) { path.mkdirs(); } List<AttachmentInfo> attachmentInfoList = new ArrayList<AttachmentInfo>(); //判断file数组不能为空并且长度大于0 if (files != null && files.length > 0) { //循环获取file数组中得文件 for (int i = 0; i < files.length; i++) { MultipartFile file = files[i]; //保存文件 if (!file.isEmpty()) { AttachmentInfo attachmentInfo = uploadSingleImageFile(file, attachmentType, imageFilePath); attachmentInfoList.add(attachmentInfo); } } } // 批量保存附件 this.saveBatch(attachmentInfoList); return attachmentInfoList; } @Override public void removeImageByIds(List<Integer> needRemoveFileIds) { // 根据id查询附件数据 List<AttachmentInfo> attachmentInfoList = this.list(Wrappers.<AttachmentInfo>lambdaQuery().in(AttachmentInfo::getId, needRemoveFileIds)); if (CollectionUtils.isNotEmpty(attachmentInfoList)) { for (AttachmentInfo attachmentInfo : attachmentInfoList) { // 获取文件路径 String fileUrl = attachmentInfo.getFileUrl(); // 拼接真实路径 String filePath = uploadPath + fileUrl; // 调用删除 File file = new File(filePath); if (file.isFile() && file.exists()) { file.delete(); } // 获取缩略图路径 String thumbnailUrl = attachmentInfo.getThumbnailUrl(); if (StringUtils.isNotBlank(thumbnailUrl)) { // 拼接真实路径 String filePath1 = uploadPath + thumbnailUrl; // 调用删除 File file1 = new File(filePath1); if (file1.isFile() && file1.exists()) { file1.delete(); } } } // 批量删除附件 this.removeByIds(needRemoveFileIds); } } public AttachmentInfo uploadSingleImageFile(MultipartFile imageFile, String attachmentType, String imageFilePath) { // 创建附件对象 AttachmentInfo attachmentInfo = new AttachmentInfo(); attachmentInfo.setAttachmentType(attachmentType); attachmentInfo.setDeleteBit(false); String fileDirectory = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String uuid = UUID.randomUUID().toString(); //拼接后台文件名称 String pathName = fileDirectory + File.separator + uuid + "." + FilenameUtils.getExtension(imageFile.getOriginalFilename()); //拼接文件路径 String filePathName = imageFilePath + File.separator + pathName; //判断文件保存是否存在 File file = new File(filePathName); if (file.getParentFile() != null || !file.getParentFile().isDirectory()) { //创建文件 file.getParentFile().mkdirs(); } InputStream inputStream = null; FileOutputStream fileOutputStream = null; try { inputStream = imageFile.getInputStream(); fileOutputStream = new FileOutputStream(file); //写出文件 byte[] buffer = new byte[2048]; IOUtils.copyLarge(inputStream, fileOutputStream, buffer); buffer = null; } catch (IOException e) { filePathName = null; throw new BusinessException("操作失败" + e.getMessage()); } finally { try { if (inputStream != null) { inputStream.close(); } if (fileOutputStream != null) { fileOutputStream.flush(); fileOutputStream.close(); } } catch (IOException e) { filePathName = null; throw new BusinessException("操作失败" + e.getMessage()); } } //拼接后台文件名称 String thumbnailPathName = fileDirectory + File.separator + uuid + "small." + FilenameUtils.getExtension(imageFile.getOriginalFilename()); if (thumbnailPathName.contains(".png")) { thumbnailPathName = thumbnailPathName.replace(".png", ".jpg"); } long size = imageFile.getSize(); /* double scale = 1.0d; if (size >= 200 * 1024) { if (size > 0) { scale = (200 * 1024f) / size; } } */ //拼接文件路劲 String thumbnailFilePathName = imageFilePath + File.separator + thumbnailPathName; try { /* if (size < 200 * 1024) { Thumbnails.of(filePathName).scale(1f).outputFormat("jpg").toFile(thumbnailFilePathName); } else { Thumbnails.of(filePathName).scale(1f).outputQuality(scale).outputFormat("jpg").toFile(thumbnailFilePathName); }*/ Thumbnails.of(filePathName).size(400, 500).toFile(thumbnailFilePathName); } catch (Exception e1) { throw new BusinessException("操作失败" + e1.getMessage()); } attachmentInfo.setFileName(imageFile.getOriginalFilename()); attachmentInfo.setFileSuffix(FilenameUtils.getExtension(imageFile.getOriginalFilename())); attachmentInfo.setFileUrl("attachment" + File.separator + "images" + File.separator + pathName); attachmentInfo.setThumbnailUrl("attachment" + File.separator + "images" + File.separator + thumbnailPathName); return attachmentInfo; } }
解析:
1、通过注解接收参数@RequestParam("fileNames") MultipartFile[] files,@RequestParam("attachmentType") String attachmentType,包括fileNames对应的多图文件,以及attachMenetType的额外参数
2、创建图片文件夹目录
3、调用图片上传方法
4、使用Thumbnails.of(filePathName).size(400, 500).toFile(thumbnailFilePathName);方法生成缩略图文件固定尺寸400*500
5、调用附件保存的接口,返回成功数据
还没有评论,来说两句吧...