《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

sum墨
• 阅读 55

一、前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

前面的文章都是先说概念,再说怎么设计和实现,今天我打算换一种写法,从一个功能需求的实现来讲一下静态资源是如何访问的?

功能需求如下:

  1. 现有一个后端应用,默认访问方式如下:http://47.120.49.119:8080/#/
  2. 用电脑、平板、手机等设备都可以访问,且不同的设备样式要适配,前端做了两套,但是访问接口都是同一个;
  3. 由于没有使用cdn,界面渲染的时候需要用到的字体库、图标库和一些图片也放在了后端应用;

光文字讲需求可能不太直观,我放一些图片就好理解了,如下:

使用电脑访问http://47.120.49.119:8080/#/出现的界面 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

使用手机或平板访问http://47.120.49.119:8080/#/出现的界面 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

可以看到这两个的样式不一样,组件也不一样,但是接口都是同一个。

前端的资源如下 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

文件有html、js、css,还有一些特殊的文件如字体,文件类型还是比较丰富的,且这样的资源有两份,一份是电脑端,一份是移动端。由于没有使用cdn,我们需要通过后端服务来访问这些资源。 那么说到这里不知道大家有没有理解这个需求呢?其实简单理解就是SpringBoot的接口访问静态资源,下面就开始讲一下我是如何实现这个功能的。

二、功能实现

1. 从访问一个index.html开始

###(1)创建一个SpringBoot项目 这个我就不啰嗦了,使用ide或者https://start.spring.io/网站创建一个即可。 ###(2)引入SpringBoot模板引擎— Thymeleaf pom.xml引入

<!-- thymeleaf -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

###(3)在resources目录下创建一个static文件夹,在static文件夹创建一个index.html 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

###(4)application.properties添加如下配置

spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML

###(5)写一个IndexController.java

package com.summo.file.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

  @GetMapping("/")
  public String index() {
    return "index";
  }
}

###(6)访问一下index.html 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

这个还是比较简单的,我已经成功了,大家成功了吗?如果发现返回的不是HTML而是字符串,检查一下IndexController的注解,是@Controller而不是@RestController,方法上也不要加 @ResponseBody。

2. 判断当前的请求来自于电脑还是手机

###(1)判断逻辑分析 这个听起来很难,其实很简单,前端在访问后端接口的时候,会携带一个USER-AGENT的请求头,通过这个USER-AGENT请求头就可以区分访问来源,判断逻辑代码如下:

/**
   * 校验是否手机端
   *
   * @param request
   * @return
   */
public static boolean isFromMobile(HttpServletRequest request) {
  //1. 获得请求UA
  String userAgent = request.getHeader("USER-AGENT").toLowerCase();
  //2.声明手机和平板的UA的正则表达式
  // \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔),
  // 字符串在编译时会被转码一次,所以是 "\\b"
  // \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔)
  String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i" + "|windows (phone|ce)|blackberry"
            + "|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp" + "|laystation portable)|nokia|fennec|htc[-_]"
            + "|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
  String tableReg = "\\b(ipad|tablet|(Nexus 7)|up.browser" + "|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";

  // 3.移动设备正则匹配:手机端、平板
  Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE);
  Pattern tablePat = Pattern.compile(tableReg, Pattern.CASE_INSENSITIVE);
  if (null == userAgent) {
    userAgent = "";
  }
  // 4.匹配
  Matcher matcherPhone = phonePat.matcher(userAgent);
  Matcher matcherTable = tablePat.matcher(userAgent);
  if (matcherPhone.find() || matcherTable.find()) {
    //来自手机或者平板
    return true;
  } else {
    //来自PC
    return false;
  }
}

###(2)新建两个文件夹,将手机端和电脑端的index界面区分开来 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

###(3)IndexController代码改一下

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index(HttpServletRequest request) {
        if (isFromMobile(request)) {
            return "mp/index";
        } else {
            return "web/index";
        }
    }

    /**
     * 校验是否手机端
     *
     * @param request
     * @return
     */
    public static boolean isFromMobile(HttpServletRequest request) {
        //1. 获得请求UA
        String userAgent = request.getHeader("USER-AGENT").toLowerCase();
        //2.声明手机和平板的UA的正则表达式
        // \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔),
        // 字符串在编译时会被转码一次,所以是 "\\b"
        // \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔)
        String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i" + "|windows (phone|ce)|blackberry"
            + "|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp" + "|laystation portable)|nokia|fennec|htc[-_]"
            + "|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
        String tableReg = "\\b(ipad|tablet|(Nexus 7)|up.browser" + "|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";

        // 3.移动设备正则匹配:手机端、平板
        Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE);
        Pattern tablePat = Pattern.compile(tableReg, Pattern.CASE_INSENSITIVE);
        if (null == userAgent) {
            userAgent = "";
        }
        // 4.匹配
        Matcher matcherPhone = phonePat.matcher(userAgent);
        Matcher matcherTable = tablePat.matcher(userAgent);
        if (matcherPhone.find() || matcherTable.find()) {
            //来自手机或者平板
            return true;
        } else {
            //来自PC
            return false;
        }
    }
}

电脑端访问 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取? 手机端访问 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

到这里,一个接口区分电脑端和手机端的功能实现了,由于index.html引入了一些js、css、ttf、woff 等静态资源,这些文件如何访问呢?继续看。

3. 其他类型的静态资源如何访问

mp目录下文件结构如下 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

index.html想要引入js和css目录下的文件,路径应该这样写

<!DOCTYPE html>
<html>
<head>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width,initial-scale=1">
    <title>summo-sbmy-front-mp</title>
    <link href=/mp/css/app.css rel=stylesheet>
</head>
<body>
<div id=app></div>
<script type=text/javascript src=/mp/js/manifest.js></script>
<script type=text/javascript src=/mp/js/vendor.js></script>
<script type=text/javascript src=/mp/js/app.js></script>
</body>
</html>

这样就可以访问到指定目录的资源了 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

整体逻辑其实很简单,我觉得最容易出问题的地方在路径的设置,有时候文件和代码都弄好了,但就是加载不出来,基本上就是路径设置错了。所以大家要是自己实现的话,尽量先按照我的文件路径来,先搞成功再说。

三、扩展知识

1. 给界面传递动态值

在Spring MVC中,将变量传递到视图通常通过Model对象或使用ModelAndView对象进行,举个例子: IndexController.java代码如下

package com.summo.file.controller;

// 确保引入相关的包
import org.springframework.ui.Model;

@Controller
public class IndexController {

  @GetMapping("/")
  public String index(HttpServletRequest request, Model model) {
    // 添加属性到model中
    model.addAttribute("message", "欢迎访问我们的网站!");
    // 设置一个版本号
    model.addAttribute("version", "1.0.0"); 

    // 根据客户端类型选择视图
    if (isFromMobile(request)) {
      return "mp/index"; // 返回移动端页面
    } else {
      return "web/index"; // 返回桌面端页面
    }
  }
}

index.html代码如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>首页</title>
  <script type="text/javascript" th:src="'/mp/js/manifest-' + ${version} + '.js'"></script>
</head>
<body>
<h1>Hello World,我是电脑端</h1>
<!-- 在Thymeleaf中使用的表达式 -->
<p th:text="${message}"></p>
</body>
</html>

访问之后可以看到参数已经替换掉了 《优化接口设计的思路》系列:第十篇—网站的静态资源怎么获取?

动态传值在实际开发中经常使用到,比如我们一般在配置文件中维护好js、css 的版本号,然后将版本号传给index.html达到动态控制前端版本。

2. 项目打包静态资源没有打进去

正常情况下,打完包后静态资源文件会在static文件夹下,但是上次我打包就发现静态资源文件没有打包进去,后来才知道需要在pom.xml文件里面配置一下,具体配置如下:

<build>
    <finalName>summo-sbmy</finalName>
    <resources>
      <resource>
        <!-- 指定配置文件所在的resource目录 -->
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.html</include>
          <include>**/*.js</include>
          <include>**/*.css</include>
        </includes>
        <filtering>true</filtering>
      </resource>
      <resource>
        <!-- 指定配置文件所在的resource目录 -->
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.woff</include>
          <include>**/*.ttf</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build> 

这里需要注意的是,.woff和.ttf这类文件比较特殊,需要单独开一块并且设置filtering为false。这个配置说明对这类文件不执行过滤,因为过滤可能破坏文件内容,字体文件应该以其原始形式包含在构建产物中。

四、总结一下

访问静态资源的接口大家接触的不多,主要是因为现在前后端分离了,前端自己使用CDN放资源,后端只用维护一个index.html文件,其他的资源都通过CDN访问,已经变得很简单了。但是有时候想要用却不知道从哪里开始,希望这篇文章可以给大家一个大概的思路,还有就是处理静态资源的框架很多,最常见的就是Thymeleaf、Velocity,这两个都可以实现上面的效果,但建议不要混用。

文末小彩蛋,自建摸鱼网站,各大网站热搜一览,上班和摸鱼很配哦!

点赞
收藏
评论区
推荐文章
九路 九路
3年前
为什么不建议Java程序员用阿里巴巴规范,而使用GoogleGuava编程
前言阿里巴巴出了一本Java规范,在国内java开发眼里被赋予了神圣的殿堂,我不推荐你用阿里巴巴的开发手册。目前GoogleGuava在实际应用中非常广泛,本篇博客将以博主对Guava使用的认识以及在项目中的经验来给大家分享!学习使用GoogleGuava可以让你快乐编程,写出优雅的Java代码,在业务允许的条件下,我推荐使用Guav
捉虫大师 捉虫大师
2年前
灵感乍现!造了个与众不同的Dubbo注册中心扩展轮子
hello大家好呀,我是小楼。作为一名基础组件开发,服务好每一位业务开发同学是我们的义务(KPI)。客服群里经常有业务开发同学丢来一段代码、一个报错,而我们,当然要微笑服务,耐心解答。有的问题,凭借多年踩坑经验,一眼就能看出;有的问题,看一眼代码也能知道原因,但有的问题,还真就光凭看是看不出来的,这时,只能下载代码,本地跑跑看了。熟悉我的朋友都知道,我从事d
低代码开发平台 | 低代码的衍生历程、优势及未来趋势
通过简单的拖拉拽操作,而不用编写复杂的代码,实现少写代码或者不写代码,就能快速高效完成业务目标。低代码平台演进1.低代码概念低代码是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法,具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。2.低代码衍生历
Stella981 Stella981
3年前
GitHub访问破百万!字节2021年Java程序员面试指导已疯传
前言:没有绝对的天才,只有持续不断的付出。对于我们每一个平凡人来说,改变命运只能依靠努力幸运,但如果你不够幸运,那就只能拉高努力的占比。2020年11月,我有幸成为了字节跳动的一名Java后端开发,正如标题所说,我从外包辞职了,10000小时后,走进字节跳动拿下了offer。相信同行都清楚,从外包进大厂有多难,运气之余,也离不开我自己的脚踏
Stella981 Stella981
3年前
Spring Boot快速开发企业级Admin管理后台
Erupt可快速的构建管理页面,零前端代码、零CURD、自动建表,仅需单个类文件简洁的注解配置,即可快速开发企业级Admin管理后台!后台管理系统非常重要,但开发存在一定的痛点,如:开发效率低、界面不美观、交互不理想、工作量重复、存在安全漏洞、后端研发被迫写前端代码等。我是程序汪Erupt提供企业级中后台管理系统的全栈解决方案,提供超多业务组
Wesley13 Wesley13
3年前
Java 程序员必备的 15 个框架,前 3 个地位无可动摇!
Java程序员方向太多,且不说移动开发、大数据、区块链、人工智能这些,大部分Java程序员都是JavaWeb/后端开发。那作为一名JavaWeb开发程序员必须需要熟悉哪些框架呢?今天,栈长我给大家列举了一些通用的、必须掌握的框架,学会这些,20K不是问题。1.Spring毫无疑问,Spring框架现在是J
Stella981 Stella981
3年前
Hibernate纯sql查询结果和该sql在数据库直接查询结果不一致
问题:今天在做一个查询的时候发现一个问题,我先在数据库实现了我需要的sql,然后我在代码中代码:selectdistinctd.id,d.name,COALESCE(c.count_num,0),COALESCE(c.count_fix,0),COALESCE(c
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
sum墨 sum墨
1星期前
《优化接口设计的思路》系列:第一篇—接口参数的一些弯弯绕绕
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
sum墨 sum墨
1星期前
《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。