Spring Boot + Vue3 前后端分离实践

发布时间:2026/5/26 1:07:50

Spring Boot + Vue3 前后端分离实践 前言在传统的Web开发中我们通常使用JSP、Thymeleaf等模板引擎将前端和后端代码混合在一起。这种开发方式在项目规模较小时还够用但随着项目复杂度的增加前后端代码耦合严重、开发效率低下、维护困难等问题逐渐暴露出来。前后端分离架构应运而生它通过将前端和后端完全解耦让前后端工程师可以独立开发、独立部署大大提高了开发效率和项目可护性。本文将以我开发的「超市货品管理系统」为例详细介绍如何使用 Spring Boot Vue3 实现前后端分离开发。项目背景超市货品管理系统是一个面向超市日常运营的管理系统主要功能包括用户认证、商品管理、图片管理、供货商管理等模块。采用前端分离架构后端提供RESTful API前端负责页面展示和交互。技术选型后端技术栈- Spring Boot 2.7.18快速构建应用简化配置- MyBatis Plus 3.5.5简化数据库操作提供CRUD通用接口- MySQL 8.0关系型数据库存储业务数据- JWT 0.11.5实现无状态认证- Knife4j 3.0.3自动生成API文档前端技术栈- Vue 3.4渐进式JavaScript框架- Vite 5.1新一代前端构建工具开发体验极佳- Vue Router 4.3官方路由管理器- Pinia 2.1Vue3官方推荐的状态管理库- Element Plus 2.6Vue3组件库- Axios 1.6HTTP客户端项目架构整体架构图┌─────────────┐│ Browser │└──────┬──────┘│▼┌─────────────┐│ Vue3 Front ││ (SPA) │└──────┬──────┘│ HTTP/Axios▼┌─────────────┐│ Nginx/Dev ││ Server │└──────┬──────┘│▼┌─────────────┐│ Spring Boot ││ Backend │└──────┬──────┘│▼┌─────────────┐│ MySQL DB │└─────────────┘后端项目结构backend/├── src/main/java/com/supermarket/│ ├── config/ # 配置类CORS、MyBatis│ ├── controller/ # 控制器层│ ├── dto/ # 数据传输对象│ ├── entity/ # 实体类│ ├── mapper/ # MyBatis Mapper│ ├── service/ # 服务层│ ├── util/ # 工具类│ └── vo/ # 视图对象└── src/main/resources/├── mapper/ # MyBatis XML└── application.yml # 配置文件前端项目结构frontend/├── src/│ ├── api/ # API接口封装│ ├── assets/ # 静态资源│ ├── components/ # 公共组件│ ├── router/ # 路由配置│ ├── stores/ # Pinia状态管理│ ├── utils/ # 工具函数│ ├── views/ # 页面组件│ ├── App.vue # 根组件│ └── main.js # 入口文件├── index.html # HTML模板├── vite.config.js # Vite配置└── package.json # 依赖管理后端开发实践1. 统一响应结果封装为了统一接口返回格式我们定义了一个通用的Result类package com.supermarket.util;import lombok.Data;Datapublic class ResultT {private Integer code;private String message;private T data;public static T ResultT success(T data) {ResultT result new Result();result.setCode(200);result.setMessage(操作成功);result.setData(data);return result;}public static T ResultT error(String message) {ResultT result new Result();result.setCode(500);result.setMessage(message);return result;}}这样所有接口都返回统一的JSON格式{code: 200,message: 操作成功,data: { }}2. RESTful API设计遵循RESTful规范设计API接口┌──────────┬─────────────────┬──────────┐│ HTTP方法 │ URL │ 说明 │├──────────┼─────────────────┼──────────┤│ GET │ /api/goods/page │ 分页查询 │├──────────┼─────────────────┼──────────┤│ GET │ /api/goods/{id} │ 查询详情 │├──────────┼─────────────────┼──────────┤│ POST │ /api/goods │ 创建资源 │├──────────┼─────────────────┼──────────┤│ PUT │ /api/goods/{id} │ 更新资源 │├──────────┼─────────────────┼──────────┤│ DELETE │ /api/goods/{id} │ 删除资源 │└──────────┴─────────────────┴──────────┘Controller示例Api(tags 商品管理)RestControllerRequestMapping(/goods)public class GoodsController {Resourceprivate GoodsService goodsService;ApiOperation(value 分页查询商品)GetMapping(/page)public ResultIPageGoodsVO getGoodsPage(RequestParam(defaultValue 1) Integer pageNum,RequestParam(defaultValue 10) Integer pageSize,RequestParam(required false) String keyword) {IPageGoodsVO page goodsService.getGoodsPage(pageNum, pageSize, keyword);return Result.success(page);}ApiOperation(value 创建商品)PostMappingpublic ResultGoodsVO createGoods(RequestBody Validated GoodsDTO goodsDTO) {GoodsVO goodsVO goodsService.createGoods(goodsDTO);return Result.success(goodsVO);}}3. JWT认证实现JWTJSON Web Token是一种无状态的认证机制适合前后端分离架构。JWT工具类Componentpublic class JwtUtil {Value(${jwt.secret})private String secret;Value(${jwt.expiration})private Long expiration;public String generateToken(Long userId, String username) {MapString, Object claims new HashMap();claims.put(userId, userId);claims.put(username, username);Date now new Date();Date expiryDate new Date(now.getTime() expiration);return Jwts.builder().setClaims(claims).setIssuedAt(now).setExpiration(expiryDate).signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS512).compact();}public boolean validateToken(String token) {try {Claims claims Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(secret.getBytes())).build().parseClaimsJws(token).getBody();return !claims.getExpiration().before(new Date());} catch (Exception e) {return false;}}}登录接口PostMapping(/login)public ResultLoginVO login(RequestBody LoginDTO loginDTO) {User user userService.getByUsername(loginDTO.getUsername());if (user null || !user.getPassword().equals(loginDTO.getPassword())) {throw new BusinessException(账号或密码错误);}String token jwtUtil.generateToken(user.getId(), user.getUsername());String refreshToken jwtUtil.generateRefreshToken(user.getId(), user.getUsername());LoginVO loginVO new LoginVO();loginVO.setToken(token);loginVO.setRefreshToken(refreshToken);loginVO.setUserInfo(user);return Result.success(loginVO);}4. CORS跨域配置前后端分离开发时前端localhost:3000和后端localhost:8080端口号不同会产生跨域问题。Configurationclass WebConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping(/**).allowedOriginPatterns(*).allowedMethods(GET, POST, PUT, DELETE, OPTIONS).allowedHeaders(*).allowCredentials(true).maxAge(3600);}}前端开发实践1. Axios请求封装统一封装axios实例添加请求和响应拦截器import axios from axiosimport { ElMessage } from element-plusimport { useUserStore } from /stores/userimport router from /routerconst request axios.create({baseURL: /api,timeout: 30000})// 请求拦截器自动添加Tokenrequest.interceptors.request.use(config {const userStore useUserStore()if (userStore.token) {config.headers.Authorization userStore.token}return config},error Promise.reject(error))// 响应拦截器统一处理错误request.interceptors.response.use(response {const res response.dataif (res.code 200) {return res.data} else {ElMessage.error(res.message || 请求失败)return Promise.reject(new Error(res.message))}},error {if (error.response?.status 401) {ElMessage.error(登录已过期请重新登录)const userStore useUserStore()userStore.logout()router.push(/login)}return Promise.reject(error)})export default request2. API模块化管理将所有API接口按模块组织// src/api/goods.jsimport request from /utils/requestexport const getGoodsPage (params) {return request({url: /goods/page,method: get,params})}export const createGoods (data) {return request({url: /goods,method: post,data})}export const deleteGoods (id) {return request({url: /goods/${id},method: delete})}3. 状态管理Pinia使用Pinia管理全局状态如用户信息// src/stores/user.jsimport { defineStore } from piniaimport { ref } from vueexport const useUserStore defineStore(user, () {const token ref(localStorage.getItem(token) || )const userInfo ref(null)const setToken (newToken) {token.value newTokenlocalStorage.setItem(token, newToken)}const setUserInfo (info) {userInfo.value info}const logout () {token.value userInfo.value nulllocalStorage.removeItem(token)}return { token, userInfo, setToken, setUserInfo, logout }})4. 路由守卫实现路由权限控制// src/router/index.jsimport { createRouter, createWebHistory } from vue-routerimport { useUserStore } from /stores/userconst routes [{ path: /login, component: () import(/views/Login.vue) },{ path: /, component: () import(/views/Layout.vue),children: [{ path: goods, component: () import(/views/Goods.vue) }]}]const router createRouter({history: createWebHistory(),routes})router.beforeEach((to, from, next) {const userStore useUserStore()if (to.path ! /login !userStore.token) {next(/login)} else {next()}})export default router前后端联调1. 开发环境代理配置在Vite配置开发代理解决跨域问题export default defineConfig({server: {port: 3000,proxy: {/api: {target: http://localhost:8080,changeOrigin: true},/upload: {target: http://localhost:8080,changeOrigin: true}}}})这样前端请求 /api/xxx 会被代理到 http://localhost:8080/api/xxx。2. API文档集成使用Knife4j自动生成API文档启动后端服务后访问 http://localhost:8080/api/doc.html 即可查看。文档包含- 接口列表和说明- 请求参数和响应格式- 在线调试功能3. 接口联调流程1. 后端先开发接口用Knife4j测试通过2. 前端根据API文档编写接口封装3. 前端调用接口查看响应数据4. 调整数据格式和展示逻辑5. 异常情况处理网络错误、权限错误等遇到的问题和解决方案1. 跨域问题问题前端请求后端接口报错 No Access-Control-Allow-Origin header解决方案- 开发环境配置Vite代理- 生产环境配置Spring Boot CORS2. Token过期处理问题用户长时间未操作Token过期导致请求失败解决方案- 响应拦截器统一处理401错误- 自动跳转到登录页面- 清除本地Token和用户信息3. 文件上传问题图片上传失败路径配置错误解决方案- 配置静态资源映射registry.addResourceHandler(/upload/**).addResourceLocations(file:F:/upload/picture/);- 确保上传目录存在且有写入权限- 前端使用FormData上传4. 分页查询问题MyBatis Plus分页查询不生效解决方案- 添加MyBatis Plus分页配置Configurationpublic class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}}总结与展望通过这次前后端分离实践我深刻体会到1. 开发效率提升前后端并行开发互不干扰2. 代码可维护性职责分离结构清晰3. 技术栈独立前端和后端可以使用不同的技术4. 便于部署扩展可以独立部署和扩展未来优化方向- 引入Redis缓存提升系统性能- 使用Docker容器化部署- 添加日志监控系统- 实现接口版本管理- 前端添加单元测试前后端分离已经成为现代Web开发的主流架构掌握这种架构对于每个开发者来说都是必备技能。希望这篇文章能够帮助到正在学习前后端分离的同学。

相关新闻