Java点餐系统毕业设计实战:从单体架构到高并发优化的完整实现

📅 发布时间:2026/7/8 14:21:48 👁️ 浏览次数:
Java点餐系统毕业设计实战:从单体架构到高并发优化的完整实现
Java点餐系统毕业设计实战从单体架构到高并发优化的完整实现摘要许多同学在毕设里把“点餐系统”做成“功能清单”却忽略了重复下单、超卖、会话混乱这些真实场景里的坑。本文用 Spring Boot MyBatis Redis 带你从 0 到 1 落地一套能扛 1000 并发、代码可读的完整项目并给出可直接粘贴的 Clean Code 片段。最后附生产避坑清单让你答辩时不再被问“如果并发上来怎么办”。1. 痛点先行为什么你的点餐系统一压就崩去年指导学弟做毕设他把“下单”写成两步查库存减库存本地 Postman 自测没问题JMeter 50 并发直接超卖 7 份黄焖鸡。问题根源无事务两步之间库存被别的线程改掉无幂等用户狂点按钮就产生 3 条订单无缓存菜品接口 QPS 200 就把 MySQL 打满先列三张“血泪表”后面代码直接对症下药。场景并发表现根因重复下单用户连点 3 次订单表 3 条记录前端未防抖 接口无幂等超卖库存剩 1却卖出 7 份读-写非原子登录态乱管理员突然变普通用户Session 共享缺失2. 技术选型Servlet 到 Spring Boot 的“进化史”很多教材还在讲 JSP Servlet毕业设计用它等于给自己挖坑手写 JDBC 连接池一不小心就内存泄漏过滤器鉴权if-else 套娃后期加微信登录想哭JSP 里写 SQL被 XSS 和 SQL 注入双重毒打直接对比维度传统 ServletSpring Boot启动速度拷 war 到 Tomcat 2 分钟java -jar5 秒依赖注入自己 new 对象Autowired一键装配事务控制手动 commit/rollbackTransactional一行搞定生态自己拼缓存、拼安全Spring 全家桶直接吃结论毕设不是考古用 Spring Boot 省下的时间足够你写 3 篇论文。3. 架构总览单体也能“高可用”先放一张脑图后面按模块拆代码。网关层Nginx 反向代理 限流应用层Spring Boot 单体内嵌 Tomcat缓存层Redis 集群毕设可单实例数据层MySQL 8.0读写分离分离主从可选4. 核心模块实战代码直接抄也能跑所有代码基于 Spring Boot 2.7 MyBatis-Plus Redis 6JDK 114.1 用户登录JWT 代替 Session痛点Session 在集群下需要复制毕设服务器只有 1 台但答辩老师会问“如果扩到 3 台怎么办”。RestController RequestMapping(/api/user) public class UserController { Resource private StringRedisTemplate redisTemplate; PostMapping(/login) public RString login(Valid RequestBody LoginForm form) { // 1. 校验账号密码 User user userService.lambdaQuery() .eq(User::getUsername, form.getUsername()) .one(); if (user null || !BCrypt.checkpw(form.getPassword(), user.getPassword())) { return R.fail(账号或密码错误); } // 2. 生成 JWT String token Jwts.builder() .setSubject(user.getId().toString()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) // 1 天 .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); // 3. 把 user 信息缓存到 Redis避免每次解析 token 都查库 redisTemplate.opsForValue() .set(user:cache: user.getId(), JSON.toJSONString(user), Duration.ofDays(1)); return R.ok(token); } }关键注释密码用 BCrypt不可逆JWT 只存用户 ID不存敏感信息Redis 缓存用户详情减少数据库往返4.2 菜品管理缓存击穿防护菜品读多写少典型缓存场景。先写查询逻辑Service public class DishService { Resource private RedisTemplateString, Object redisTemplate; public ListDishVO listWithCache(Long categoryId) { String key dish:cat: categoryId; ListDishVO list (ListDishVO) redisTemplate.opsForValue().get(key); if (list null) { // 双重检查锁防击穿 synchronized (this) { list (ListDishVO) redisTemplate.opsForValue().get(key); if (list null) { list this.lambdaQuery() .eq(Dish::getCategoryId, categoryId) .eq(Dish::getStatus, 1) // 在售 .list() .stream() .map(d - BeanUtil.copyProperties(d, DishVO.class)) .collect(Collectors.toList()); redisTemplate.opsForValue().set(key, list, Duration.ofMinutes(30)); } } } return list; } }注意缓存 30 分钟后台改菜立即redisTemplate.delete(key)清掉单体双重检查锁够用集群请用 Redis 分布式锁4.3 订单创建幂等 分布式锁防超卖订单接口是并发重灾区两步优化前端生成 UUID 作为幂等令牌后端用 Redis 分布式锁包住所减库存Service public class OrderService { Resource private RedissonClient redissonClient; Transactional(rollbackFor Exception.class) public Long createOrder(CreateOrderDTO dto) { // 1. 幂等校验 String idempotencyKey order:idemp: dto.getUuid(); Boolean absent redisTemplate.opsForValue() .setIfAbsent(idempotencyKey, 1, Duration.ofMinutes(5)); if (Boolean.FALSE.equals(absent)) { throw new BizException(订单正在处理中请勿重复提交); } // 2. 分布式锁按商品维度加锁降低粒度 RLock lock redissonClient.getLock(stock:lock: dto.getDishId()); try { if (!lock.tryLock(3, 5, TimeUnit.SECONDS)) { throw new BizException(系统繁忙请稍后再试); } // 3. 再次查库存悲观锁 Dish dish dishMapper.selectByIdForUpdate(dto.getDishId()); if (dish.getStock() dto.getQuantity()) { throw new BizException(库存不足); } // 4. 减库存 dish.setStock(dish.getStock() - dto.getQuantity()); dishMapper.updateById(dish); // 5. 写订单 Order order new Order(); order.setUserId(UserContext.getUserId()); order.setDishId(dto.getDishId()); order.setQuantity(dto.getQuantity()); order.setAmount(dish.getPrice() * dto.getQuantity()); this.save(order); return order.getId(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BizException(系统异常); } finally { lock.unlock(); } } }关键点Transactional与selectByIdForUpdate保证库存行锁Redisson 分布式锁看门狗 5 秒续期防业务执行超长幂等令牌 5 分钟过期既防重复又防脏数据长期占坑4.4 支付回调防重复通知微信/支付宝会多次回调必须做“通知去重”。PostMapping(/pay/notify) public String payNotify(HttpServletRequest request) { String orderId request.getParameter(out_trade_no); String key notify:deal: orderId; Boolean first redisTemplate.opsForValue() .setIfAbsent(key, 1, Duration.ofMinutes(10)); if (Boolean.FALSE.equals(first)) { return success; // 直接返回不重复处理 } // 修改订单状态、发货、记账... orderService.afterPay(Long.valueOf(orderId)); return success; }5. 高并发压测1 核 2 G 云主机也能扛 1000 QPS用阿里云 ecs.t5-lc1m2.small1 核 2 G MySQL 5.7 单实例 Redis 单实例JMeter 场景菜品查询 800 QPS缓存命中 95 %RT 20 ms下单 200 QPS线程 200错误率 0 %库存无超卖瓶颈与优化连接池默认 10提到 50数据库 CPU 从 90 % 降到 40 %接口限流Bucket4j 令牌桶 100/s超量直接返回“系统繁忙”保护后端日志异步logback-asyncIO 线程不再阻塞业务线程6. 生产环境避坑指南坑点现象解法SQL 注入MyBatis${}拼接被扫盲全部用#{} 参数化敏感信息裸奔日志打印手机号脱敏工具类DesensitizedUtil.phone()日志无链路多线程下找不到单次请求MDC 放入 UUID日志格式%X{traceId}大字段传输菜品图片 3 M 一并返回用 OSS 生成带签名的临时 URL热更新改配置重启丢会话配置放 Nacos监听刷新7. 下一步把单体拆成微服务用户、菜品、订单、支付四个服务独立用 Nacos 注册中心订单服务调用库存服务用 OpenFeign库存服务再独立成微服务加 Seata 分布式事务扫码点餐前端uni-app 一套代码同时编译成微信小程序 H5调用后端 REST 接口即可8. 小结答辩老师最爱问的三句话“并发上来怎么保证不超卖”——用 Redis 分布式锁 行锁“重复下单怎么办”——接口幂等令牌“登录集群怎么做”——JWT Redis 缓存把这三点讲透再甩出压测截图基本稳过。代码已开源在 GitHub搜索“java-order-graduation”拿去改包名就能交。祝你毕业顺利也欢迎把扫码点餐前端 PR 回来一起把毕设卷出新高度。