SpringBoot+Vue商城系统毕设实战:从数据库设计到前后端联调

发布时间:2026/9/5 18:17:16
SpringBoot+Vue商城系统毕设实战:从数据库设计到前后端联调 毕业设计选题“撞车”不可怕可怕的是你拿到一个商城系统却只能告诉老师“我用了 SpringBoot 和 Vue”至于登录令牌怎么生效、订单金额从哪里来、库存扣减和数据库表结构是什么关系一问三不知。实际上SpringBoot Vue 的线上商城系统恰好是一个被低估的优质毕设载体它把后端接口、数据库建模、前端路由、登录鉴权、订单状态流转、前后端联调打包成了一条完整的链路。这篇文章不打算给你堆功能清单而是把这类毕设项目从选题价值、数据库设计、后端实现、前端页面、联调部署到答辩避坑按一条能真正跑通的路径拆开讲。如果你正卡在“项目跑不起来”或者“跑起来了但讲不明白”这篇文章能帮你建立完整的问题排查思路。如果你的项目包里带了源码、数据库 SQL、文档报告和代码讲解也可以按文中的顺序逐个环节去对照检查而不是拿到项目就直接npm run dev遇到报错就不知道该看哪里。1. 这类毕设项目的真正价值很多同学担心商城系统“太烂大街”老师一眼就能看出来。这里需要先澄清一个判断烂大街的不是商城系统而是只有增删改查、没有业务逻辑的商城系统。如果项目只有用户表、商品表前端页面套几个接口连登录鉴权都没有那显然撑不起一场毕业答辩。但一个结构完整的前后端分离商城项目至少要处理用户认证、商品管理、购物车、订单生成、库存扣减、订单状态变化、管理端权限划分这些问题每个问题在真实企业中都有对应解法拿出来都有得讲。前后端分离本身是这套项目最值得强调的技术点。早年的 Java Web 毕设普遍使用 JSP服务端直接渲染 HTML前端页面和后端逻辑耦合在一个工程里。而前后端分离架构中后端只需要提供 RESTful API 和 JSON 数据前端通过 HTTP 请求获取数据并渲染页面。这样带来三个实际好处第一前后端可以独立开发只要接口约定好两边并行推进后端同学不需要关心页面长什么样前端同学也不需要在 IDEA 里启动一个庞大工程第二后期部署更灵活前端是静态资源可以交给独立 Web 服务器托管后端只需要运行一个 SpringBoot 打包出来的 jar第三更容易做团队分工毕设如果是两人以上合作天然可以一人管前端、一人管后端。这里要给你一个建议不要把“前后端分离”包装成“前后端完全没联系”而是要说成“通过接口契约完成前后端解耦”。答辩时老师问“为什么前后端要分离”抓住接口、独立部署、并行开发这三个关键词展开就比单纯背概念更有说服力。当然也不要为了追求亮点把项目做成“大而全”。一个电商级的真实系统会有秒杀、限流、分布式事务、消息队列、推荐系统这些不是普通毕设必须解决的问题。你的目标是做一个功能边界清楚、核心链路完整、代码结构清晰的教学级商城系统。功能宁缺毋滥但用户从注册、登录到浏览商品、把商品加入购物车、提交订单、订单状态变化这条主链路必须闭环。2. 系统功能设计与技术选型2.1 功能模块边界线上商城系统最常见的分区方式是把系统分成用户前台和管理员后台两个部分。用户前台面向普通消费者提供用户注册登录、商品分类浏览、商品搜索、商品详情、购物车、订单提交和订单列表查询等功能管理后台面向运营人员通常包含商品分类管理、商品上下架、库存管理、订单处理、用户状态管理等。在数据库层面可以通过用户表中的一个角色字段如USER和ADMIN来区分身份前端根据角色控制按钮和页面的显示后端接口再通过拦截器判断当前登录用户是否有权限执行操作。购物车是很多同学容易做复杂的地方。有人会把购物车设计成一张独立的业务表关联用户、商品记录用户勾选了哪些商品、数量是多少。这样的设计没有错但要注意购物车本质上是用户在一次会话中选中的商品集合如果做成数据库表需要考虑“用户删除商品后购物车关联数据怎么处理”“一次性提交多个商品购物车记录要不要批量删除”这些细节。如果只想降低代码复杂度也可以用前端localStorage保存购物车但这样就失去了“多端同步”的能力答辩时容易被追问。更稳妥的方案还是把购物车数据存到后端数据库至少能讲清楚“用户在任意设备登录都能看到同一份购物车”。2.2 技术栈选型与版本兼容技术栈方面后端主体是 SpringBoot搭配 MySQL 数据库持久层可以选择 MyBatis-Plus也可以使用 Spring Data JPA。考虑到很多毕设项目都需要手写多表联查和分页MyBatis-Plus 因为自带BaseMapper和分页插件使用起来更简洁。前端 Vue 项目通常会搭配 Element UI 或 Element Plus 这类组件库让页面看起来更接近真实后台管理系统同时能减少样式开发成本。这里要注意版本兼容问题。如果你是初学者建议优先选择“稳定组合”而不是“最新组合”。比如 JDK 8 搭配 SpringBoot 2.7.x、Vue 2 搭配 Element UI是一套经过大量项目验证的组合如果你选择 JDK 17 搭配 SpringBoot 3.x则需要注意部分第三方组件的包名和配置发生了变化。Vue 3 搭配 Element Plus、vue-router4也是主流方向代码整体会更现代化但查资料时要注意区分 Vue 2 和 Vue 3 的语法差异。核心原则是不要混搭例如 Vue 2 项目里不要引入 Element PlusVue 3 项目里也不要使用只兼容 Vue 2 的组件库。3. 数据库设计核心表结构与关系数据库设计是答辩时最容易暴露问题的地方也是整个项目最值得花时间打磨的环节。商城系统建议至少包含六张核心表用户表、商品分类表、商品表、购物车表、订单表、订单明细表。下面分别说明每张表的定位和关键字段并给出一个可以在 MySQL 中执行建表 SQL 的核心表示例。3.1 用户表用户表用于保存登录账号和基本信息。密码字段不能存明文建议存的是 BCrypt 加密后的字符串。用户角色字段用于区分普通用户和管理员字段类型可以是VARCHAR默认值为USER。CREATE TABLE tb_user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, role varchar(20) NOT NULL DEFAULT USER COMMENT 角色USER/ADMIN, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1启用0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;3.2 商品表与订单表商品表保存商品标题、主图、价格、库存、分类等核心信息。价格字段要注意使用DECIMAL(10,2)不要使用FLOAT或DOUBLE避免浮点数运算产生精度问题。商品状态字段用于控制是否在前台展示。商品分类表可以做成支持无限极分类的结构但普通毕设一般使用两级分类即可通过parent_id表示父子关系。订单表和订单明细表是必须拆开的。一个订单可以包含多种商品一个商品在同一个订单里会有购买数量和成交价格。如果只在订单表里存一个“商品名称”字段就没法正确表达“一个订单多个商品”的关联关系。更合理的做法是订单表只保存订单编号、用户、总金额、状态、收货信息和下单时间订单明细表逐行保存每个商品的下单快照。订单明细中的商品名称、商品主图、商品价格为什么要单独保存一份原因是商品表的价格和名称会变化。用户下单时商品价格是 99 元过几天商品涨到 129 元订单表不能跟着变否则订单金额就失真了。因此订单明细里保存的是一份历史快照而不是实时关联商品表查询。这个设计思想在答辩中属于亮点可以主动讲给老师听。3.3 建表 SQL 示例下面给出一个简化但可运行的商品表和订单表建表示例演示字段命名和关系表达方式。CREATE TABLE tb_category ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(50) NOT NULL COMMENT 分类名称, parent_id bigint NOT NULL DEFAULT 0 COMMENT 父分类ID, sort int NOT NULL DEFAULT 0 COMMENT 排序, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; CREATE TABLE tb_product ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, category_id bigint NOT NULL COMMENT 分类ID, name varchar(100) NOT NULL COMMENT 商品标题, main_image varchar(255) DEFAULT NULL COMMENT 商品主图, price decimal(10,2) NOT NULL COMMENT 价格, stock int NOT NULL DEFAULT 0 COMMENT 库存, sales int NOT NULL DEFAULT 0 COMMENT 销量, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1上架0下架, description text COMMENT 商品描述, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; CREATE TABLE tb_order ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待支付1已支付2已发货3已完成4已取消, receiver_name varchar(50) NOT NULL COMMENT 收货人, receiver_phone varchar(20) NOT NULL COMMENT 收货电话, receiver_address varchar(200) NOT NULL COMMENT 收货地址, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE tb_order_item ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, order_id bigint NOT NULL COMMENT 订单ID, product_id bigint NOT NULL COMMENT 商品ID, product_name varchar(100) NOT NULL COMMENT 下单时商品名, product_image varchar(255) DEFAULT NULL COMMENT 下单时商品图, product_price decimal(10,2) NOT NULL COMMENT 下单时单价, quantity int NOT NULL COMMENT 购买数量, total_price decimal(10,2) NOT NULL COMMENT 小计金额, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;创建项目时可以把这些 SQL 放在src/main/resources/db/mall.sql或者独立的sql目录下用 Navicat 或命令行导入即可。4. SpringBoot 后端环境搭建与工程结构4.1 环境准备清单开始写后端代码之前建议先确认本机环境具备以下内容JDK 8 或 11IDEA 2019 以上版本社区版或专业版都可以Maven 3.6 以上版本也可以在 IDEA 中直接使用内置 MavenMySQL 5.7 或 8.0Navicat、DataGrip 或 MySQL Workbench 等数据库客户端前端环境 Node.js 14 以上npm 随 Node.js 一起安装这里先说一个容易踩的坑IDEA 中新建 SpringBoot 工程时Spring Initializr 默认会使用当前较新的 SpringBoot 版本。如果你本机是 JDK 8创建项目后启动报错多半是因为选到了需要 JDK 17 的 SpringBoot 3.x。建议在创建时把 SpringBoot 版本切换为 2.7.x后续使用也会更稳定。项目本身的具体版本以你拿到的毕设源码为准但核心逻辑是相通的。4.2 后端分层结构后端工程建议按职责分为controller、service、mapper或dao、entity或pojo、config、common、dto、vo、utils等包。这种分层不是形式主义而是为了让每一层职责单一答辩时也更容易解释系统架构。在入口启动类上如果使用 MyBatis记得添加MapperScan注解扫描 Mapper 接口如果使用 Spring Data JPA则在启动类或配置类中配置实体扫描。一个常见的目录结构大致如下src/main/java/com/example/mall ├── MallApplication.java ├── config │ ├── CorsConfig.java │ └── MybatisPlusConfig.java ├── controller │ ├── UserController.java │ ├── ProductController.java │ └── OrderController.java ├── service │ ├── UserService.java │ ├── ProductService.java │ └── OrderService.java ├── mapper │ ├── UserMapper.java │ ├── ProductMapper.java │ └── OrderMapper.java ├── entity │ ├── User.java │ ├── Product.java │ └── Order.java ├── dto │ └── LoginDTO.java ├── common │ ├── ApiResult.java │ └── BusinessException.java └── utils └── JwtUtil.javaController 层负责接收请求和参数校验不写业务逻辑Service 层处理具体业务比如登录判断、库存扣减、订单生成Mapper 层负责持久层数据操作。初学者最容易犯的错误是把代码全都堆在 Controller 里看起来功能能实现实际上项目越大越难维护。一旦老师问“如果要把下单逻辑复用怎么办”没有 Service 层就很难解释。4.3 核心依赖与配置后端pom.xml中比较关键的依赖包括 Web、MyBatis-Plus、MySQL 驱动、Lombok 和 JWT 相关依赖。为了控制篇幅这里给出一个重点依赖片段具体版本请结合你项目所使用的 SpringBoot 版本进行调整。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version /dependency /dependencies数据库连接配置放在src/main/resources/application.yml中。配置数据库时需要注意serverTimezone、useSSL和characterEncoding避免出现时区错误或中文乱码。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl如果你的 MySQL 是 5.7驱动类可以写成com.mysql.cj.jdbc.Driver也可以正常运行。如果数据库密码不是 root需要同步修改配置。启动项目前最简单也最有效的检查方式就是先在数据库客户端中测试这个 URL 和账号密码能不能连通。5. 后端核心代码实现后端功能不要求一次写完所有代码建议先实现用户登录与鉴权再实现商品查询最后实现下单业务每一步都能独立运行和验证。这里给出三个可参考的核心代码片段。5.1 用户登录与 JWT 生成登录接口不是简单查一次数据库而是需要完成“用户名密码校验 生成令牌 返回前端”三个步骤。下面这段登录逻辑使用 MyBatis-Plus 的LambdaQueryWrapper查询用户再调用 JwtUtil 生成 token。/** * 文件路径src/main/java/com/example/mall/service/impl/UserServiceImpl.java * 省略了类的注解与依赖注入代码只展示核心登录方法 */ public String login(LoginDTO dto) { User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, dto.getUsername()) ); if (user null) { throw new BusinessException(用户名或密码错误); } if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } if (user.getStatus() 0) { throw new BusinessException(账号已被禁用); } return JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); }这里要解释两个常被忽略的细节。第一登录失败时不应该提示“用户名不存在”或“密码错误”而应该统一提示“用户名或密码错误”防止别人通过接口批量探测有效账号。第二密码匹配不能把数据库中的密码取出来直接用equals比较必须使用 BCryptPasswordEncoder 这类加密算法代码中的passwordEncoder可以通过new BCryptPasswordEncoder()创建也可以从 Spring 容器中获取。JWT 工具类负责生成和解析 token。token 中放入用户 ID、用户名、角色这几个关键信息并设置过期时间。签名密钥在示例中写成了字符串常量但在实际生产环境必须通过环境变量或配置中心注入不能直接暴露在源码里。/** * 文件路径src/main/java/com/example/mall/utils/JwtUtil.java * 依赖 jjwt 0.11.5 */ public class JwtUtil { private static final String SECRET your-secret-key-for-mall-project; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; public static String createToken(Long userId, String username, String role) { Key key Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); return Jwts.builder() .claim(userId, userId) .claim(username, username) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(key, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { Key key Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } }5.2 登录拦截器配置有了 token 之后还需要通过拦截器统一校验需要登录的接口。拦截器的主要作用是读取请求头中的Authorization字段解析 token并把用户信息放进请求上下文。下面的配置代码演示了登录拦截器如何注册到项目中同时放行登录、注册、商品浏览等公开接口。/** * 文件路径src/main/java/com/example/mall/config/WebConfig.java */ Configuration public class WebConfig implements WebMvcConfigurer { Resource private JwtInterceptor jwtInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/api/**) .excludePathPatterns( /api/user/login, /api/user/register, /api/product/** ); } }这里真正容易踩坑的地方是放行路径太宽。如果把/api/**全放行拦截器就形同虚设如果把路径写得太严格前端访问某个接口时会一直提示未登录。排查时可以打开浏览器 F12看请求返回的是 401 还是 403再对照拦截器配置逐条检查。5.3 商品列表与下单流程商品列表是前台页面的核心数据来源。列表接口需要支持分页同时可以按商品名称模糊搜索、按分类筛选。MyBatis-Plus 分页需要配置分页插件不配置时IPage查询会失效这是一个非常常见的问题。/** * 文件路径src/main/java/com/example/mall/config/MybatisPlusConfig.java */ Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }下单流程相对复杂。用户从前端提交订单时后端需要校验商品是否存在、库存是否充足计算总价生成订单号保存订单主表和订单明细同时扣减库存。这个过程必须包含事务控制如果保存订单明细后库存扣减失败订单就应该整体回滚不能出现“有订单但没扣库存”或者“库存扣了但订单没生成”的情况。/** * 文件路径src/main/java/com/example/mall/service/impl/OrderServiceImpl.java */ Transactional(rollbackFor Exception.class) public Order createOrder(OrderCreateDTO dto, Long userId) { Product product productMapper.selectById(dto.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } if (product.getStock() dto.getQuantity()) { throw new BusinessException(库存不足); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(dto.getQuantity()))); order.setStatus(0); order.setReceiverName(dto.getReceiverName()); order.setReceiverPhone(dto.getReceiverPhone()); order.setReceiverAddress(dto.getReceiverAddress()); orderMapper.insert(order); OrderItem item new OrderItem(); item.setOrderId(order.getId()); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setProductImage(product.getMainImage()); item.setProductPrice(product.getPrice()); item.setQuantity(dto.getQuantity()); item.setTotalPrice(product.getPrice().multiply(new BigDecimal(dto.getQuantity()))); orderItemMapper.insert(item); productMapper.deductStock(product.getId(), dto.getQuantity()); return order; }这个示例中用到了deductStock自定义 SQL原因是简单的“先查库存再更新库存”在高并发场景下可能超卖。更稳妥的做法是使用一条带条件的更新语句UPDATE tb_product SET stock stock - #{count} WHERE id #{id} AND stock #{count}只有当更新影响行数为 1 时才认为扣减成功。这个细节在答辩中属于进阶加分点可以结合并发场景展开。需要注意的是事务方法必须由 Spring 代理对象调用才能生效不能在一个普通方法内部直接调用同类中的另一个Transactional方法否则事务会失效。6. Vue 前端项目开发与页面实现后端接口准备到“能返回 JSON”之后就可以开发前端页面。前端的核心任务包括项目初始化、Axios 请求封装、路由配置、页面组件编写。6.1 使用 Vite 创建 Vue 项目如果你从零开始搭建前端推荐使用 Vite 命令创建 Vue 3 项目npm create vitelatest mall-web -- --template vue cd mall-web npm install npm install axios element-plus vue-router4 npm run dev这里需要说明一点Vue 2 和 Vue 3 的路由、组件语法不完全一致。如果你拿到的毕设源码是 Vue 2那么依赖应该使用vue-router3和 Element UI而不是 Element Plus。不同版本混用是前端最常见的启动报错原因。一个小技巧是查看package.json中的vue字段版本号再对照路由库的主版本号确保兼容。6.2 Axios 请求封装前端所有请求建议统一走一个封装好的 request 模块而不是在页面里直接axios.get。原因是登录后每个请求都要带 token接口返回 401 时还要统一处理跳转登录页如果每个请求单独写一套代码会非常冗余。/** * 文件路径src/utils/request.js */ import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization 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 || Error)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.response?.data?.message || 网络异常) return Promise.reject(error) } ) export default request使用该封装的请求方法时页面内可以直接调用例如import request from /utils/request export function getProductList(params) { return request({ url: /product/list, method: get, params }) }注意实际项目中baseURL和后端 Controller 的RequestMapping前缀要对应。比如后端接口路径为/api/product/list前端请求的url写/product/list请求最终拼接为/api/product/list这样就不会重复。6.3 路由守卫商城系统需要区分登录状态。普通用户未登录时访问购物车、个人中心、结算页面应该被重定向到登录页。Vue Router 的全局前置守卫可以实现这个逻辑。/** * 文件路径src/router/index.js示例为 Vue 3 vue-router4 */ import { createRouter, createWebHistory } from vue-router import Login from /views/Login.vue import ProductList from /views/ProductList.vue import Cart from /views/Cart.vue import OrderConfirm from /views/OrderConfirm.vue const routes [ { path: /login, name: Login, component: Login }, { path: /products, name: ProductList, component: ProductList }, { path: /cart, name: Cart, component: Cart, meta: { requiresAuth: true } }, { path: /order/confirm, name: OrderConfirm, component: OrderConfirm, meta: { requiresAuth: true } } ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } }) export default router路由守卫只解决“页面入口”层面的拦截。真正安全性必须由后端接口保证前端隐藏一个按钮不代表后端接口安全因为用户仍然可以直接调用接口。这个观念要早点建立也值得写进文档报告的安全设计部分。7. 前后端联调的关键细节前后端独立开发时各自调用本地接口没有任何问题一旦把两个工程同时启动就会出现“接口调不通”的情况。最常见的原因是跨域问题。浏览器出于安全策略默认禁止不同端口的页面发送跨域 AJAX 请求而后端端口通常为 8080前端 Vite 默认为 5173这样就构成了跨域请求。跨域有两种常见解决办法。第一种是在后端配置 CORS允许所有域名访问接口。这种方式适合开发环境但对于生产环境应只允许前端域名访问不建议直接把allowedOriginPatterns全部放开。第二种更符合前后端分离工程实践就是在前端开发服务器中配置代理由前端服务器把/api开头的请求转发到后端接口。Vite 的开发服务器代理配置如下/** * 文件路径vite.config.js */ import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:808