做全栈开发这几年被问得最多的一个问题就是Spring Boot和Vue到底怎么配合起来很多人单独学后端、单独学前端都没问题一到了自己从头搭一个前后端分离的项目就卡在跨域、登录态、接口对接这些地方。这篇指南就是把这些卡点一个个拆开从环境准备、项目初始化、核心功能实现到前后端联调再到真实项目里一定会遇到的坑全部过一遍。项目从一个最简单的用户管理模块切入带出登录认证、列表分页、CRUD、路由权限这些高频需求代码可以直接拿去改。这套内容适合谁准备做毕业设计的学生、想转行全栈的开发、以及公司里需要独立负责小项目的同学。不管你是后端想补前端还是前端想补后端按着这篇走一遍就能建立起一条完整的前后端分离开发链路以后再接到类似需求心里就有底了。1. 全栈开发的第一课先把技术选型搞清楚1.1 为什么是Spring Boot Vue而不是别的组合现在市面上的技术选型非常多后端有Spring Boot、Go、Node.js、Python Flask前端有Vue、React、Angular。但Spring Boot Vue至今仍然是国内中小型项目和个人项目的绝对主流原因很朴素第一生态成熟。Spring Boot 把 SSM 那一套繁琐的配置几乎全自动掉了内嵌 Tomcat一个 jar 包就能跑后端所有需要的组件都找得到现成封装。Vue 学习曲线比 React 平滑中文文档完整社区问答资料多遇到问题一搜就有答案。对于全栈开发者来说这意味着踩坑的时间成本极低。第二前后端分离模式已经是行业标准。过去用 JSP 写页面前端后端耦合在一起改个样式都要重新部署。现在前端独立工程、独立部署后端只提供 JSON 接口两边可以并行开发互不阻塞联调阶段再对齐。这套协作模式本身就是 Spring Boot Vue 最能发挥价值的场景。第三从就业和实际项目角度看这套组合需求量大。无论是外包项目、企业管理系统、还是个人毕设乙方一提技术栈十有八九是“Spring Boot Vue”。先把这个组合吃透往后你学微服务、学分布式甚至转去写小程序底层思路都是相通的。1.2 项目结构规划前后端到底怎么分很多人第一次做全栈项目就把 Java 代码和 Vue 代码放同一个目录或者前端文件塞进后端的 static 目录里。短期看是省事了长期看全是麻烦。我的建议是严格分离两个独立项目用 Git 分仓库管理。一个典型的前后端分离项目结构长这样fullstack-demo/ ├── backend/ # Spring Boot 后端工程 │ ├── src/main/java │ ├── src/main/resources │ ├── pom.xml │ └── target/ ├── frontend/ # Vue 前端工程 │ ├── src/ │ ├── public/ │ ├── index.html │ ├── package.json │ └── vite.config.js └── README.md为什么这么分因为前后端的生命周期完全不一样。前端依赖 Node 工具链后端依赖 JDK 和 Maven前端打包出的是静态文件后端打包出的是可执行 jar。混在一起会导致构建脚本互相干扰部署也要互相等。分开之后前端开发用 npm run dev后端开发用 mvn spring-boot:run各自独立启动完全互不影响。1.3 版本选型不被新版本坑到版本选择是很多人忽略的一步。直接下最新的 Spring Boot 3.5或者最新的 Vue 3.5看起来没问题但一旦某个第三方依赖还没适配你就会陷入“依赖冲突地狱”。这里给一份我实测比较稳的版本组合组件推荐版本说明JDK17 或 21Spring Boot 3.x 起强制要求 JDK 17用 LTS 版本最稳Spring Boot3.3.x 或 3.5.x3.3.x 更稳3.5.x 支持虚拟线程特性Maven3.8 / 3.9配合 IDE 内置就行Node.js18 LTS 或 20 LTS用 LTS别追奇数版本Vue3.4用 Vite 创建别再用 Vue CLIVite5.x构建快配置简单MyBatis-Plus3.5.x适配 Spring Boot 3 的版本号已正常MySQL8.08.0 已是主流注意驱动配置差异选 LTS 版本求的不是“最新”而是“最少踩坑”。我从没后悔过在版本上保守一点。2. 环境准备与项目脚手架搭建2.1 后端Spring Boot 项目初始化后端项目我一般不用 IDE 的脚手架而是直接去 Spring Initializr 网页生成选好参数下载 zip再用 IDE 打开。这种方式生成的项目结构最干净pom.xml 也是标准化的。生成时需要注意几个关键配置项ProjectMavenGradle 也很好但国内 Maven 更常见LanguageJavaSpring Boot选 3.3.x 或 3.5.x下拉框里选稳定版Groupcom.example按自己公司/域名反写Artifactbackend模块名Java17 或 21DependenciesSpring Web必选提供 REST 接口能力Spring Boot DevTools开发热重启强烈推荐Lombok简化代码Validation参数校验MySQL Driver数据库驱动MyBatis Plus Framework这里注意Initializr 里可能没有需要手动在 pom.xml 添加如果你和我一样是手动加 MyBatis-Pluspom.xml 里加这段dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency2.2 前端Vue 项目初始化与依赖安装前端环境的坑主要集中在 Node 和 npm 版本上。很多新手用 Vue 2 的老教程跑去用 Vue CLI结果现在 Vue 官方推荐的是 Vite。这两者的区别我后面细说先给一套完整的创建命令# 检查 node 版本建议 18 或 20 node -v # 使用 Vite 创建 Vue 3 项目 npm create vitelatest frontend -- --template vue # 进入项目目录 cd frontend # 安装依赖 npm install这里有个很关键的点npm create vitelatest 里的 latest 会拉取 Vite 最新版本如果执行报错多半是 Node 版本太低。先升级 Node再执行创建命令不要盲目去换镜像源。项目创建好了之后先跑一下npm run dev看到默认页面大概率是 Vite Vue 的标志页。这说明你的 Node 环境没问题了。2.3 第三方库装哪些Axios、Element Plus、Pinia、Vue Router一个全栈项目前端除了 Vue 本体还需要几个配套库。我用得最多的四个# 安装路由负责页面跳转 npm install vue-router4 # 安装状态管理负责全局数据共享 npm install pinia # 安装 HTTP 请求库负责和后端接口通信 npm install axios # 安装 UI 组件库负责页面样式和组件 npm install element-plus这几个库的选择逻辑我解释一下为什么不用 fetch 而用 axiosfetch 是浏览器原生能力但 axios 封装了请求拦截器、响应拦截器、取消请求、统一错误处理。在全栈项目里你要在请求头统一注入 token、在响应里统一处理 401用 axios 的拦截器可以少写几百行代码。为什么用 Pinia 不用 VuexVuex 是 Vue 2 时代的产物Pinia 是 Vue 3 官方现在推荐的状态管理库。Pinia 的 API 更简洁去掉了 mutations直接用 setup 风格的 store写起来像普通 composable 函数新手更好理解。Element Plus 是锦上添花。如果你不想用现成 UI 库自己手写 CSS 也可以但 Element Plus 的表格、表单、分页组件和后台管理系统的需求几乎完美契合能省下大量时间。依赖装完之后在 main.js 里完成注册import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import router from ./router import App from ./App.vue const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount(#app)注意Element Plus 的样式文件必须单独引入只注册组件不引入样式页面会变成“裸奔”状态这是新手很容易漏的一步。3. 核心功能实现从登录到数据展示3.1 后端接口设计三层架构与统一响应体拿到了项目骨架接下来可以从一个最简单的功能切入用户登录和列表查询。后端代码我推荐按经典的三层架构组织com.example.backend ├── controller/ # 接收请求返回响应 ├── service/ # 业务逻辑 ├── mapper/ # 数据访问 ├── entity/ # 数据库实体 ├── common/ # 通用类如统一响应体、异常处理 └── config/ # 配置类三层架构的规矩是Controller 层只做参数接收和响应封装不写业务逻辑Service 层写具体业务规则Mapper 层只做数据操作。业务逻辑写在 Controller 里是很多新手最爱犯的错短期看代码少但项目一复杂Service 层的缺失会让代码完全没法维护。后端接口的排第一项规范就是统一响应体。所有接口返回的 JSON 结构必须一致前端才能做统一处理。我的做法是定义一个 Result 类Data public class ResultT { private Integer code; // 200 成功500 失败401 未授权 private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T ResultT error(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }这套结构配合前端 axios 的响应拦截器可以在几行代码内完成所有接口的统一错误提示。3.2 MyBatis-Plus 配置与 XML 文件位置的经典坑后端如果用了 MyBatis-Plus有两点配置经常让人卡住需要重点讲一下。第一Mapper 扫描路径。在启动类或者配置类上必须加 MapperScan否则运行时找不到 Mapper 接口SpringBootApplication MapperScan(com.example.backend.mapper) public class BackendApplication { public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); } }第二XML 文件和 Mapper 接口放在同一个目录下时Maven 默认不会把 XML 打包到 target。如果用了 XML 写复杂 SQL又想让 XML 和 Mapper 接口放同一个包必须在 pom.xml 里加资源配置build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource resource directorysrc/main/resources/directory /resource /resources /build同时还需要在 application.yml 里写 Mapper 位置mybatis-plus: mapper-locations: classpath*:com/example/backend/mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里的classpath*:带星号是为了支持扫描多个 jar 中的 XML。如果你用的是classpath:不带星号可能会出现“Invalid bound statement (not found)”的报错排查到最后发现只是路径的问题非常浪费时间。日志建议开成 StdOutImpl开发阶段可以在控制台直接看到 SQL 和参数联调时排查数据问题能省一半时间。3.3 登录认证的实现JWT 拦截器全栈项目的登录认证怎么做最经典、最常用的是 JWTJSON Web Token方案流程是这样的用户输入用户名密码前端 axios 发送到后端 /api/login后端校验用户名密码校验通过后生成一个 JWT 令牌返回给前端前端把 token 存到 localStorage / Pinia后续所有请求axios 拦截器自动在请求头加上Authorization: Bearer token后端用拦截器统一校验 token不合法直接返回 401JWT 的核心代码不复杂但有几处细节要注意。生成 token 的时候千万别把用户密码塞进去token 要设置过期时间一般 2 到 24 小时服务端需要加一个 JWT 密钥这个密钥要放到配置文件里不要硬编码在代码里。以 Java 代码为例生成 token 需要引入 jjwt 依赖。生成的 token 就是一个很长的字符串可以放在任何地方但记住 token 本身是可以被解密的敏感信息一定不能放进去。后端接口需要写一个拦截器校验所有/api/**请求的 token登录接口自己除外校验失败时返回 401 和统一响应体Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } try { // 校验 token从 token 中解析出用户信息 // 校验失败会抛出异常 Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json); response.getWriter().write({\code\:401,\message\:\未授权\}); return false; } } }这个拦截器可以配合 Spring Security 使用也可以不用 Spring Security自己实现拦截器对新手来讲理解成本更低。如果项目要求不高直接手动拦截器 JWT 完全够用。3.4 前端路由与权限控制前端拿到 token 之后要干的第二件事就是做路由权限控制。我这里的做法比较简练第一路由配置文件里分两类公开路由和管理页路由。公开路由只有登录页管理页路由都在一个父路由下并设置meta: { requiresAuth: true }。const routes [ { path: /login, component: LoginView }, { path: /, component: Layout, meta: { requiresAuth: true }, children: [ { path: users, component: UserListView }, { path: dashboard, component: DashboardView } ] } ]第二在 main.js 里注册路由守卫。所谓路由守卫就是每次跳转页面前执行的检查函数。token 不存在就强制跳转到/login并带上redirect参数登录成功后再跳回原来要访问的页面router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })为什么用 localStorage 而不是用 Vuex 存 token因为状态管理存的数据刷新页面就没了而 localStorage 是持久化的。正确做法是token 存 localStorage用户信息存 Pinia。3.5 用 Axios 拦截器统一处理请求和响应现在前端所有请求都要带上 token所有响应都要统一处理错误。这是 axios 拦截器的用武之地// /utils/request.js import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动加 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request这套封装是前后端分工的交叉点。前端只需要调用request.get(/users)后端返回的code如果不是 200前端自动弹出错误提示如果是 401自动跳回登录页。这比每个接口都写一遍if (res.code ! 200)要省事得多。3.6 自定义 v-model弹窗组件的写法Vue 3 里自定义 v-model 是高频知识点。在实际全栈项目中很多业务模块都是“点击新增按钮 - 弹出表单弹窗 - 提交保存”。这个弹窗组件一般会封装成一个子组件父组件控制弹窗开关子组件内部提交数据。这就是自定义 v-model 最典型的应用场景。Vue 3 自定义 v-model 的写法是这样的。在子组件里声明一个 modelValue 的 prop再通过 update:modelValue 事件更新父组件的值!-- 子组件 UserFormDialog.vue -- template el-dialog :model-valuemodelValue update:model-valueval $emit(update:modelValue, val) !-- 表单内容 -- /el-dialog /template script setup defineProps({ modelValue: Boolean }) const emit defineEmits([update:modelValue]) /script在父组件里用法非常直观UserFormDialog v-modelshowDialog /如果你没接触这个语法可能会觉得奇怪为什么要专门写 update:modelValue 这种长名字。其实它是 Vue 的约定v-model 本质是:modelValuexxxupdate:modelValuexxx $event的语法糖。理解了这一点自定义 v-model 就一点不神秘了。3.7 完整链路从点击按钮到数据展示把这几个模块串起来一个用户列表的完整操作链路是这样的前端页面 onMounted 时调用request.get(/users)axios 拦截器自动带 token请求进入后端 JwtInterceptor校验 token通过则放行进入 UserController调用 Service 层查询 ListService 调用 Mapper 接口MyBatis-Plus 自动拼接 SQL 查询数据库数据封装成 Result.success() 返回 JSON 给前端前端拿到 res.data赋给响应式变量Element Plus 表格组件自动渲染如果你能完整讲清楚这条链路面试聊全栈项目的时候基本就能把技术点串成故事线了。4. 前后端联调实战跨域、代理与接口测试4.1 为什么浏览器会报跨域错误前后端分离开发中最常见的报错就是浏览器控制台那一行大字Access to XMLHttpRequest at http://localhost:8080/api/users from origin http://localhost:5173 has been blocked by CORS policy这是“跨域”问题。核心原因在于浏览器安全模型规定不同协议、不同端口、不同域名的网页默认不能互相请求资源。前端运行在 5173 端口后端运行在 8080 端口浏览器认为这是两个不同的源所以拦截了请求。这不是后端接口的问题也不是前端代码的问题只是浏览器履行了安全职责。解决办法有三个。4.2 三种解决办法后端CORS、前端代理、生产环境反向代理第一种后端允许跨域。Spring Boot 里写一个配置类全局放开 CORSConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这种方法在开发阶段最省事但问题也很明显生产环境如果也放开所有域名谁都能请求你的接口安全隐患很大。这个方法只适合本地联调用。第二种前端 Vite 代理。这是我个人推荐开发阶段的方案。原理是前端开发服务器在 5173 端口启动后把/api开头的请求转发到后端的 8080 端口。因为 Nginx 在中间做了一层转发浏览器只看到了同源的请求就不会触发跨域拦截。在vite.config.js里这样写export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这种方法的好处是前端代码里的 baseURL 不变始终是/api开发阶段上代理生产阶段上 Nginx 转发代码可以零改动。第三种生产环境用 Nginx 反向代理。部署上线时前端打包成静态文件放到 NginxNginx 配置一条规则把/api的请求反代到后端服务地址server { listen 80; server_name your-domain.com; location / { root /var/www/frontend; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }有同学会问为什么不在前端直接用http://localhost:8080作为请求地址反而要绕一大圈用代理原因有两个一是硬编码后端地址会导致环境切换必须改代码二是浏览器的跨域限制始终存在Nginx 转发是从根源上规避了跨域问题。上线后推荐使用第三种方案。4.3 联调技巧先 Mock 还是直接联调我在实际项目中的经验是前端开发阶段不要等后端接口写好再动手。前后端并行开发时前端可以先 Mock 数据把页面写出来等后端接口 ready 再切换到真实接口。我常用的做法是定义 api 层所有请求函数集中在一个文件里逻辑层和页面层只管调用函数不看具体实现// /api/user.js import request from /utils/request export function getUsers(params) { return request.get(/users, { params }) } export function createUser(data) { return request.post(/users, data) }这样做的最大好处是如果后端接口还没好你可以先在这个 api 文件里返回假数据页面代码完全不改动。等后端接口 ready把假数据替换成 request 调用即可。联调阶段再也不用去页面里一个个改请求地址。4.4 接口联调时最常用的调试方法联调阶段如果请求报错了不要凭感觉猜按下面的顺序排查看浏览器 Network 面板确认请求发出去了没有接口地址对不对请求方法对不对看控制台报错类型CORS 错误先看是不是跨域404 看清请求路径多半是 Controller 的 RequestMapping 没对齐415 是 contentType 不对后端需要 JSON 但你发送的是表单格式看后端控制台日志如果后端是 Spring Boot控制台会打印异常堆栈和 SQL 日志。定位到了异常行号问题基本就解决了一半用 Postman / Apifox 单独测接口绕过前端直接发请求。接口用 Postman 通了说明后端没问题问题在前端Postman 都通不了说明后端接口有问题前端再怎么调也没用这一步很多新手会搞反前端改来改去改不明白其实是后端报错。有了上面的排查顺序遇到问题就不会慌。5. 进阶技巧与踩坑实录5.1 Spring Boot 3.x 中 Spring Security 的配置变化如果项目引入了 Spring SecuritySpring Boot 3.x 相比于 2.x 有一个非常大的配置差异Security 的配置类不再继承 WebSecurityConfigurerAdapter而是需要手动声明 SecurityFilterChain 的 Bean。Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) .authorizeHttpRequests(auth - auth .requestMatchers(/api/login).permitAll() .anyRequest().authenticated() ) .sessionManagement(session - session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ); return http.build(); } }注意Spring Boot 3.x 里默认是禁止跨域之外的 CSRF 保护的如果你前后端用了 JWT 认证CSRF 保护必须关闭。否则所有 POST/PUT 请求都会返回 403。我当时就在这个坑里浪费了将近半天后来才发现是 Security 默认配置导致的。如果你只是做一个中小型项目完全可以用第 3 节里手写的 JwtInterceptor 方案不引入 Spring Security。Spring Security 的学习曲线比较陡对于简单项目来说性价比不高等业务复杂度上来了再引入也不迟。5.2 使用 Java 21 Spring Boot 3.5 开启虚拟线程JDK 21 带来的虚拟线程Virtual Threads是 Java 性能提升的大事件。Spring Boot 3.2 之后原生支持虚拟线程如果你的项目是 Spring Boot 3.5 Java 21开启方式非常简单在 application.yml 里加一行配置spring: threads: virtual: enabled: true开启后Tomcat 处理每一个请求都会在一个虚拟线程上运行而不是传统线程池里的平台线程。虚拟线程的创建和切换开销极小可以支撑更高的并发量。如果你的项目是 IO 密集型应用比如频繁查数据库、调外部接口这个特性可以给你带来很明显的性能提升。不过要注意如果你用的是 ThreadLocal 存用户信息配合虚拟线程使用会出现问题。因为虚拟线程数量可以远大于平台线程数量ThreadLocal 可能撑爆内存或者导致线程复用出错。在全栈项目里我建议用请求参数或者上下文对象传递用户信息或者改用 ScopedValue 这种新机制来替代 ThreadLocal。5.3 日志规范别再用 System.out.println 打日志看到很多人做项目排查问题全靠 System.out.println这个习惯一定要改。原因很简单println 打印到控制台的内容不会被日志系统记录线上环境开了日志级别过滤之后你根本看不到任何输出。你写一百个 println等于埋了一百个雷。Spring Boot 3.x 标配的门面是 SLF4J Logback在需要打日志的类里加一行private static final Logger log LoggerFactory.getLogger(UserServiceImpl.class);也可以使用 Lombok 的 Slf4j 注解省掉这行声明。但在项目要做模块拆分的场景这个注解会让人搞不清当前日志来自哪个模块所以在复杂项目里我还是倾向手动声明 logger。日志级别从低到高是DEBUG、INFO、WARN、ERROR。开发环境可以把日志级别设成 DEBUG看 SQL 和参数。上线后建议设成 INFO避免过多日志刷屏。在 application.yml 里可以这样配置logging: level: com.example.backend: DEBUG org.springframework: INFO如果你的项目用 MyBatis-PlusSQL 日志可以不依赖 logback直接在 MyBatis-Plus 配置里开 log-impl 参数上面第 3.2 节提到过。这样它会打印出完整的 SQL 语句和绑定参数排查“数据对不上”的问题非常高效。5.4 前端常见问题路由参数、v-model、样式隔离前端有几个高频问题值得单独念叨念叨。路由参数有两种传法。一种是 query 字符串URL 长这样/users?page1size10用route.query取值一种是 path 参数URL 长这样/users/1用route.params取值。新手最容易踩坑的是用 params 的方式传参页面刷新后参数丢失。这是 Vue Router 的一种边界行为解决方案是把 params 参数也拼接进 URL比如通过 query 或动态路由路径或者有需要就用 Pinia 存一份。建议业务数据走 query临时状态走状态管理。自定义 v-model 在上面第 3.6 节已经讲了我这里补充一个易错点如果你写的是defineProps({ modelValue: Boolean })父组件里v-modelshowDialog是完全符合预期的如果你在子组件内部直接改 modelValue 的值会报 “Unexpected mutation of prop” 的警告。正确做法永远是触发update:modelValue事件来让父组件改值。关于单页面的样式隔离。Vue 单文件组件里的 style 标签如果不加 scoped 属性样式是全局生效的。多个组件里都定义了.title类的样式后加载的组件会覆盖先加载的组件。解决办法就是给每个组件的 style 加上 scoped或者用 CSS Modules。这个规则的代价很小但能避免大量样式互相污染的问题。5.5 常见问题速查表把我在实战中遇到最多的问题整理成了一张表建议收藏。每一个都是真实踩过坑的。问题现象解决方法CORS 跨域 403浏览器控制台 Access-Control-Allow-Origin 报错开发阶段用 Vite proxy生产用 Nginx 反代不要依赖后端放开所有跨域后端 404前端请求路径找不到接口检查 Controller 的 RequestMapping 路径对比前端 api 文件里的 URL 是否一致数据传过去是 null前端传的是 JSON后端接收不到检查 RequestBody 注解是否加上检查实体类字段名是否和 JSON 的 key 对应Mapper 数据查不到SQL 报 Invalid bound statement检查 MapperScan 和 mapper-locations 路径XML 文件是否在 target 目录下数据库中文乱码插入后数据库显示 ??URL 加参数?useUnicodetruecharacterEncodingutf8确认数据库表也是 utf8mb4 字符集依赖冲突Maven 导入报 jar 包冲突用 IDEA 的 Maven Helper 插件exclude 冲突依赖或检查版本是否匹配npm 安装慢npm install 卡死用国内镜像源npm config set registry https://registry.npmmirror.com刷新页面 404部署到 Nginx刷新 /users 页面显示 404Nginx 配置 try_files 规则try_files $uri $uri/ /index.html;这是前端路由的 history 模式导致的虚拟线程 ThreadLocal 报错并发场景下 ThreadLocal 取值错乱虚拟线程场景避免用 ThreadLocal改用上下文参数传递5.6 集成 MinIO 做文件存储全栈项目十有八九会遇到文件上传的需求。头像、附件、图片如果都存到数据库或本地磁盘时间一长项目就变成垃圾场。MinIO 是一个开源的对象存储服务S3 协议兼容本地部署非常轻量是个人项目的理想选择。后端引入 MinIO 依赖后配置一个客户端minio: endpoint: http://localhost:9000 access-key: your-access-key secret-key: your-secret-key bucket-name: your-bucket上传文件的 Controller 服务里核心逻辑就是把 MultipartFile 转成流调用 MinIO 客户端上传再把文件 URL 返回给前端。前端只需要用 Element Plus 的 Upload 组件把文件 POST 到接口然后把接口返回的 URL 塞进表单一起提交即可。这里有个细节上传文件的接口不建议走 JWT 拦截器的统一认证吗通常有两种做法一种是对/api/files/**路径做白名单放行另一种是保留认证。我的建议是保留认证但要对文件大小和类型做校验防止有人上传恶意文件。5.7 从开发到部署一条完整的流水线项目开发基本完结后面临的最后一个问题就是怎么上线。后端部署流程在项目根目录执行mvn clean package -DskipTests在 target 目录下生成一个可执行 jar然后把 jar 传到服务器执行java -jar backend.jar。如果想让日志不刷屏后台启动用nohup java -jar backend.jar app.log 21 。前端部署流程在 frontend 目录执行npm run build生成一个 dist 目录里面是纯静态文件。把这个 dist 目录丢到 Nginx 的 html 目录下或者用scp传到服务器上的/var/www/frontend。还有一个比较关键的地方Java 启动时如果内存不够可以调 JVM 参数java -Xms256m -Xmx512m -jar backend.jar但内存别调得太夸张云服务器 2G 内存默认堆 25% 就够用了。你压到 512M反而会增加 GC 的压力。部署阶段的易错点在配置上前后端开发地址是 localhost生产环境是真实域名。如果代码里硬编码了 localhost部署后接口必然不通。正确做法是用相对路径/api加 Nginx 反向代理上面 4.2 节写过环境差异留给配置文件去解决不要用环境变量切换也行但最省心的方案就是统一走/api。做全栈开发踩过那么多坑我最想说的其实是一句话前后端分离的关键不是“会写”接口和页面而是“对齐”——接口字段、错误码、登录态、部署方式都要对齐。前端和后端是两个世界但业务是一个整体。最后再分享一个小技巧在后端定义一个全局异常处理器用 RestControllerAdvice 捕获所有未处理异常统一返回 Result.error(e.getMessage())这样前端永远拿到的是结构一致的 JSON而不是一堆格式随机的报错信息。这个小改动能让联调阶段的沟通成本降低非常多。
企业数字化 ERP 产品动态
相关推荐
AgentScope实战指南:多智能体编排、分布式部署与Java集成 1. 为什么我会推荐AgentScope——从一个多智能体项目说起先说说我最近手头的一个活儿。客户要做一个智能客服升级项目,表面上是一个客服,背后实际需要调度意图识别、知识库检索、情绪分析、工单生成、二次回访等至少六七个独立的智能体协作。最开始我图省… · 2026/9/24 23:02:20
公寓租赁系统毕设全攻略:SpringBoot+Vue前后端分离实战解析 公寓租赁系统这个题目,在计算机毕设里属于那种看起来平平无奇、但实际做起来非常能体现综合能力的选题。它不像“基于深度学习的图像识别”那样听起来高大上,也不像“网上商城系统”那样已经被做烂了,而是恰好卡在“业务逻辑有一定复杂度”和… · 2026/9/24 23:02:20
Lenav:轻量级命令行目录导航工具,打造个性化终端工作流 1. 从“路径苦手”到“秒切目录”:为什么我会写Lenav先说说这个工具解决的真实痛点。如果你每天要辗转十几个项目目录,大概率经历过这样的场景:上午在/var/www/html/project-a里改接口,下午切到/home/user/work/client-b/frontend… · 2026/9/24 23:02:13
回溯算法全攻略:从决策树到剪枝去重,8道题彻底搞懂 我最近集中刷完了一批回溯相关的 LeetCode 题目,从"找出所有子集的异或总和再求和"到"全排列 II",再到"电话号码字母组合""括号生成""组合""目标和""组合总和""字母大小写全… · 2026/9/24 23:38:07
UVa 12450 SpaceRecon Tournament 题目描述
SpaceRecon\texttt{SpaceRecon}SpaceRecon 是一款 201120112011 年流行的实时策略游戏,支持三种种族。游戏内置了 Actionweb\texttt{Actionweb}Actionweb 平台用于举办 2M2^{M}2M 名玩家参加的锦标赛。锦标赛采用单败淘汰制,共 MMM 轮。前 RRR … · 2026/9/24 23:38:07
基于RankIQA的无参考图像质量评价与人脸识别应用 简介:基于Python的无参考图像质量评价实现,包含完整源码与配套数据,面向图像处理与计算机视觉方向的学生和开发者。压缩包内共274个文件,压缩后约12.37MB,其中包含163个Python脚本、Caffe模型配置(prototxt… · 2026/9/24 23:38:07
Buck-Boost电路建模:从状态空间平均到环路补偿实战 简介:面向电力电子、开关电源方向的学生与工程师,这是一份关于Buck-Boost电路建模与分析的完整技术文档。文档从稳态分析入手,梳理连续导通模式(CCM)与非连续导通模式(DCM)下的电压转换关系&… · 2026/9/24 23:38:00
Claude Code团队级配置:从API密钥治理到AI工程流水线 1. 这不是“装个插件就完事”的配置——Claude Code 是 AI 工程团队的协作操作系统你搜“Claude Code 配置指南”,刷出来的大多是“三步安装 VS Code 插件”“复制粘贴 API Key 就能用”。但如果你真带过 3 人以上的开发团队,或者正在从零搭建一个能稳定… · 2026/9/24 23:38:00
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44