SpringBoot中的上传文件

上传文件的操作在有些功能中属于比较常用的环节,这里整理下SpringBoot环境中上传文件的实现方式。

这里实现的是上传文件的后台接口,前端部分可以用测试工具模拟实现,就先不在这里表述了。

Dto层

使用MultipartFile类型的变量来接收文件

package com.bbzd.business.file.entity.fileService.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.*;

/**
 * Description:
 * Copyright: Copyright (c) 2013
 * Company: www.bbzd.com
 *
 * @author wangjianzhong
 * @version 1.0
 */
@Data
public class UploadFileDto {

    /**
     * 文件组令牌
     */
    @ApiModelProperty(value = "文件组令牌")
    @NotBlank(message = "文件组令牌不能为空")
    private String token;

    /**
     * 文件
     */
    @ApiModelProperty(value = "文件")
    @NotNull(message = "文件不能为空")
    private MultipartFile file;

    /**
     * 用户代码
     */
    @ApiModelProperty(value = "用户代码")
    @NotBlank(message = "用户代码不能为空")
    private String userCode;

    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    @NotBlank(message = "用户名不能为空")
    private String userName;

    /**
     * 搜素标识符
     */
    @ApiModelProperty(value = "搜素标识符")
    private String searchIdentifier;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注")
    private String remark;
}
Controller层

请求类型 POST
参数类型 MediaType.MULTIPART_FORM_DATA_VALUE

package com.bbzd.business.file.controller;

import com.bbzd.business.file.entity.fileService.dto.UploadFileDto;
import com.bbzd.business.file.service.FileService;
import com.bbzd.common.annotation.LogAnnotation;
import com.bbzd.common.base.Result;
import com.bbzd.common.enums.LogOperateTypeEnum;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * Title: FileServiceController
 * Description:
 * Copyright: Copyright (c) 2013
 * Company: www.bbzd.com
 *
 * @author wangjianzhong
 * @version 1.0
 */
@RestController
@RequestMapping("/webApi/file/fileService")
@Api(tags = "file-fileService-文件服务")
public class FileServiceController {

    @Resource
    FileService fileService;

    /**
     * @method: 查询文件信息列表
     * @author wangjianzhong
     * @date 2024/03/05
     */
    @PostMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ApiOperation(value = "上传文件", notes = "上传文件")
    @LogAnnotation(operationType = LogOperateTypeEnum.ADD, operateContent = "上传文件")
    public Result<?> pageData(@Validated UploadFileDto dto){
        return fileService.saveFile(dto);
    }
}
Service层

代码有些臃肿,有时间再简化下,本质就是:
将文件转换为字节,再用输出流输出下就可以了

package com.bbzd.business.file.service.impl;

import com.bbzd.business.bsi.audao.MbgBsiParamMapper;
import com.bbzd.business.bsi.model.auGened.MbgBsiParam;
import com.bbzd.business.file.audao.MbgBsiFileGroupMapper;
import com.bbzd.business.file.audao.MbgBsiFileMapper;
import com.bbzd.business.file.entity.fileService.dto.UploadFileDto;
import com.bbzd.business.file.model.auGened.MbgBsiFile;
import com.bbzd.business.file.model.auGened.MbgBsiFileExample;
import com.bbzd.business.file.model.auGened.MbgBsiFileGroup;
import com.bbzd.business.file.model.auGened.MbgBsiFileGroupExample;
import com.bbzd.business.file.service.FileService;
import com.bbzd.business.utils.DateUtils;
import com.bbzd.business.utils.FileTokenUtils;
import com.bbzd.business.utils.StringUtils;
import com.bbzd.common.base.Result;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.stream.Collectors;

/**
 * Description: 文件服务实现
 * Copyright: Copyright (c) 2013
 * Company: www.bbzd.com
 *
 * @author wangjianzhong
 * @version 1.0
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class FileServiceImpl implements FileService {

    @Value("${constant.file.saveRootPath}")
    private String fileSaveRootPath;
    @Value("${constant.file.commonFileDir}")
    private String commonFileDir;
    @Value("${constant.file.bigFileDir}")
    private String bigFileDir;


    private final String fileSeparator= File.separator;

    @Resource
    MbgBsiFileGroupMapper mbgBsiFileGroupMapper;
    @Resource
    MbgBsiFileMapper mbgBsiFileMapper;
    @Resource
    private MbgBsiParamMapper mbgBsiParamMapper;

    private Set<String> suffixSet=new ConcurrentSkipListSet<>();



    public FileServiceImpl(@Qualifier("mbgBsiParamMapper") MbgBsiParamMapper mbgBsiParamMapper) {
        MbgBsiParam param = mbgBsiParamMapper.selectByPrimaryKey("file_bigFile_suffix");
        if(param!=null&¶m.getValue()!=null){
            String[] suffixArr=param.getValue().split("\\|");
            Collections.addAll(suffixSet,suffixArr);
        }
    }

    @Scheduled(cron="0 */1 * * * ?")
    public void fresh(){
        MbgBsiParam param = mbgBsiParamMapper.selectByPrimaryKey("file_bigFile_suffix");
        if(param!=null&¶m.getValue()!=null){
            suffixSet.clear();
            String[] suffixArr=param.getValue().split("\\|");
            Collections.addAll(suffixSet,suffixArr);
        }
    }

    /**
     * 存储文件
     *
     * @param dto 输入参数
     * @return 结果
     */
    @Override
    public Result saveFile(UploadFileDto dto) {

        //检查输入参数
        String groupToken= dto.getToken();
        MbgBsiFileGroup fileGroup=null;
        MbgBsiFileGroupExample fileGroupExample=new MbgBsiFileGroupExample();
        MbgBsiFileGroupExample.Criteria fileGroupCriteria=fileGroupExample.createCriteria();
        fileGroupCriteria.andGroupNoEqualTo(groupToken);
        List<MbgBsiFileGroup> fileGroupList=mbgBsiFileGroupMapper.selectByExample(fileGroupExample);
        if(!CollectionUtils.isEmpty(fileGroupList)){
            fileGroup=fileGroupList.get(0);
        }
        if(fileGroup==null){
            return Result.fail("无效令牌,禁止操作");
        }

        MultipartFile file=dto.getFile();
        if(file.isEmpty()){
            return Result.fail("文件为空,禁止操作");
        }
        System.out.println("文件名1:"+file.getName());
        System.out.println("文件名2:"+file.getOriginalFilename());
        System.out.println("文件名类型:"+file.getContentType());
        System.out.println("文件大小:"+file.getSize());

        //检查文件空间大小是否满足
        MbgBsiFileExample fileExample=new MbgBsiFileExample();
        MbgBsiFileExample.Criteria fileCriteria=fileExample.createCriteria();
        fileCriteria.andGroupNoEqualTo(fileGroup.getGroupNo());
        List<MbgBsiFile> fileList=mbgBsiFileMapper.selectByExample(fileExample);

        long fileSizeSum=0L;
        if(!CollectionUtils.isEmpty(fileList)){
            fileSizeSum=fileList
                    .stream()
                    .mapToLong(MbgBsiFile::getSize).sum();
        }
        long currFileSize=fileSizeSum+file.getSize();
        if(currFileSize>fileGroup.getMaxFileSize()){
            return Result.fail("文件组空间不够容纳本次上传文件,禁止操作");
        }

        String suffix=StringUtils.getFileSuffix(file.getOriginalFilename());
        String dirTypePath="";
        if(suffixSet.contains(suffix.toUpperCase())){
            dirTypePath=bigFileDir;
        }else{
            dirTypePath=commonFileDir;
        }

        Date now=new Date();
        String dateDir=
            DateUtils.getYearStr(now)+fileSeparator+
            DateUtils.getMonthStr(now)+fileSeparator+
            DateUtils.getDateStr(now);

        String dirPath=fileSaveRootPath+fileSeparator+dirTypePath+fileSeparator+dateDir;

        File dir=new File(dirPath);
        if(!dir.exists()){
            dir.mkdirs();
        }

        String fileName=file.getOriginalFilename();
        String filePath=dirPath+fileSeparator+fileName;

        boolean f=false;
        FileOutputStream out=null;
        try {
            out=new FileOutputStream(filePath);
            out.write(file.getBytes());
            out.flush();
            f=true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        if(f){
            MbgBsiFile mbgBsiFile=new MbgBsiFile();
            mbgBsiFile.setFileNo(FileTokenUtils.getFileGroupToken());
            mbgBsiFile.setFileId(FileTokenUtils.getFileGroupToken());
            mbgBsiFile.setPath(filePath);
            mbgBsiFile.setEn(1);
            mbgBsiFile.setFileName(file.getOriginalFilename());
            mbgBsiFile.setVersion(1);
            mbgBsiFile.setUserCode(dto.getUserCode());
            mbgBsiFile.setUserName(dto.getUserName());
            mbgBsiFile.setDateTime(now);
            mbgBsiFile.setUrl("http://XXXXXXXXXXXXX");
            mbgBsiFile.setGroupNo(fileGroup.getGroupNo());
            mbgBsiFile.setSuffix(suffix);
            mbgBsiFile.setSearchIdentifier(dto.getSearchIdentifier());
            mbgBsiFile.setSize(file.getSize());
            mbgBsiFile.setRemark(dto.getRemark());
            mbgBsiFileMapper.insertSelective(mbgBsiFile);

            MbgBsiFileGroup udpFileGroup=new MbgBsiFileGroup();
            udpFileGroup.setId(fileGroup.getId());
            udpFileGroup.setCurrFileSize(currFileSize);
            mbgBsiFileGroupMapper.updateByPrimaryKeySelective(udpFileGroup);
        }


        return Result.success("OK");
    }
}
前端测试

实用工具模拟前端发起请求,工具PostMan
比较简单,截图备忘,记录些要点吧:
请求类型 POST
参数类型 form-data
参数名要与dto中的参数名一致,参数类型是file

springboot 上传文件服务器 springboot上传文件接口_spring boot

SpringBoot 上传文件大小限制问题及处理

原理:
SpringBoot默认限制单个上传文件的大小为10MB。如果不在项目中作特殊配置的话,当上传的文件超过这个大小时就会报错。

报错示例:

2024-03-07 10:47:35.167 ERROR 44368 --- [nio-8530-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException: the request was rejected because its size (40487302) exceeds the configured maximum (10485760)] with root cause

两种解决办法:
1.在配置文件中添加对上传文件大小的配置

spring:
  servlet:
    multipart:
      max-file-size: 500MB
      max-request-size: 500MB

springboot 上传文件服务器 springboot上传文件接口_spring boot_02

maxFileSize 是单个文件大小
maxRequestSize 是设置总上传的数据大小

只能是MB和KB两种类型,字母大小写随意,Long类型可以的

参数说明:

# 是否开启文件上传,默认true
spring.servlet.multipart.enabled=true
# 写入磁盘的阈值,默认0
spring.servlet.multipart.file-size-threshold=0
# 上传文件的临时保存位置
spring.servlet.multipart.location=E:\\Gitee\\my-work-space\\chapter01\\tmp
# 单文件上传大小限制
spring.servlet.multipart.max-file-size=1MB
# 多文件上传大小限制
spring.servlet.multipart.max-request-size=10MB
# 文件是否延迟解析,默认false
# 当前文件和参数被访问时是否再解析成文件
spring.servlet.multipart.resolve-lazily=false
  1. 在启动文件中配置上传文件的大小
/**
 * 配置上传文件的最大值
 * @return
 */
@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    //单个文件最大
    factory.setMaxFileSize(DataSize.ofBytes(200*1024*1024));
    //设置总上传数据总大小
   factory.setMaxRequestSize(DataSize.ofBytes(200*1024*1024));
    return factory.createMultipartConfig();
}