
网上讲宠物寄养系统的教程不算少但大多数代码例程到“生成订单”就停了后面的退租、结算、评价基本靠线下想象。真正把“寄养”和“退租”做成一个完整闭环、同时适合当计算机毕设或简历项目参考的反而少见。这篇来拆一个基于SpringBoot的宠物家庭式寄养系统——从需求分析到数据表设计从寄养下单到退租结算把整条业务线讲透。无论你是正在准备毕设的Java后端学习者还是想做一个C2C平台类项目练手这篇都能直接照着搭。1. 这个系统到底解决什么问题宠物寄养的业务场景与需求拆解1.1 宠物寄养的痛点为什么是“家庭式”而不是宠物店传统宠物店寄养的主要问题是环境陌生、猫狗集中、可能会产生应激反应。对不少宠物主人来说让宠物在类似家庭的环境里呆几天比关在笼子里踏实得多。家庭式寄养本质上是一种C2C模式有空闲时间和空间的家庭腾出一块区域接收宠物平台在中间做撮合和信任背书。这个场景放到系统里就产生了几类核心角色宠物主人找寄养家庭、下单、退租、评价。寄养家庭申请入驻、维护可接单状态、接单、确认退租。平台管理员审核寄养家庭入驻、监管订单、处理异常。不少学生一上来就把它理解成“宠物店的订单管理系统”只做简单的增删改查。这是个方向性错误。家庭式寄养的关键是“人”和“信任”不是“货”和“库存”。这一点会在后面的权限、审核、评价模块反复体现。1.2 标题里“寄养与退租一体化”到底指什么很多毕设项目把重心放在“下单”上订单状态做到“寄养中”就结束了退租变成线下行为系统里没有记录。所谓“一体化”是指从用户提交寄养订单开始一直到宠物归还、押金退回、订单关闭整个过程都能在系统内闭环完成。拆开来看两个动作缺一不可寄养选家庭、选宠物、选时间、算价格、支付、接单、进入寄养状态。退租用户发起退租、系统到期自动提醒、家庭确认归还、逾期费用计算、押金结算、评价。其中退租是很多初学项目最容易做含糊的部分。因为退租不是简单把订单状态改成“已完成”它牵扯到时间计算、扣费规则、结算明细这些恰恰是答辩时能跟老师讲清楚“业务逻辑完整”的加分点。1.3 一个贴近毕设的MVP需求清单如果从零开始做我建议只保留下面这些功能先保证闭环不急着堆功能。端侧功能模块说明用户端注册登录、宠物档案每种宠物需要单独的档案退租和评价都围绕宠物展开用户端寄养家庭列表、下单支付列表按评分、距离、可接单数量排序用户端订单查询、申请退租、评价订单状态实时可见寄养端入驻申请、家庭信息维护需要管理员审核寄养端接单、确认退租、查看结算接单后订单才进入真正的“寄养中”管理端寄养家庭审核、订单监控、用户管理审核通过后才能出现在用户端列表这个MVP清单已经足够支撑一篇合格的毕设论文和演示业务线完整、角色清晰、状态流转有复杂度但又不过度。后面所有技术设计都围绕这几项展开。2. 技术选型逻辑宁可“标准”也不要“炫技”2.1 为什么SpringBoot几乎是这类题目的标准答案看到一个“Java毕设宠物寄养系统”的标题第一反应大概率就是SpringBoot。原因很直接SpringBoot的自动配置和起步依赖把SSH时代一堆繁琐的XML配置全部抹平了新手拿到代码就能跑内嵌Tomcat让部署变成一条java -jar命令演示的时候不用经历服务器配置翻车的尴尬。更重要的是SpringBoot生态足够标准答辩时每个环节都有现成的成熟说法。比如自动装配、依赖注入、事务管理这些基础知识老师一听就知道你会不会。用冷门框架虽然显得有个性但风险是老师不熟悉很多设计决策解释起来费劲。毕设不是创新大赛稳定、可解释、业务完整比技术冷门重要得多。2.2 配套选型的“最小但完整”清单我给这套系统的推荐选型如下层次选型理由后端框架SpringBoot 2.7.x稳定资料多避免3.x某些变化ORMMyBatis-Plus单表CRUD基本不用写SQL自带分页和乐观锁插件数据库MySQL 8.0状态字段、时间字段处理方便权限框架Spring Security 或 Sa-Token角色型权限够用Sa-Token对新手更友好前端Vue 3 Element Plus配合管理端页面组件齐全视觉效果加分缓存/分布式锁Redis可选用在下单防并发重复属于亮点点缀如果前端不熟也可以用Thymeleaf模板渲染。但B/S类毕设通常管理端页面较多用Vue写起来更省力视觉效果也更好。这里我给一个个人偏好不要为了“技术完整”强行引入消息队列、微服务、分布式事务。一个宠物寄养系统根本没有这种并发量堆一堆中间件只会让系统变重答辩时反而被追问到答不上来。2.3 为什么“克制”本身也是一种设计我见过有同学把寄养系统做成订单服务、家庭服务、用户服务三个微服务还上了Nacos和OpenFeign。看起来技术栈很漂亮但演示时服务一停全崩排查问题要开三个终端。毕设项目的评审重点永远是“你能不能讲清楚自己的业务”而不是“你用了多少中间件”。把SpringBoot单应用做好把事务边界、状态机、定时任务做明白这套系统已经能超出大多数同题目的平均水平。3. 数据库设计三张核心表和一张结算表怎么撑起整个平台3.1 主表结构用户、宠物、寄养家庭数据库设计是这个项目的地基我建议表名都用t_前缀清晰且统一。核心表就三张CREATE TABLE t_user ( id bigint(20) NOT NULL AUTO_INCREMENT, nickname varchar(50) NOT NULL, phone varchar(11) NOT NULL, user_type tinyint(4) DEFAULT 1 COMMENT 1-普通用户 2-寄养家庭 3-管理员, audit_status tinyint(4) DEFAULT 0 COMMENT 0-未申请 1-审核中 2-通过 3-拒绝, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) COMMENT用户表;user_type我建议直接放在用户表里原因是这个项目里“寄养家庭”并不是一个独立的注册实体而是普通用户申请入驻后变身为寄养角色。audit_status专门用来标识寄养家庭入驻审核状态普通用户那边用不到。宠物档案表CREATE TABLE t_pet ( id bigint(20) NOT NULL AUTO_INCREMENT, owner_id bigint(20) NOT NULL COMMENT 主人关联t_user, pet_name varchar(30) NOT NULL, pet_type tinyint(4) DEFAULT 1 COMMENT 1-狗 2-猫 3-其他, breed varchar(50) DEFAULT COMMENT 品种, age int(11) DEFAULT NULL COMMENT 月龄, health_status varchar(255) DEFAULT COMMENT 疫苗/驱虫情况, PRIMARY KEY (id) ) COMMENT宠物档案表;寄养家庭表CREATE TABLE t_home ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 属于哪个用户关联t_user, home_name varchar(50) NOT NULL COMMENT 家庭名称, address varchar(255) NOT NULL, latitude decimal(10,6) DEFAULT NULL, longitude decimal(10,6) DEFAULT NULL, max_pets int(11) DEFAULT 1 COMMENT 最多可同时接收宠物数, current_pets int(11) DEFAULT 0, service_desc text COMMENT 环境照片/服务描述, avg_score decimal(2,1) DEFAULT 5.0, status tinyint(4) DEFAULT 0 COMMENT 0-停用 1-启用, PRIMARY KEY (id) ) COMMENT寄养家庭表;经纬度字段是为了支持“按距离排序”的展示逻辑。毕设不需要接地图SDK只需要能算两个坐标之间的直线距离在SQL里用Haversine公式或直接用Math.sqrt近似排序都行。3.2 订单表的状态设计一个状态机解决所有边界订单表是核心中的核心它的状态字段决定了整个系统的流转逻辑CREATE TABLE t_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint(20) NOT NULL, pet_id bigint(20) NOT NULL, home_id bigint(20) NOT NULL, start_date date NOT NULL, end_date date NOT NULL, unit_price decimal(10,2) NOT NULL COMMENT 寄养日单价, total_amount decimal(10,2) NOT NULL COMMENT 寄养费增值服务费, deposit_amount decimal(10,2) NOT NULL DEFAULT 300.00 COMMENT 押金, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0-待支付 1-待接单 2-寄养中 3-待退租 4-已完成 5-已取消 6-已退款, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) COMMENT寄养订单表;状态流转严格按照下面的方向走正向0待支付 - 1待接单 - 2寄养中 - 3待退租 - 4已完成。取消0待支付或1待接单时可以取消到5已取消。退款异常情况下从2或3可以走到6已退款。为什么要单独设“待退租”这个中间态因为退租不是瞬间完成的。用户发起退租后寄养家庭还要确认宠物已经安全归还才算真正结束。如果直接把订单从“寄养中”改成“已完成”那么退租时间、逾期天数、结算明细这些数据就无处安放整个退租逻辑就塌了。3.3 退租结算表为什么不能把扣费直接写死在订单里我在不少项目里看到有人把退租扣费金额直接加到订单表的某个字段上。这样做不是不行但很不利于演示和追溯。我更推荐单独建一张结算表CREATE TABLE t_settlement ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL, actual_return_time date NOT NULL COMMENT 实际上归还时间, overdue_days int(11) DEFAULT 0 COMMENT 逾期天数, overdue_fee decimal(10,2) DEFAULT 0.00 COMMENT 逾期扣费, damage_fee decimal(10,2) DEFAULT 0.00 COMMENT 损坏赔偿, refund_amount decimal(10,2) NOT NULL COMMENT 实际退款金额, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) COMMENT退租结算表;这个设计的好处是每个订单都能对应一条独立结算记录原始金额和扣减明细都在。演示的时候点开一个已退租订单页面能同时展示订单信息和结算明细逻辑闭环一眼就能看出来。而且后续如果要加“押金是否原路退回”“管理员是否修改扣费金额”这些功能也不会动到订单主表。4. 寄养下单的完整链路从选择家庭到订单状态机的代码实现4.1 下单流程与费用计算规则下单流程从用户视角看很简单打开家庭列表 - 选日期 - 选宠物 - 选增值服务 - 支付。但到代码层面需要在同一个事务里完成校验、算价、生成订单三个动作。费用计算规则建议这样约定寄养费用 寄养天数 × 家庭日单价。寄养天数 ChronoUnit.DAYS.between(startDate, endDate) 1也就是说开始当天也算一天。这个约定要在前端和文档里写清楚否则用户会疑惑为什么1月1日到1月2日收了两天钱。增值服务费 每一项服务单独计费后累加比如加价洗澡、每日遛狗。押金固定300元退租时按规则扣除。核心代码如下Service public class OrderServiceImpl implements OrderService { Override Transactional(rollbackFor Exception.class) public Order createOrder(CreateOrderRequest request) { // 1. 校验家庭可接单状态 Home home homeMapper.selectById(request.getHomeId()); if (home null || home.getStatus() ! 1) { throw new BizException(寄养家庭不存在或已停运); } if (home.getCurrentPets() home.getMaxPets()) { throw new BizException(该家庭当前可接收宠物数已满); } // 2. 校验日期开始时间不能晚于结束时间 long days ChronoUnit.DAYS.between(request.getStartDate(), request.getEndDate()) 1; if (days 0) { throw new BizException(寄养日期不合法); } // 3. 算价 BigDecimal careFee request.getUnitPrice().multiply(BigDecimal.valueOf(days)); BigDecimal extraFee request.getExtraFee() null ? BigDecimal.ZERO : request.getExtraFee(); BigDecimal deposit new BigDecimal(300.00); // 4. 生成订单 Order order new Order(); order.setOrderNo(OrderNoGenerator.generate()); order.setUserId(request.getUserId()); order.setPetId(request.getPetId()); order.setHomeId(request.getHomeId()); order.setStartDate(request.getStartDate()); order.setEndDate(request.getEndDate()); order.setTotalAmount(careFee.add(extraFee)); order.setDepositAmount(deposit); order.setStatus(OrderStatus.WAIT_PAY.getCode()); orderMapper.insert(order); // 5. 预占家庭名额防止反复下单 homeMapper.increaseCurrentPets(request.getHomeId()); return order; } }注意最后一步increaseCurrentPets这是非常容易被忽略的点。如果不做这个预占就会出现一个家庭在相同时间段被反复下单到了现场才发现宠物已经满了。简单起见在t_home里维护一个current_pets字段下单时加1取消或退租完成时减1基本够用。4.2 状态流转的落地写法枚举和状态机校验缺一不可订单状态如果没有任何约束代码写多了就容易出现状态乱跳比如用户还在“待支付”订单直接被改成“已完成”。我习惯在状态变更前做一个统一校验。先定义枚举Getter public enum OrderStatus { WAIT_PAY(0, 待支付), WAIT_ACCEPT(1, 待接单), CARING(2, 寄养中), WAIT_RETURN(3, 待退租), FINISHED(4, 已完成), CANCELED(5, 已取消), REFUNDED(6, 已退款); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } }再写一个状态机校验类public class OrderStatusMachine { private static final MapInteger, SetInteger TRANSITIONS new HashMap(); static { TRANSITIONS.put(0, Set.of(1, 5)); // 待支付 - 待接单/已取消 TRANSITIONS.put(1, Set.of(2, 5)); // 待接单 - 寄养中/已取消 TRANSITIONS.put(2, Set.of(3)); // 寄养中 - 待退租 TRANSITIONS.put(3, Set.of(4, 6)); // 待退租 - 已完成/已退款 TRANSITIONS.put(4, Set.of()); TRANSITIONS.put(5, Set.of()); TRANSITIONS.put(6, Set.of()); } public static boolean canTransition(int from, int to) { return TRANSITIONS.getOrDefault(from, Collections.emptySet()).contains(to); } }所有订单状态更新操作在改库前都必须先调canTransition不满足就抛业务异常。这套逻辑虽然只有十几行代码但在答辩时非常加分因为它直接展示了“业务边界意识”——知道哪些操作是不被允许的比只会写CRUD显得专业得多。4.3 家庭接单机制人工接单比自动分配更适合毕设寄养家庭端有一个“待接单”列表里面是所有状态为待接单的订单。家庭可以点接单也可以点拒单。接单后订单状态变成寄养中此时才意味着宠物确实已经被家庭接收。自动分配这个功能对毕设来说性价比不高一是匹配规则很难定复杂二是容易出边界问题。做一个人工接单流程把列表排序做好——比如按距离、评分、历史接单数排序——已经足够体现“平台撮合”的逻辑了。4.4 日期的边界处理跨天计算的坑时间计算是这个项目里最容易出错的地方。我强烈建议LocalDate只用来表示日期不要掺入时分秒。订单的开始和结束日期是date类型从MySQL查出后直接用LocalDate接收。计算天数时用long days ChronoUnit.DAYS.between(startDate, endDate) 1;这里有一个坑很多新手会写成LocalDate.now()和结束日期比较结果因为时区或者字符串转换问题导致“明明到了退租日系统还显示寄养中”。最稳妥的做法是所有日期计算统一走LocalDate绝不用java.util.Date去减也不要直接拿字符串日期做加减。5. 退租结算一体化押金、逾期扣费与自动结算的细节5.1 两条退租触发路径用户主动发起和系统定时自动触发退租不能只靠用户手动操作否则超过结束日期没人处理系统就卡在“寄养中”了。所以要设计两条路径第一条是用户主动发起。用户在“寄养中”的订单上点“申请退租”订单状态从2变成3等待家庭确认。第二条是系统自动触发。用定时任务每天凌晨扫描所有处于“寄养中”但结束日期已经小于今天日期的订单把它们批量改成“待退租”。Component public class OrderAutoReturnTask { Resource private OrderMapper orderMapper; Resource private OrderLogMapper orderLogMapper; Scheduled(cron 0 30 2 * * ?) public void autoTriggerReturn() { ListOrder expiredOrders orderMapper.selectList( new LambdaQueryWrapperOrder() .eq(Order::getStatus, OrderStatus.CARING.getCode()) .lt(Order::getEndDate, LocalDate.now())); for (Order order : expiredOrders) { // 记录状态变更日志 OrderLog log new OrderLog(); log.setOrderId(order.getId()); log.setFromStatus(order.getStatus()); log.setToStatus(OrderStatus.WAIT_RETURN.getCode()); orderLogMapper.insert(log); order.setStatus(OrderStatus.WAIT_RETURN.getCode()); orderMapper.updateById(order); } } }定时任务执行完并不等于退租完成家庭还需要确认宠物确实已经归还。如果宠物主人迟了几天才来接家庭端可以在“待退租”列表里看到订单确认退租时系统会顺带把逾期费用算出来。5.2 押金扣除规则用公式把话说清楚退租结算时对押金的处理是整个项目里最需要跟老师讲明白的业务规则。我建议用一套明确的公式正常退租返还全部押金。逾期退租逾期费用 超期天数 × 家庭日单价 × 1.5倍。宠物造成损坏由家庭在确认退租时录入损坏赔偿金额管理员可复核。最终退还金额 押金 - 逾期费用 - 损坏赔偿最低为0。Transactional(rollbackFor Exception.class) public Settlement settleOrder(Long orderId, BigDecimal damageFee) { Order order orderMapper.selectById(orderId); if (order.getStatus() ! OrderStatus.WAIT_RETURN.getCode()) { throw new BizException(当前状态不允许结算); } LocalDate actualReturn LocalDate.now(); long overdueDays 0; BigDecimal overdueFee BigDecimal.ZERO; if (actualReturn.isAfter(order.getEndDate())) { overdueDays ChronoUnit.DAYS.between(order.getEndDate(), actualReturn); overdueFee order.getUnitPrice() .multiply(BigDecimal.valueOf(overdueDays)) .multiply(new BigDecimal(1.5)); } BigDecimal refund order.getDepositAmount() .subtract(overdueFee) .subtract(damageFee null ? BigDecimal.ZERO : damageFee) .max(BigDecimal.ZERO); Settlement settlement new Settlement(); settlement.setOrderId(orderId); settlement.setActualReturnTime(actualReturn); settlement.setOverdueDays(overdueDays); settlement.setOverdueFee(overdueFee); settlement.setDamageFee(damageFee); settlement.setRefundAmount(refund); settlementMapper.insert(settlement); order.setStatus(OrderStatus.FINISHED.getCode()); orderMapper.updateById(order); return settlement; }这里用BigDecimal.max(BigDecimal.ZERO)可以保证最终退款不会变成负数。虽然正常情况下押金足够覆盖扣费但万一出现极端情况——比如日单价很高、逾期很多天——退款金额被扣成负数就很奇怪。5.3 结算与评价的顺序先结算后评价很多人会把评价和结算同时处理但我建议先完成结算再开放评价入口。原因很简单如果用户还没退租就先评价评价内容可能建立在不满意的寄养体验上而后台的逾期扣费又和安全无关。把评价放在结算完成之后等于把“体验反馈”和“费用结算”两个动作解耦业务上更合理。具体做法是在结算接口返回成功后前端把评价按钮点亮。后端评价接口额外校验订单状态必须是FINISHED且没有评价记录。6. 家庭式寄养特有的审核与权限为什么这套逻辑比普通商城复杂6.1 “个人既是用户又是寄养家庭”的身份模型这个系统最容易被低估的地方在于寄养家庭不是独立注册的账号而是一个普通用户“变身”成的角色。普通用户可以下单寄养也可以申请入驻成为寄养家庭去接单。也就是说t_user表中的角色不是互斥关系而是可以叠加的。在设计权限时建议用Sa-Token的角色配置或者直接在接口上用注解判断。比如普通用户只能访问下单、退租、评价接口。寄养家庭额外拥有接单、确认退租接口。管理员额外拥有审核、用户管理接口。如果一个用户既申请入驻成功又去别人家下单那么他同时拥有两端的权限这是合理且必须支持的。在代码层面只需要在注册或登录时把用户角色放到登录态里接口鉴权时判断“是否包含某个角色”即可不要让角色互斥。6.2 入驻审核接口一个带来大量演示画面的模块寄养家庭入驻审核是整个平台信任机制的第一道关。流程建议这样走用户提交申请填写家庭地址、可接收宠物数量、服务描述并把audit_status改成1审核中。管理员后台看到审核列表点击通过或拒绝。审核通过后用户的user_type增加寄养家庭角色同时t_home表插入对应家庭记录状态置为启用。审核前用户端家庭列表里看不到该家庭。PostMapping(/admin/audit/sitter) SaCheckRole(admin) public Result auditSitter(RequestBody AuditRequest request) { User user userMapper.selectById(request.getUserId()); if (user null) { return Result.fail(用户不存在); } if (request.getPass()) { // 升级为寄养家庭角色 user.setUserType(UserType.SITTER.getCode()); user.setAuditStatus(AuditStatus.AUDIT_PASS.getCode()); userMapper.updateById(user); // 同步创建寄养家庭信息 Home home new Home(); home.setUserId(user.getId()); home.setHomeName(request.getHomeName()); home.setAddress(request.getAddress()); home.setMaxPets(request.getMaxPets()); home.setCurrentPets(0); home.setStatus(1); homeMapper.insert(home); } else { user.setAuditStatus(AuditStatus.AUDIT_REJECT.getCode()); userMapper.updateById(user); } return Result.success(); }审核这个模块虽然代码量不大但它是“家庭式寄养”区别于普通商城的核心。答辩时如果老师问“你怎么保证寄养家庭靠谱”你指着这个审核流程讲一遍比背任何概念都更有说服力。6.3 家庭列表的匹配逻辑用户端进入寄养家庭列表后系统应该按什么顺序展示家庭最基本的是三个维度评分从高到低。距离按经纬度计算后由近到远。可接数量currentPets maxPets的家庭优先展示。如果不想接地图服务可以用近似公式算直线距离public double distance(Double lat1, Double lng1, Double lat2, Double lng2) { double radLat1 lat1 * Math.PI / 180.0; double radLat2 lat2 * Math.PI / 180.0; double a radLat1 - radLat2; double b lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; double s 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); return s * 6371000; // 单位:米 }这个公式放到工具类里家庭列表查询出来后逐一计算再排序。毕设级别的数据量完全够用不需要引入Elasticsearch或者空间索引。6.4 双向评价与纠纷记录家庭式寄养的特殊需求在家庭式寄养里不光是主人评价寄养家庭寄养家庭也应该能评价主人。原因很现实寄养家庭最怕遇到宠物有攻击性、主人隐瞒健康问题的情况。双向评价机制能把双方的行为都纳入考量提高平台的整体可信度。评价表和订单表关联即可建议加一个evaluate_type字段区分是“主人评家庭”还是“家庭评主人”。同时可以加一个纠纷留言表记录双方对某个订单的意见。这个功能不复杂但能把平台规则讲得更有血有肉演示时展示一张完整的评价页面比空谈安全机制直观得多。7. 实际开发中踩过的四个坑和对应解法7.1 退租状态被直接改成“已完成”结算数据全丢了有一次帮人调代码发现订单状态从“寄养中”直接跳到“已完成”结算表里完全没有记录押金也查不到去向。原因是对接前端时为了省事把“确认退租”按钮直接调了updateStatus(orderId, FINISHED)把中间的结算逻辑跳过了。解法就是按本文第5章的思路来状态只能按状态机允许的路径流转待退租到已完成必须经过结算接口结算成功后生成t_settlement记录同时才更新状态。7.2 MyBatis-Plus乐观锁配置后不生效用MyBatis-Plus的乐观锁最常见的问题就是配置了插件但更新时版本号不自动加。原因是Version注解加到了实体类的version字段上但数据库的version没有默认值插入时是nullMyBatis-Plus就不会执行版本自增。正确做法是数据库字段设置DEFAULT 0实体类version字段加Version并且在配置类里注册乐观锁插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }7.3 Scheduled方法里调用同类Service方法事务失效定时任务里如果直接调用本类的另一个Transactional方法事务会失效。这是Spring AOP的经典坑类内部自调用走的是this对象而不是代理对象。解法有三种把被调用的方法拆到独立的Bean里或者通过ApplicationContext.getBean拿到代理对象再调用或者直接在当前方法上整体加Transactional。我的建议是拆Bean结构更清晰也方便复用。7.4 同一宠物的同时间段被重复下单“当前家庭其实还有空位但下单已经超了”这个并发问题在演示时很难触发但如果答辩时被问到并发场景答不上来就会扣分。我推荐用Redis分布式锁锁的key格式为home:order:{homeId}下单时先加锁处理完释放至少保证同一家庭的同一时刻只存在一个创建订单的事务。如果不想引Redis也可以用数据库的唯一索引或者乐观锁但逻辑上会绕一些。我个人建议这个项目加一个简版Redis锁代码量不大但能展示并发意识。写到最后一点个人体会这套宠物寄养系统我前前后后帮几届同学梳理过最有价值的其实不是SpringBoot本身而是把“订单状态机”“日期边界”“退租结算”这三件事想清楚。很多人一开始觉得增删改查很简单真到自己写的时候才发现状态跳着跳着就乱了日期算着算着就差了退租改成“已完成”就再也回不去了。如果你也在做这个题目我建议先把订单状态流转图画在纸上然后把结算公式写清楚再动手写代码。这三件事通了剩下的大部分功能其实都是沿着业务线把接口补完的事。最后再分享一个小技巧演示的时候故意展示一个“逾期退租”的订单让老师看结算明细里逾期扣费怎么自动出现这种细节比任何技术点都更能证明你把业务流程吃透了。