Springboot实现文件上传,并防止同文件重复上传

目录

    • 主要流程
    • 编写接受文件上传的Controller
    • 编写文件操作结果类
    • 编写文件操作类
    • 知识总结
    • 参考

主要流程

  1. 在配置文件中添加文件操作的配置,示例:
storage:
  image:
  	#保存位置
    save-path: D:\classdesign-photo\images\
    #允许上传的类型
    allow-type:
      - jpg
      - png
  1. 编写文件操作配置类,示例:
/**
 * 图片操作配置类
 */
@Configuration
//用于自动获取配置文件中storage.image中的字段
@ConfigurationProperties("storage.image")
@Data
public class ImageConfig {
    private String savePath;
    private List<String> allowType;
}

  1. 编写接受文件上传的Controller方法,并带上参数MultipartFile file,如:
public T upload(MultipartFile file) throws IOException {...}
  1. 计算文件的字节数组的MD5的值,查找数据库中是否有重复的MD5值,防止重复上传相同文件(可以使用Hutool计算MD5),后面有具体实现
  2. 保存文件到对应文件夹,并往数据库中添加一条记录,数据库只存储文件的路径、MD5值、上传用户等信息

编写接受文件上传的Controller

在SpringBoot接受文件比较简单,只需要在Controller方法上加上参数MultipartFile file即可获取前端上传的文件

	@PostMapping("/upload")
    public Response<FileHandlerResult> upload(MultipartFile image) throws IOException {
    	//自定义的通用回复类
        Response<FileHandlerResult> res = new Response<>();
        //自定义文件保存结果通用类
        FileHandlerResult saveRes = fileManager.saveImage(image);
        if(saveRes.getCode()==-1){
            //保存失败
            res.fail(saveRes.getDesc());
            return res;
        }
        else if(saveRes.getCode() == 0){
            //图片已存在
            res.setDesc(saveRes.getDesc());
            res.setData(saveRes);
        }
        res.success(saveRes);
        return res;
    }

编写文件操作结果类

因为保存文件的过程中可能出现成功、失败、异常三种情况,因此编写一个通用的文件操作结果类来返回信息

/**
 * 文件操作结果
 */
@Data
public class FileHandlerResult{
    /**
     * 状态码,成功:1,失败:-1,其他:0(如:图片已存在)
     */
    private int code;
    private String md5;//文件字节数组的md5,用于防止重复上传
    private String path;//文件存储路径
    private String desc;//结果状态描述

    public void success(String md5, String path){
        this.code = 1;
        this.md5 = md5;
        this.path = path;
        this.desc = "保存文件成功";
    }

    public void alreadyExisted(String md5, String path){
        this.code = 0;
        this.md5 = md5;
        this.path =path;
        this.desc = "文件已存在,请勿重复保存";
    }

    public void fail(String desc){
        this.code = -1;
        this.desc = desc;
    }

}

编写文件操作类

此类中通过文件后缀来判断文件类型的方式并不安全(文件后缀可以伪造),应通过魔数判断,可参考:Java 通过魔数判断上传文件的类型

/**
 * 文件操作类
 * 用于文件的基本
 */
@Component
public class FileManager<T extends BaseEntity> {
    @Autowired
    BaseFileDao<T> dao;

    @Autowired
    ImageConfig imageConfig;//一开始编写的文件配置类

    /**
     *
     * @param uploadFile 从控制器接收到的文件
     * @return
     */
    public FileHandlerResult saveImage(MultipartFile uploadFile) {
        //获取文件类型,根据文件后缀判断文件类型的方式不安全!
        String contentType = uploadFile.getContentType();
        String type = contentType.substring(contentType.indexOf("/")+1);

        //文件操作返回结果
        FileHandlerResult handlerResult = new FileHandlerResult();
        if(!imageConfig.getAllowType().contains(type)){
        	//判断是否为允许的文件类型
            handlerResult.fail("保存失败,仅支持:"+imageConfig.getAllowType());
            return handlerResult;
        }

        try{
            File file = new File(imageConfig.getSavePath());
            if(!file.exists()){
                //创建文件夹,会自动创建父文件夹
                file.mkdirs();
                //创建目录说明文件
                String descFilePath = new File(imageConfig.getSavePath()).getParentFile().toString()+"\\目录说明.txt";
                try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(descFilePath)))){
                    writer.write("此目录为保存 xxx 项目文件的目录");
                }
            }
            byte[] bytes = uploadFile.getBytes();
            //图片字节数组的md5
            String md5 = SecureUtil.md5(uploadFile.getInputStream());
            List<T> list = dao.getByMd5(md5);
            //图片保存路径
            String path = imageConfig.getSavePath() + md5+"."+type;
            if(list.size() != 0){
                //图片已存在
                handlerResult.alreadyExisted(md5, path);
                return handlerResult;
            }
            try(BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(path))){
                os.write(bytes);
            }
            handlerResult.success(md5, path);
            return handlerResult;
        }
        catch(FileNotFoundException e){
            e.printStackTrace();
            handlerResult.fail(e.getMessage());
            return handlerResult;
        }
        catch (IOException e){
            e.printStackTrace();
            handlerResult.fail(e.getMessage());
            return handlerResult;
        }
    }
}

知识总结

  • SpringBoot 使用MultipartFile类型的参数接受前端上传的文件
  • 通过计算文件字节数组的MD5值,可用于防止文件重复上传
  • 通过File类的创建目录时:
    mkdir() 创建目录必须确保路径的父目录已存在
    mkdirs()如果父文件夹不存在时并且最后一级子文件夹不存在,它就自动新建所有路经里写的文件夹;如果父文件夹存在,它就直接在已经存在的父文件夹下新建子文件夹。

参考

  • MultipartFile 类
  • Java 通过魔数判断上传文件的类型
  • SpringBoot实现多文件上传
  • java File类mkdir()与mkdirs()方法的区别

你可能感兴趣的:(springboot,java,springboot,文件上传)