你不知道的SpringBoot与Vue部署解决方案


Posted in Javascript onNovember 09, 2020

前言

前段时间公司外网部署的演示环境全部转到内网环境中去,所有对外演示的环境都需要申请外网映射才能访问某个服务。我用一个外网地址 www.a.com 映射到一个内网地址 http://ip:port ,然后在这个地址 http://ip:port 用 nginx 做代理转发到各个组的项目 http://ipn:portn 上去,其中也遇到一些静态资源 404,主要是是解决这个 404 问题。

最近又做了一个项目,考虑到用户的体验,减少部署的复杂性,我想了一个办法用 SpringBoot 做 web 服务器映射前端资源为 web 资源 。

条件允许或者对性能要求比较高,推荐是前后端分离部署,nginx 做 web 服务器,后端只提供接口服务

以前部署的项目 A 外网访问地址是 http://ip1:8080 ,外网映射后只能访问 http://ip/app1 ,以前项目 B 外网访问地址是 http://ip1:8081 ,项目访问地址是 http://ip/app2 。这也算是一个不大不小的变动,但是切换之后遇到的第一个问题就是静态资源转发导致 404

比如以前项目 A 访问地址是 http://ip1:8080 它是没有上下文的。

而现在 A 的访问地址为 http://ip/app1 ,有一个上下文 app1 在这里,导致有一些资源 404。

比如说:原来 http://ip1:8080 请求到了 index.html 资源,现在只能 http://ip/app1 请求到 index.html。

<!-- index.html -->
<!-- 原来部署环境写法 -->
<link href="/index.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">

以前访问 index.css 地址是 http://ip1:8080/index.css ,但是现在变成访问了 http://ip/index.css 导致 404,实际 index.css 地址为 http://ip/app1/index.css

前端使用 vue 编写,html 中的静态资源路径可以很好解决,修改 webpack 打包即可。

<!-- 原来部署环境写法 -->
<link href="/index.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">

<!-- 写成相对路径 -->
<link href="./index.css" rel="external nofollow" rel="stylesheet">

<!-- 结合 webpack 打包时进行路径补充 -->
<link href="<%= BASE_URL %>index.css" rel="external nofollow" rel="stylesheet">

但是项目中有一些组件的请求没有办法统一处理,只能改代码。但我不想动代码,webpack 打包都不想动,基于这些需求想了一个办法来解决。

本文内容

  • Nginx 部署 vue 项目,怎么能友好处理静态资源的丢失
  • SpringBoot 提供 web 服务器的功能映射 vue 项目为 web 资源,并处理 vue 路由转发 index.html 问题。

演示代码地址

Nginx 部署 Vue 项目

server {
  listen 8087;
  # 它的作用是不重定向地址,比如浏览器输入 /app1 访问,也可以访问到 /app1/ ,而浏览器地址是不改变的 /app1 。没办法,强迫症
  location / {
    try_files $uri $uri/;
  }
  root /Users/zhangpanqin/staic/;
  location ~ /(.*)/ {
    index index.html /index.html;
    try_files $uri $uri/ /$1/index.html;
  }
}

/Users/zhangpanqin/staic/ 放部署的项目,比如 app 的项目资源放到 /Users/zhangpanqin/staic/app 下。 访问地址为 http://ip/8087/app

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- 也可以改成类似的地址 BASE_URL 等于 vue.config.js 配置的 publicPath-->
  <link rel="icon" href="<%= BASE_URL %>favicon.ico" rel="external nofollow" >
  <!-- 部署之后,访问不到 index.css -->
  <link href="/index.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
</html>

为了可以在浏览器输入 vue 的路由 /app/blog 也可以访问页面,需要添加 vue-router 中的 base 属性。

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
  },
  {
    path: '/blog',
    name: 'Blog',
    component: () => import('@/views/Blog.vue'),
  },
  {
    // 匹配不到路由的时候跳转到这里
    path: '*',
    name: 'Error404',
    component: () => import('@/views/Error404.vue'),
  }
];
const router = new VueRouter({
  // 主要是修改这里,可以根据 vue mode 环境来取值。
  // https://cli.vuejs.org/zh/guide/mode-and-env.html
  // https://router.vuejs.org/zh/api/#base
  base: process.env.VUE_APP_DEPLOY_PATH,
  mode: 'history',
  routes,
});

export default router;

你不知道的SpringBoot与Vue部署解决方案

http://localhost:8087/app/index.css 为 css 的真实地址。所以想办法为这些不以 /app 开头的资源加上 /app 就可以了,想了想只有 cookie 能做到。

x_vue_path 记录每个项目的路径,然后静态资源去这个路径下寻找, $cookie_x_vue_path/$uri

下面这个配置使用了 try_files 内部重定向资源,是不会在浏览器端发生重定向的。

# gzip ,缓存 和 epoll 优化的都没写
server {
  listen 8087;
  # 它的作用是不重定向地址,比如浏览器输入 /app1 访问,也可以访问到 /app1/ ,而浏览器地址是不改变的 /app1 。没办法,强迫症
  location / {
    try_files $uri $uri/;
  }
  root /Users/zhangpanqin/staic/;

  # (.*) 匹配是哪个项目,比如说 app1 app2 等
  location ~ /(.*)/.*/ {
    index index.html /index.html;
    add_header Set-Cookie "x_vue_path=/$1;path=/;";
    # /Users/zhangpanqin/staic/+/$1/index.html 可以到每个项目下 index.html
    try_files $uri $uri/ /$1/index.html @404router;
  }
  # 查找静态资源,也可以在这里添加缓存。
  location ~ (.css|js)$ {
    try_files $uri $cookie_x_vue_path/$uri @404router;
  }
  location @404router {
    return 404;
  }
}

你不知道的SpringBoot与Vue部署解决方案

下面这个是重定向的配置

server {
  listen 8087;
  root /Users/zhangpanqin/staic/;

  location ~ /(.*)/.*/? {
    index index.html /index.html;
    add_header Set-Cookie "x_vue_path=/$1;path=/;";
    try_files $uri $uri/ /$1/index.html @404router;
  }
  location ~ (.css|js)$ {
    # 匹配到 /app/index.css 的资源,直接访问
    rewrite ^($cookie_x_vue_path)/.* $uri break;
    # 访问的资源 /index.css 302 临时重定向到 /app/index.css
    rewrite (.css|js)$ $cookie_x_vue_path$uri redirect;
  }
  location @404router {
    return 404;
  }
}

你不知道的SpringBoot与Vue部署解决方案

根据这个思路就可以把所有的资源进行转发了,不用改业务代码,只需给 vue-router 加上一个 base 基础路由。

SpringBoot 部署 Vue 项目

Nginx 走通了,SpringBoot 依葫芦画瓢就行了,还是 java 写的舒服,能 debug,哈哈。

SpringBoot 映射静态资源

@Configuration
public class VueWebConfig implements WebMvcConfigurer {
  /**
   * 映射的静态资源路径
   * file:./static/ 路径是相对于 user.dir 路径,jar 包同级目录下的 static
   */
  private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"file:./static/", "classpath:/META-INF/resources/",
      "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // 添加静态资源缓存
    CacheControl cacheControl = CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic();
    registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS).setCacheControl(cacheControl);
  }


  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 配置要拦截的资源,主要用于 添加 cookie 
    registry.addInterceptor(new VueCookieInterceptor()).addPathPatterns("/test/**");
  }

  // vue 路由转发使用的,也做 接口请求找不到的
  @Bean
  public VueErrorController vueErrorController() {
    return new VueErrorController(new DefaultErrorAttributes());
  }
}

项目静态资源路径添加 cookie

public class VueCookieInterceptor implements HandlerInterceptor {
  public static final String VUE_HTML_COOKIE_NAME = "x_vue_path";

  public static final String VUE_HTML_COOKIE_VALUE = "/test";

  /**
   * 配置请求资源路径 /test 下全部加上 cookie
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    final Cookie cookieByName = getCookieByName(request, VUE_HTML_COOKIE_NAME);
    if (Objects.isNull(cookieByName)) {
      final Cookie cookie = new Cookie(VUE_HTML_COOKIE_NAME, VUE_HTML_COOKIE_VALUE);
      // 项目下的 url 都带能带上
      cookie.setPath("/");
      cookie.setHttpOnly(true);
      response.addCookie(cookie);
    }
    return true;
  }

  public static Cookie getCookieByName(HttpServletRequest httpServletRequest, String cookieName) {
    final Cookie[] cookies = httpServletRequest.getCookies();
    if (Objects.isNull(cookieName) || Objects.isNull(cookies)) {
      return null;
    }
    for (Cookie cookie : cookies) {
      final String name = cookie.getName();
      if (Objects.equals(cookieName, name)) {
        return cookie;
      }
    }
    return null;
  }
}

请求出现错误做资源的转发

访问错误的跳转要分清楚 接口请求和静态资源的请求,通过 accept 可以判断。

@RequestMapping("/error")
public class VueErrorController extends AbstractErrorController {

  private static final String ONLINE_SAIL = VUE_HTML_COOKIE_NAME;

  private static final String ERROR_BEFORE_PATH = "javax.servlet.error.request_uri";

  public VueErrorController(DefaultErrorAttributes defaultErrorAttributes) {
    super(defaultErrorAttributes);
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }

  @RequestMapping
  public ModelAndView errorHtml(HttpServletRequest httpServletRequest, HttpServletResponse response, @CookieValue(name = ONLINE_SAIL, required = false, defaultValue = "") String cookie) {
    final Object attribute = httpServletRequest.getAttribute(ERROR_BEFORE_PATH);
    if (cookie.length() > 0 && Objects.nonNull(attribute)) {
      response.setStatus(HttpStatus.OK.value());
      String requestURI = attribute.toString();
      // 访问的路径没有以 vue 部署的路径结尾,补充上路径转发去访问
      if (!requestURI.startsWith(cookie)) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setStatus(HttpStatus.OK);
        // 静态资源不想转发,重定向的话,修改为 redirect
        String viewName = "forward:" + cookie + requestURI;
        modelAndView.setViewName(viewName);
        return modelAndView;
      }
    }
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setStatus(HttpStatus.OK);
    modelAndView.setViewName("forward:/test/index.html");
    return modelAndView;
  }

  // 处理请求头为 accept 为 application/json 的请求,就是接口请求返回json 数据
  @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
  public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    HttpStatus status = getStatus(request);
    if (status == HttpStatus.NO_CONTENT) {
      return new ResponseEntity<>(status);
    }
    final Map<String, Object> errorAttributes = getErrorAttributes(request, true);
    return new ResponseEntity<>(errorAttributes, status);
  }

首页跳转

@Controller
public class IndexController {
  @RequestMapping(value = {"/test", "/test"})
  public String index() {
    return "forward:/test/index.html";
  }
}

本文由 张攀钦的博客 www.mflyyou.cn/ 创作。 可自由转载、引用,但需署名作者且注明文章出处。

到此这篇关于你不知道的SpringBoot与Vue部署解决方案的文章就介绍到这了,更多相关SpringBoot与Vue部署内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JavaScript类和继承 this属性使用说明
Sep 03 Javascript
javascript使用eval或者new Function进行语法检查
Oct 16 Javascript
多种方式实现JS调用后台方法进行数据交互
Aug 20 Javascript
jQuery不使用插件及swf实现无刷新文件上传
Dec 08 Javascript
简述JavaScript的正则表达式中test()方法的使用
Jun 16 Javascript
用JS中split方法实现彩色文字背景效果实例
Aug 24 Javascript
对js中回调函数的一些看法
Aug 29 Javascript
AngularJS入门教程之数据绑定原理详解
Nov 02 Javascript
vue.js实例todoList项目
Jul 07 Javascript
jQuery实现每隔一段时间自动更换样式的方法分析
May 03 jQuery
微信小程序scroll-view仿拼多多横向滑动滚动条
Apr 21 Javascript
JS校验与最终登陆界面功能完整示例
Jan 13 Javascript
在vue中使用eslint,配合vscode的操作
Nov 09 #Javascript
原生JavaScript实现五子棋游戏
Nov 09 #Javascript
Nuxt的动态路由和参数校验操作
Nov 09 #Javascript
jQuery实现移动端扭蛋机抽奖
Nov 08 #jQuery
JS实现炫酷轮播图
Nov 15 #Javascript
JS实现购物车基本功能
Nov 08 #Javascript
Vue实现购物车基本功能
Nov 08 #Javascript
You might like
一漂亮的PHP图片验证码实例
2014/03/21 PHP
jQuery 使用手册(五)
2009/09/23 Javascript
JS实现程序暂停与继续功能代码解读
2013/10/10 Javascript
jQuery操作表格(table)的常用方法、技巧汇总
2014/04/12 Javascript
JavaScript表格常用操作方法汇总
2015/04/15 Javascript
javascript原型模式用法实例详解
2015/06/04 Javascript
jQuery实现tab选项卡效果的方法
2015/07/08 Javascript
Vue.js系列之vue-router(上)(3)
2017/01/03 Javascript
详谈for循环里面的break和continue语句
2017/07/20 Javascript
js模块加载方式浅析
2017/08/12 Javascript
js将当前时间格式化为 年-月-日 时:分:秒的实现代码
2018/01/20 Javascript
vue.js获得当前元素的文字信息方法
2018/03/09 Javascript
bootstrap table实现合并单元格效果
2018/12/24 Javascript
JS无限级导航菜单实现方法
2019/01/05 Javascript
推荐几个不错的console调试技巧实现
2019/12/20 Javascript
Python实现的tab文件操作类分享
2014/11/20 Python
Python Requests安装与简单运用
2016/04/07 Python
PyQt5 pyqt多线程操作入门
2018/05/05 Python
对Python3+gdal 读取tiff格式数据的实例讲解
2018/12/04 Python
Python 隐藏输入密码时屏幕回显的实例
2019/02/19 Python
Python实现的矩阵转置与矩阵相乘运算示例
2019/03/26 Python
超简单使用Python换脸实例
2019/03/27 Python
详解Numpy数组转置的三种方法T、transpose、swapaxes
2019/05/27 Python
python 数据提取及拆分的实现代码
2019/08/26 Python
基于Tensorflow使用CPU而不用GPU问题的解决
2020/02/07 Python
python进行二次方程式计算的实例讲解
2020/12/06 Python
利用Bootstrap实现漂亮简洁的CSS3价格表实例源码
2017/03/02 HTML / CSS
html5新特性与用法大全
2018/09/13 HTML / CSS
Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?
2015/08/04 面试题
南京迈特望C/C++面试题
2012/07/09 面试题
最受欢迎的自我评价
2013/12/22 职场文书
市场总经理岗位职责
2014/04/11 职场文书
诚信承诺书模板
2014/05/26 职场文书
实习计划书范文
2015/01/16 职场文书
《中国机长》观后感:敬畏生命,敬畏职责
2019/11/12 职场文书
python自动统计zabbix系统监控覆盖率的示例代码
2021/04/03 Python