学习日报 20260423|Vue SPA 部署到 Spring Boot:404/500 错误排查与解决方案1

一、项目背景

技术栈

  • 前端:Vue 3 + Vite + Vue Router(History 模式)
  • 后端:Spring Boot 3 + MyBatis-Plus
  • 部署方式 :Vue 打包后的静态资源放入 Spring Boot 的 src/main/resources/static/ 目录,由 Spring Boot 统一提供服务

问题现象

打包部署后,访问 localhost:8080 出现以下错误:

  1. 500 Internal Server Error(Whitelabel Error Page)
  2. 404 Not Found(跳转子页面或刷新时)
二、错误案例剖析

案例 1:500 错误 - Whitelabel Error Page

  • 错误日志

    复制代码
    There was an unexpected error (type=Internal Server Error, status=500).
    java.lang.StackOverflowError: null
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(...)
  • 问题代码(错误做法)

    复制代码
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            //  错误:会导致循环转发
            registry.addViewController("/*")
                    .setViewName("forward:/index.html");
        }
    }
  • 原因分析

    1. 用户访问 http://localhost:8080/
    2. ViewControllerRegistry 匹配到 /*,转发到 /index.html
    3. Spring Boot 的静态资源处理器 也开始处理 /index.html
    4. DispatcherServlet 和静态资源处理器产生冲突
    5. 请求在两者之间循环转发 ,最终栈溢出 → 500 错误
  • 核心矛盾

    • WebMvcConfigurer 是在 DispatcherServlet 层面拦截请求
    • 静态资源处理器也是在 DispatcherServlet 的过滤链中
    • 两者处理同一个请求时产生死循环

案例 2:404 错误 - 跳转子页面或刷新时

  • 场景重现

    1. 首页正常显示:http://localhost:8080/
    2. 在 Vue 内点击按钮跳转到 /window (前端路由,无请求)
    3. 直接输入 URLhttp://localhost:8080/window 404
    4. 刷新子页面 :F5 刷新 /window 404
  • 原因分析

    复制代码
    用户访问:http://localhost:8080/window
             ↓
    浏览器发送请求:GET /window
             ↓
    Spring Boot 收到请求
             ↓
    查找 Controller:没有 @RequestMapping("/window")  ✗
             ↓
    查找静态资源:static/ 目录下没有 window 文件  ✗
             ↓
    返回 404 Not Found 
  • 核心问题

    • Vue Router 使用 History 模式,URL 看起来像真实路径
    • 服务器不知道 /window前端路由标识
    • 服务器尝试查找真实资源,找不到就返回 404
三、正确解决方案:SpaForwardFilter

完整代码

复制代码
package com.example.liverank.config;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class SpaForwardFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String path = httpRequest.getRequestURI();

        // 排除 API 请求和静态资源
        if (path.startsWith("/api/") ||
                path.contains(".") ||
                path.equals("/")) {
            chain.doFilter(request, response);
            return;
        }

        // 其他所有请求转发到 index.html(支持 Vue History 模式)
        request.getRequestDispatcher("/index.html").forward(request, response);
    }
}

代码逐行解析

  1. @Component 注解
    • 将 Filter 注册为 Spring Bean
    • Spring Boot 自动将其添加到 Servlet 过滤链中
  2. implements Filter
    • 实现 Servlet 规范的 Filter 接口
    • 重写 doFilter() 方法处理请求
  3. 请求路径判断逻辑
    • // 条件 1:放行 API 请求

      复制代码
      if (path.startsWith("/api/")) {
          chain.doFilter(request, response);
          return;
      }

      作用 :所有 /api/* 的请求(如 /api/staff/list)直接放行,交给 Spring MVC 的 Controller 处理。

    • // 条件 2:放行静态资源

      复制代码
      if (path.contains(".")) {
          chain.doFilter(request, response);
          return;
      }

      作用 :包含 . 的路径认为是静态资源(如 /assets/index.js/favicon.svg),放行给 Spring Boot 的静态资源处理器。

    • // 条件 3:放行根路径

      复制代码
      if (path.equals("/")) {
          chain.doFilter(request, response);
          return;
      }

      作用 :用户首次访问 http://localhost:8080/ 时,直接返回 index.html。

    • // 核心逻辑:转发所有 Vue 路由请求

      复制代码
      request.getRequestDispatcher("/index.html").forward(request, response);

      作用 :其他所有路径(如 /window/admin)统一转发到 index.html,让 Vue Router 处理。

四、为什么 Filter 能解决问题?

请求处理链的优先级

复制代码
浏览器请求
  ↓
┌─────────────────────────────────────┐
│  Filter(最早拦截)                  │
│  ├─ API 请求 → 放行                  │
│  ├─ 静态资源 → 放行                  │
│  └─ Vue 路由 → 转发到 index.html    │
└─────────────────────────────────────┘
  ↓
┌─────────────────────────────────────┐
│  DispatcherServlet(Spring MVC)     │
│  ├─ 查找 @RestController             │
│  └─ 查找静态资源处理器               │
└─────────────────────────────────────┘
  ↓
┌─────────────────────────────────────┐
│  Controller / 静态资源返回           │
└─────────────────────────────────────┘

Filter vs WebMvcConfigurer 对比

特性 Filter WebMvcConfigurer
执行时机 Servlet 容器层面(最早) DispatcherServlet 之后
与静态资源冲突 不会冲突 会冲突(500 错误)
适用范围 所有请求 仅 Spring MVC 管理的请求
推荐场景 SPA 路由转发、跨域、认证 视图映射、消息转换器
五、完整请求流程图

场景:用户访问 http://localhost:8080/window

复制代码
1. 浏览器发送 GET /window
         ↓
2. SpaForwardFilter 拦截
   path = "/window"
         ↓
3. 判断逻辑
   ├─ 是 /api/* 吗?   → 否 ✗
   ├─ 包含 "." 吗?     → 否 ✗
   ─ 等于 "/" 吗?     → 否 ✗
         ↓
4. 执行 forward("/index.html")
         ↓
5. 服务器返回 index.html
   (包含 Vue 应用的 JS/CSS)
         ↓
6. 浏览器加载 Vue 应用
         ↓
7. Vue Router 读取 URL:/window
         ↓
8. 匹配路由规则,加载 WindowPage 组件
         ↓
9.  页面正常显示
六、面试回答模板

面试官问:"Vue 部署到 Spring Boot 遇到 404/500 怎么解决?"
标准回答:

"这个问题是因为 Vue Router 使用了 History 模式导致的。

404 的原因 :当用户直接访问子页面 URL(如 /window)或刷新页面时,浏览器会向服务器发送请求。Spring Boot 收到请求后,尝试查找对应的 Controller 或静态资源,找不到就返回 404。实际上 /window 只是 Vue 的前端路由标识,不是真实路径。

500 的原因 :我之前尝试用 WebMvcConfigureraddViewController 来转发请求,但这会和 Spring Boot 的静态资源处理器产生冲突,导致循环转发和栈溢出,最终报 500 错误。

解决方案 :我使用了一个 Filter,在请求到达 DispatcherServlet 之前就进行拦截。对于 API 请求(/api/*)和静态资源(包含 . 的路径)直接放行,其他所有路径统一转发到 index.html。这样 Vue 应用就能正常加载,由 Vue Router 来解析 URL 并显示对应的组件。

为什么用 Filter:Filter 的执行时机在 Servlet 容器层面,比 DispatcherServlet 更早,避免了与静态资源处理器的冲突。这是 Spring Boot 官方推荐的 SPA 部署方案。"

七、扩展知识

forward() vs sendRedirect()

方法 特点 URL 变化 请求次数 适用场景
forward() 服务器内部转发 不变 1 次 SPA 路由转发
sendRedirect() 客户端重定向 改变 2 次 登录跳转、外部链接

我们使用 forward() 是为了保持 URL 不变,让 Vue Router 能正确解析。

生产环境最佳实践

实际生产环境通常使用 Nginx 反向代理

复制代码
server {
    listen 80;
    
    # 静态资源
    location / {
        root /var/www/live-rank;
        try_files $uri $uri/ /index.html;
    }
    
    # API 转发
    location /api/ {
        proxy_pass http://localhost:8080;
    }
}

优势

  • 静态资源由 Nginx 直接处理,性能更高
  • 减轻 Spring Boot 服务器的压力
  • 更容易做负载均衡和 CDN 加速
八、总结
问题 原因 解决方案
500 错误 WebMvcConfigurer 与静态资源处理器冲突 改用 Filter
404 错误 服务器不认识 Vue 路由,以为是真实路径 Filter 转发到 index.html
刷新子页面 404 浏览器发送请求,服务器找不到资源 Filter 统一处理

核心原理一句话:

"Filter 在请求处理链的最前端,通过判断请求类型,将 Vue 路由请求转发到 index.html,避免与 Spring Boot 静态资源处理器冲突,完美支持 SPA History 模式。"

九、学习建议
  1. 理解 HTTP 请求的生命周期:Filter → Interceptor → Controller
  2. 掌握 forward 和 redirect 的区别:面试常考点
  3. 了解前端路由的两种模式:Hash vs History
  4. 动手实验:打开浏览器 Network 面板,观察不同操作的网络请求
相关推荐
炽烈小老头1 小时前
【每天学习一点算法 2026/04/23】盛最多水的容器
学习·算法
寒秋花开曾相惜2 小时前
(学习笔记)4.1 Y86-64指令集体系结构(4.1.6 一些Y86-64指令 )
linux·运维·服务器·开发语言·笔记·学习·安全
英俊潇洒美少年2 小时前
Vue2 高德地图地址选择器完整实战(组件抽离+高并发优化+@amap/amap-jsapi-loader最佳实践)
前端·javascript·vue.js
晴天丨2 小时前
🛡️ Vue 3 错误处理完全指南:全局异常捕获、前端监控、用户反馈
前端·vue.js
蓝桉~MLGT2 小时前
Ai-Agent学习历程—— Harness和Memory介绍和应用 & vibe Coding工具选择
人工智能·学习
河阿里2 小时前
Vue3:全流程开发
vue.js
召田最帅boy2 小时前
一次OOM排查实录
linux·jvm·spring boot·adb
GHL2842710902 小时前
LangChain学习
学习·ai·langchain
知识分享小能手2 小时前
ECharts入门学习教程,从入门到精通,综合实战——ECharts电商数据可视化完整知识点+案例代码(8)
学习·信息可视化·echarts