使用SpringBoot时关于静态资源的访问问题

一、先说默认的静态资源路径

下面截取了一段ResourceProperties类的源码,可以看到定义了一个final数组CLASSPATH_RESOURCE_LOCATIONS并初始化了一些值,这些值就是默认的静态资源路径,这些文件夹下的文件可以直接访问。

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}
  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/

这几个路径分别对应项目中的如下文件夹


图1

二、覆盖默认配置 or 增加静态资源路径

目前我只知道两种覆盖默认配置的方式

Ⅰ、使用 spring.resources.static-locations配置

其中的classpath:/my-path/是自定义的路径,其他的是SpringBoot默认的路径,当然也可以不加默认路径。
application.properties如下:

# 项目路径
server.servlet.context-path=/test-demo
# 静态资源配置
spring.resources.static-locations=
  classpath:/META-INF/resources/,
  classpath:/resources/,
  classpath:/static/,
  classpath:/public/,
  classpath:/templates/,
  classpath:/my-path/

这样配置,除了可以直接访问spring默认的路径下的静态资源,也可以直接访问classpath:my-path下的静态资源。

Ⅱ、使用spring.mvc.static-path-pattern配置

aplication.properties如下:

# 项目路径
server.servlet.context-path=/test-demo
# 静态资源配置
spring.mvc.static-path-pattern=/static/**

敲黑板
当使用上面第Ⅱ种配置方式时,只能指定一个静态资源的路径,且访问时url必须含有路径名称如static,比如http://ip:port/test-demo/static/xxx.txt。而当使用上面第Ⅰ种配置方式时,可以配置多个静态资源路径,不能加路径名称,正常url应该例如http://ip:port/test-demo/xxx.txt。否则都不能达到预期结果。那你可能要问第Ⅰ中方式既然不能指定要访问那个静态资源路径,那么怎么去找到文件,springboot会按照配置的路径顺序依次检索,找到了就返回。

三、自定义静态资源映射

在实际的开发中,有可能我们会上传文件到服务器,然后返回一个url直接访问这个文件。但是当文件比较多,体积也比较大时,不可能将这些文件全部存放在jar服务中。这个时候,这种方式就特别有用,因为我们可以把磁盘上的一个位置映射为静态资源访问路径,通过访问静态资源路径就可以直接访问到磁盘上的资源,当文件保存在磁盘上时,就可以给jar服务减轻很大的压力。
那么。。。怎么做?
如下创建一个配置类并且重写addResourceHandlers方法。

  • addResourceHandler("/customer-path/**")是添加url匹配规则,只要是以http://ip:port/test-demo/customer-path/开头的url都会被认为是访问静态资源的url。
  • addResourceLocations("file:"+"D:/data/")是指定静态资源映射的位置,file表示这是磁盘路径,如果把file改成classpath那就是类路径,但是我们就是不能存到jar服务中去了,所有这里用file而不用classpath,D:/data/就是路径了。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @author chenzhiyuan
* @date 2020-01-03 18:00
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
   /**
    * 自定义静态资源映射
    */
   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
       registry.addResourceHandler("/customer-path/**")
               .addResourceLocations("file:"+"D:/data/");
   }
}

如图2,我在D:/data下放了一张图片12345.jpg作为静态资源。

图2

如图3,项目启动后访问 http://localhost:9000/test-demo/customer-path/12345.jpg,就访问到了静态资源。
图3



下面记一下项目中的实际使用,方便日后再次遇到使用CV大法。
需求:上传图片并返回url
需要考虑到的问题:映射路径,磁盘路径什么的都别写死了,需要支持可配置;另外需要考虑到在windows和非window(linux,mac)下都顺利启动应用,所以搞了一个WebAppDataProperties类,其中WebAppData是有业务含义的,并不是指当前应用是个web应用。

1.WebAppDataProperties

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.File;

/**
 * @author chenzhiyuan
 * @date 2020-01-03 14:08
 * 作用: 用于配置静态资源映射的一些属性
 * accessPath: 访问静态资源时的路径,如访问ip:port/visual/webappdata/xxx/xxx,其中visual是项目路径,webappdata便是accessPath
 * mappingLocationWindows: 指明在windows下运行时accessPath映射到服务器磁盘的绝对路径,比如映射到D:/data
 * mappingLocationNotWindows: 指明在Linux或Mac下运行时accessPath映射到服务器磁盘的绝对路径,比如映射到/usr/data
 * pathPattern: 配置静态资源映射时的参数,表示路径匹配规则,如/webappdata/**表示以ip:port/visual/webappdata/开头的url
 * 都会认为是访问静态资源,然后到mappingLocation指定的路径下去寻找资源
 * resourceLocation: 配置静态资源映射时的参数,表示磁盘的绝对路径,如file:/usr/webappdata/,必须加上file。
 */
@Data
@Component
@ConfigurationProperties(prefix = "webapp.data")
public class WebAppDataProperties {
    private String accessPath;
    private String pathPattern;
    private String mappingLocationWindows;
    private String mapperLocationNotWindows;

    public String getMappingLocation() {
        String os = System.getProperty("os.name");
        return (os.toLowerCase().startsWith("win")) ? mappingLocationWindows : mapperLocationNotWindows;
    }

    public String getResourceLocation() {
        String os = System.getProperty("os.name");
        if (os.toLowerCase().startsWith("win")) {
            File file = new File(mappingLocationWindows);
            if (!file.exists()) {
                file.mkdirs();
            }
            return "file:" + mappingLocationWindows;
        } else {
            File file = new File(mapperLocationNotWindows);
            if (!file.exists()) {
                file.mkdirs();
            }
            return "file:" + mapperLocationNotWindows;
        }
    }
}

2.具体配置内容

webapp:
  data:
    access-path: /webappdata
    path-pattern: ${webapp.data.access-path}/**
    mapping-location-windows: D:/visual_webappdata/
    mapping-location-not-windows: /usr/visual_webappdata/

3.WebMvcConfig配置

@Slf4j
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Autowired
    private WebAppDataProperties webAppDataProperties;

    /**
     * 自定义静态资源映射
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler(webAppDataProperties.getPathPattern())
                .addResourceLocations(webAppDataProperties.getResourceLocation());
    }
}

4.最后贴一下上传图片的代码,controler如下

   /**
     * 为某个WebApp上传图片
     *
     * @param srcImage
     * @param appId
     * @return 返回图片的url
     */
    @PostMapping("/uploadImage")
    public String uploadImageForWebApp(@RequestParam("file") MultipartFile srcImage,
                                       @RequestParam(name = "appId") String appId) {
        log.info("upload image for webapp start: {}", appId);
        String url = webAppService.uploadImage(srcImage, appId);
        log.info("upload image for webapp end: {}", url);
        return url;
    }

5.service实现

    @Override
    public String uploadImage(MultipartFile srcImage, String appId) {
        // 图片的存储路径
        File imagePath = UploadUtils.getWebAppImagePath(webAppDataProperties.getMappingLocation(), appId);
        // 获取上传时的文件名
        String imageName = srcImage.getOriginalFilename();
        // 截取后缀
        String imageSuffix = imageName.substring(imageName.lastIndexOf("."));
        // 使用UUID作为保存时的文件名
        String newImageName = UUID.randomUUID().toString().replaceAll("-", "") + imageSuffix;
        File destImage = new File(imagePath, newImageName);
        try {
            srcImage.transferTo(destImage);
        } catch (IOException e) {
            e.printStackTrace();
        }

        StringBuilder imageUrl = new StringBuilder().append(serverConfig.getEndpoint())
                                                    .append("/visual")
                                                    .append(webAppDataProperties.getAccessPath())
                                                    .append("/")
                                                    .append(appId)
                                                    .append("/images/")
                                                    .append(newImageName);
        return imageUrl.toString();
    }

6.其中getWebAppImagePath方法如下,其实也就是构建一个文件夹路径,不存在就mkdirs

  public static File getWebAppImagePath(String root, String appId) {
        String imageDir = root + "/" + appId + "/images";
        File imageDirFile = getOrCreate(imageDir);
        return imageDirFile;
    }

    private static File getOrCreate(String path) {
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        return file;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容