目录
[2、使用WebMvcConfigurer 配置](#2、使用WebMvcConfigurer 配置)
在以往的项目中,我们把图片,视频等资源都放在static目录下。这种资源被成为静态资源,这种资源的特点是不能被动态写入和修改。
例如,用户希望上传和更换头像,首先把资源的路径以字符串的形式存在数据库中,除此之外还需要把真实资源(例如图片,音频,视频等)存放在外部目录里。不能存在在static下,那么一个SpringBoot项目是如何维护和管理这些资源的呢?本节我们就以用户上传头像为例,讲解这个过程。
一、Jar包的内部结构
在开始本节之前我们需要搞懂项目打包后,代码、配置文件,静态资源、依赖...被打包到哪里,能不能读写。
因此我们先尝试打包一个项目,然后观察Jar包的结构。

使用如下指令来查看jar包的目录结构
bash
jar tf my-app-1.0.0.jar
能看到如下目录信息

项目越大文件越多,目录就越复杂。我们可以简单的总结成如下目录:

可以发现除了pom文件被打包到jar包里,还有一些外部库,src里的所有文件都被打包进去了。
也就是说,如果我们建立一个与src同级的目录,在打包时默认会被忽略。
META-INF是这个jar包的"身份证明",BOOT-INF则是这个jar包的所有系统文件,可以运行,这个目录下的所有文件都是只读状态,static在这个目录下,因此只读不可写。
二、建立外部资源目录
我们说用户资源不应该放在src/main/resources/static目录下,需要新建一个外部目录,我们计划放到uploads/avatars下。
那么我们先建立目录。

现在我们加入一张照片,看是否可以访问到。

我们尝试这种方式能否访问:
可以发现,无法访问。
这是因为这个目录不是静态资源的目录,SpringBoot项目默认static下的文件可以直接访问,而非static下的资源无法直接访问。
例如,在static下有一张照片,我们尝试访问。
http://localhost:8080/photo/avatar/animal1.jpg就可以访问到。

因此我们还需要把这个目录作为资源目录,对外暴露。
三、手动映射外部资源目录
1、使用static-locations配置项
我们首先考虑使用配置文件来映射外部资源,在application.yaml文件中做如下配置:
XML
spring:
web:
resources:
static-locations: file:./uploads/, classpath:/static/

可以发现,这样确实访问成功了,现在我们解决为什么这样做就可以。
但是我们发现我们配置的是静态资源,static-location,一个是file,一个是classpath,前者指向项目所在的磁盘上的文件,后者指向jar内部的文件。这是两个不同的位置。
file:后可以指向磁盘的任意位置,例如file:D:/myfiles/,也可以相对路径:file:./myfiles ,注意**./**表示项目的根目录(pom文件所在的目录)。
classpath:后则是jar包内部的目录,从BOOT-INF/classes/开始,那么/就表示这个目录,所以从/开始指定static目录为静态资源。
现在只是做到了让外部访问,那么后端能否读写呢?关键看是否可以写,只有这样用户才能上传照片。
我们写如下测试代码:
XML
@PostMapping("/modifyAvatars")
public ApplicationResult<Void> modifyAvatar(MultipartFile file) {
// 拿到工作目录和file:./配合
String dir = System.getProperty("user.dir") + "/uploads/avatars/";
File saveFile = new File(dir+"test.png");
try {
file.transferTo(saveFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
return ApplicationResult.success();
}
这是要使用System.getProperty("user.dir")来拿到工作目录。
利用MutipartFile的transferTo(newFile)来保存文件。

我们又想,这种做法只是把/uploads和/static都映射到访问目录/了而已,假如两个目录下有同一张照片img.jpg,这样后期访问的时候就只能看配置顺序了,在访问层面完全无法区分。
http://localhost:8080/img.jpg 无法确认到底是/static/img.jpg还是/uploads/img.jpg
因此这种做法好像是不可行的。
如果我们能从访问路劲上加上区分就好了,不用动后端的代码。
2、使用WebMvcConfigurer 配置
我们删除掉原来的配置,自定义如下配置:
XML
# application.yml - 只放配置参数,不做静态资源映射
app:
upload:
avatar-path: ./uploads/avatars/
attachment-path: ./uploads/attachments/
temp-path: ./uploads/temp/
这里的配置是我们自己定义的,方便我们后续读取这个配置和修改。
avatar-path就是头像地址,设置正确后,实现WebMvcConfigurer
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 读取配置文件里的配置
@Value("${app.upload.avatar-path}")
private String avatarUrl ;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/avatars/**")
.addResourceLocations(avatarUrl);
}
}
addResourceHandler("/")从访问目录的根目录开始,只要匹配到/avatars就把目录指定到file./upload/avatars这样就可以实现只需要在访问路径上做区分,后端文件系统一点都不用变,如下图:

原来的static目录下的文件依然可以访问,因此完全不用担心。
这样我们就实现了用户上传资源到服务器了,数据库只需要存当时写入的文件路径的相对路径即可。数据库不需要存放过多的上层目录,避免暴露过多细节。
例如,在没有做访问路径映射前,数据库可能存放:uploads/avatars/img.jpg
但是做了路径映射后,数据库就可以只存放:avatars/img.jpg,甚至只存放img.jpg,前缀由前端自己拼接。
四、项目部署后资源文件上传
我们前面说,src外出了pom和外部库,其他文件都是不会被一起打包的,因此我们需要手动压缩资源文件,并且解压到和jar的同级目录处(./工作目录也是jar包的目录)。
如果没有把资源压缩并上传到指定目录,只运行Jar包是访问不了资源的。

我们先将资源压缩成压缩包上传到jar包同级目录处

紧接着解压。
bash
unzip uploads.zip
然后再次访问即可。

这样资源就可以访问成功了。
五、总结
本节我们以用户上传头像为例,讲解了非静态资源如何读写的问题。
最佳实践是对访问路径进行映射,这样不需要改动后端的文件目录。
对于非静态资源在项目打包时不会打进Jar包,需要手动上传到jar同级目录。