ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot构建中医理疗馆管理系统:毕业设计全流程解析

SpringBoot构建中医理疗馆管理系统:毕业设计全流程解析 做毕业设计管理系统大部分同学会下意识去选电商、图书管理、教务管理这些已经快做烂的题目。我做完中医理疗馆管理系统之后发现这种“垂直业务真实门店场景”的题目反而更好发挥技术栈用SpringBoot保证主流业务里有预约排班、会员储值、理疗记录这些足够复杂的逻辑答辩时也有东西可讲。这篇文章就把我用SpringBoot做中医理疗馆管理系统的完整思路写出来从需求拆解、数据库设计到几个关键代码的实现和踩坑记录给准备做类似课题的同学直接抄作业的参考。1. 为什么选中医理疗馆这个场景需求拆解比写代码更花时间1.1 先搞清门店里的真实业务链条中医理疗馆的业务链路表面上看是“客户到店、做个项目、付钱走人”实际拆开看要复杂得多。完整链条是这样的客户到店后前台先判断是散客还是会员散客第一次来会先做档案登记记录基本信息、过敏史、当前身体状况然后根据客户情况推荐理疗项目拔罐、推拿、艾灸、正骨等等确定理疗师和时间段。如果客户办过会员卡或买了疗程套餐前台要查卡内余额、确认折扣、扣减对应次数理疗师做完项目之后还要填一条理疗记录写清楚这次做了什么手法、客户反应如何、下次建议什么时候来。如果客户是定期调理比如每周一次腰椎推拿预约管理就要能处理连续性的时间安排。这一套业务如果全靠手工记账痛点非常明显客户的到店记录和理疗记录都是纸质档案时间一长就散乱丢失理疗师的排班靠口头约定客户约了时间、理疗师有事改期两边信息不同步最麻烦的是会员卡的余额和赠送金额计算充1000送200、充3000送600不同档位还配不同折扣前台用计算器都容易按错错一次门店就亏一次。这些痛点就是管理系统存在的价值。1.2 系统角色的诉求决定了功能模块的走向做管理系统最重要的是想清楚“谁在用、用什么、关心什么”。中医理疗馆系统的角色可以拆成四类每类角色的诉求直接决定功能模块角色核心诉求对应功能模块系统管理员维护基础数据、看经营统计理疗项目管理、理疗师管理、统计报表前台/收银快速建档、预约登记、收银结算客户管理、预约管理、结算管理、会员卡操作理疗师看自己的排班、填理疗记录预约查询、理疗记录填写客户查看预约情况、历史记录Web端查询以会员身份登录1.3 毕业设计场景下的需求边界哪些要做哪些坚决不做容易犯的错误是想把进销存、员工工资、营销裂变、积分商城全部塞进系统。我一个很直接的感受是毕业设计管理系统题目需求边界比需求数量更重要。你把模块清单列出来给导师看他说“够了”你就真的到此为止。我最终锁定的功能边界是七个模块基础数据管理理疗项目、理疗师、客户管理建档、档案查询、预约管理预约、取消、到场确认、理疗记录管理、会员卡管理开卡、充值、消费明细、结算管理收费、退费、统计报表营收、项目热度、理疗师工作量。这个范围既覆盖了门店核心业务闭环也不会让工作量失控。2. 技术选型SpringBoot是承重墙但其他“建材”也要配好2.1 为什么确定选SpringBoot而不是SSH、SSM或Flask现在做管理系统SpringBoot几乎属于默认答案。和早年的SSHSpringHibernateStruts相比它把大量的XML配置砍掉了自动配置机制让你只需要关注业务代码。和SSM相比SpringBoot内嵌Tomcat这一点非常舒服本地开发不用单独装Tomcat一个main方法启动毕业设计演示的时候把Jar包拷过去就能跑。也有人问用Flask或FastAPI行不行技术上当然可以但有几个现实问题一方面SpringBoot在业务系统里的“基础设施”更齐全参数校验、全局异常处理、事务管理、安全控制都有非常成熟的解决方案另一方面你答辩的时候说“SpringBoot自动配置 约定优于配置”老师是认同的而用Flask做传统业务管理系统反而容易被追问“为什么不用Java生态”。这里有个特别关键的版本注意点SpringBoot 2.x和3.x差异非常大。3.x要求JDK17以上包名从javax改成了jakarta很多依赖跟着要升级。如果你对JDK版本迁移不熟建议直接用SpringBoot 2.7.x JDK8稳而且网上资料多如果导师要求新栈再上3.x JDK17但要做好心理准备部分第三方依赖会有兼容坑。2.2 持久层选型MyBatis-Plus比原生MyBatis更适合很多教材和课程项目还在教原生MyBatis但实际做这类管理系统我强烈建议用MyBatis-Plus。它没有抛弃XML写SQL的灵活性同时又提供了BaseMapper单表的增删改查基本不用写SQL分页插件一行搞定乐观锁、逻辑删除这些都是内置插件对毕业设计来说太友好了。为什么不用JPA我也试过比较过。JPA的自动建表能力很方便但一对多、多对一的关系映射在序列化返回时经常出现懒加载异常或者循环引用问题排查起来很折腾。做业务系统尤其是要快速交付的管理系统MyBatis-Plus更贴近国内开发者的习惯最重要的是排查问题的中文资料特别多随手一搜就有现成案例。2.3 前端方案老实二选一别两头摇摆前端方案有两种主流选择没有绝对的对错区别是你要给答辩老师展示什么。第一种用Thymeleaf模板引擎直接渲染页面项目打包后一个Jar跑起来部署演示最省心适合后端方向、时间紧张的情况。第二种用Vue3 Element Plus做前后端分离后端只提供RESTful接口论文里可以写“前后端分离架构”这个亮点但需要额外处理跨域、接口联调、打包到静态资源里等问题。我个人建议如果距离交论文只有两三个月选Thymeleaf原生Bootstrap把精力留给业务逻辑和论文如果你想强调前后端分离选Vue3但一定要留出至少两周的联调时间。我自己实际做的时候用的是Vue3SpringBoot的组合答辩时讲系统分层比较清晰但代价是踩了好几个跨域和接口设计上的坑。2.4 数据库设计八张核心表撑起整个系统理疗馆的业务关系不复杂核心表我认为拆成这样比较合理表名作用关键字段admin系统登录用户username, password(加密存储), rolecustomer客户档案name, phone, gender, allergy, health_conditiontherapist理疗师信息name, title, specialty, phone, statusproject理疗项目name, description, duration, priceappointment预约记录customer_id, therapist_id, project_id, appointment_date, start_time, end_time, statusmember_card会员卡card_no, customer_id, balance, discount, recharge_total, statuscard_recharge_record充值流水card_id, amount, bonus, create_timesettlement结算记录appointment_id, total_amount, discount_amount, actual_amount, pay_type, statustreatment_record理疗记录appointment_id, customer_id, therapist_id, content, suggestion, create_time这张表设计里有两个值得展开的点。第一个是预约表为什么把日期和时间段拆开成appointment_date、start_time、end_time三个字段而不是直接存一个开始时间加一个结束时间。原因很简单理疗馆的统计报表基本都是按天聚合拆开之后date条件查询的效率更高而且前端时间选择器也通常是“选日期选时段”两个组件。第二个点是会员卡的余额为什么不通过充值流水汇总实时算出而是直接冗余一个balance字段。因为结算时扣余额是一个非常高频的操作每次实时SUM流水记录会很慢而且容易因为历史数据变更导致余额计算错误。直接维护余额字段配合充值流水和消费明细做审计是业务系统里很典型的空间换时间思路。还有一个建议所有金额字段都用DECIMAL(10,2)不要用FLOAT或DOUBLE。理疗项目和充值金额涉及钱浮点数在MySQL里的精度问题会坑哭你比如0.10.2不等于0.3这种经典问题。DECIMAL是定点数精度可控这是财务数据的基本要求。3. 核心模块实现用代码把业务闭环跑通3.1 预约模块时间冲突校验怎么写才不容易漏预约是理疗馆系统的第一核心场景。客户选了理疗师、选了日期和时段系统必须先判断这个时段是否已经被别人占用了。如果不做这个校验两个客户约到同一个理疗师的同一时段现场就会吵架。时间重叠的判断逻辑其实很简洁两个时间段重叠的条件是“新预约的开始时间小于已有预约的结束时间并且新预约的结束时间大于已有预约的开始时间”。我直接在Service层写查询来校验核心代码是这一段Service public class AppointmentService { Autowired private AppointmentMapper appointmentMapper; public boolean isTimeSlotAvailable(Appointment appointment) { Integer count appointmentMapper.countConflictAppointment( appointment.getTherapistId(), appointment.getAppointmentDate(), appointment.getStartTime(), appointment.getEndTime(), appointment.getId() ); return count 0; } }对应的XML里SQL是这样写的select idcountConflictAppointment resultTypejava.lang.Integer SELECT COUNT(*) FROM appointment WHERE therapist_id #{therapistId} AND appointment_date #{appointmentDate} AND status IN (1, 2) AND (#{startTime} end_time AND #{endTime} start_time) if testappointmentId ! null AND id ! #{appointmentId} /if /select解释一下这段SQL。status IN (1, 2) 的意思是只统计“已预约”和“已到店”这两个有效状态因为“已取消”的预约不用参与冲突判断。id不为当前预约的条件是给“改期”场景准备的同一个预约修改时间时不应该把自己也算作冲突。为什么重叠条件要写成“新开始旧结束 且 新结束旧开始”而不是其他写法我拿时间轴来举例。假设已有预约是9:00到10:00新预约是10:00到11:00虽然新开始等于旧结束但两者没有重叠因为客户通常会在整点交接不会重叠占用。反过来新预约是9:30到10:30新开始9:30小于旧结束10:00同时新结束10:30大于旧开始9:00条件满足冲突成立。这个写法是“不可重叠”区间的标准判断记住了就永远不会漏场景。3.2 会员卡充值余额变更与流水记录必须双写会员卡模块的核心是两步操作充值以及在结算时用余额抵扣。充值这一块业务规则通常按不同充值档位给不同的赠送金额和折扣率比如充500送100享95折、充1000送300享9折、充3000送1000享85折。我在实现时把卡档位设计成一个独立的配置实体而不是写死在代码里。这样以后门店调活动规则前台在后台页面改配置即可。核心的充值逻辑分两步更新会员卡余额、插入充值流水。这两个操作必须放在同一个事务里否则会出现余额加了但流水丢了或者流水记了但余额没变的问题。Transactional(rollbackFor Exception.class) public void recharge(Long cardId, BigDecimal rechargeAmount) { MemberCard card memberCardMapper.selectById(cardId); if (card null) { throw new BusinessException(会员卡不存在); } RechargeStrategy strategy strategyService.getMatchStrategy(rechargeAmount); BigDecimal bonus strategy.getBonus(); BigDecimal totalAmount rechargeAmount.add(bonus); card.setBalance(card.getBalance().add(totalAmount)); card.setRechargeTotal(card.getRechargeTotal().add(rechargeAmount)); memberCardMapper.updateById(card); CardRechargeRecord record new CardRechargeRecord(); record.setCardId(cardId); record.setAmount(rechargeAmount); record.setBonus(bonus); record.setCreateTime(LocalDateTime.now()); rechargeRecordMapper.insert(record); }这里有一个容易被忽略的细节充值时为什么要记录amount和bonus两个字段而不是只记一个totalAmount因为后面做财务报表时门店需要区分“实际收入”和“赠送成本”。如果只记总额月底老板问“这个月充值赠送了多少”你就哑口无言了。分离字段设计就是为了方便统计口径的切换。3.3 结算模块计算顺序决定钱有没有算错结算模块是理疗馆业务闭环的收口。一次理疗做完最后要收钱了。这里面的计算顺序非常关键先算项目原价再判断有没有折扣然后判断是否用会员卡余额支付最后记录实付金额。我把结算逻辑拆分成了四个明确的步骤第一步查出预约关联的理疗项目拿到原价第二步判断客户是否有会员卡有卡则按卡的折扣率计算折后金额第三步判断支付方式如果是会员卡支付校验余额是否充足足够则扣减余额否则提示余额不足并阻止操作第四步生成结算记录设置状态为“已支付”。Transactional(rollbackFor Exception.class) public Settlement settleAppointment(Long appointmentId, String payType) { Appointment appointment appointmentMapper.selectById(appointmentId); Project project projectMapper.selectById(appointment.getProjectId()); BigDecimal originalAmount project.getPrice(); BigDecimal discountAmount originalAmount; MemberCard card memberCardMapper.selectByCustomerId(appointment.getCustomerId()); if (card ! null) { discountAmount originalAmount.multiply(card.getDiscount()) .setScale(2, RoundingMode.HALF_UP); } if (MEMBER_CARD.equals(payType)) { if (card.getBalance().compareTo(discountAmount) 0) { throw new BusinessException(会员卡余额不足); } card.setBalance(card.getBalance().subtract(discountAmount)); memberCardMapper.updateById(card); } Settlement settlement new Settlement(); settlement.setAppointmentId(appointmentId); settlement.setTotalAmount(originalAmount); settlement.setDiscountAmount(discountAmount); settlement.setActualAmount(discountAmount); settlement.setPayType(payType); settlement.setStatus(PAID); settlementMapper.insert(settlement); appointment.setStatus(3); appointmentMapper.updateById(appointment); return settlement; }这里有一个我印象很深的细节折扣金额的计算用的是multiply后setScale(2, RoundingMode.HALF_UP)。如果直接用BigDecimal的toString和数据库DECIMAL交互可能因为小数位数不一致出问题。统一在业务层做四舍五入到分是避免结算出现“几分钱差异”的关键习惯。另外settlement和appointment状态的更新也在同一个事务里。这样避免了“钱收了但客户状态还是已到店”的数据不一致。这个事务边界非常重要结算时千万不要把状态更新放到PaymentCallback之类的外部逻辑里。3.4 理疗记录客户健康档案的长期价值理疗记录功能从根本上讲是“客户档案的延续”。客户第一次来和第十次来理疗师如果能看到之前的手法和建议服务质量会完全不同。数据库里我设计了一张treatment_record表关联appointment_id保证一次理疗只能产生一条记录避免前台重复录入。Transactional(rollbackFor Exception.class) public void createTreatmentRecord(TreatmentRecord record) { Appointment appointment appointmentMapper.selectById(record.getAppointmentId()); if (appointment null || appointment.getStatus() 2) { throw new BusinessException(无效的预约状态无法登记理疗记录); } int count treatmentRecordMapper.countByAppointmentId(record.getAppointmentId()); if (count 0) { throw new BusinessException(该预约已存在理疗记录); } treatmentRecordMapper.insert(record); }注意我的校验逻辑理疗记录只能在预约状态至少为“已到店”status2之后填写这是为了防止数据乱入。理疗师在客户做完项目之后把理疗部位、手法、客户反馈、下次建议提交。这个功能和前端联动后客户在Web端查询自己的历史记录系统的手术做得好不好就非常直观了。4. 毕业设计最容易翻车的硬核细节并发、事务与状态机4.1 预约并发场景乐观锁怎么解决“超约”问题如果上面预约模块的校验已经写好大部分场景是够用的。但有一个隐患在多用户并发请求下可能出现两个请求同时通过冲突校验然后又同时插入成功最终同一理疗师同一时段出现两条预约。这就像电商的“超卖”问题一样。这个问题有两种解法。悲观锁用SELECT ... FOR UPDATE但锁的粒度不好控制。我实际采用的是MyBatis-Plus内置的乐观锁方案。给appointment表增加一个version字段更新状态时带上版本号Version private Integer version;在配置类里启用乐观锁插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这样在业务代码中正常的UPDATE语句会自动变成“UPDATE appointment SET ... WHERE id ? AND version ?”并执行version1。如果更新影响行数为0就说明版本号被其他线程改过了这次操作就需要重试或提示客户该时段被占用。这是一道经典的“并发场景下防止超售”面试题答辩时提这个点老师基本都会认可。4.2 金额操作的事务边界不是所有方法都要加Transactional事务用得好是加分项用不好就是事故现场。我的经验是按“业务语义”来划分事务边界而不是简单粗暴地在每个方法上都加Transactional。比如一次“客户到店结算”应该是一个事务扣会员卡余额、生成结算单、更新预约状态这三步必须全部成功或者全部回滚。如果扣了钱却生成结算单失败客户余额少了但账单纪录不存在门店年底盘点就对不上账。反过来导入导出、发送通知、生成报表这类操作就不适合用一大坨事务裹着。我记得有人写过把Excel解析和批量入库放在同一个事务里的代码结果数据量稍微大一点一个单元格格式错误就会把前面几千条有效数据全部回滚。合理的做法是先在校验阶段把数据全部检查一遍再统一入库即使要入库也应该分批提交。Transactional(rollbackFor Exception.class)有一个容易被忽视的细节默认情况下它只回滚RuntimeException和Error如果方法里throws Exception是受检异常它是不会回滚的。所以rollbackFor Exception.class这个参数务必加上不然你会在某一次数据报错后发现钱已经扣了但业务单没生成到时候排查到怀疑人生。4.3 预约与结算的状态机设计状态怎么流转、怎么防止乱跳管理系统里状态字段如果放任自由流转很容易出现“已退款的订单还能再次结算”这种逻辑漏洞。我建议把预约和结算分别设计成状态机在代码里统一控制流转方向。预约表的状态我设计了四个值1 已预约客户预约成功等待到店2 已到店客户到店确认等待理疗3 已完成理疗结束并完成结算4 已取消预约取消分为客户取消和管理员取消状态流转方向是已预约既可以变更为已到店也可以变更为已取消而已取消状态不允许再回到已预约否则可能出现“取消又恢复”的盘根错节逻辑。已到店只能变更为已完成不能直接取消。结算表状态则简单很多PAID已支付和REFUNDED已退款从PAID到REFUNDED要求先校验该结算单对应预约是否还能退款比如已完成理疗的一般不能退除非门店特殊情况。在代码实现中我会写一个专门的状态变更方法把“当前状态是否允许目标状态”的判断收敛到一处public void changeAppointmentStatus(Appointment appointment, Integer targetStatus) { MapInteger, ListInteger statusFlow new HashMap(); statusFlow.put(1, Arrays.asList(2, 4)); // 已预约 - 已到店 / 已取消 statusFlow.put(2, Arrays.asList(3)); // 已到店 - 已完成 statusFlow.put(3, Collections.emptyList()); // 已完成不能继续流转 statusFlow.put(4, Collections.emptyList()); // 已取消不能恢复 ListInteger allowed statusFlow.get(appointment.getStatus()); if (allowed null || !allowed.contains(targetStatus)) { throw new BusinessException(非法的预约状态流转); } appointment.setStatus(targetStatus); appointmentMapper.updateById(appointment); }状态机的设计不需要多复杂的框架HashMap就能表达“当前状态 - 允许的目标状态列表”关键是把所有流转规则定义清楚防止数据乱窜。5. 实战踩坑记录毕业设计里我踩过的那些坑5.1 SpringBoot版本、JDK版本、Maven仓库“三座大山”第一个坑是SpringBoot版本太高导致各种依赖不兼容。我一开始图省事直接选了当时最新的SpringBoot版本结果JDK版本不够SpringBoot里的某些自动配置类因为依赖了新的Java特性而启动报错。后来我明白了毕业设计不追求“最新”追求“最稳”。SpringBoot 2.7.x配合JDK8是一个经过大量验证的组合网上教程、博客、GitHub项目基本都是这个版本体系遇到问题随手能搜到答案。第二个坑是Maven下载依赖特别慢。解决办法是改Maven的setting.xml用阿里云镜像。这个操作不止一次地节省了我的生命如果你还没配置过现在就去搜一下“maven aliyun mirror”配上之后依赖下载速度能快一个量级。第三个坑是JDK17下使用Lombok。新版Lombok对JDK17的支持偶尔会有编译问题如果确认是Lombok导致的报错建议升级Lombok版本或检查IDEA里Annotation Processors是否开启。这个坑特别隐蔽因为报错信息往往不是“Lombok不兼容”而是一长串看不懂的编译异常。5.2 时间格式化的时区陷阱在SpringBoot做Web接口时LocalDateTime返回给前端的默认序列化格式经常是“2024-05-20T10:30:00”带着一个T看着别扭不说前端如果直接展示体验也不对。网上常见的做法是在application.yml里统一配置jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8需要注意的是这个配置对java.util.Date生效对Java 8的LocalDateTime需要额外加上Jackson的JSR310模块配置或者在字段上使用JsonFormat注解。我的习惯是两种方式都写接口返回的时间格式就能稳定统一。另外还有一个时区隐患数据库连接串里的serverTimezone参数。MySQL 8.x的驱动要求带上时区一般推荐写成serverTimezoneAsia/Shanghai否则日期时间字段可能会差8个小时。这个坑不配电根本查不出来因为数据存在时是对的读取映射到Java对象之后就错了8小时。5.3 Jackson序列化的循环引用在管理员查询客户详情时客户关联了预约预约关联了理疗师理疗师又关联了预约如果这些实体类直接作为JSON返回Jackson会陷入无穷递归最后抛出一个StackOverflowError。这种情况在MyBatis-Plus实体关系比较复杂的时候特别常见。解决思路有几种。最正规的做法是定义DTO/VO对象按需返回字段不透出实体类的完整关联结构。但很多同学毕业设计时间紧会直接给实体类加JsonIgnore注解把不需要序列化的字段忽略掉。这个办法能用但要注意忽略掉的字段在接收前端参数时也会受影响因为反序列化同样会跳过它。我的实际做法是在实体类上对不需要序列化的关联属性加JsonIgnore对确实需要返回的关联信息在Service层手动组装成VO对象返回。后者虽然多写几个转换方法但接口结构非常可控答辩时也更好讲解。简单说实体类尽量“瘦”VO对象负责“胖”这样序列化问题基本能根治。5.4 预约时间选择的边界处理还有一个很实操的体验问题前端让客户选预约时间时如果只给一个开始时间后端自动加项目时长算出结束时间那么理疗师拖堂和接单很容易在边界处冲突。最稳妥的做法是把时间段本身作为一个完整概念来管理前端弹出的是“开始时间”的选择器但展示给客户时明确显示“9:00-10:00推拿60分钟”。这样客户心理预期清晰后端校验也有明确的endTime而不是笼统地按60分钟推导。我在开发时还处理过“项目时长是否包含准备时间”这个业务问题。推拿项目标签时长60分钟但客户换衣服、沟通诉求可能需要额外15分钟。经过和实际门店沟通我决定在数据库里给project表加一个prepare_time字段预约冲突校验时默认把准备时间也算进去。这个细节看起来脚本化实际却是门店运营满意度的重要影响因素。6. 论文组织结构与答辩准备让项目在理论上也能站得住6.1 论文章节安排怎么和系统设计呼应很多同学的论文写得像“需求说明书流水账”原因是没有把系统实现和软件工程理论结合起来。我建议论文章节按“选题背景-需求分析-系统设计-系统实现-系统测试”这条主线组织但每一章都必须和你的实际代码呼应。需求分析章节重点是画清楚用例图把四类角色的用例边界讲明白系统设计章节除了讲架构重点放在数据库设计上E-R图不要画得花里胡哨重点是表之间的关联关系和主外键设计理由系统实现章节不需要把每个Controller的代码都贴上去而是选三个有说服力的核心功能预约冲突校验、会员卡事务、状态机流转做重点展示配合核心代码片段和流程图描述。这里有一个很实用的经验测试章节千万不要写成“功能测试全部通过”这种空话。把测试数据、测试用例、预期结果和实际结果用表格呈现出来比如预约冲突场景测试里插入两个重叠预约预期第二个被拒绝实测返回统一错误码这个表格放在论文里非常加分。6.2 答辩复盘老师最爱追问的七个问题结合我实际答辩的经验老师针对这类管理系统通常集中在七个问题上提前准备完全能应对。第一个问题“SpringBoot的自动配置原理是什么”标准答法是当项目启动时EnableAutoConfiguration注解通过AutoConfigurationImportSelector加载META-INF/spring.factories里的自动配置类再根据ConditionalOnClass等条件注解决定是否生效。口语化解释就是“你一引入相关依赖框架自动帮你把Bean组装好”。第二个问题“为什么不用JWT做登录认证”很多管理系统做简单的登录拦截就够了Session方案在单个Web应用里没有任何问题。如果你用了JWT要能解释清楚它的无状态特性和Token刷新方案。第三个问题“你的数据库为什么要冗余balance字段”按我上面的设计回答减少高频查询时的实时汇总开销并用流水表做审计补偿体现数据冗余的有意识取舍。第四个问题“如果预约并发超卖怎么解决”直接说乐观锁方案然后简要描述version字段的工作机制。第五个问题“项目里的金额计算会不会有浮点精度问题”回答用了BigDecimal和DECIMAL(10,2)并说明四舍五入策略为HALF_UP。第六个问题“理疗师的排班你怎么处理”说明排班是前台手工登记预约状态控制未涉及到自动排班算法这是当前系统的边界。承认边界比吹牛更稳妥。第七个问题“统计报表是怎么实现的”用一周营收为例说明按天分组SUM结算表金额再用ECharts在前端画柱状图数据链路非常清晰。6.3 演示现场的一个救命操作答辩现场最怕网络和数据库出问题。一个救命操作是本地开发环境用MySQL答辩前导出一份演示数据把数据库服务启动状态确认好启动类里配置好数据源端口所有页面数据先用固定演示账号登录。更保险的做法是准备好一张“备用演示手机图”的截图万一现场网络崩溃至少能讲页面功能截图不至于冷场。另一个细节是答辩现场投屏时浏览器缩放比例、页面字体大小可能和你开发机不一样页面布局错乱会很尴尬。正式答辩前用投影仪或外接显示器实际跑一遍核心流程登录、预约、结算、查记录确认核心操作路径上的按钮在缩放下依然可点击这个准备动作能避免很多意外。做完整套理疗馆管理系统我最大的体会是毕业设计管理系统类题目真正的门槛不在于框架用得多新而在于你是否把一个熟悉的业务场景搞透并且用合理的工程手段把每个细节落地。预约冲突校验、事务边界、状态机流转这些看似基础的能力恰恰是工作以后天天用到的核心技能。代码写得再朴素只要业务自洽、边界可控、能讲清设计理由就是一份扎实的毕业设计。
返回列表