SpringBoot+Vue商城系统毕设实战:从数据库设计到前后端联调
2026/9/5 18:17:15 网站建设 项目流程

毕业设计选题“撞车”不可怕,可怕的是你拿到一个商城系统,却只能告诉老师“我用了 SpringBoot 和 Vue”,至于登录令牌怎么生效、订单金额从哪里来、库存扣减和数据库表结构是什么关系,一问三不知。实际上,SpringBoot + Vue 的线上商城系统恰好是一个被低估的优质毕设载体,它把后端接口、数据库建模、前端路由、登录鉴权、订单状态流转、前后端联调打包成了一条完整的链路。这篇文章不打算给你堆功能清单,而是把这类毕设项目从选题价值、数据库设计、后端实现、前端页面、联调部署到答辩避坑,按一条能真正跑通的路径拆开讲。

如果你正卡在“项目跑不起来”或者“跑起来了但讲不明白”,这篇文章能帮你建立完整的问题排查思路。如果你的项目包里带了源码、数据库 SQL、文档报告和代码讲解,也可以按文中的顺序逐个环节去对照检查,而不是拿到项目就直接npm run dev,遇到报错就不知道该看哪里。

1. 这类毕设项目的真正价值

很多同学担心商城系统“太烂大街”,老师一眼就能看出来。这里需要先澄清一个判断:烂大街的不是商城系统,而是只有增删改查、没有业务逻辑的商城系统。如果项目只有用户表、商品表,前端页面套几个接口,连登录鉴权都没有,那显然撑不起一场毕业答辩。但一个结构完整的前后端分离商城项目,至少要处理用户认证、商品管理、购物车、订单生成、库存扣减、订单状态变化、管理端权限划分这些问题,每个问题在真实企业中都有对应解法,拿出来都有得讲。

前后端分离本身是这套项目最值得强调的技术点。早年的 Java Web 毕设普遍使用 JSP,服务端直接渲染 HTML,前端页面和后端逻辑耦合在一个工程里。而前后端分离架构中,后端只需要提供 RESTful API 和 JSON 数据,前端通过 HTTP 请求获取数据并渲染页面。这样带来三个实际好处:第一,前后端可以独立开发,只要接口约定好,两边并行推进,后端同学不需要关心页面长什么样,前端同学也不需要在 IDEA 里启动一个庞大工程;第二,后期部署更灵活,前端是静态资源,可以交给独立 Web 服务器托管,后端只需要运行一个 SpringBoot 打包出来的 jar;第三,更容易做团队分工,毕设如果是两人以上合作,天然可以一人管前端、一人管后端。

这里要给你一个建议:不要把“前后端分离”包装成“前后端完全没联系”,而是要说成“通过接口契约完成前后端解耦”。答辩时老师问“为什么前后端要分离”,抓住接口、独立部署、并行开发这三个关键词展开,就比单纯背概念更有说服力。

当然,也不要为了追求亮点把项目做成“大而全”。一个电商级的真实系统会有秒杀、限流、分布式事务、消息队列、推荐系统,这些不是普通毕设必须解决的问题。你的目标是做一个功能边界清楚、核心链路完整、代码结构清晰的教学级商城系统。功能宁缺毋滥,但用户从注册、登录到浏览商品、把商品加入购物车、提交订单、订单状态变化这条主链路必须闭环。

2. 系统功能设计与技术选型

2.1 功能模块边界

线上商城系统最常见的分区方式是把系统分成用户前台和管理员后台两个部分。用户前台面向普通消费者,提供用户注册登录、商品分类浏览、商品搜索、商品详情、购物车、订单提交和订单列表查询等功能;管理后台面向运营人员,通常包含商品分类管理、商品上下架、库存管理、订单处理、用户状态管理等。在数据库层面,可以通过用户表中的一个角色字段(如USERADMIN)来区分身份,前端根据角色控制按钮和页面的显示,后端接口再通过拦截器判断当前登录用户是否有权限执行操作。

购物车是很多同学容易做复杂的地方。有人会把购物车设计成一张独立的业务表,关联用户、商品,记录用户勾选了哪些商品、数量是多少。这样的设计没有错,但要注意,购物车本质上是用户在一次会话中选中的商品集合,如果做成数据库表,需要考虑“用户删除商品后购物车关联数据怎么处理”“一次性提交多个商品,购物车记录要不要批量删除”这些细节。如果只想降低代码复杂度,也可以用前端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-router@4也是主流方向,代码整体会更现代化,但查资料时要注意区分 Vue 2 和 Vue 3 的语法差异。核心原则是不要混搭,例如 Vue 2 项目里不要引入 Element Plus,Vue 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

3.2 商品表与订单表

商品表保存商品标题、主图、价格、库存、分类等核心信息。价格字段要注意使用DECIMAL(10,2),不要使用FLOATDOUBLE,避免浮点数运算产生精度问题。商品状态字段用于控制是否在前台展示。商品分类表可以做成支持无限极分类的结构,但普通毕设一般使用两级分类即可,通过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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

创建项目时,可以把这些 SQL 放在src/main/resources/db/mall.sql或者独立的sql目录下,用 Navicat 或命令行导入即可。

4. SpringBoot 后端环境搭建与工程结构

4.1 环境准备清单

开始写后端代码之前,建议先确认本机环境具备以下内容:

  • JDK 8 或 11
  • IDEA 2019 以上版本,社区版或专业版都可以
  • Maven 3.6 以上版本,也可以在 IDEA 中直接使用内置 Maven
  • MySQL 5.7 或 8.0
  • Navicat、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 后端分层结构

后端工程建议按职责分为controllerservicemapper(或dao)、entity(或pojo)、configcommondtovoutils等包。这种分层不是形式主义,而是为了让每一层职责单一,答辩时也更容易解释系统架构。

在入口启动类上,如果使用 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.java

Controller 层负责接收请求和参数校验,不写业务逻辑;Service 层处理具体业务,比如登录判断、库存扣减、订单生成;Mapper 层负责持久层数据操作。初学者最容易犯的错误是把代码全都堆在 Controller 里,看起来功能能实现,实际上项目越大越难维护。一旦老师问“如果要把下单逻辑复用,怎么办”,没有 Service 层就很难解释。

4.3 核心依赖与配置

后端pom.xml中比较关键的依赖包括 Web、MyBatis-Plus、MySQL 驱动、Lombok 和 JWT 相关依赖。为了控制篇幅,这里给出一个重点依赖片段,具体版本请结合你项目所使用的 SpringBoot 版本进行调整。

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> </dependency> </dependencies>

数据库连接配置放在src/main/resources/application.yml中。配置数据库时需要注意serverTimezoneuseSSLcharacterEncoding,避免出现时区错误或中文乱码。

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 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 LambdaQueryWrapper<User>() .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 vite@latest mall-web -- --template vue cd mall-web npm install npm install axios element-plus vue-router@4 npm run dev

这里需要说明一点:Vue 2 和 Vue 3 的路由、组件语法不完全一致。如果你拿到的毕设源码是 Vue 2,那么依赖应该使用vue-router@3和 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-router@4) */ 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询