宠物寄养系统毕业设计:从零构建一个高可用的入门级后端架构
2026/5/9 4:39:46 网站建设 项目流程


背景痛点:为什么“能跑”≠“能毕业”

每年答辩季,老师最爱问的三句话:

  1. “如果两个人同时下单最后一只笼子,你怎么保证不超卖?”
  2. “订单状态是‘已支付’,但库位没锁住,寄养师却点了‘拒绝’,数据对不上,谁背锅?”
  3. “演示时明明点了支付,刷新页面又回到‘待支付’,这是特性还是 Bug?”

大多数同学把功能点堆完就以为大功告成:界面能跑、数据库有数据、PPT 做得飞起。结果现场一并发,并发问题、事务缺失、状态漂移全暴露。老师一句话:“代码健壮性不够,工作量不饱满”,直接打回重做。

痛点总结:

  • 没有事务边界,支付成功写库失败,订单卡在“中间态”。
  • 订单状态靠 if-else 硬编码,新增一个“退款中”状态要改七八个类。
  • 接口不做幂等,用户双击提交就生成两条订单,老板看了直摇头。
  • 测试数据随手插,主键 ID 写死,现场演示一重启全乱套。

想一次过关,得把“能跑”升级为“可靠”。下面给出一条“新手也能复制”的落地路线。

技术选型:Spring Boot 为什么更适合“小白”快速出活

毕业设计周期通常 8-10 周,还要留时间写论文、做 PPT。选技术栈的第一指标是:开发效率 + 身边能问到人

框架上手机器成本生态成熟度打包部署身边可问到的“学长”数量
Django低,但 Python 语法对纯 Java 课背景学生有切换成本需要额外解决静态文件收集
Node.js(Koa/Nest)中,异步思维门槛高,毕设答辩老师多数看不懂 Promise 链单文件可跑,但 TS 编译、部署脚本要自己写一般
Spring Boot高,但国内教材、网课、身边代码库全是 Spring 系列;IDEA 一键生成工程极高直接 java -jar,内嵌 Tomcat,服务器只需装 JRE极多

结论:Spring Boot 不是最“潮”,却是最能在有限时间内让你把“事务、状态机、安全、部署”一条龙跑通,而且老师、学长、GitHub 样例一搜一大把,出了问题能搜到答案,比炫技更重要。

核心实现:用户-宠物-订单三元模型 + 状态机

1. 业务实体关系

  • 一个用户(User)可拥有多只宠物(Pet)。
  • 一个用户可下多个订单(Order),但一个订单只寄养一只宠物。
  • 订单与笼位(Cage)多对一:一个笼位在同一时段只能被一个订单占用。

ER 图简化如下:

2. 订单状态机——别写 if-else,用枚举 + 状态模式

订单状态一共 5 个:

  • WAIT_PAY待支付
  • PAID已支付
  • BOARDING寄养中
  • FINISHED已完成
  • CANCE_refund退款中(扩展备用)

状态迁移规则:

  • 只有WAIT_PAY能到PAID
  • 只有PAID能到BOARDING
  • 只有BOARDING能到FINISHED

代码里用枚举把迁移规则写死,避免魔法值:

public enum OrderState { WAIT_PAY { @Override public OrderState pay() { return PAID; } @Override public OrderState cancel() { return CANCELLED; } }, PAID { @Override public OrderState start() { return BOARDING; } }, BOARDING { @Override public OrderState finish() { return FINISHED; } }, FINISHED, CANCELLED; public OrderState pay() { throw newIllegalTransition(); } public OrderState cancel() { throw newIllegalTransition(); } public OrderState start() { throw newIllegalTransition(); } public OrderState finish() { throw newIllegalTransition(); } private IllegalStateException newIllegalTransition() { return new IllegalStateException("非法状态迁移"); } }

Service 层只关心业务动作,不盲写 if:

order.setState(order.getState().pay()); // 直接调用对应方法

3. 关键代码:下单接口(含事务 + 幂等)

需求:同一用户、同一宠物、同一时段,只能存在一笔待支付订单。

实现要点:

  1. 数据库层给(user_id, pet_id, start_date, end_date, state)建联合唯一索引,状态为WAIT_PAY时才算冲突。
  2. 程序层用 Redis 分布式锁(或简单synchronized本机锁)兜底,防止 1 秒内并发打到不同 JVM。
  3. Service 方法加@Transactional,保证“扣减笼位库存 + 写订单”原子性。
  4. 前端传入幂等令牌idempotentToken,后端用INSERT ... ON DUPLICATE KEY UPDATE避免重复写。

代码片段(精简):

@RestController @RequiredArgsConstructor @RequestMapping("/api/orders") public class OrderController { private final OrderService orderService; @PostMapping public ApiResult<Long> create(@RequestBody CreateOrderRequest req, @RequestHeader String idempotentToken) { Long orderId = orderService.createOrder(req, idempotentToken); return ApiResult.success(orderId); } } @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepo; private final CageRepository cageRepo; @Transactional public Long createOrder(CreateOrderRequest req, String token) { // 1. 幂等校验:令牌已存在则直接返回 Optional<Order> exist = orderRepo.findByIdempotentToken(token); if (exist.isPresent()) { return exist.get().getId(); } // 2. 库存预扣 Cage cage = cageRepo.findByIdWithLock(req.getCageId()) .orElseThrow(() -> new BizException("笼位不存在")); if (cage.getAvailable() <= 0) { throw new BizException("笼位已满"); } cage.decrement(); // 乐观锁版本号在 XML 里写 UPDATE ... SET available = available -1, version = version +1 ... // 3. 构建订单 Order order = Order.builder() .userId(req.getUserId()) .petId(req.getPetId()) .cageId(req.getCageId()) .startDate(req.getStartDate()) .endDate(req.getEndDate()) .state(OrderState.WAIT_PAY) .idempotentToken(token) .build(); orderRepo.save(order); return order.getId(); } }

注意:

  • findByIdWithLockSELECT ... FOR UPDATE把笼位行锁提前,避免两个订单同时读到“剩余 1 个”。
  • 事务范围只包住“库存扣减 + 订单写入”,支付回调、消息通知另起事务,减少锁时间。
  • 幂等令牌建议用前端 UUID,放在 Header,防重复提交。

性能与安全:把“老师随口一问”提前解决

1. 冷启动延迟

Spring Boot FatJar 第一次解压、JVM 字节码验证、MyBatis 映射扫描,30 秒起步。演示现场重启一次,台下老师开始刷手机。解决:

  • 本地演示用 Spring Boot 的spring-context-indexer提前建索引,启动缩短 20%。
  • 服务器用java -server -XX:TieredStopAtLevel=1 -noverify先快速启动,再换成正常参数。
  • 把 jar 放/dev/shm内存盘,磁盘 IO 瓶颈消失。

2. SQL 注入

MyBatis 只写#{}占位符,不用${}字符串拼接;额外给 MySQL 账号开SELECT/INSERT/UPDATE权限,禁止DROP权限,即使代码有漏也炸不掉库。

3. 敏感信息脱敏

  • 日志用logback-desensitize正则把手机号、身份证打码。
  • 返回给前端的 VO 用 Jackson 注解@JsonSerialize(using = MaskSerializer.class)统一脱敏,避免谁忘了在前端“*”号。

生产环境避坑指南:那些“本地能跑,上线就炸”的细节

  1. 服务器时区
    把 Docker 容器、MySQL、JVM 全设成Asia/Shanghai,写入docker-compose.yml

    environment: TZ: Asia/Shanghai

    否则“今天下单明天入住”算天数会少一天,账单金额对不上。

  2. 测试数据隔离
    application-test.yml指定spring.datasource.url=jdbc:mysql://.../pet_test,并给 Test 容器建独立 Schema。演示前一键flyway clean migrate,保证主键自增 ID 从 1 开始,PPT 截图与现场数据一致。

  3. 避免 N+1
    订单列表要展示宠物名称、笼位编号。直接在OrderMapper.xmlLEFT JOIN pet p ON o.pet_id = p.id LEFT JOIN cage c ON o.cage_id = c.id,一次把列带回来。切忌 for 循环里再查宠物。

  4. 日志级别
    线上开INFO即可,MyBatis SQL 日志别开DEBUG,否则高并发瞬间把磁盘打满。用async-appender异步写日志,请求线程不阻塞。

  5. 演示脚本
    提前准备demo.sh

    • 调用下单接口 3 次,生成 3 笔不同状态订单。
    • 调用支付模拟回调,把订单推到PAID
    • 调用“开始寄养”“完成寄养”把状态走到终态。 现场只需./demo.sh | jq,JSON 高亮输出,老师一看“接口流畅、状态正确”,印象分直接 +20。

下一步:把“可用”升级成“好用”

整个系统已满足毕业答辩的“功能 + 可靠”双要求。但如果想继续炫技,可以思考:

  • 微信小程序前端:用微信登录换取openid当用户标识,后端新增wx_login接口,统一User表即可。模板消息推送“宠物喂食视频”提醒,体验更闭环。
  • 引入 Redis 缓存:热点笼位库存、订单状态读多写少,用Redis + Lua脚本做库存预扣,MySQL 压力骤降;还能给接口加RateLimiter,防学生室友帮你“压测”。

把这两个点写进论文“展望”章节,老师会觉得你“有产业视角”,分数再提一档。


以上就是在校生版“宠物寄养系统”落地笔记。代码量确不多,但把事务、状态、并发、安全、部署这些“工程化”细节串成线,足以让毕设从“能跑”进化到“敢上线”。祝你一次答辩通关,早日把键盘收好去毕业旅行。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询