☰
Spring Boot MySQL游戏商城:模块拆解、事务与部署避坑
2026/10/30 16:04:05 网站建设 项目流程

简介:这是一份基于Spring Boot与MySQL的游戏周边交易商城系统完整项目源码,面向Java学习者及有毕业设计、课程设计需求的学生。系统覆盖用户注册登录、商品发布管理、购物车操作、订单生成与支付流转、管理员权限控制等核心电商模块,功能链路完整,可直接运行演示。

压缩包共632个文件,大小仅16.37MB,包含89个Java后端源码、116个Vue前端页面、48个HTML、31个JS脚本、43个JAR依赖、30个XML配置及1个SQL数据库脚本等,前后端代码与依赖划分清晰。内置.bat环境脚本覆盖Maven配置、镜像源设置、一键安装与无源运行等场景,有效降低部署门槛。

已有173人学习下载。项目涉及JWT身份验证、第三方支付集成、数据库表设计等实际开发要点。适合以此为主线逐模块研读,理解Web应用完整开发流程,并在此基础上扩展出属于自己的商城系统。

1. 游戏周边交易商城:基于Spring Boot MySQL的完整Java Web项目,应该怎么往下改

如果你搜到这份基于Spring Boot MySQL实现的游戏周边交易商城系统资源,大概率是准备交毕业设计或者课程设计。这个项目本身不复杂,它解决的是“从零搭一个能演示的Java商城”这件事:用户登录注册、商品分类浏览、购物车、订单流程记录、后台商品管理这些模块它都有,MySQL负责把数据落库,Spring Boot负责把前后端完整串起来跑通。它适合三类人:没系统做过Web项目的在校生、需要快速出一个能答辩的演示系统的毕业生、以及想在本地跑一套商城代码再二次开发的工程师。拿到手别急着点启动,先按后面几步把模块边界、表结构和订单主链路过一遍。

2. 系统模块与技术选型:先看清单体结构再动手

2.1 为什么毕业设计普遍用单体而不用微服务

游戏周边商城这类项目,数据是核心,交易流程是主线。把它拆成微服务在毕设阶段没有收益,反而会引入服务间调用、消息队列、分布式事务这些毕设根本用不上的复杂度。单体应用把所有模块放进同一个工程确实有短板:并发能力上限低,模块强耦合。但在课程设计和毕业设计这个阶段,单体换来的是最直接的收益——部署一个jar包就能跑,调试时一个进程就能跟踪完整链路,订单写入、库存扣减这类操作可以直接用本地事务包住,不需要考虑跨服务的数据一致性问题。

很多答辩老师会问一个问题:为什么不做微服务?你不需要回避,直接把取舍讲清楚就行。单体在数据一致性上的实现复杂度最低,事务的原子性靠数据库和Spring就能保证;将来要扩展,用户、商品、订单、后台管理本来就可以按这四个边界打散成独立服务。这么回答,说明你理解架构取舍,而不是只会套框架。

页面方案上也容易看出项目的风格。解压以后先看resources目录下有没有templates文件夹:有templates文件夹且html文件里有th:标签,就是Thymeleaf模板渲染;没有templates而前端页面放在static目录里,通过ajax请求后端接口,这就是前后端分离的雏形。后者对前端调试更友好,也更容易在答辩时演示接口调用过程。

2.2 包结构分层与接口清单

解压工程后,第一步不是看代码,而是看包结构。这类项目通常按controller、service、mapper、entity/pojo、dto、vo、config、interceptor这几个包来组织。一个标准的分层职责如下表:

包或层职责典型类名
controller接收请求、参数校验、返回结果ItemController
service业务逻辑、事务边界ItemService / ItemServiceImpl
mapperSQL与数据库交互ItemMapper
entity与数据表对应的实体Item
dto接收外部参数的传输对象OrderCreateDTO
vo返回给前端的结果对象ItemVO / CartVO
interceptor登录校验等横切逻辑LoginInterceptor
config全局配置、WebMvc配置WebMvcConfig

看代码之前,先把resources目录下的sql脚本和application配置找到。一个可运行的商城工程,resources里必须有application.yml或application.properties,里面配了数据源、端口、MyBatis相关配置。把这些拿到手以后,我把整个项目先扫成一张接口清单:登录接口、分类接口、商品列表接口、商品详情接口、加入购物车接口、下单接口、后台增删改接口。后面所有调试和答辩演示,都围绕这张清单来,不会跑偏。

回到代码本身,大多数这种商城工程会有一个统一的Result对象包装返回结果,结构一般是code、msg、data三个字段。code=200表示成功,code=500表示服务端异常,code=401表示未登录。前端页面拿到返回值以后先判断code,再决定渲染数据还是弹错误提示。你后续改接口时,保持这个三段式结构不变,前端代码就基本不用动。

2.3 环境版本:JDK、Spring Boot、MySQL的最小可行性组合

市面上流通的毕业设计资源,Spring Boot 2.x配JDK 8最常见,Spring Boot 3.x配JDK 17是另一套组合。两个版本之间最大的差别不只是写法,而是javax包名和jakarta包名不兼容。如果你的工程是Spring Boot 2.3.x,本机却装了JDK 17,启动时大概率会在依赖加载阶段报错。

MySQL的版本影响更直接。8.0之后的连接驱动、认证插件、时区处理方式和5.7差别明显,最容易踩的是时区问题。连接串里不写serverTimezone,会报“Server returns invalid timezone”之类的错误。下面是我处理这类资源时推荐的最小版本组合:

环境建议版本注意事项
JDK1.8或17,取决于pom.xml先看pom里java.version再装环境
Maven3.6+建议配阿里云镜像加速依赖下载
Spring Boot2.3.x / 2.6.x 或匹配的3.x注意javax/jakarta包名差异
MySQL5.7和8.0均可连接串必须带serverTimezone
数据库客户端Navicat或MySQL Workbench导入sql脚本用

pom.xml里还有一个让新手容易卡的坑:Maven从中央仓库拉依赖在国内经常超时。解压以后第一件事是检查IDEA的Maven设置,User settings file指向的settings.xml里有没有配阿里云镜像。不配镜像的话,项目依赖加起来几十上百MB,会一直卡在download。我处理这类资源时的习惯是先把settings.xml配好,初始化完依赖再去看代码。

3. 数据库设计与建表:先看清表和字段再改需求

3.1 商城核心表的职责边界与关系

游戏周边商城的核心数据模型由六张表支撑:用户表、商品类目表、商品表、购物车表、订单表、订单项表,再加一张收货地址表也不少见。表之间的关系很简单,类目对商品是一对多,用户对订单是一对多,订单对订单项是一对多,用户对购物车记录是一对多。

需要特别强调的是订单和订单项为什么要拆成两张表。很多新手建表时,习惯把商品名、商品价格、购买数量直接塞进订单表的一个字段里,这是一个典型的翻车设计。商品会改价、会下架、会改变描述,订单一旦生成,订单里的商品名称和成交价格就必须永久定格。所以订单项表里要冗余存商品快照信息——商品ID、商品名、商品图片、成交单价、购买数量。查询历史订单时只查订单项表,不再去join商品表,这样商品表未来怎么改都不影响历史数据。

3.2 建表SQL:从user到order_item的落库写法

sql脚本通常放在resources目录或项目根目录的database文件夹下。下面这套建表逻辑覆盖了商城最常见的需求,SQL直接兼容MySQL 5.7和8.0:

CREATE DATABASE IF NOT EXISTS game_mall DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE game_mall; -- 用户表 CREATE TABLE `user` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密码', `nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 商品类目表 CREATE TABLE `category` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '类目名称', `parent_id` BIGINT NOT NULL DEFAULT 0 COMMENT '父级ID,0表示顶级', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '排序值', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品类目表'; -- 商品表 CREATE TABLE `item` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `category_id` BIGINT NOT NULL COMMENT '所属类目', `name` VARCHAR(200) NOT NULL COMMENT '商品名称', `subtitle` VARCHAR(500) DEFAULT NULL COMMENT '副标题', `main_image` VARCHAR(500) DEFAULT NULL COMMENT '主图地址', `price` DECIMAL(10,2) NOT NULL COMMENT '当前售价', `stock` INT NOT NULL DEFAULT 0 COMMENT '库存数量', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 0下架', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'; -- 购物车表 CREATE TABLE `cart` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `user_id` BIGINT NOT NULL COMMENT '用户ID', `item_id` BIGINT NOT NULL COMMENT '商品ID', `quantity` INT NOT NULL DEFAULT 1 COMMENT '数量', PRIMARY KEY (`id`), UNIQUE KEY `uk_user_item` (`user_id`, `item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表'; -- 订单表 CREATE TABLE `orders` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '业务订单号', `user_id` BIGINT NOT NULL COMMENT '下单用户', `total_price` DECIMAL(10,2) NOT NULL COMMENT '订单总价', `receiver_name` VARCHAR(50) DEFAULT NULL COMMENT '收货人快照', `receiver_phone` VARCHAR(20) DEFAULT NULL COMMENT '收货电话快照', `receiver_address` VARCHAR(200) DEFAULT NULL COMMENT '收货地址快照', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待付款 1已付款 2已发货 3已完成 4已取消', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- 订单项表 CREATE TABLE `order_item` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `order_id` BIGINT NOT NULL COMMENT '所属订单', `item_id` BIGINT NOT NULL COMMENT '商品ID,仅作关联', `item_name` VARCHAR(200) NOT NULL COMMENT '商品名称快照', `item_image` VARCHAR(500) DEFAULT NULL COMMENT '商品图片快照', `item_price` DECIMAL(10,2) NOT NULL COMMENT '成交单价快照', `quantity` INT NOT NULL COMMENT '购买数量', PRIMARY KEY (`id`), KEY `idx_order` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';

上面这套SQL有几个值得注意的点。第一,金额字段全部用DECIMAL(10,2),商品价格和订单价格都不能用FLOAT或DOUBLE,二进制浮点数存在精度误差,账目算不齐在答辩时是致命伤。第二,订单表没有直接叫order,因为order是MySQL的保留字,直接建表会被语法校验卡住。第三,订单表里冗余了receiver_name、receiver_phone、receiver_address这一组收货地址快照,下单那一刻地址就定格,用户后面改地址不影响已有订单。第四,购物车表用user_id和item_id建了唯一索引,同一个用户重复加购同一商品时直接走索引冲突,在Java代码里捕获DuplicateKeyException然后走数量累加,比先查再更新更稳。

3.3 字符集、事务隔离级别和索引选择

数据库连接串里,字符集参数要写在URL里。推荐写法是加上useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai,多花几秒钟把参数写全,能避开大部分乱码和时区故障。

订单和库存的并发更新涉及MySQL事务处理,默认隔离级别是REPEATABLE READ,在课程设计这个量级下完全够用,不建议为了显得专业去改隔离级别,改动之后反而会在不同MySQL版本上出现行锁和间隙锁的行为差异。索引设计上,外键字段和查询条件字段建索引就够了,不要每列都加。入库数据量连十万都不到的课程项目,三到五个索引已经是极限,索引太多反而拖慢写入。

4. 核心流程实现:登录、下单和库存扣减这条主链路

4.1 登录状态管理:Session或JWT的取舍

游戏周边商城的前端页面通常和Spring Boot工程放在一起,同一个域名下访问,这种场景用Session管理登录状态是最省事的。需求方要的是能跑通、能演示,Session方案不需要额外依赖,也不需要考虑Token过期刷新的问题。具体实现是登录成功后把userId放进HttpSession,然后注册一个拦截器,拦截所有非登录可访问的路径。

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object userId = session.getAttribute("userId"); if (userId == null) { response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}"); return false; } return true; } }

拦截器逻辑很简单:从Session里拿不到userId就说明没登录,直接返回401,放行则继续走Controller。需要在WebMvcConfig里注册拦截器并配置排除放行的路径,通常是登录接口、注册接口、商品列表和商品详情。支付回调接口也必须放行,否则第三方回调进不来。

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/user/login", "/user/register", "/item/**", "/category/**"); } }

注意excludePathPatterns里的路径要和你Controller里的@RequestMapping完全对应。有的项目接口写的是/api/user/login,有的写的是/user/login,拦截器排除路径写错一个,页面就会一直跳登录。如果你拿到的资源是前后端分离项目,前端独立部署在Vue服务上,跨域场景Session方案就得改成JWT Token。改动也不复杂,登录成功后用userId生成Token返回给前端,前端每次请求在Header里带Authorization字段,后端用拦截器去解析校验Token。两种方式二选一,判断标准就看前端页面是Spring Boot模板渲染出来的,还是独立前端工程。

4.2 商品分页列表接口的参数设计

商品列表和后台管理列表都离不开分页。Controller接收pageNum和pageSize两个参数,pageNum从1开始,pageSize一般限定在10到50之间。

@RestController @RequestMapping("/item") public class ItemController { @Autowired private ItemService itemService; @GetMapping("/list") public Result<PageResult<ItemVO>> list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Long categoryId) { PageResult<ItemVO> page = itemService.pageItems(pageNum, pageSize, categoryId); return Result.success(page); } }

Service层用MyBatis Plus的Page对象作为分页载体,设置当前页和每页条数,最后把entity列表转换成ItemVO返回。转换这一步不要省,entity里如果有库存字段,直接返回给前端,等于把运营数据和展示数据混在一起了。

@Override public PageResult<ItemVO> pageItems(Integer pageNum, Integer pageSize, Long categoryId) { LambdaQueryWrapper<Item> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Item::getStatus, 1); if (categoryId != null) { wrapper.eq(Item::getCategoryId, categoryId); } wrapper.orderByDesc(Item::getCreateTime); Page<Item> page = new Page<>(pageNum, pageSize); Page<Item> result = itemMapper.selectPage(page, wrapper); PageResult<ItemVO> pageResult = new PageResult<>(); pageResult.setTotal(result.getTotal()); pageResult.setList(result.getRecords().stream().map(this::toVO).collect(Collectors.toList())); return pageResult; }

参数说明里有个容易被忽略的细节:pageNum和pageSize都要做兜底处理。前端传一个pageNum=999、pageSize=9999进来,SQL层面虽然不会报错,但查询性能会很难看。后端校验逻辑一般把pageSize限制在100以内,超过就按100处理。这是一个很小的防御性编程,答辩时讲出来能加分。

4.3 下单扣库存:事务边界和SQL原子性

订单模块是整个商城的核心。下单操作要做的事包括校验商品状态、校验库存、计算总价、插入订单表、插入订单项、扣减库存这几个步骤,必须放在一个Service方法里,加上@Transactional注解。为什么不拆开?订单表和库存表的数据一致性要求极高,一旦订单表插入成功但库存扣减失败,系统里会出现超卖或者数据对不上的问题。

扣库存这里有一个常见误用。不少初学写法是先查询库存,Java代码里判断if (stock > 0)再执行update,这在单线程下没问题,并发一高就会出现两个请求同时读到stock=1,然后都通过判断去下单,最终库存变成负数。正确做法是把判断条件放进SQL语句本身:

UPDATE item SET stock = stock - #{quantity}, update_time = NOW() WHERE id = #{itemId} AND stock >= #{quantity}

返回的int值就是受影响行数,1表示扣减成功,0表示库存不足。UPDATE操作在MySQL里是行级原子性的,不需要额外加锁,也避免了先查后改带来的并发窗口。Service层的核心逻辑如下:

@Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 1. 校验商品状态 Item item = itemMapper.selectById(dto.getItemId()); if (item == null || item.getStatus() != 1) { throw new BizException("商品不存在或已下架"); } // 2. 扣减库存,SQL行级原子性防超卖 int rows = itemMapper.deductStock(dto.getItemId(), dto.getQuantity()); if (rows != 1) { throw new BizException("库存不足"); } // 3. 生成订单并保存 Orders order = new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setTotalPrice(item.getPrice().multiply(new BigDecimal(dto.getQuantity()))); order.setStatus(0); orderMapper.insert(order); // 4. 保存订单项快照 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setItemId(item.getId()); orderItem.setItemName(item.getName()); orderItem.setItemImage(item.getMainImage()); orderItem.setItemPrice(item.getPrice()); orderItem.setQuantity(dto.getQuantity()); orderItemMapper.insert(orderItem); return orderVO; }

这段代码有两点值得说明。第一,@Transactional注解的rollbackFor必须写成Exception.class,只写@Transactional不指定回滚策略时,Spring默认只在运行时异常时回滚,受检异常抛出来事务不会回滚。第二,下单总价不能信任前端传进来的值,要以服务端查询到的商品价格实时计算。前端传参只传商品ID和数量,价格以数据库里的为准,否则用户改一下请求体就能低价下单。

提示:如果下单接口在并发压测下频繁报库存不足,先检查是不是库存判断写进了UPDATE语句本身。用了先查再改的写法,并发场景一定会出问题。

生成订单号这一块,最简单的可用方案是时间戳加随机数,比如yyyyMMddHHmmss加四位随机数字。课程项目不需要引入分布式ID中间件,一个订单号兼顾可读性和唯一性就够。唯一索引uk_order_no兜底,万一随机数碰撞,第一次插入会抛异常,重试一次就能过去。

5. 部署与避坑:从零跑到通的五个常见问题记录

5.1 MySQL连接报错:SSL、时区和驱动三者没对齐

现象:点启动后控制台报Communications link failure,或者直接看到Caused by: java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。

原因:MySQL 8.x的驱动对时区要求严格,而jdbc连接串里没写serverTimezone,同时用了MySQL 8默认的SSL认证。

解决:把数据源配置改成下面这种完整写法:

spring: datasource: url: jdbc:mysql://localhost:3306/game_mall?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

allowPublicKeyRetrieval=true这个参数很重要,MySQL 8.0默认使用caching_sha2_password认证,走公钥检索时需要这个开关,不写就可能出现Access denied for user的诡异报错。driver-class-name最好显式写com.mysql.cj.jdbc.Driver,不要写旧的com.mysql.jdbc.Driver。

提示:如果本机MySQL还是5.7,driver-class-name同样推荐换成cj驱动,MySQL 8的驱动能向下兼容连接5.7的库,两边都能跑通。

5.2 SQL脚本导入不完整导致表缺失

现象:项目能启动,但注册或登录时后端报SQLSyntaxErrorException: Table 'game_mall.user' doesn't exist。

原因:sql脚本没完整导入,或者脚本本身没有CREATE DATABASE这一段,数据导入到了别的数据库里。

解决:用Navicat或MySQL Workbench打开sql脚本整体执行一遍,执行前检查脚本开头有没有USE game_mall;,有这条才会落到正确库。执行完以后在客户端里刷一遍表列表,把表数量和设计文档对一下。有的资源在sql脚本文件里还会放几行测试数据,商品表里没数据,登录进去看到空列表,也是同样的排查路径。

5.3 数据库连接池报错:连接不够或连接被断

现象:并发场景下后台报Cannot get a connection, pool exhausted,或者请求中途出现连接超时。

原因:默认配置里连接池太小,而且MySQL服务端wait_timeout默认会把长时间空闲的连接断掉,业务层拿到的却是已经失效的连接。

解决:在数据源配置里增加连接池参数,idle-timeout要略小于MySQL的wait_timeout。参考配置如下:

spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000

这组参数的意思是最少保持5个连接,最多20个,连接超时30秒,最长生命周期30分钟。max-lifetime一定不能大于MySQL的wait_timeout,否则连接池里的连接会被服务端清掉,请求时拿到的全是死连接。用Druid的项目也有类似参数,核心思路一样,连接存活时间必须比数据库服务端的断开阈值短。

5.4 中文乱码:库、表、连接三层都要对齐utf8mb4

现象:商品名称、订单地址在数据库里正常,页面上显示一串问号,或者后台写入的数据直接变成???。

原因:数据库表字段虽然是utf8,但连接串里没有带characterEncoding=utf8,或者前端页面本身是GBK编码。

解决:最省事的方法是统一成utf8mb4。先修改建库语句为DEFAULT CHARSET utf8mb4,再在jdbc连接串里加上useUnicode=true&characterEncoding=utf8。MySQL 5.7环境里如果字符集修改不彻底,可以执行ALTER TABLE item CONVERT TO CHARACTER SET utf8mb4;把存量表转过去。处理完以后记得重启应用再验证,连接池里缓存的旧连接可能还带着旧的字符集设置。

5.5 启动端口被占用和依赖冲突

现象:启动到一半报Web server failed to start. Port 8080 was already in use。

原因:本机已有进程占了默认端口,最常见的是之前启动过的Spring Boot进程没杀掉,或者别的开发工具占了8080。

解决:先把占用进程找出来杀掉,或者直接在application.yml里改端口,改成8081、8090都行。改完一定要同步检查前端页面的请求地址有没有写死8080。依赖冲突的表现是NoSuchMethodError、ClassNotFoundException,原因多是pom里引入了重复版本的依赖,解决办法是在IDEA右侧Maven面板里看Dependency Hierarchy,找到冲突的依赖后用exclusion排除一个。

这一层如果你用的是IntelliJ IDEA社区版跑项目,它是没有Spring官方插件和Spring Initializr界面的,但不影响打开和调试Spring Boot工程。社区版装好Lombok插件,更新一下Maven依赖,启动类上右键就能run,跟收费版在跑Java工程上没差别。

6. 进阶:给商城加一个健康检查和连接池调优

6.1 健康检查接口:用最少的代码让项目具备可运维性

答辩时只讲CRUD很容易被问住,加一个健康检查接口是性价比很高的增量内容。不需要引入监控平台,先做一个返回当前运行状态和数据库状态的小接口就够。

@RestController @RequestMapping("/monitor") public class MonitorController { @Autowired private DataSource dataSource; @GetMapping("/health") public Map<String, Object> health() { Map<String, Object> result = new HashMap<>(); result.put("status", "UP"); try (Connection conn = dataSource.getConnection()) { result.put("db", "UP"); result.put("dbUrl", conn.getMetaData().getURL()); } catch (SQLException e) { result.put("db", "DOWN"); result.put("error", e.getMessage()); } return result; } }

实现很直白:从Spring容器里拿出DataSource,尝试拿一个数据库连接,拿到就说明MySQL还活着。将来想再进一步,可以在这个基础上引入Spring Boot Actuator或Spring Boot Admin,暴露/actuator/health、/actuator/info这些标准端点。对毕业设计而言,自定义这个Monitor接口已经足够展示你在可维护性上的考虑。

6.2 连接池参数与事务验证

数据库连接池不是越大越好,也不是默认配置就能扛住订单峰值。以HikariCP为例,经验值是minimum-idle=5,maximum-pool-size=20,当并发请求量超过20时请求会排队等待空闲连接,这正好保护了下游数据库不被瞬时流量压垮。这组参数要和事务配合验证:开两个浏览器对同一件只剩1件库存的商品同时下单,正常情况应该只有一个订单成功,另一个拿到“库存不足”。如果两次都成功且库存为负,就是前面4.3节讲的扣库存SQL写成了先查再改。

我一般拿到这类资源解压以后,有个习惯是强制走一遍“注册新用户 → 浏览商品 → 加购物车 → 下单 → 后台改库存”这条完整链路,每个环节看一眼数据库对应表是否更新。任何环节出问题,排查路径都是先看后端接口日志,再看事务有没有回滚,最后查数据库当前状态。从那以后我每次拿到传过来的项目,第一件事就是打开资源配置文件把端口、数据源和依赖树过一遍,确认没问题再启动。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询