你不知道的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 相关文章推荐
jquery全选/全不选/反选另一种实现方法(配合原生js)
Apr 07 Javascript
jquery高效反选具体实现
May 05 Javascript
解析使用js判断只能输入数字、字母等验证的方法(总结)
May 14 Javascript
鼠标移动到图片名上,显示图片的简单实例
Jul 14 Javascript
jquery实现表单验证并阻止非法提交
Jul 09 Javascript
Javascript实现的简单右键菜单类
Sep 23 Javascript
原生JavaScript制作微博发布面板效果
Mar 11 Javascript
jQuery实现iframe父窗体和子窗体的相互调用
Jun 17 Javascript
NODE.JS跨域问题的完美解决方案
Oct 20 Javascript
JavaScript中无法通过div.style.left获取值的解决方法
Feb 19 Javascript
微信小程序授权登录解决方案的代码实例(含未通过授权解决方案)
May 10 Javascript
js动态生成表格(节点操作)
Jan 12 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 session劫持和防范的方法
2013/11/12 PHP
php curl模拟post提交数据示例
2013/12/31 PHP
PHP使用星号隐藏用户名,手机和邮箱的实现方法
2016/09/22 PHP
PHP实现登录搜狐广告获取广告联盟数据的方法【附demo源码】
2016/10/14 PHP
javascript new一个对象的实质
2010/01/07 Javascript
js null undefined 空区别说明
2010/06/13 Javascript
JQuery.closest(),parent(),parents()寻找父结点
2012/02/17 Javascript
jquery $(&quot;#variable&quot;) 循环改变variable的值示例
2014/02/23 Javascript
js实现checkbox全选、不选与反选的方法
2015/02/09 Javascript
js实现文本框中输入文字页面中div层同步获取文本框内容的方法
2015/03/03 Javascript
javascript实现复选框选中属性
2015/03/25 Javascript
jQuery代码实现表格中点击相应行变色功能
2016/05/09 Javascript
JavaScript循环_动力节点Java学院整理
2017/06/28 Javascript
vue计算属性时v-for处理数组时遇到的一个bug问题
2018/01/21 Javascript
详解基于Vue cli生成的Vue项目的webpack4升级
2018/06/19 Javascript
JS中的算法与数据结构之二叉查找树(Binary Sort Tree)实例详解
2019/08/16 Javascript
JavaScript数组常用的增删改查与其他属性详解
2020/10/13 Javascript
[05:49]DOTA2-DPC中国联赛 正赛 Elephant vs LBZS 选手采访
2021/03/11 DOTA
python实现监控linux性能及进程消耗性能的方法
2014/07/25 Python
详解Python的单元测试
2015/04/28 Python
举例详解Python中循环语句的嵌套使用
2015/05/14 Python
利用Hyperic调用Python实现进程守护
2018/01/02 Python
pandas中的series数据类型详解
2019/07/06 Python
Python之数据序列化(json、pickle、shelve)详解
2019/08/30 Python
在matplotlib中改变figure的布局和大小实例
2020/04/23 Python
英国第一蛋白粉品牌:Myprotein
2016/09/14 全球购物
机械工程师的岗位职责
2013/11/17 职场文书
总裁岗位职责
2013/12/04 职场文书
会计学生自我鉴定
2014/02/06 职场文书
2014年护士个人工作总结
2014/11/11 职场文书
捐资助学感谢信
2015/01/21 职场文书
中班下学期个人总结
2015/02/12 职场文书
工作迟到检讨书范文
2015/05/06 职场文书
学生乘坐校车安全责任书
2015/05/11 职场文书
医院员工辞职信范文
2015/05/12 职场文书
2016年教师政治思想表现评语
2015/12/02 职场文书