本文仅展示了部分文件

# 一、搭建 SpringBoot 开发环境

# 1. 创建项目(略)
# 2. 配置 application.yml 参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ("${file.upload.path}")
  • 使配置参数可以自动绑定到 POJO 类。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/**
* 配置信息
* @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功能

1
2
3
4
5
6
7
8
9
10
11
12
@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来处理异常信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* 文件管理表
* @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";
}

异常信息处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

/**
* 文件上传下载
*
* @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实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

/**
* 文件管理
* @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文件(只是为了查询文件名称)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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>

# 二、接口测试

20221116201344