Решения для развертывания SpringBoot и Vue, о которых вы не знаете

Java

предисловие

Некоторое время назад все демонстрационные среды, развернутые во внешней сети компании, были перенесены во внутреннюю сетевую среду, и всем внешним демонстрационным средам необходимо было подать заявку на сопоставление внешней сети для доступа к сервису. Я использую внешний сетевой адресwww.a.comСопоставление с адресом интрасетиhttp://ip:port, то по этому адресуhttp://ip:portПроекты, использующие nginx в качестве прокси для пересылки в различные группыhttp://ipn:portnПоднявшись, я также столкнулся с каким-то статическим ресурсом 404, в основном для решения этой проблемы 404.

Недавно я сделал еще один проект.Учитывая пользовательский опыт и упрощая развертывание, я подумал о способе использования SpringBoot в качестве веб-сервера для сопоставления внешних ресурсов с веб-ресурсами.

Разрешающие условия или требования к производительности относительно высоки, рекомендуется разделить переднюю и заднюю части, в качестве веб-сервера используется nginx, а задняя часть предоставляет только интерфейсные сервисы.

Адрес доступа к внешней сети ранее развернутого проекта A:http://ip1:8080, доступ возможен только после сопоставления внешней сетиhttp://ip/app1, адрес доступа к внешней сети предыдущего проекта B былhttp://ip1:8081, адрес доступа к проектуhttp://ip/app2. Это можно считать незначительным изменением, но первая проблема, с которой столкнулись после переключения — это переадресация статических ресурсов.404.

Например, предыдущий адрес доступа к проекту А былhttp://ip1:8080Это бесконтекстно.

И теперь адрес доступа Ahttp://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.csshttp://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 предоставляет функции веб-сервера для сопоставления проектов vue с веб-ресурсами и решает проблему маршрутизации 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/Поместите развернутые проекты, такие как ресурсы проекта приложения, в/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Базовое свойство в .

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Вот и все, подумайте об этом, это могут сделать только куки.

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 еще удобно писать, отлаживать умеет, ха-ха.

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());
    }
}
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/творчество. Ее можно свободно воспроизводить и цитировать, но с обязательной подписью автора и указанием источника статьи.

При перепечатке в публичную учетную запись WeChat добавьте QR-код публичной учетной записи автора в конец статьи. Имя общедоступной учетной записи WeChat: Mflyyou