基于SpringBoot的汉服租赁系统毕设:高效率开发与性能优化实战
一、背景痛点:毕设里那些“跑不动”的代码
去年辅导学弟做汉服租赁系统,初版一上线就卡成 PPT:首页加载 5 s、下单接口 3 s、并发 20 就 502。我把代码拉下来一看,典型“学生味道”十足:
- 订单列表 for 循环查用户、查库存,N+1 查询把数据库堵死
- 支付回调用 Thread.sleep 轮询,同步阻塞把 Tomcat 线程池吃光
- 每个 Controller 里 copy 同一套参数校验,重复代码上千行
- 库存字段无索引,where costume_id = ? 全表扫描 3 万条记录
这些问题不解决,功能再花哨也扛不住答辩现场的“多用户演示”。于是我把优化目标拆成两条:开发效率——一周迭代一个版本;运行效率——接口 RT < 200 ms、并发 200 无错误。下面把完整踩坑与提速过程按模块摊开,方便直接抄作业。
二、技术选型:为什么不是 JPA、不是 Memcached?
- SpringBoot:脚手架最成熟,IDEA 一键生成,插件市场丰富,省掉 70% 配置
- MyBatis-Plus:
- 比 JPA 更直观,写复杂 SQL 不头疼
- 内置分页、乐观锁、主键自动生成,减少重复代码
- Redis:
- 单线程模型,Lua 脚本保证原子性,库存扣减比 MySQL 行锁轻量
- 支持 key 过期,天然防缓存穿透
- 放弃 Memcached 的原因:数据结构单一,无法执行 Lua;放弃 JPA 的原因:懒加载容易 N+1,调优需要改全局 fetch 策略,对学生不友好
一句话总结:让每一行代码都“看得见 SQL、看得见缓存”,出了问题能 5 分钟内定位。
三、核心实现:三个高频场景的效率改造
3.1 订单创建的幂等性设计
需求:用户双击“立即租赁”不会重复下单。
方案:前端生成 UUID,后端用 Redis SETNX 做分布式锁,key=order:{userId}:{uuid},过期 5 s,防止宕机死锁。
流程:
- 进入 OrderService.createOrder() 先 setIfAbsent
- 返回 false 直接抛 DuplicateSubmitException
- 提交成功立即 deleteKey,释放锁
3.2 库存扣减的并发控制
库存表 costume_stock 字段:id、costume_id、available、version
利用 MyBatis-Plus 乐观锁插件:
@Version private Integer version;更新 SQL:
update costume_stock set available = available - ?, version = version + 1 where costume_id = ? and version = ?更新返回 0 表示并发冲突,后台重试 3 次,仍失败则提示“库存不足”。
压测 200 并发零超卖,QPS 从 120 提到 950。
3.3 基于 CompletableFuture 的异步通知
支付成功后需:
- 发短信 2. 发邮件 3. 刷新缓存
如果串行执行,RT 直接 +600 ms。
改造:
CompletableFuture.allOf( smsFuture, emailFuture, cacheRefreshFuture ).orTimeout(2, TimeUnit.SECONDS);Tomcat 线程立即返回,三个任务丢进自定义线程池(核心 8,最大 16),接口 RT 稳定在 80 ms 以内。
四、Clean Code 示例:带注释的关键片段
@RestController @RequiredArgsConstructor @RequestMapping("/order") public class OrderController { private final OrderService orderService; private final StringRedisTemplate redisTemplate; /** * 创建订单 * 1. 防重提交 * 2. 库存扣减 * 3. 异步通知 */ @PostMapping public ApiResult<Long> create(@Valid @RequestBody CreateOrderDTO dto) { // 1. 幂等校验 String key = "order:" + dto.getUserId() + ":" + dto.getUuid(); Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofSeconds(5)); if (Boolean.FALSE.equals(absent)) { throw new BizException("订单已提交,请勿重复点击"); } // 2. 业务处理 Long orderId = orderService.createOrder(dto); // 3. 异步通知 notifyAsync(dto.getMobile(), dto.getEmail(), orderId); return ApiResult.success(orderId); } private void notifyAsync(String mobile, String email, Long orderId) { CompletableFuture.runAsync(() -> smsService.send(mobile, orderId)); CompletableFuture.runAsync(() -> emailService.send(email, orderId)); } }Service 层同样保持“一个方法一件事”,超过 20 行就继续拆,读代码像读故事,答辩老师一眼看懂。
五、性能与安全:压测、脱敏、防注入
JMeter 压测
场景:200 线程,每线程 20 次下单,Ramp-up 10 s
结果:- 平均 RT 92 ms
- 95% RT 150 ms
- 错误率 0%
- 服务器 CPU 占用 42%,内存 1.2 G
SQL 注入
MyBatis-Plus 自带 #{} 预编译,额外开启全局 SQL 注入过滤器,阻断 "union"、"script" 关键字。敏感数据脱敏
日志、返回体统一用 Jackson 脱敏序列化器:@JsonSerialize(using = MaskSerializer.class) private String idCard;输出 371***********1234,防止泄露。
六、生产环境避坑指南
事务边界误用
在 Service 里把“库存扣减”与“消息投递”包在同一事务,一旦消息队列超时回滚,库存被重复恢复。解决:事务只包住本地数据库,消息表采用本地消息表+定时任务,最终一致性。Redis 缓存穿透
空值也缓存,设置 30 s 过期;布隆过滤器预加载热门商品 ID,拦截 99% 非法 key。冷启动延迟
SpringBoot 默认懒加载注解扫描,首次请求才加载 Bean。开启:spring: main: lazy-initialization: false并在 DockerFile 里加
RUN java -Dexit.on.init=true -jar app.jar做一次预热,容器启动即完成类加载,用户第一次访问不再“卡顿 2 s”。线程池配置
默认线程池队列无限,高峰期 OOM。按“核心线程=CPU 核数+1,队列长度=200,拒绝策略=CallerRuns”设置,宁可降级也不把内存打爆。
七、写在最后:毕设周期有限,效率与功能如何兼得?
两周需求、一周联调、一周写论文,是大多数本科毕设的现实。我的经验是:
- 先跑通黄金链路(下单、支付、归还),其余功能按“能异步就异步、能缓存就缓存”原则迭代;
- 每完成一个模块立刻写单元测试与 JMeter 脚本,性能回退立即发现;
- 代码合并前强制走一遍“IDEA 代码检查+阿里规约插件”,把潜在 N+1、空指针全扫光。
汉服租赁系统虽小,但把“分层+缓存+异步+压测”这套组合拳练熟,就能套用到任何高并发业务。
如果你已经写完初版,不妨挑最慢的一个接口,按本文思路重构:
- 把 SQL 拉到控制台 explain 一下
- 把重复代码提成公用方法
- 把同步调用换成 CompletableFuture
亲手把 RT 从 3 s 压到 100 ms 的那一刻,你会对“效率”二字有真金白银的体会。祝你答辩顺利,也欢迎把测试结果留言交流,一起把毕设做成能上线运营的真系统。