Springboot+Mybatis 文件上传

ps:【对之前的另一篇博客:FastDFS+SpringBoot+Mybatis 整合 实现文件上传下载又实现了改造,单文件上传。】

 1、文件上传需要创建临时路径,否者上传可能会报错。(文件上传报错:Could not parse multipart servlet request; nested exception is java.io.IOException)

package com.dhcc.znbt.business.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.MultipartConfigElement;
import java.io.File;

/**
 * @Author: Lucifer
 * @Date: 2019/1/31 16:08
 * @Description: 创建文件临时路径 配置类
 */
@Configuration
public class MultipartConfig {

    @Autowired
    public OperatingSystemChangeConfiguration configuration;


    /**
     * 文件上传临时路径
     * 注:文件存储临时文件夹必须有,否则上传文件,可能会报错:
     * Could not parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location...
     */
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //System.getProperty("user.dir") 获取当前工作空间
        String path = System.getProperty("user.dir") + "/temp";
        //判断是否有文件夹,没有就创建
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        //文件路径,如:d:\temp\
        factory.setLocation(path);
        return factory.createMultipartConfig();
    }

}

2、根据不同操作系统读取不同配置(因为测试是Linux,生产是Windows系统,公司有人又用苹果电脑,为了考虑这点,按操作系统读取不同配置)

package com.dhcc.znbt.business.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;

/**
 * @Author: Lucifer
 * @Date: 2019/1/31 17:43
 * @Description: 根据不同操作系统读取不同配置
 */
@Component
@Slf4j
public class OperatingSystemChangeConfiguration {

    @Value("${xxxx.upload.filePath.windowsBackPart}")
    private String windowsBackPart;

    @Value("${xxxx.upload.filePath.linux}")
    private String linuxFilePath;

    @Value("${xxxx.upload.filePath.mac}")
    private String macFilePath;

    private static final String windows = "Windows";
    private static final String Linux = "Linux";
    private static final String Mac = "Mac OS X";


    /**
     * 选择系统盘符
     *
     * @return
     */
   public String chooseSystemDisk() {
        File[] roots = File.listRoots();
        String systemDisk;
        //生产C、E盘 默认是不选择C盘存储
        for (int i = 0; i < roots.length; i++) {
            //过滤掉非本地硬盘的所有盘符
            FileSystemView fileSystemView = FileSystemView.getFileSystemView();
            // 获取磁盘的类型描述信息
            String diskType = fileSystemView.getSystemTypeDescription(roots[i]);
            //盘符类型包括:本地磁盘、可移动磁盘、CD 驱动器等
            if (diskType.equals("本地磁盘")) {
                if (!roots[i].toString().contains("C:")) {
                    systemDisk = roots[i].toString();
                    log.info("【你的操作系统选择的盘符】:" + systemDisk);
                    return systemDisk;
                }
            }
        }
        return null;
    }


    /**
     * 文件存储地址根据操作系统判断
     *
     * @return
     */
    public String changeConfigurationAccordingToOperatingSystem() {
        final String os = System.getProperty("os.name");
        if (os != null) {
            if (os.startsWith(windows)) {
                return chooseSystemDisk() + windowsBackPart;
            } else if (os.startsWith(Linux)) {
                return linuxFilePath;
            } else if (os.startsWith(Mac)) {
                return macFilePath;
            } else {
                try {
                    throw new IOException("Unknown operating system:" + os);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }


}

ps:

          (原本的猜想)如果不排除CD驱动器或者U盘,又出现下图这种情况,就会导致配置类选择CD 驱动器,而不是本地硬盘E盘,然后导致文件上传不进去,建议排除非本地磁盘的所有盘。

Springboot+Mybatis 文件上传_第1张图片

Springboot+Mybatis 文件上传_第2张图片

 

    由于本地是win10,测试是Linux,生产是win2008,本地和测试获取盘符都没有任何问题,但是生产调用chooseSystemDisk()去选择盘符中,需要获取盘符属性,会报错。我猜想是因为windows系统存在了CD 驱动器,然后FileSystemView去调用getSystemTypeDescription报的错。

    因此我将方法改变一下:chooseSystemDisk方法如下:(但是有个缺点是,如果电脑后面插入了U盘,而且U盘的盘符假设是D,那么会选择U盘,而这时候把U盘拔了,文件肯定就不在Windows系统中了,目前就这么解决吧。)

 /**
     * 选择系统盘符
     *
     * @return
     */
    private String chooseSystemDisk() {
        File[] roots = File.listRoots();
        String systemDisk;
        //生产C、E盘 默认是不选择C盘存储
        for (File root : roots) {
            if (!root.toString().contains("C:")&&root.getFreeSpace()!=0) {
                systemDisk = root.toString();
                log.info("【你的操作系统选择的盘符】:" + systemDisk);
                return systemDisk;
            }
           /* //过滤掉非本地硬盘的所有盘符
            FileSystemView fileSystemView = FileSystemView.getFileSystemView();
            // 获取磁盘的类型描述信息
            String diskType = fileSystemView.getSystemTypeDescription(root);
            //盘符类型包括:本地磁盘、可移动磁盘、CD 驱动器等
            if (diskType.equals("本地磁盘")&& StringUtils.isNotBlank(diskType)) {
                if (!root.toString().contains("C:")) {
                    systemDisk = root.toString();
                    log.info("【你的操作系统选择的盘符】:" + systemDisk);
                    return systemDisk;
                }
            }*/
        }
        return null;
    }

 

3、文件上传的自定义配置类

package com.dhcc.znbt.business.util;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:04
 * @Description: 文件上传的配置类
 */
@Data
@ConfigurationProperties(prefix = "xxxx.upload")
public class UploadProperties {

    //允许上传文件的格式
    private List allowTypes;

    //允许上传文件大小
    private long fileSize;


}

4、定时清理无效文件数据,由于表单保存跟文件上传是两个接口,所以文件上传成功,表单这时候点取消

package tk.mybatis.springboot.controller;

import com.dhcc.znbt.business.mybatis.uploadFile.mapper.UploadFileMapper;
import com.dhcc.znbt.business.mybatis.uploadFile.model.UploadFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @Author: Lucifer
 * @Date: 2019/1/30 14:30
 * @Description: 定时清理无效文件和数据库无效文件数据
 */

@Component
@Slf4j
public class CleanUnUseFileTask {

    @Autowired
    private UploadFileMapper uploadFileMapper;

   // @Scheduled(cron = "0 0/5 * * * ?") //每5分钟执行一次,用于测试
    //@Scheduled(cron = "0 0 3 1/1 * ?") //每天凌晨3点钟执行
    public void cleanUnUseFile() {

        log.info("定时任务开始:=====================================》清理无效文件数据时间为:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        /**
         * 查询支付报销单、差旅报销单、借款单 均未与upload_file表关联的文件信息
         */
        List uploadFileList = uploadFileMapper.selectUnUseFile();
        log.info("预计清理个数:==========================================》" + uploadFileList.size());
        for (UploadFile uploadFile : uploadFileList) {
            log.info("准备清理====================》文件数据:" + uploadFile);
            //删除文件
            File file = new File(uploadFile.getFilesPath());
            if (file.exists()) {
                file.delete();
            }
            //删除数据库
            Example example = new Example(UploadFile.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("uid", uploadFile.getUid());
            uploadFileMapper.deleteByExample(example);
        }

    }


}

5、mapper接口 

package com.dhcc.znbt.business.mybatis.uploadFile.mapper;

import com.dhcc.znbt.business.mybatis.uploadFile.model.UploadFile;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

import java.util.List;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:50
 * @Description:附件上传mapper接口
 */

public interface UploadFileMapper extends Mapper, MySqlMapper {

    List selectUnUseFile();
}

6、mapper.xml 





    
        
        
        
        
        
        
        
        
    


    

7、文件上传的控制层

package tk.mybatis.springboot.controller.uploadFile;

import com.dhcc.znbt.business.mybatis.cw.base.ResponseBean;
import com.dhcc.znbt.business.mybatis.uploadFile.model.BXAndUploadDTO;
import com.dhcc.znbt.business.mybatis.uploadFile.model.UploadFile;
import com.dhcc.znbt.business.uploadFile.UploadFileService;
import groovy.util.logging.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:10
 * @Description: 文件上传的控制层
 */
@Slf4j
@RestController
@RequestMapping(value = "upload")
public class UploadFileController {

    @Autowired
    private UploadFileService uploadFileService;


    /**
     * 上传文件
     *
     * @param file
     * @param dto
     * @return
     */
    @PostMapping(value = "uploadFile")
    public ResponseBean uploadFile(@RequestParam("file") MultipartFile file, BXAndUploadDTO dto) {
        Map map;
        try {
            map = uploadFileService.uploadFile(file, dto);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseBean.fail(e.getMessage());
        }
        return ResponseBean.success(map);
    }


    /**
     * 删除文件
     *
     * @param uploadFile
     */
    @PostMapping(value = "delFile")
    public ResponseBean delFile(@RequestBody UploadFile uploadFile) {
        Map map;
        try {
            map = uploadFileService.deleteFile(uploadFile);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseBean.fail(e.getMessage());
        }
        return ResponseBean.success(map);
    }


    /**
     * 查询文件
     *
     * @param uploadFile
     * @return
     */
    @PostMapping(value = "fileFileByCode")
    public ResponseBean findFileByCode(@RequestBody UploadFile uploadFile) {
        Map map;
        try {
            map = uploadFileService.findFileByCode(uploadFile);
        } catch (Exception e) {
            return ResponseBean.fail(e.getMessage());
        }
        return ResponseBean.suc(map);
    }
}

8、文件上传service接口

package com.dhcc.znbt.business.uploadFile;

import com.dhcc.znbt.business.exception.ZnbtException;
import com.dhcc.znbt.business.mybatis.uploadFile.model.BXAndUploadDTO;
import com.dhcc.znbt.business.mybatis.uploadFile.model.UploadFile;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:41
 * @Description:附件上传接口
 */

public interface UploadFileService {

    /**
     * 上传文件
     *
     * @param bxAndUploadDTO
     * @param file
     * @throws Exception
     */
    Map uploadFile(MultipartFile file, BXAndUploadDTO bxAndUploadDTO) throws Exception;


    /**
     * 删除附件
     *
     * @param uploadFile
     * @throws Exception
     */
    Map deleteFile(UploadFile uploadFile) throws Exception;

    /**
     * 查询文件
     * @param uploadFile
     * @return
     */
    Map findFileByCode(UploadFile uploadFile);
}

9、文件上传service实现类(注:由于预览中是ip+虚拟地址+文件完整名称,所以查看中地址返回的不包含盘符,预览地址是拼接的。详情查看13)

package com.dhcc.znbt.business.uploadFile.impl;

import com.dhcc.znbt.business.mybatis.uploadFile.mapper.UploadFileMapper;
import com.dhcc.znbt.business.mybatis.uploadFile.model.BXAndUploadDTO;
import com.dhcc.znbt.business.mybatis.uploadFile.model.UploadFile;
import com.dhcc.znbt.business.uploadFile.UploadFileService;
import com.dhcc.znbt.business.util.FileUtil;
import com.dhcc.znbt.business.util.OperatingSystemChangeConfiguration;
import com.dhcc.znbt.business.util.UploadProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:42
 * @Description: 附件上传实现类
 */
@Slf4j
@Service
@EnableConfigurationProperties(UploadProperties.class)
public class UploadFileServiceImpl implements UploadFileService {

    @Autowired
    private UploadProperties uploadProperties;

    @Autowired
    private UploadFileMapper uploadFileMapper;

    @Autowired
    private OperatingSystemChangeConfiguration configuration;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map uploadFile(MultipartFile aFile, BXAndUploadDTO dto) throws Exception {

        Map map = new HashMap<>();
        String path = null;
        String originalFilename;
        try {
            //检查文件类型,大小
            checkFileType(aFile);
            //带后缀名,文件名
            originalFilename = aFile.getOriginalFilename();
            //不带后缀名,文件名
            String fileName = FileUtil.getFileName(aFile);
            //后缀名
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            //时间戳
            Long time = System.currentTimeMillis();
            path = configuration.changeConfigurationAccordingToOperatingSystem() + time + suffix;
            File filePath = new File(path);
            if (!filePath.getParentFile().exists()) {
                filePath.getParentFile().mkdirs();
            }
            if (StringUtils.isBlank(dto.getBorrowCode())) {
                throw new RuntimeException("报销单号不允许为空");
            }
            if (StringUtils.isBlank(dto.getUserName())) {
                throw new RuntimeException("附件上传人不能为空");
            }
            Example example = new Example(UploadFile.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("borrowCode", dto.getBorrowCode());
            if (uploadFileMapper.selectByExample(example).size() > 0) {
                throw new RuntimeException("报销单号:" + dto.getBorrowCode() + ",数据库中已存在");
            }
            UploadFile uploadFile = new UploadFile();
            String fileId = UUID.randomUUID().toString().replace("-", "");
            uploadFile.setUid(fileId);
            uploadFile.setName(fileName);
            uploadFile.setFilesType(suffix);
            uploadFile.setFilesPath(path);
            uploadFile.setUploadTime(time);
            uploadFile.setSize(aFile.getSize());
            map.put("filePath", path);
            map.put("fileId", fileId);
            uploadFile.setBorrowCode(dto.getBorrowCode());
            uploadFile.setCreateUser(dto.getUserName());
            int insert = uploadFileMapper.insert(uploadFile);
            if (insert > 0) {
                //将文件写入硬盘
                aFile.transferTo(filePath);
            }
        } catch (IOException e) {
            log.error("文件写入失败,路径:" + path);
            throw new IOException("文件写入失败");
        } catch (Exception e) {
            //判断文件是否存在
            if (StringUtils.isNotBlank(path) && new File(path).exists()) {
                new File(path).delete();
            }
            log.error("文件上传失败,正在清理文件,{}", e.getMessage());
            e.printStackTrace();
            throw new Exception(e.getMessage());
        }
        return map;
    }


    /**
     * 根据文件名删除本地文件,根据id删除数据库数据
     *
     * @param uploadFile
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map deleteFile(UploadFile uploadFile) throws Exception {

        boolean b;
        Map map = new HashMap<>();
        try {
            if (StringUtils.isBlank(uploadFile.getFilesPath())) {
                throw new RuntimeException("文件路径不能为空!");
            }
            if (StringUtils.isBlank(uploadFile.getUid())) {
                throw new RuntimeException("文件id不能为空!");
            }
            //若filePath 不包含盘符的完整路径,则重写文件路径
            String filePath = uploadFile.getFilesPath();
            if (!filePath.contains(configuration.changeConfigurationAccordingToOperatingSystem())) {
                filePath = configuration.changeConfigurationAccordingToOperatingSystem() + filePath;
            }
            String fileId = uploadFile.getUid();
            File file = new File(filePath);
            log.info("正在删除文件,文件路径为:" + filePath);
            Example example = new Example(UploadFile.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("uid", fileId);
            if (uploadFileMapper.selectByExample(example) == null) {
                log.error("文件id:" + fileId + "数据库中不存在!");
                throw new RuntimeException("文件id在数据库中不存在!");
            }
            //判断文件是否存在
            if (file.exists()) {
                b = file.delete();
            } else {
                log.info("文件不存在,路径:" + uploadFile.getFilesPath());
                throw new RuntimeException("文件不存在");
            }
            if (b) {
                uploadFileMapper.deleteByExample(example);
                log.info("文件路径:" + uploadFile.getFilesPath() + ",删除文件成功");
            } else {
                log.info("文件路径:" + uploadFile.getFilesPath() + ",删除文件失败");
                throw new RuntimeException("文件删除失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(e.getMessage());
        }
        return map;
    }


    @Override
    public Map findFileByCode(UploadFile uploadFile) throws Exception {
        String s ;
        Map map;
        try {
            if (StringUtils.isBlank(uploadFile.getBorrowCode())) {
                throw new RuntimeException("报销单号不能为空");
            }
            UploadFile up = new UploadFile();
            up.setBorrowCode(uploadFile.getBorrowCode());
            UploadFile one = uploadFileMapper.selectOne(up);
            if (one != null && StringUtils.isNotBlank(one.getFilesPath())) {
                s = one.getFilesPath().replace(configuration.changeConfigurationAccordingToOperatingSystem(), "").trim();
                File file = new File(one.getFilesPath());
                //数据库、硬盘 查询
                if (StringUtils.isBlank(s) || !file.exists()) {
                    throw new RuntimeException("文件不存在");
                }
                one.setFilesPath(s);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw new Exception(e.getMessage());
        }
        map = new HashMap<>();
        map.put("data", uploadFile);
        return map;
    }

    /**
     * 检查文件是否是允许的类型
     *
     * @param uploadFile
     */
    private void checkFileType(MultipartFile uploadFile) {
        String type = uploadFile.getContentType();
        //校验文件类型是否被允许可以上传
        if (!uploadProperties.getAllowTypes().contains(type)) {
            log.info("该文件类型不允许上传,{}", type);
            throw new RuntimeException("该文件类型不允许上传,目前只允许的上传类型:" + uploadProperties.getAllowTypes());
        }
        if (uploadFile.getSize() > uploadProperties.getFileSize()) {
            log.info("本次上传附件大小:{},最大允许上传附件大小为:{}", convertFileSize(uploadFile.getSize()), convertFileSize(uploadProperties.getFileSize()));
            throw new RuntimeException("允许上传的附件最大为:" + convertFileSize(uploadProperties.getFileSize()));
        }
    }

    /**
     * 将字节大小转换为KB/MB/GB/B
     *
     * @param size
     * @return
     */
    public static String convertFileSize(long size) {
        long kb = 1024;
        long mb = kb * 1024;
        long gb = mb * 1024;

        if (size >= gb) {
            return String.format("%.1f GB", (float) size / gb);
        } else if (size >= mb) {
            float f = (float) size / mb;
            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
        } else if (size >= kb) {
            float f = (float) size / kb;
            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
        } else {
            return String.format("%d B", size);
        }
    }

}

 10.返回状态码

package com.dhcc.znbt.business.mybatis.cw.base;

import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 */
public class ResponseBean extends HashMap {
    private static final long serialVersionUID = 1L;

    public ResponseBean() {
    }

    public ResponseBean put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    public static ResponseBean success(Object map) {
        ResponseBean r = new ResponseBean();
        r.put("statusCode", 200);
        r.put("massage", "文件操作成功!");
        r.put("data", map);
        return r;
    }

    public static ResponseBean suc(Map oMap) {
        ResponseBean r = new ResponseBean();
        Map map = new HashMap<>();
        map.put("statusCode", 200);
        map.put("massage", "查询成功!");
        map.put("data",oMap.get("data"));
        r.put("data", map);
        return r;
    }

    public static ResponseBean fail(String message) {
        ResponseBean r = new ResponseBean();
        Map map = new HashMap<>();
        map.put("statusCode", 500);
        map.put("massage", message);
        r.put("data", map);
        return r;
    }

}

11、文件上传实体类

package com.dhcc.znbt.business.mybatis.uploadFile.model;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Table;

/**
 * @Author: Lucifer
 * @Date: 2019/1/21 15:38
 * @Description: 文件上传实体类
 */
@Data
@Table(name = "upload_file")
public class UploadFile {

    @ApiModelProperty(value = "ID")
    @Column(name = "id")
    private String uid;

    @ApiModelProperty(value = "上传时间")
    @Column(name = "uploadTime")
    private Long uploadTime;

    @ApiModelProperty(value = "文件名")
    @Column(name = "filesName")
    private String name;

    @ApiModelProperty(value = "文件地址")
    @Column(name = "filesPath")
    private String filesPath;

    @ApiModelProperty(value = "文件类型")
    @Column(name = "filesType")
    private String filesType;

    @ApiModelProperty(value = "文件大小")
    @Column(name = "fileSize")
    private Long size;

    @ApiModelProperty(value = "报销单号")
    @Column(name = "borrowCode")
    private String borrowCode;

    @ApiModelProperty(value = "上传人")
    @Column(name = "createUser")
    private String createUser;

}

12、springboot配置文件

xxxx:
    upload:
        filePath:  #上传存储本地文件的位置
            windowsBackPart: uploadFile\  #本地或者生产 完整路径:如:D:\uploadFile\,此处不需要加盘符  盘符动态获取,如:D:\
            linux: /home/xxxx/uploadFile/   #测试
            mac: /usr/uploadFile/
        allowTypes:   #允许上传的文件类型
            - application/pdf
        fileSize: 10485760 #字节,允许上传文件大小10M

13、如果想实现文件预览。可以利用Tomcat。本地的话,谷歌浏览器为了考虑安全性,是不允许js去打开本地文件,比如:D:\A.pdf,当然直接复制文件路径在浏览器是可以打开的,不过对于需要前端打开本地文件,这个好像并没有什么作用,因此,需要使用虚拟路径。

参考另一篇博客:通过tomcat 用ip访问Linux路径下的文件

你可能感兴趣的:(SpringBoot技术篇,Mybatis实战篇)