news 2026/9/15 18:36:35

Spring Boot+MyBatis家电商城完整版:库存、订单、支付闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot+MyBatis家电商城完整版:库存、订单、支付闭环

简介:基于 Java 打造的家电商城完整版源码包含商品浏览、搜索、购物车、订单管理、支付处理等电商核心功能的代码实现,适合课程设计、毕业设计或作为 Java Web 开发进阶的实操参考。压缩包共 29 个文件、53KB,其中 14 个 class 为编译后的业务类,7 个 java 覆盖用户、商品、Service 接口与实现以及入口类,5 个 xml 用于 IDE 模块与运行配置,另有 readme.txt 部署说明、.iml 项目文件和 .gitignore 版本控制文件。项目通过 JDBC、Servlet 等 Java 标准技术串联数据访问与请求处理,可导入 IntelliJ IDEA 直接运行学习,便于观察电商系统从持久层到界面层的完整链路。已有 264 人学习下载,适合中初级开发者对照源码理解电商平台的分层设计、集中式配置管理和 Git 忽略规则的工程实践。

1. 家电商城的完整版到底“完整”在哪

商城源码很好找,能照着改源码的 Java 开发也很多,但真正把家电交易闭环写全的完整版不常见。常见的电商源码里,能注册、能搜索、能加购,一到下单就露馅:库存扣减先查询再更新,优惠券叠加结算账是负的,支付回调只改订单状态、不校验金额。家电的复杂度又比卖书卖日用品高出一截,一台电视拆成屏幕尺寸、能效等级、安装方式好几个可选组合,价格从几千到上万,大件物流费用要单独算,所以“完整版”意味着这些规则都存在,标题要表达的是那份把库存、下单、支付、售后串成闭环的 Java 工程。它适合两类人:拿完整工程当毕设和面试项目的开发,以及想把交易链路嵌进现有供应链系统的后端工程师。

2. 技术选型:Java 生态里最常见的 Spring Boot + MyBatis 组合

2.1 为什么选择 Spring Boot + MyBatis,而不是 JPA 或微服务

看到“完整版”这三个字,最容易踩的坑是先去拆微服务:商品一个服务、订单一个服务、库存一个服务。如果是在企业供应链之上叠加交易链路,这么拆是合理的;但如果目标是让接手的人能在几天内把整条链路改明白,绝大多数源码会选择单应用多模块。Java 生态里,Spring Boot 3.x 加 MyBatis 是这几年一致性最强的组合。

选 MyBatis 而不是 Spring Data JPA,核心原因是商城查询几乎不存在纯粹的实体操作:一个商品列表要把库存、价格、促销三层数据拼起来,用 JPQL 和 Specification 反而要写一堆关联对象。反过来,MyBatis 把 SQL 交回给代码,哪些查询要防重、哪些更新要带where stock >= ?条件,一眼就能看明白。MyBatis-Plus 把单表 CRUD 用通用 Mapper 兜底,业务 SQL 自己写,这一层折中是这类源码里最常见的取舍。

有个版本问题容易被忽略。现在新源码大多要求 JDK 17 或 21 跑 Spring Boot 3.x,如果本机还在 JDK 8,启动时大概率会抛UnsupportedClassVersionError,这时就得退回 Spring Boot 2.7.x。下载代码后先检查pom.xml里的 parent 版本和本机java -version,比把报错贴回搜索引擎更快。

组合JDK 要求复杂查询字段变更适配适合场景
Spring Boot 3.x + MyBatis-Plus17+XML 手写,可控大多数商城源码
Spring Boot 2.7 + MyBatis-Plus8+XML 手写,可控旧环境团队
Spring Data JPA17+Specification 学习成本高字段稳定的小系统

2.2 Maven 依赖:一个能直接启动的最小集合

完整版的 pom.xml 通常会堆上十几个依赖,含验证码、支付 SDK、文档组件等等。去掉这些外围,一个家电商城后端至少要有下面四组依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <!-- mybatis-plus 3.5.x 对 Spring Boot 3 的兼容最稳 --> <version>3.5.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

spring-boot-starter-web提供 Web 容器和 MVC,MyBatis-Plus 负责数据库访问,Redis 用于购物车、验证码和支付锁,MySQL 驱动负责底层连接。注意 MyBatis-Plus 在 Spring Boot 3 和 2.7 下不是同一个 starter,前者要带spring-boot3标记,选错会在启动时出现 Mapper 注册失败。这个差异是 Java 面试里常被追问的版本兼容问题,也是背八股背不到、真正排查一遍就能记住的知识点。

2.3 启动前要先改的三个配置项

拿到源码后第一件事不是启动,而是把配置文件里这三组参数核一遍,它们分布在src/main/resources/application.yml

spring: datasource: url: jdbc:mysql://localhost:3306/appliance_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: changeme hikari: maximum-pool-size: 20 minimum-idle: 5 data: redis: host: localhost port: 6379 database: 0 mybatis-plus: mapper-locations: classpath:mapper/**/*.xml configuration: map-underscore-to-camel-case: true

characterEncoding=utf8直接决定商品名会不会乱码,中文乱码多数不是数据库字符集不对,而是 JDBC URL 没带编码参数。maximum-pool-size不要拍脑袋填一个大值,连接池占满时新请求会排队超时,家用场景 20 足够,压测时再调大并配合数据库的max_connectionsmapper-locations指定 XML 位置,运行时报Invalid bound statement,第一嫌疑人就是路径和 XML 里的 namespace 不一致。

提示:MyBatis-Plus 的逻辑删除会拦截updateByIdselectById这类内置方法,但手写在 XML 里的update要自己补上deleted = 0条件,否则可能更新到一条已经标记删除的历史 SKU。

2.4 包结构就是这页源码的骨架

完整版的“设计”体现在包结构上。一个合理的家电商城后端,会按模块而不是按 controller/service/dao 这种技术层来分包,因为后者在代码量大了以后,改一个订单功能要跨五个目录。

com.example.mall ├── common # 统一返回、异常、工具类 ├── config # 拦截器、Redis 配置、线程池 ├── module │ ├── user # 用户注册/登录/收货地址 │ ├── product # 商品:SPU/SKU/分类/品牌 │ ├── cart # 购物车 │ ├── order # 订单、订单明细、售后 │ ├── stock # 库存锁定、回补、入库单 │ └── pay # 支付单、回调、对账 └── job # 超时未支付关单、自动确认收货

按模块分包后,商品模块内部才有自己的 controller、service、mapper 子包,改动边界清晰。排查问题时打开 module 目录,按名字就能定位,不需要满项目搜索。这也是“完整版”和“脚手架”的区别:脚手架给的是技术骨架,完整源码给的是能支撑买卖双方的业务模块划分。

3. 商品模型:把家电规格属性落进 SPU/SKU 与库存表

3.1 家电为什么必须拆 SPU 和 SKU

卖书只需要一本书对应一个 ISBN,卖家电不行。同一款电视可以选择屏幕尺寸、颜色、能效等级、安装方案,用户下单时买的是这些维度的具体组合。此类结构被称为 SPU(Standard Product Unit)和 SKU(Stock Keeping Unit):SPU 是“货”,SKU 是“能卖的那一件”。

这个拆分对库存的意味很明显:库存记在 SKU 上,不记在 SPU 上。一款电视有 55 英寸和 65 英寸两个 SKU,55 缺货不应拦截 65 的加购。很多初学源码把库存做成 SPU 级整数字段,表面看简化了,实际下单时不校验规格,结算价格也取不到对应型号。在完整版家电商城设计里,“库存一定在 SKU 上”是第一原则。

3.2 建表:SPU、SKU 与规格 JSON

完整版会把 SPU 和 SKU 拆成两张表,规格属性用 JSON 存放在 SKU 上,不做几十个定死字段。这样加属性不用改表结构,查询时对高频属性做精确匹配即可:

CREATE TABLE spu ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL COMMENT '商品标题,如 某某品牌 65英寸液晶电视', category_id BIGINT NOT NULL COMMENT '三级类目ID', brand_id BIGINT DEFAULT NULL COMMENT '品牌ID', main_image VARCHAR(500) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT '商品标准单元'; CREATE TABLE sku ( id BIGINT NOT NULL AUTO_INCREMENT, spu_id BIGINT NOT NULL, spec_json JSON NOT NULL COMMENT '规格值,如 {"颜色":"银色","能效":"一级","尺寸":"65英寸"}', price_cent INT NOT NULL COMMENT '价格,单位分', stock INT NOT NULL DEFAULT 0 COMMENT '可售库存', sold INT NOT NULL DEFAULT 0 COMMENT '累计销量,列表排序用', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除,1已删 0未删', PRIMARY KEY (id), KEY idx_spu (spu_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT '库存单元';

价格用INT存“分”,是交易系统里很常见的做法,浮点运算在优惠分摊时有精度问题,整数分在 Java 里也方便调试。逻辑删除deleted配合 MyBatis-Plus 的logic-delete-field配置,删 SKU 走更新而不是物理删除,避免订单明细关联不到历史商品。建表脚本显式写utf8mb4,否则特殊符号在部分 MySQL 8 的默认配置下会报字符集错误。

3.3 商品列表查询:从缓存兜底到条件过滤

商品详情页是典型的读多写少,完整版会在 Mapper 之上加一层 Redis 缓存。缓存粒度有讲究:SPU 标题和图片这类静态信息可以长期缓存,价格和库存必须读实时值,否则会出现下单时才发现价格对不上的尴尬。下面这段 XML 是规格条件下的 SKU 列表查询,也是家电筛选页的核心 SQL:

<select id="listSkuByCondition" resultType="com.example.mall.module.product.vo.SkuVO"> SELECT s.id, s.spu_id, s.spec_json, s.price_cent, s.stock FROM sku s INNER JOIN spu p ON s.spu_id = p.id WHERE p.category_id = #{categoryId} AND p.brand_id = #{brandId} AND s.stock > 0 AND s.deleted = 0 ORDER BY s.sold DESC, s.id DESC LIMIT #{offset}, #{pageSize} </select>

条件里用s.deleted = 0过滤逻辑删除商品,用s.stock > 0把无货 SKU 直接排除,避免用户选到不可售的规格。排序用s.sold DESC而不是价格,家电的价格敏感度比快消品低,销量更容易吸引点击。LIMIT #{offset}, #{pageSize}是简单分页写法,SKU 规模到百万级后再换成WHERE s.id > #{lastId} LIMIT #{pageSize}的键集分页,SQL 主体仍是这一套。

提示:用 JSON 存规格后,如果硬要按“能效等级”筛选,MySQL 8 支持JSON_CONTAINS(spec_json, '"一级"'),但会失去索引优化空间。SKU 在几十万以内,更稳的做法是 Java 层过滤或加一层内存缓存。

4. 订单闭环:家电下单、锁定库存与支付回调

4.1 从购物车提交到订单主表的链路

加购场景可以只操作 Redis,但提交订单必须落库。浏览器传来购物车里的一组 SKU ID 后,后端先按用户维度查回购物车,校验商品是否下架;第二步读 SKU 当前价格,注意不能用购物车的快照价格,避免用户停留较久后价格已变;第三步调用库存服务锁定库存;全部通过后生成订单主表和明细表,订单状态设为UNPAID,最后把已下单的购物车条目删掉。

4.2 库存锁定:用一条 UPDATE 解决并发问题

库存扣减最忌讳的做法是“先SELECT stock,在 Java 里判断大于 0,再UPDATE”。两个并发请求同时读到 stock=5,各自判断都能通过,最后把库存扣成负数。正确处理是让数据库保证原子性,在 UPDATE 语句里把“剩余库存足够”作为条件:

<update id="deductStock"> UPDATE sku SET stock = stock - #{quantity}, sold = sold + #{quantity} WHERE id = #{skuId} AND stock >= #{quantity} AND deleted = 0 </update>

这条语句的成功与否看影响行数,update 返回 0 说明库存不足或 SKU 已删,业务层直接抛异常。stock = stock - #{quantity}是原子自减,InnoDB 在 UPDATE 时会自动锁行,并发正确性交给数据库。如果业务要求“下单预留、支付后才真正扣减”,可以再加一个lock_stock字段,同样的思路:SET stock = stock - #{quantity}, lock_stock = lock_stock + #{quantity},订单超时取消时把两列加回去。

4.3 支付回调:幂等、金额校验、状态推进

“完整版”和“能演示版”在支付回调这里的差别最大。能演示的代码收到回调就改订单状态为已支付,完整版必须处理:重复通知、伪造回调、回调金额与订单应付不一致。

@Transactional(rollbackFor = Exception.class) public void handlePayNotify(String orderNo, Integer paidAmount) { // 1. 用订单号做 Redis 锁,防止同一笔回调并发进入 Boolean locked = redisTemplate.opsForValue() .setIfAbsent("pay:notify:" + orderNo, "1", 10, TimeUnit.SECONDS); if (!Boolean.TRUE.equals(locked)) { return; } try { OrderDO order = orderMapper.selectByOrderNo(orderNo); // 2. 幂等:订单已经越过待支付状态,不再重复处理 if (!"UNPAID".equals(order.getStatus())) { return; } // 3. 金额校验:单位统一为分,不一致直接驳回 if (!order.getPayAmount().equals(paidAmount)) { log.warn("支付金额不匹配 orderNo={}, expect={}, actual={}", orderNo, order.getPayAmount(), paidAmount); return; } order.setStatus("PAID"); order.setPaidAt(LocalDateTime.now()); orderMapper.updateById(order); } finally { redisTemplate.delete("pay:notify:" + orderNo); } }

三个关键点能记很久:幂等不靠支付平台保证,靠状态机自己挡;金额校验不信任回调参数,信任订单里存的应付值;Redis 锁这里防的是两个相同回调同时打到服务里,第二次进入看到状态已经不是UNPAID就直接返回。这段逻辑如果不写完整,支付成功率看着高,偶发的重复回调会把一个订单处理两遍。

4.4 订单状态机:用一张表把闭环串起来

家电订单状态不能只靠一个字符串,完整版会定义好状态机和允许的跳转,否则售后阶段会出现从“待支付”直接到“已完成”的脏数据。最小状态集如下:

当前状态事件跳转状态涉及操作
UNPAID支付回调成功PAID记录支付时间,通知仓库
PAID发货SHIPPED写物流单号,可选短信通知
SHIPPED用户确认收货COMPLETED释放预占积分
PAID / SHIPPED申请退货REFUNDING冻结退款单,等仓储验货
UNPAID定时任务超时CLOSED回补锁定库存

状态机里最容易漏的是REFUNDING -> 回补库存这一步。售后退货不是把订单删掉,而是把卖出的数量退回 SKU 库存池,同时把销量扣回去,否则首页销量只增不减。完整版源码通常把这类过渡写进 service 方法而不是散落在 controller,后接手的人看一眼状态表就能知道当前卡在哪个环节。

5. 家电商城源码的启动顺序与上线前自检清单

5.1 按这个顺序把环境搭起来

这套 Java 技术栈落地到新环境,常见做法是五步走:先装 MySQL 8 和 Redis 7,再导入 SQL 初始化脚本,接着改 application.yml 里的数据源,然后 Maven 打包启动,最后用 curl 验证链路。下面这组命令可以直接用:

# 打包,跳过测试避免因缺测试数据失败 mvn clean package -DskipTests # 启动,--spring.profiles.active 指定环境,默认读 application-dev.yml java -jar target/mall-1.0.0.jar --spring.profiles.active=dev # 验证商品查询接口 curl "http://localhost:8080/api/product/sku?spuId=1001&page=1" # 加购前先确认登录态,购物车接口依赖用户上下文 curl -H "Content-Type: application/json" \ -d '{"skuId":1001,"quantity":1}' \ http://localhost:8080/api/cart/add

启动参数--spring.profiles.active=dev会覆盖配置文件里的默认值,环境隔离是完整版常见的做法,生产用 prod profile,不把生产库地址写死在源码里。curl 验证先走商品接口,确认数据库、Redis、MyBatis 全部就绪,再走加购;不要在还没登录时就调用下单接口,否则会被鉴权拦截,得出“代码有问题”的错误结论。

5.2 上线前把这张表过一遍

完整版源码一般不会在应用启动时自动建库建表,而是提供独立的初始化 SQL 脚本,手动导入这一步是默认动作。正式联调前,按下面这张表逐项核对:

检查项验证方式常见失败原因
数据库版本SELECT VERSION()SQL 用了 MySQL 8 新语法,在 5.7 上报错
数据源密码看启动日志是否出现连接成功密码含特殊字符未转义或漏改
Redis 连通redis-cli ping密码配置漏在 yml 里
定时关单造一个 UNPAID 订单等 30 分钟主类漏加 @EnableScheduling
支付回调模拟手动 POST 一笔金额错误的回调未做金额校验,订单状态被改

定时关单和支付回调这两项,最好在联调时各造一次真实数据:把订单created_at改成 30 分钟前,重启服务或者等定时任务跑完,看锁定库存有没有回补;再给支付回调接口发一笔金额对不上的请求,确认订单状态不会被推进。完整版源码能跑通页面不算数,能跑通这几个边界才有底气自称“完整版”。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 18:35:19

富文本编辑器开发实战:从技术选型到性能优化的完整指南

做内容管理后台&#xff0c;十个项目里九个逃不开“做个编辑器”这个需求。标题就两个词“editor”&#xff0c;看起来简单&#xff0c;但实际上这个需求背后藏着大量的技术决策和工程坑。作为一个折腾过好几代内容编辑器的前端老手&#xff0c;我把从需求梳理到最终落地的完整…

作者头像 李华
网站建设 2026/9/15 18:32:46

Fast-LIO2激光SLAM核心原理与工业级调优实战

1. 这不是“看懂代码”而是“吃透激光SLAM闭环逻辑”的实战拆解Fast-LIO2不是一段能靠CtrlC/V跑起来的示例程序&#xff0c;它是一套把激光雷达点云、IMU高频运动状态、紧耦合优化器、李代数微分更新全部拧成一股绳的精密系统。我第一次在古月居视频里看到它实时建图帧率稳定在…

作者头像 李华
网站建设 2026/9/15 18:29:37

Three.js 实现 3D-Gaussian-Splatting 的完整实践路径

简介&#xff1a;本资源是一套基于Three.js实现3D-Gaussian-Splatting算法的Web端三维重建实战项目&#xff0c;面向前端工程师、计算机视觉初学者及WebGL图形开发爱好者&#xff0c;解决在浏览器中轻量级部署高斯溅射三维重建的技术落地难题。压缩包共83个文件&#xff0c;含5…

作者头像 李华
网站建设 2026/9/15 18:29:24

Zettlr 图片渲染机制详解:从 Markdown 语法到所见即所得预览

Zettlr 图片渲染机制详解&#xff1a;从 Markdown 语法到所见即所得预览 【免费下载链接】Zettlr Your One-Stop Publication Workbench 项目地址: https://gitcode.com/GitHub_Trending/ze/Zettlr Zettlr 是一个以 Markdown 为核心的"一站式出版工作台"&…

作者头像 李华