乐趣区

关于java:你不知道的SpringBoot与Vue部署解决方案

前言

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

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

<font color=red> 条件容许或者对性能要求比拟高,举荐是前后端拆散部署,nginx 做 web 服务器,后端只提供接口服务 </font>

以前部署的我的项目 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="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="stylesheet">

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

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

然而我的项目中有一些组件的申请没有方法对立解决,只能改代码。但我不想动代码,webpack 打包都不想动,基于这些需要想了一个方法来解决。

本文内容

  • Nginx 部署 vue 我的项目,怎么能敌对解决动态资源的失落
  • SpringBoot 提供 web 服务器的性能映射 vue 我的项目为 web 资源,并解决 vue 路由转发 index.html 问题。

演示代码地址

https://github.com/zhangpanqin/vue-springboot

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">
    <!-- 部署之后,拜访不到 index.css -->
    <link href="/index.css" 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;

<img src=”http://oss.mflyyou.cn/blog/20200727234702.png?author=zhangpanqin” alt=”image-20200727234702928″ style=”zoom: 25%;” />

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;}
}

上面这个是重定向的配置

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;}
}

依据这个思路就能够把所有的资源进行转发了,不必改业务代码,只需给 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";}
}

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

如转载至微信公众号,请在文末增加作者公众号二维码。微信公众号名称:Mflyyou

退出移动版