简介:基于SpringBoot的校园二手交易平台系统源码与数据库脚本(毕业设计)项目,面向计算机相关专业毕业生及Java学习者,可用于毕业设计、课程设计或期末大作业。系统前端涉及产品分类、产品明细、用户注册、产品公告、在线支付、个人中心等模块;后台管理包含用户管理、产品管理、订单管理、钱包管理和系统设置,功能完整、界面简洁。压缩包共1081个文件,含350个HTML页面、326个JavaScript文件、68个CSS样式、67个Java源码文件及SQL脚本,另有大量图片与说明文档,整体约5.89MB,目录结构清晰,便于按前端、后台与数据库快速定位。目前已有5508人学习下载,适合用于毕设搭建和项目实战练习。内含可直接运行的完整源码、数据库脚本和项目说明,经过严格调试,可直接作为毕设使用;前后台分模块组织,方便二次开发与功能扩展,能帮助读者快速理解该系统的完整实现方案。
1. 这个 zip 里装的是一套能跑通闭环的毕设代码,先看清它是什么
打开这份命名为“基于SpringBoot的校园二手交易平台系统源码+数据库脚本(毕业设计).zip”的压缩包,你会看到两个典型组成部分:一个 Maven 工程(源码),一个可以导入 MySQL 的数据库脚本。SpringBoot 负责把后台服务独立跑起来,校园二手交易平台负责业务闭环——用户注册、商品上架、下单购买、订单状态流转,这套东西解决的是“我要交一份能演示完整流程的毕业设计”的需求,而不是只交一篇论文。适合 Java 方向的应届生拿来做毕设底子,或者给想快速搭一个交易类原型的人参考。但你得先搞明白一件事:这套代码能不能跑,取决于 SpringBoot 版本、JDK 版本、MySQL 字符集和静态资源映射这几个关键点,下面按落地顺序讲透。
2. 把源码跑起来:SpringBoot 项目结构与最小启动配置
2.1 Maven 项目结构:先分清源码和脚本的分工
校园二手交易平台这类毕设项目,源码目录基本遵循 SpringBoot 标准 Maven 布局,你解压后看到的第一层应该是src、pom.xml,外加一个school_market.sql(脚本文件名可能不同)。src/main/java下面按 controller、service、mapper、entity 分包,src/main/resources下面放着application.yml和 Mapper XML 文件。很多同学拿到包后直接java -jar启动,结果数据库连接失败,就是因为没分清:源码负责业务逻辑,数据库脚本负责把表结构、初始数据灌进 MySQL,两边要各自就位才能联动。
看pom.xml时先盯住三个依赖:spring-boot-starter-web(提供 Web 容器和 SpringMVC 能力)、MyBatis 相关依赖(操作数据库)、MySQL 驱动。这里有个容易翻车的小细节,我一般会先看 SpringBoot 父版本再决定 JDK。SpringBoot 2.x 用 JDK 8、javax 命名空间;SpringBoot 3.x 必须配 JDK 17 以上,而且包名从javax.*换成jakarta.*。一份毕业设计源码如果写的是javax.servlet.http.HttpServletRequest,你却用 JDK 17 跑 SpringBoot 3.x,编译期就过不去。拿到手先统一这三件事:Java 版本够不够、Maven 仓库能不能连、MySQL 端口和密码对不对。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <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.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>这段依赖声明里,SpringBoot 2.7.18 是一个相对稳妥的选型:JDK 8 就能跑,学校机房和老笔记本都不会卡在工具链上。MyBatis-Plus 3.5.5 比原生 MyBatis 省掉大量 XML 配置,适合毕设这种要快速出效果的场景。MySQL 驱动的坐标放在runtime作用域,意思是只在运行时生效,编译期不需要显式引用。如果你拿到的是 SpringBoot 3.x 版本,驱动坐标要改成com.mysql:mysql-connector-j,这是我踩过一次的坑:复制了 2.x 的依赖到 3.x 项目里,启动时直接报驱动类找不到。
2.2 application.yml 里必调的三个配置项
application.yml是整个系统的黑匣子入口,报错先看这里。核心配置有三块:服务端口、数据源、MyBatis 相关设置。默认端口是 8080,但毕设演示时这个端口经常被占用,我习惯先改成8888或9090,避开系统里其他 Java 进程。数据源配置里url、username、password三个值务必和本机 MySQL 一致,很多“启动失败”其实不是代码问题,而是密码没改。
server: port: 8888 spring: datasource: url: jdbc:mysql://localhost:3306/school_market?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: root password: "你的密码" driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplurl里的characterEncoding=utf8mb4是中文不乱码的前提,serverTimezone=Asia/Shanghai解决 MySQL 8.x 时区报错的问题——这两项缺一个,你的商品标题和订单备注在写入时就会出现问号或者异常。driver-class-name用了com.mysql.cj.jdbc.Driver,这是 MySQL 8.0 后的新驱动类名,老资料里写的com.mysql.jdbc.Driver在 8.x 驱动下会直接启动报错。map-underscore-to-camel-case: true让数据库的create_time自动映射到实体的createTime,这不是可选项,关掉以后所有带下划线的字段查出来全是 null。log-impl配成 StdOut 能在控制台看到 SQL,排查问题时比睁眼猜靠谱得多。
2.3 两种启动方式:从 IDEA 到命令行
启动方式有两种,对应两个场景。日常开发在 IDEA 里打开工程,等 Maven 把依赖拉完,直接运行src/main/java下带@SpringBootApplication注解的启动类。这种方式的好处是热更新方便、断点调试直观,坏处是依赖 IDEA 环境——答辩现场如果用的不是你的电脑,这套就不好使。所以我更推荐第二种:先用 Maven 打成可执行 jar,交给谁都能跑。
mvn clean package -DskipTests java -jar target/school-market-0.0.1-SNAPSHOT.jarmvn clean package会先清空 target 目录再重新编译打包,-DskipTests跳过单元测试——毕设项目里的测试类经常因为数据库连接失败导致打包中断,直接跳过省心。打出来的 jar 是 SpringBoot 内嵌 Tomcat 的可执行包,java -jar一条命令就能起服务,演示时再也不怕“在我电脑上是好的”这种尴尬。启动后看控制台日志,出现“Started Application in X seconds”就说明 Web 容器起来了,再用浏览器访问http://localhost:8888看首页是否响应。有一点要注意:如果控制台报“Application run failed”,八成是前面说的数据源配置没对上,先去检查密码和数据库名,而不是重新导入代码。
3. 核心业务模块落地:从建表思路到关键接口实现
3.1 先想清楚订单状态怎么流转,再写代码
校园二手交易平台最核心的不是商品 CRUD,而是订单状态机。我见过不少毕设把订单状态做成一个随意改的字段,前端传什么就存什么,结果用户付款后商品没下架、发货后订单还能取消,演示时被老师问得下不来台。先把状态定义写死:商品状态0表示上架中、1表示已售出、2表示下架;订单状态0待付款、1待发货、2已发货、3已完成、4已取消。状态流转只允许按固定方向走,比如待付款只能变成待发货或已取消,不能跳成已完成。
订单模块的 Controller 和 Service 要严格分层。Controller 只做参数接收和返回统一响应体,Service 里写业务判断,Mapper 层只碰数据库。下面这段是创建订单的核心方法,我加了三层保护:第一层用乐观锁防止卖家用两个订单同时买走同一件商品,第二层判断商品状态必须是0(上架中),第三层通过@Transactional保证“改商品状态 + 插订单”要么都成功、要么都回滚。
@Override @Transactional(rollbackFor = Exception.class) public Result createOrder(Long goodsId, Long buyerId) { // 乐观锁扣减:status=0 是过滤条件,update_count 用于版本控制 Goods goods = goodsMapper.selectById(goodsId); if (goods == null || goods.getStatus() != 0) { return Result.error("商品已下架或已售出"); } int rows = goodsMapper.updateStatusWithLock(goodsId, 0, 1); if (rows == 0) { return Result.error("手慢了,商品刚刚被买走"); } Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getUserId()); order.setStatus(0); order.setPrice(goods.getPrice()); orderMapper.insert(order); return Result.success(order); }@Transactional(rollbackFor = Exception.class)里的rollbackFor很关键——默认情况下 Spring 事务只对 RuntimeException 回滚,如果你在业务里抛的是Exception子类,不加这个参数事务不会回滚,订单就可能“半成功”。updateStatusWithLock(goodsId, 0, 1)执行的是UPDATE goods SET status = 1, update_count = update_count + 1 WHERE goods_id = ? AND status = 0,这行 SQL 利用数据库的行锁保证并发请求只有一个能改成功,比先 select 再 update 安全得多。generateOrderNo() 一般用时间戳加随机数,格式类似202501151430001234,方便答辩时解释“订单号可读性强、能反查时间”。
3.2 登录与鉴权:拦截器留好三个放行口
登录几乎是所有毕设项目的第一个功能,但也是最容易被攻击的环节。二手交易平台的密码不能明文存数据库,至少在 Service 层做一次 MD5 加盐,或者用 Spring Security 的 BCrypt。毕设项目我常用 MD5 加固定盐,因为 Security 的过滤器链对新手不友好,演示时容易把自己拦住。用户登录成功后把 userId 和 username 放进 Session 或生成 token 返回前端,后续接口通过拦截器校验身份。
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); // 放行注册、登录、商品列表、图片访问 if (uri.startsWith("/api/user/login") || uri.startsWith("/api/user/register") || uri.startsWith("/api/goods/list") || uri.startsWith("/images/")) { return true; } HttpSession session = request.getSession(); Object userId = session.getAttribute("userId"); if (userId == null) { response.setContentType("application/json;charset=utf8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}"); return false; } return true; }这段拦截器用的是HandlerInterceptor,实现preHandle方法做前置校验。四个放行路径要写全:登录接口、注册接口、商品列表、图片访问。漏掉商品列表会导致用户没登录就看不了首页商品,漏掉图片访问会导致上传的图片显示不出来——这两处是新手最容易翻车的地方。拦截器注册时还得注意:/images/**是静态资源映射,SpringBoot 默认只放行/static下的静态资源,自定义目录必须手动配。返回 401 时用 JSON 响应而不是重定向,是因为前后端分离项目里前端要统一弹“登录超时”提示,而不是跳页面。
3.3 商品发布与图片上传:静态资源映射是隐藏门槛
商品发布接口接收表单数据:标题、描述、价格、图片文件。图片不能直接存数据库,正确做法是存到本地磁盘或对象存储,数据库只保留访问路径。毕设项目的常见做法是保存到项目根目录下的upload/文件夹,文件名用 UUID 重命名,避免用户上传同名文件互相覆盖。上传成功后返回给前端的 URL 形如/images/20250115/uuid.jpg,然后配置虚拟路径映射。
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /images/** 映射到本地磁盘的 upload 目录 registry.addResourceHandler("/images/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/upload/"); } }System.getProperty("user.dir")返回的是当前工作目录,在 IDEA 里跑是项目根目录,打成 jar 跑是 jar 所在目录。file:前缀不能省,这是告诉 Spring 按文件系统路径解析。很多 zip 里的代码到这一步是残缺的——没有这个配置类,或者路径写死成D:/upload/,换台电脑就崩。我一般建议把 upload 目录做成可配置项:在application.yml里加file.upload-path,用@Value注入,这样答辩时换电脑只用改一行配置。演示时最尴尬的瞬间是商品列表刷新后图片全是破图,问题基本都出在没配这个映射或者路径写死。
3.4 订单交易与商品搜索:查询接口要处理的两个细节
商品搜索接口看起来简单,实际有坑。校园二手平台的搜索词通常是“教材”“自行车”“台灯”,前端拿关键词来查询商品标题,SQL 用LIKE '%' #{keyword} '%'就够了。但如果商品表有几千条数据,LIKE查询不走索引,页面上转圈几秒才出结果,老师的评价不会太好。我一般会在status = 0的前提下再加一个销量或时间排序,让腐烂的商品沉底。另一个细节是价格存储:用DECIMAL(10,2)而不是float,浮点数在 Java 里做金额运算是医院级别的笑话,两个 0.1 加不出 0.2,答辩时讲一句“我用 BigDecimal 避免浮点精度问题”,比背十页论文都加分。
订单列表接口也要注意隔离:买家只能看自己买的订单,卖家只能看自己卖出的订单,管理员才能看全部。这个过滤在 SQL 里加buyer_id = ?或seller_id = ?,而不是在 Java 内存里过滤。理由是数据库层面过滤能走索引、减少传输数据量,内存过滤则会把整表记录捞出来再删,数据量大了就是灾难。前端如果是 Vue 项目,后端接口统一返回{code, msg, data}格式,前端 axios 拦截器只判断 code 是不是 200,这样所有接口的处理逻辑都一致,不用每个页面单独写错误处理。
4. 数据库脚本:导入方式、表结构解读与常见盲区
4.1 先把脚本导进去:命令行与图形工具两种做法
拿到数据库脚本,第一个动作是看文件开头有没有CREATE DATABASE语句。有的话,导入时不用手动建库,MySQL 会自动创建;没有的话,得先建库再导表。常用的导入方式有两种,命令行适合演示前的最终部署,图形工具适合开发时反复重置数据。
mysql -u root -p < school_market.sql这条命令会把 school_market.sql 里的内容批量发给 MySQL 执行。执行前要确认 MySQL 的local_infile参数是否开启,报错“LOAD DATA LOCAL INFILE 被禁用”时,需要临时加--local-infile=1。脚本文件本身的编码统一要求 UTF-8,Windows 下用记事本另存为时留意“UTF-8 with BOM”,BOM 可能会导致第一行CREATE DATABASE报语法错误。常见的报错 1064 基本都是这个原因,而不是你写错了 SQL。
图形工具我用 Navicat 或 DataGrip 比较顺。Navicat 右键连接,选择“运行 SQL 文件”,选中脚本等进度条跑满。DataGrip 更严格,会校验每条语句的语义,但有个好处是导入失败时会明确告诉你第几行出错,方便定位。无论用哪种方式,导入完成后先执行一句USE school_market; SHOW TABLES;,看到用户表、商品表、订单表都出现了,才算完成。这一步很多人跳过,直接启动后端,结果找不到表名报错,白花十分钟排查。
4.2 四大核心表:字段设计与为什么要冗余
校园二手交易平台的数据库脚本通常包含四类核心表:用户表、商品表、订单表、留言表。下面这份表结构把关键字段列出来,重点不是字段数量多,而是每个字段都回答一个业务问题。
| 表名 | 关键字段 | 设计意图 |
|---|---|---|
| t_user | user_id, username, password, nickname, avatar, phone, create_time | password 存加密串,avatar 存图片路径,phone 用于联系 |
| t_goods | goods_id, user_id, title, description, price, cover, status, update_count, create_time | price 用 DECIMAL(10,2),status 用 int 区分上架/售出/下架,update_count 做乐观锁 |
| t_order | order_id, order_no, goods_id, buyer_id, seller_id, price, status, create_time | order_no 唯一索引,buyer_id 和 seller_id 双外键指向用户表 |
| t_message | message_id, goods_id, from_user_id, to_user_id, content, create_time | 记录买家与卖家的问答,用于详情页沟通 |
商品表里冗余seller_name或user_id,我倾向于只存user_id,查询时联表拿昵称。但有的脚本会直接冗余一列seller_nickname,理由是详情页查商品时不想每次都联用户表。毕设项目里两种做法都能答辩,冗余的取舍标准很简单:如果商品详情页要和用户昵称一起高频展示,冗余一个字段省一次 JOIN,代价是用户改昵称后历史商品显示旧昵称。我习惯不冗余,因为数据量小,联表的性能损耗可以忽略,逻辑也更干净。
4.3 外键、索引与时间字段:数据库脚本里的三个质量分水岭
毕设数据库脚本最常见的两个问题:一个是不建任何索引,另一个是到处建物理外键。前者导致数据量稍大查询变慢,后者导致删除用户时被订单表的外键约束卡住。行业内对表设计的共识是:物理外键能不用就不用,用逻辑外键(即业务层面保证外键字段合法)代替。交易类系统的用户表和订单表尤其不能加ON DELETE CASCADE——用户要注销账号,直接把订单连带删了?这不符合平台需要保留交易记录的真实需求。
索引方面,订单表的order_no加唯一索引,商品表的status加普通索引就够了。status字段的业务形态适合建索引,因为它只有 0/1/2 几个值,查询时能快速过滤掉大量已售和下架商品。时间字段统一用datetime,默认值写CURRENT_TIMESTAMP,尽量避免用timestamp——timestamp 的范围只到 2038 年,二手交易平台虽然生命周期短,但毕业设计答辩时如果老师问“为什么不用 datetime”,你能说出“timestamp 有 2038 问题”这个理由,就比只会背八股文强。
4.4 从 IDEA 导出数据库脚本的正确姿势
毕设要求里经常有“提交系统源码+数据库脚本”这一项,于是很多人直接在 IDEA 里导出 SQL。IDEA 的数据库工具面板提供了导出功能,但直接导出的脚本常常包含DROP TABLE IF EXISTS开头和大量注释,在老师的电脑上导入时如果字符集不一致,中文注释就变成乱码。我一般会这样处理:导出前把数据库连接 URL 加上?useUnicode=true&characterEncoding=utf8,导出后检查文件头部有没有SET NAMES utf8mb4,没有就手动加一行。这是数据库脚本的后悔药,别等导入失败再想起来。
另一个坑是导出时把视图、函数、存储过程一起导出来,但毕设项目的脚本只需要表结构和数据。IDEA 导出界面里取消勾选 View、Routines、Triggers,只留 Tables 和 Data。还有一点:脚本末尾经常有一大坨外键约束的ALTER TABLE语句,导入顺序不对时会报错找不到表名。解决方案有两种——要么按表依赖关系排序(先导父表再导子表),要么干脆不建物理外键,业务层面保证逻辑一致性。
5. 避坑清单:从导入崩溃到图片 404 的 5 个常见问题
5.1 导入 SQL 报 1064 语法错误,尤其第一行
现象:Navicat 或命令行导入 school_market.sql,提示ERROR 1064 (42000): You have an error in your SQL syntax,错误位置恰好是文件开头。原因:文件被 Windows 记事本保存成带 BOM 的 UTF-8,BOM 字符EF BB BF混进了第一条 SQL 语句开头,MySQL 解析器不认这个字符。解决:用 Notepad++ 或 VS Code 打开脚本,右下角把编码切换为 UTF-8(无 BOM),保存后再导入。还有一个隐蔽原因:脚本里写了CREATE DATABASE IF NOT EXISTS school_market DEFAULT CHARACTER SET utf8mb4;,但 MySQL 5.7 及以上才支持 utf8mb4,如果你的环境是 MySQL 5.6,会直接语法报错,此时改用utf8或者升级 MySQL。
5.2 后端启动失败,报 Communications link failure
现象:运行 SpringBoot 启动类,控制台抛出Cannot create PoolableConnectionFactory或Communications link failure的异常堆栈。原因:三种。一是 MySQL 服务没启动(拿到的电脑上没装 MySQL 或者服务没开),二是application.yml里的密码填错,三是数据库名写错了,连到了不存在的库上。解决:先做链路验证而不是猜。第一步,命令行执行mysql -u root -p看能不能登录本机 MySQL;第二步,执行SHOW DATABASES;确认 school_market 库存在;第三步,检查 yml 里的 username、password、url 里面的库名,逐一对照。如果三步都正常,再看 JDBC 驱动版本和 MySQL 版本是否匹配——MySQL 8.x 必须用com.mysql.cj.jdbc.Driver。
5.3 图片上传成功,页面访问却 404
现象:前端能发起上传请求,后端日志也显示文件已保存,但回显的图片地址http://localhost:8888/images/xxx.jpg打开是 404。原因:SpringBoot 默认只把classpath:/static/和classpath:/public/这些目录当作静态资源目录,你在代码里自己建的upload/磁盘目录不在映射范围内,必须通过WebMvcConfigurer.addResourceHandlers手动声明。解决:参考前文的 WebMvcConfig 配置,addResourceHandler("/images/**").addResourceLocations("file:...")。注意file:后面一定要跟绝对路径,写相对路径会和“当前工作目录”绑定,而导致 IDEA 里能跑、打包后跑不了。我建议把路径抽到application.yml,用@Value注入,就不会出现“开发环境 ok、生产环境 404”这种玄学问题。
5.4 登录后访问商品详情接口,被拦截器拦住
现象:前端用正确的账号密码登录成功后,跳转商品详情页,接口返回 401 未登录。原因:拦截器的放行路径没写全。很多项目详情页路径是/api/goods/detail/1,而拦截器只放行了/api/goods/list,导致未登录用户访问详情页被挡住。解决:梳理整个应用的匿名接口清单,注册、登录、商品列表、商品详情、首页轮播图,凡是用户不登录就能看的,全部加进白名单。我一般把白名单统一放到一个常量数组里,preHandle里循环判断,比一个个 if 清晰,新增接口时不容易漏。排查时可以在拦截器里临时输出 uri,看一眼每次请求到底匹配了什么规则,很快就能定位。
5.5 分页查询 total 不对,第一页数据永远丢失
现象:商品列表分页,前端传page=1、pageSize=10,返回结果却从第 11 条开始,total 和真实条数对不上。原因:分页插件用的current起始值没对齐。MyBatis-Plus 的Page对象current从 1 开始计,前端如果用的是 element-ui 的el-pagination,它的currentPage也是从 1 开始,对齐没问题;但如果你复用了某个前端模板,它传的page是从 0 开始,那么“第 1 页”传给后端就成了 0,MyBatis-Plus 会把 0 当成非法值处理。解决:在后端 Service 入口处统一做current = Math.max(current, 1)的兜底,或者在 Controller 里把前端参数减 1 再传给 Page。这个坑不检查出来,演示时点第一页就是空的,老师会觉得你这个分页是假的。
6. 答辩前验证技巧:把演示流程做成不会翻车的脚本
6.1 演示前把配置切到生产模式,避免现场踩坑
答辩前一天,我会把application.yml里的spring.datasource密码改成演示机 MySQL 的实际密码,把端口改成固定值,然后用mvn clean package -DskipTests打一个完整 jar 包。现场直接java -jar启动,不依赖 IDEA 的热更新,也不怕网络波动影响 Maven 拉依赖。启动后别急着开浏览器,先在命令行敲curl http://localhost:8888/api/goods/list,返回 JSON 再继续。这一步花一分钟,能挡掉“页面白屏但不知道是前端问题还是后端问题”的尴尬。
6.2 用数据造出三条能讲故事的订单
验证系统最好的办法不是临时上传商品,而是提前造一套“能讲出业务逻辑”的演示数据。我一般建两个账号:买家账号、卖家账号。卖家上传三件商品,分别是“九成新高数教材”“校园自行车”“蓝牙耳机”,价格设置为 15 元、180 元、120 元。演示时先以买家身份下单教材,走完整流程:待付款 → 模拟付款后待发货 → 卖家发货 → 买家确认收货 → 已完成。然后再演示一次重复购买同一商品,故意触发“商品已售出”的提示,证明乐观锁生效。这套顺序讲完,一个完整的交易闭环就立住了,比随机点几个按钮强得多。
6.3 进阶验证:用 @Scheduled 定时任务兜底未支付订单
如果想在答辩时多一个亮点,可以给平台加一个定时任务:每天凌晨自动取消超时未支付的订单,并恢复商品状态到可售。实现方式是在启动类加@EnableScheduling,然后新建一个OrderScheduleTask类,用@Scheduled(cron = "0 0 2 * * ?")每天凌晨 2 点执行一次取消逻辑。注意@Scheduled默认是单线程执行,多个定时任务共用一个调度线程,毕设场景够用,不用上分布式锁。这个功能体现的不是技术难度,而是你考虑了真实交易系统里的“僵死订单”问题,这就是一个值得讲的产品化细节。
我当年演示时吃过图片加载不出来的亏,后来养成的习惯是:任何代码改动后先验证静态资源和数据库连接这两条生命线,再谈业务功能。上面这些检查和验证顺序,就是我现在拿到任何 SpringBoot 交易类代码的第一套动作。希望帮到你。
本文还有配套的精品资源,点击获取