本文仅展示了部分文件

一、搭建SpringBoot开发环境

1.创建项目(略)
2.配置application.yml参数
server:
  port: 8036
spring:
  datasource:
    username: ****
    password:  ****
    url:  ****
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.mysql.cj.jdbc.MysqlConnectionPoolDataSource
  application:
    name: hospital-file
  thymeleaf:
    cache: false
  servlet:
    multipart:
      enabled: true #开启 multipart 上传功能
      max-file-size: 500MB
      file-size-threshold: 2KB
      max-request-size: 1024MB
mybatis-plus: 
  mapper-locations: classpath*:mybatis/*.xml
file:
  locations: D:/home/

其中file.upload.path =D:/aplus参数为自定义的参数,有两种方式可以获取到此参数

  • 使用@Value("$")
  • 使配置参数可以自动绑定到POJO类。

使配置参数绑定实体类详细代码如下:


/**
 * 配置信息
 * @author fengbo
 *
 */
@ConfigurationProperties(prefix = "file")
public class FileProperty {

    private String filePath;

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }
}

必须在@SpringbootUploadApplication注解的类中添加@EnableConfigurationProperties注解以开启ConfigurationProperties功能

@EnableConfigurationProperties({
        FileProperty.class
})
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
public class FileApplication {

    public static void main(String[] args) {
        SpringApplication.run(FileApplication.class, args);
    }

}
3.实体响应类和异常信息类

文件成功后需要有的响应实体类UploadFileResponse和文件出现上传异常类FileException来处理异常信息

/**
 * 文件管理表
 * @author fengbo
 */
@ApiModel(value="文件管理表")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_file")
public class SysFileDto implements Serializable {
    /**
     * 主键id
     */
    @TableId(value = "id", type = IdType.INPUT)
    @ApiModelProperty(value="主键id")
    private String id;

    /**
     * 文件名称
     */
    @TableField(value = "file_name")
    @ApiModelProperty(value="文件名称")
    private String fileName;

    /**
     * 文件类型
     */
    @TableField(value = "file_type")
    @ApiModelProperty(value="文件类型")
    private String fileType;

    /**
     * 文件大小
     */
    @TableField(value = "file_size")
    @ApiModelProperty(value="文件大小")
    private Long fileSize;

    /**
     * 保存名称
     */
    @TableField(value = "save_name")
    @ApiModelProperty(value="保存名称")
    private String saveName;

    /**
     * 保存路径
     */
    @TableField(value = "save_path")
    @ApiModelProperty(value="保存路径")
    private String savePath;

    /**
     * 下载路径
     */
    @TableField(value = "download_path")
    @ApiModelProperty(value="下载路径")
    private String downloadPath;

    /**
     * 删除标识
     */
    @TableField(value = "`valid`")
    @ApiModelProperty(value="删除标识")
    private Integer valid;

    /**
     * 创建时间
     */
    @TableField(value = "create_at")
    @ApiModelProperty(value="创建时间")
    private Long createAt;

    /**
     * 修改时间
     */
    @TableField(value = "update_at")
    @ApiModelProperty(value="修改时间")
    private Long updateAt;

    /**
     * 容器名称
     */
    @TableField(value = "bucket_name")
    @ApiModelProperty(value="容器名称")
    private String bucketName;

    /**
     * 用户所属系统
     */
    @TableField(value = "solution_code")
    @ApiModelProperty(value="用户所属系统")
    private String solutionCode;

    /**
     * 用户id
     */
    @TableField(value = "user_id")
    @ApiModelProperty(value="用户id")
    private String userId;

    /**
     * 文件后缀
     */
    @TableField(value = "suffix")
    @ApiModelProperty(value="文件后缀")
    private String suffix;


    public SysFileDto(String fileName, String downloadPath, String fileType,
                      long fileSize, String suffix,String userId,String solutionCode,
                      Long updateAt,Long createAt,Integer valid,String savePath,String saveName,String id) {
        this.fileName = fileName;
        this.userId = userId;
        this.solutionCode = solutionCode;
        this.updateAt = updateAt;
        this.createAt = createAt;
        this.valid = valid;
        this.savePath = savePath;
        this.id = id;
        this.saveName = saveName;
        this.suffix = suffix;
        this.downloadPath = downloadPath;
        this.fileType = fileType;
        this.fileSize = fileSize;
    }

    public SysFileDto(String fileName, String downloadPath, String fileType, long fileSize) {
        this.fileName = fileName;
        this.downloadPath = downloadPath;
        this.fileType = fileType;
        this.fileSize = fileSize;
    }
    private static final long serialVersionUID = 1L;

    public static final String COL_ID = "id";

    public static final String COL_FILE_NAME = "file_name";

    public static final String COL_FILE_TYPE = "file_type";

    public static final String COL_FILE_SIZE = "file_size";

    public static final String COL_SAVE_NAME = "save_name";

    public static final String COL_SAVE_PATH = "save_path";

    public static final String COL_DOWNLOAD_PATH = "download_path";

    public static final String COL_VALID = "valid";

    public static final String COL_CREATE_AT = "create_at";

    public static final String COL_UPDATE_AT = "update_at";

    public static final String COL_BUCKET_NAME = "bucket_name";

    public static final String COL_SOLUTION_CODE = "solution_code";

    public static final String COL_USER_ID = "user_id";

    public static final String COL_SUFFIX = "suffix";
}

异常信息处理类

/**
 * @author fengbo
 * @Title: FileException
 * 异常信息配置
 */
public class FileException extends RuntimeException {

    public FileException(String message) {
        super(message);
    }

    public FileException(String message, Throwable cause) {
        super(message, cause);
    }
}
4.创建FileController

/**
 * 文件上传下载
 *
 * @author fengbo
 */
@RestController
@Component
@Api(tags = "文件管理")
public class FileController {

    @Autowired
    private FileService fileService;

    /**
     * 单文件上传
     *
     * @param file
     * @return
     */
    @PostMapping("/uploadFile")
    public SysFileDto uploadFile(@RequestParam("file") MultipartFile file) {
        return fileService.uploadFile(file);
    }

    /**
     * 多文件上传
     *
     * @param files
     * @return
     */
    @PostMapping("/uploadMultipleFiles")
    public List<SysFileDto> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
        return fileService.uploadMultiple(files);

    }

    /**
     * 下载
     *
     * @param fileName
     * @param request
     * @return
     */
    @GetMapping("/downloadFile/{fileName:.*}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
        return fileService.download(fileName, request);
    }
}

FileService实现类


/**
 * 文件管理
 * @author fengbo
 */
@Service
@Component
public class FileServiceImpl extends ServiceImpl<FileMapper, SysFileDto> implements FileService{

    private static final Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    private Path fileStorageLocation; // 文件在本地存储的地址
    @Autowired
    FileMapper fileMapper;

    public FileServiceImpl( @Value("${file.locations}") String path) {
        this.fileStorageLocation = Paths.get(path).toAbsolutePath().normalize();
        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (IOException e) {
            throw new FileException("Could not create the directory", e);
        }
    }

    /**
     * 存储文件到系统
     * @param file 文件
     * @return 文件名
     */
    @Override
    public String storeFile(MultipartFile file) {
        // Normalize file name
        String fileName = UUID.randomUUID().toString()+file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."),file.getOriginalFilename().length());
//        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

        try {
            // Check if the file's name contains invalid characters
            if(fileName.contains("..")) {
                throw new FileException("Sorry! Filename contains invalid path sequence " + fileName);
            }

            // Copy file to the target location (Replacing existing file with the same name)
            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

            return fileName;
        } catch (IOException ex) {
            throw new FileException("Could not store file " + fileName + ". Please try again!", ex);
        }
    }
    @Override
    public Resource loadFileAsResource(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            if(resource.exists()) {
                return resource;
            } else {
                throw new FileException("File not found " + fileName);
            }
        } catch (MalformedURLException ex) {
            throw new FileException("File not found " + fileName, ex);
        }
    }

    /**
     * 单文件上传
     * @param file
     * @return
     */
    @Override
    public SysFileDto uploadFile(MultipartFile file) {
        String fileName = this.storeFile(file);

        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                .path("/downloadFile/")
                .path(fileName)
                .toUriString();
        //填充dto
        SysFileDto sysFileDto = new SysFileDto();
        sysFileDto.setFileSize(file.getSize());
        sysFileDto.setFileName(fileName);
        sysFileDto.setFileType(file.getContentType());
        sysFileDto.setDownloadPath(fileDownloadUri);
        sysFileDto.setCreateAt(System.currentTimeMillis());
        sysFileDto.setUpdateAt(System.currentTimeMillis());
        sysFileDto.setId(UUID.randomUUID().toString());
        sysFileDto.setSaveName(file.getOriginalFilename().replace(" ",""));
        sysFileDto.setUserId("测试");
        sysFileDto.setSavePath(fileStorageLocation.toFile().getPath());
        //默认生效0-生效1-失效
        sysFileDto.setValid(0);
        sysFileDto.setSuffix(file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."),file.getOriginalFilename().length()));
        this.save(sysFileDto);
        return sysFileDto;
    }

    /**
     * 多文件上传
     * @param files
     * @return
     */
    @Override
    public List<SysFileDto> uploadMultiple(MultipartFile[] files){
        List<SysFileDto> list = new ArrayList<>();
        if (files != null) {
            for (MultipartFile multipartFile:files) {
                SysFileDto uploadFileResponse = uploadFile(multipartFile);
                list.add(uploadFileResponse);
            }
        }
        return list;
    }

    @Override
    public ResponseEntity<Resource> download(String fileName, HttpServletRequest request) {
        Resource resource = this.loadFileAsResource(fileName);
        String contentType = null;
        try {
            request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException e) {
            logger.info("Could not determine file type.");
        }
        if(contentType == null) {
            contentType = "application/octet-stream";
        }

        List<String> saveName  = fileMapper.findSaveNameByFileName(fileName);
        String name = saveName.get(0);
        try {
            name = URLEncoder.encode(name,"UTF-8");
        }catch(UnsupportedEncodingException e){
            logger.info("文件名转换异常");
            e.printStackTrace();
        }
        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" +name+ "\"")
                .body(resource);
    }
}


需要注意的是,文件名称中文乱码的问题。这里使用了URLEncoder.encode();

文件名使用uuid生成储存。下载时替换为原文件名下载。

mapper文件(只是为了查询文件名称)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hostpital.file.mapper.FileMapper">
    <resultMap id="BaseResultMap" type="com.hostpital.file.vo.SysFileVo">
        <result column="id" property="id"/>
        <result column="file_name" property="fileName"/>
        <result column="file_type" property="fileType"/>
        <result column="file_size" property="fileSize"/>
        <result column="save_name" property="saveName"/>
        <result column="save_path" property="savePath"/>
        <result column="download_path" property="downloadPath"/>
        <result column="valid" property="valid"/>
        <result column="create_at" property="createAt"/>
        <result column="update_at" property="updateAt"/>
        <result column="bucket_name" property="bucketName"/>
        <result column="solution_code" property="solutionCode"/>
        <result column="user_id" property="userId"/>
        <result column="suffix" property="suffix"/>
    </resultMap>
    <sql id="Base_Column_List">
        id,
        file_name,
        file_type,
        file_size,
        save_name,
        save_path,
        download_path,
        `valid`,
        create_at,
        update_at,
        bucket_name,
        solution_code,
        user_id,
        suffix
    </sql>
    <select id="findSaveNameByFileName" resultType="java.lang.String">
        select save_name
        from sys_file
        where file_name=#{fileName}
    </select>
</mapper>

二、接口测试

fileUpload

Q.E.D.


一个爱折腾的小伙子