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

一、前言

大家好!我是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或者start.spring.io/网站创建一个即可。 ###(2)引入SpringBoot模板引擎--- Thymeleaf pom.xml引入

xml 复制代码
<!-- thymeleaf -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

###(3)在resources目录下创建一个static文件夹,在static文件夹创建一个index.html

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

properties 复制代码
spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML

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

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请求头就可以区分访问来源,判断逻辑代码如下:

java 复制代码
/**
   * 校验是否手机端
   *
   * @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代码改一下

java 复制代码
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目录下的文件,路径应该这样写

html 复制代码
<!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代码如下

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代码如下

java 复制代码
<!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文件里面配置一下,具体配置如下:

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,这两个都可以实现上面的效果,但建议不要混用。

文末小彩蛋,自己花一个星期做的小网站,放出来给大家看看,网址如下:[http://47.120.49.119:8080](http://47.120.49.119:8080)

相关推荐
灵感__idea几秒前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
咖啡啡不加糖几秒前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
小小小小宇3 分钟前
前端双Token机制无感刷新
前端
小小小小宇6 分钟前
重提React闭包陷阱
前端
小小小小宇22 分钟前
前端XSS和CSRF以及CSP
前端
大鸡腿同学25 分钟前
纳瓦尔宝典
后端
UFIT25 分钟前
NoSQL之redis哨兵
java·前端·算法
超级土豆粉32 分钟前
CSS3 的特性
前端·css·css3
星辰引路-Lefan33 分钟前
深入理解React Hooks的原理与实践
前端·javascript·react.js
wyn2000112844 分钟前
JavaWeb的一些基础技术
前端