前言
前段时间公司外网部署的演示环境全部转到内网环境中去,所有对外演示的环境都需要申请外网映射才能访问某个服务。我用一个外网地址 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;
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 ~ /(.*)/.*/"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";
}
}
本文由 张攀钦的博客 www.mflyyou.cn/ 创作。 可自由转载、引用,但需署名作者且注明文章出处。
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。


