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

标签: 知道 springboot vue | 发表时间:2020-07-28 02:22 | 作者:张攀钦
出处:https://juejin.im/welcome/backend

前言

前段时间公司外网部署的演示环境全部转到内网环境中去,所有对外演示的环境都需要申请外网映射才能访问某个服务。我用一个外网地址 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.css 地址是 http://ip1:8080/index.css ,但是现在变成访问了 http://ip/index.css 导致 404,实际 index.css 地址为 http://ip/app1/index.css

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

  






但是项目中有一些组件的请求没有办法统一处理,只能改代码。但我不想动代码,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

  


    
    
    
    

为了可以在浏览器输入 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;复制代码
image-20200727234702928

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;
    }
}复制代码

image-20200728014849158

下面这个是重定向的配置

  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;
    }
}复制代码

image-20200728014654144

根据这个思路就可以把所有的资源进行转发了,不用改业务代码,只需给 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   > error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity<>(status);
        }
        final Map 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/ 创作。 可自由转载、引用,但需署名作者且注明文章出处。

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

相关 [知道 springboot vue] 推荐:

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

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

超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!

- - 掘金后端
项目代码: github.com/MarkerHub/v…. 项目视频: www.bilibili.com/video/BV1PQ…. 文章总体分为2大部分,Java后端接口和vue前端页面,比较长,因为不想分开发布,真正想你4小时学会,哈哈. 从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等.

SpringBoot-Metrics监控

- -
Metrics基本上是成熟公司里面必须做的一件事情,简单点来说就是对应用的监控,之前在一些技术不成熟的公司其实是不了解这种概念,因为业务跟技术是相关的. 当业务庞大起来,技术也会相对复杂起来,对这些复杂的系统进行监控就存在必要性了,特别是在soa化的系统中,完整一个软件的功能分布在各个系统中,针对这些功能进行监控就更必要了.

ssr vuejs/vue-hackernews-2.0: HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering

- -
This is a demo primarily aimed at explaining how to build a server-side rendered Vue app, as a companion to our SSR documentation. #install dependenciesnpm install#or yarn#serve in dev mode, with hot reload at localhost:8080npm run dev#build for productionnpm run build#serve in production modenpm start.

Vue 移动端框架

- - IT瘾-jianshu
vonic 一个基于 vue.js 和 ionic 样式的 UI 框架,用于快速构建移动端单页应用,很简约. 中文文档| github地址| 在线预览. vux 基于WeUI和Vue(2.x)开发的移动端UI组件库. 基于webpack+vue-loader+vux可以快速开发移动端页面,配合vux-loader方便你在WeUI的基础上定制需要的样式.

vue路由权限校验

- - 掘金前端
做后台系统的时候,难免会有用户权限的判断. admin可以查看全部菜单,user只能查看部分菜单. 一开始接触这个需求的时候,完全是纯前端做的. 在配置路由的时候,加一个roles的属性,通过判断用户的roles是否与路由的roles属性相匹配来作为显示隐藏的依据. // 过滤路由 menuList-菜单 roles-用户角色 const checkMenuList = (menuList, roles) => { for (let i = 0; i < menuList.length; i++) {.

SpringBoot的事务管理

- - ImportNew
Springboot内部提供的事务管理器是根据autoconfigure来进行决定的. 比如当使用jpa的时候,也就是pom中加入了spring-boot-starter-data-jpa这个starter之后(之前我们分析过 springboot的自动化配置原理). Springboot会构造一个JpaTransactionManager这个事务管理器.

springboot aop日志记录

- - 编程语言 - ITeye博客
一、POM增加AOP JAR包. 三、SysAspect类. 注:@annotation(cn.com.hfai.controller.system.Logweb) 一定要指定Logweb类. 四、在Controller类的方法之上加上注解 @Logweb 即可. 注:这个只是打印在控制台上,若想放到数据库中,则需要增加操作数据库的业务代码.

springboot单元测试技术

- - 海思
整个软件交付过程中,单元测试阶段是一个能够最早发现问题,并且可以重复回归问题的阶段,在单元测试阶段做的测试越充分,软件质量就越能得到保证. 具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-unit-test.

K8S部署SpringBoot应用_都超的博客-CSDN博客_k8s springboot

- -
K8S环境机器做部署用,推荐一主双从. Docker Harbor私有仓库,准备完成后在需要使用仓库的机器docker login. 开发机器需要Docker环境,build及push使用. 一、构建基本Springboot工程,本例所用版本及结构如下图. 创建测试代码,简单打印几行log. .