电商系统毕业设计:从单体架构到高内聚模块的实战拆解
摘要:许多学生在完成“电商系统毕业设计”时,常陷入功能堆砌、架构混乱、代码耦合度高的困境,导致系统难以扩展或演示。本文以技术科普视角,系统讲解如何基于领域驱动设计(DDD)划分核心模块(商品、订单、支付),选用轻量级技术栈(Spring Boot + MyBatis + Redis)实现高内聚低耦合结构,并通过幂等性控制与事务管理保障数据一致性。读者将掌握可落地的工程化开发流程,显著提升系统健壮性与答辩表现力。
1. 背景痛点:学生项目“三座大山”
毕设电商系统最容易被导师三连问:
- 下单扣库存成功,却忘了写事务,重启后库存对不上——无事务控制。
- 用户手抖多点一次“提交订单”,数据库里多两条订单——接口无幂等。
- 商品、订单、支付代码揉在一个
service类里,改一行牵动全身——模块紧耦合。
这三座大山让演示现场秒变翻车现场。“能跑”≠“能毕业”,我们需要一套可落地的工程套路。
2. 技术选型对比:别让“重武器”拖垮进度
| 维度 | Spring Boot + MySQL | Django + SQLite |
|---|---|---|
| 学习曲线 | Java 系学生更熟,注解式开发上手快 | Python 语法简洁,但 ORM 抽象高,调试绕 |
| 事务完整 | MySQL InnoDB 支持本地/分布式事务,导师认可度高 | SQLite 只支持库级写锁,高并发下锁表 |
| 生态插件 | MyBatis-Plus、Redisson、ShardingSphere 毕设够用 | Django 插件多,但性能调优资料少 |
| 部署成本 | 阿里云 1C2G 能跑 Jar,Docker 镜像 100M 内 | SQLite 零配置,但多人演示时文件锁易冲突 |
结论:毕设周期 8-10 周,选Spring Boot + MySQL最稳;SQLite 仅适合本地原型。
【图:技术选型思维导图】
3. 核心实现细节:订单创建流程(含事务+状态机)
3.1 流程总览
- 用户提交
CreateOrderDTO - Controller 层做参数校验 + 幂等令牌校验
- Service 层开启本地事务,顺序执行:
- 锁定库存(
stock_lock表插入,状态DEDUCTING) - 创建订单(
order表插入,状态INIT) - 发送延迟消息(Redis ZSET,30 min 后关单)
- 锁定库存(
- 事务提交后,异步更新库存为
DEDUCTED;若异常则回滚,库存记录消失,保证最终一致性
3.2 状态机防“中间态”迷失
订单状态枚举:
public enum OrderState { INIT, // 初始 PAID, // 已支付 CANCELLED // 已取消 }状态流转只允许:INIT -> PAID | CANCELLED,其他抛IllegalStateException,杜绝脏数据。
3.3 本地事务模板
Spring 的@Transactional足够毕设场景,别一上来就 Seata,把问题想复杂:
@Transactional(rollbackFor = Exception.class) public Long createOrder(Long uid, List<OrderItemDTO> items, String idempotentKey){ // 1. 幂等判断 if(redis.setnx(idempotentKey,"1",Duration.ofSeconds(30))-spinGet()==0){ throw new BizException("订单已提交,请勿重复点击"); } // 2. 扣库存 items.forEach(i-> stockService.lockStock(i.getSkuId(),i.getQuantity())); // 3. 写订单 Order order = Order.create(uid,items); orderMapper.insert(order); // 4. 延迟关单消息 redis.zadd("order_timeout",Instant.now().plusSeconds(1800),order.getId()); return order.getId(); }4. 完整代码示例(Clean Code 版)
只列核心三层,其余 GitHub 自取。所有异常转译后返回
Result<T>,Controller 不再写 try-catch。
4.1 Controller
@RestController @RequiredArgsConstructor @RequestMapping("/order") public class OrderController { private final OrderService orderService; /** * 创建订单接口,幂等令牌由前端传入 UUID */ @PostMapping public Result<Long> create(@RequestBody @Valid CreateOrderDTO dto, @RequestHeader("Idempotent-Token") String token){ Long orderId = orderService.createOrder(dto.getUserId(), dto.getItems(), token); return Result.success(orderId); } }4.2 Service
@Service @RequiredArgsConstructor public class OrderService { private final OrderMapper orderMapper; private final StockService stockService; private final StringRedisTemplate redis; @Transactional(rollbackFor = Exception.class) public Long createOrder(Long userId, List<OrderItemDTO> items, String idempotentKey){ // 幂等控制 String key = "order:uid:" + userId + ":token:" + idempotentKey; Boolean absent = redis.opsForValue().setIfAbsent(key, "1", Duration.ofSeconds(30)); if (Boolean.FALSE.equals(absent)) { throw new BizException(ResultCode.REPEAT_REQUEST); } // 领域模型创建订单 Order order = Order.create(userId, items); // 锁定库存 items.forEach(i -> stockService.lockStock(i.getSkuId(), i.getQuantity())); // 持久化 orderMapper.insert(order); // 延迟关单 long score = Instant.now().plusSeconds(30 * 60).toEpochMilli(); redis.opsForZSet().add("order_timeout", order.getId().toString(), score); return order.getId(); } }4.3 Mapper(MyBatis-Plus)
@Mapper public interface OrderMapper extends BaseMapper<Order> { // 无需写 CRUD,MP 已代理 }小提示:用
BaseMapper省 50% 代码量,但复杂查询仍写 XML,保持 SQL 可控。
5. 性能与安全:别让“小水管”泄洪
缓存穿透
商品热点查询先走 Redis,miss 后布隆过滤器拦截非法 SKU,防止请求打到 DB。密码存储
用户模块直接用BCryptPasswordEncoder,强度 10 足够,别自己拼 MD5。接口限流
基于 Redis + Lua 脚本令牌桶,每秒 20 次,超了返回429,低成本护住服务器。前后分离
所有接口返回统一Result<T>,状态码与 HTTP 状态分离,前端好判断,后端好日志。
【图:安全模块分层示意图】
6. 生产环境避坑指南(血泪版)
- 配置外置:把
application.yml里数据库密码、Redis 地址抽到环境变量,Docker 启动时-e注入,避免源码泄露。 - 日志可追踪:使用
%X{traceId}打印同一请求链路,ELK 可搜,答辩时也能秒级定位 bug。 - 并发竞争态:库存扣减在 DB 层加乐观锁
version字段,高并发下update ... where version = ? and stock > 0,失败重试三次。 - 定时任务幂等:关单 Job 加 Redis 分布式锁
SET lock:close:orderId NX EX 30,防止集群多实例重复取消。 - 别让 Tomcat 默认线程背锅:
server.tomcat.max-threads=200足够演示,压测时再调,别一上来就 1000。
7. 留给你的思考题
如何给上述订单接口补充单元测试?
提示:使用@DataJpaTest+@AutoConfigureTestDatabase(replace = NONE)嵌入 H2,回滚事务即可重复执行。动手实现库存扣减的幂等接口:
要求:同一个requestId多次调用,库存只扣一次,且返回相同结果。试试把requestId与库存流水做唯一索引约束。
把这两个小任务搞定,你会发现系统不仅“能跑”,还“好测”,答辩时面对导师的“并发怎么测”问题,也能稳稳接招。祝你毕业设计一遍过,早日把项目部署到简历上!