《智选网开发系列》之图片上传功能(八)

原创  郑建华   2021-06-14   181人阅读  0 条评论

image.png

    在做商品管理模块时,需要实现一个图片上传的功能,用作商品图片的上传。在后续的计划中,商品的图片需要生成缩略图用于展示。基于这个原因,前端采用了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、调用附件保存的接口,返回成功数据


image.png

本文地址:https://www.zjh336.cn/?id=2043
版权声明:本文为原创文章,版权归 郑建华 所有,欢迎分享本文,转载请保留出处!

发表评论


表情

还没有留言,还不快点抢沙发?