基于Spring Boot的体检套餐定制系统:从规则引擎到预约闭环
直接说结论体检套餐定制系统是目前Java毕业设计里少有的“业务逻辑够复杂、技术点够丰富、但又不至于让你做不完”的选题。这三四年我带学生做毕设见到太多人一头扎进商城、博客、教务系统这些老掉牙的方向结果开题就被毙、答辩被追问到哑火。而这个题目正好卡在“看起来不难、实际有内容”的位置上——它的核心难点不在CRUD而在“个性化推荐”和“预约-结果”这条完整业务链怎么闭环。我自己用Spring Boot MyBatis Plus MySQL Redis这套组合从数据库设计、推荐规则引擎到预约状态机完整跑通过一遍。下面把整个系统的设计思路、核心实现、踩坑记录全部摊开讲尽量做到你照着这个思路走哪怕没写过几行Java也能在两个月内交出能过审、能答辩、能演示的东西。1. 先想清楚这个系统到底要解决什么问题1.1 体检行业的真实痛点很多人做毕设容易犯一个毛病拿到题目就开写代码连这个系统“在真实世界里怎么被使用”都没搞清楚。体检套餐定制系统不是让你做个花架子——你去任何一家体检中心看看排队的那些叔叔阿姨、刚入职场的年轻人绝大多数不知道自己该查什么。传统的做法是销售顾问拿着纸质套餐册子推荐用户一脸懵地选个“全身豪华套餐”花了钱过度检查或者选个基础套餐该查的没查。这个系统要解决的就是三件事用户来了帮他科学地选套餐选完之后能顺利预约到线下体检时间和机构体检完成后能在线查看报告、追溯历次结果变化。这三点对应到功能上就是个性化套餐推荐、体检预约管理、结果查询与报告解读。你把这三条主线做完整论文和答辩的内容量一下就满了。1.2 个性化推荐是核心亮点还是加分项这个系统在毕业设计里的含金量很大程度上由“个性化推荐”这四个字决定。如果你只做一套增删改查把推荐做成简单的套餐列表展示那这个题目就浪费了——答辩老师一眼就能看出来你没深入。反过来你只要能给出“为什么给这个人推荐这些项目”的可解释逻辑不管是规则匹配还是评分排序都能占住“设计有亮点”的位置。我的建议是走规则评分这条路线而不是非得整机器学习算法。原因很现实体检数据量小没那么多样本让你训练模型。规则引擎的逻辑可以用清晰的流程图标出来论文好写答辩好讲。体检行业本来就有成熟的临床指南和专家共识规则就是行业经验的数字化。1.3 即刻能落地的功能边界给这个系统划一个清晰的功能边界什么都不多做但每个模块都闭环用户端注册登录、健康档案填写、套餐定制问卷推荐结果、套餐对比、在线预约、订单管理、报告查看。管理端套餐管理含套餐包含的检查项目管理、体检项目库管理、预约排班管理、报告录入与发布、用户健康档案查看。核心流程问卷采集 → 推荐引擎计算 → 套餐生成与确认 → 预约提交 → 机构排期确认 → 到店体检 → 报告上传 → 用户查看报告。有些同学还想加支付、加优惠券、加积分商城我的意见是毕业设计最重要的是把主流程走通、把技术点讲透。附加功能越多出bug的地方越多论文却未必能写出深度。2. 技术选型Java生态里怎么搭最稳妥2.1 为什么是Spring Boot MyBatis Plus这套组合Java这块的毕业设计近年几乎被Spring Boot统治了理由不言自明配置简化、内嵌服务器、生态成熟你随便扔进搜索引擎都能找到资料。但我要多说一句同样是选Spring Boot持久层框架的选择直接影响你的开发速度上限。不用JPA/Hibernate学习成本和潜在的性能坑太多尤其适合做简单查询复杂一点就抓瞎。不用原生MyBatis写XML映射和大量SQL容易累死而且答辩时很难解释清楚自己写了什么。用MyBatis PlusCRUD直接内置分页插件、条件构造器都现成你能把时间省下来去做真正的业务逻辑。数据库老老实实选MySQL缓存用Redis前后端分离的话后端框架用Spring Boot前端Vue还是Thymeleaf取决于你的前端水平。我下面的代码示例以Vue前后端分离为背景但核心业务逻辑在后端前端技术栈影响不大。2.2 前后端分离还是服务端渲染这个问题很多人纠结。我的建议如果前端水平一般、时间紧用Thymeleaf服务端渲染就够了少一套接口联调工作论文也能写“基于Spring Boot的Web应用开发”。如果前端有一定基础或者答辩想展示点全栈能力用Vue3 Element Plus做管理端和用户端接口统一用RESTful风格。我自己这次做的时候选了前后端分离原因是一来想多练点东西二来推荐引擎的结果需要异步渲染纯服务端模板做起来不太顺手。不过如果你时间只剩一个月别学我——老老实实Thymeleaf稳妥交差才是真的。2.3 表结构设计先把核心表画明白数据库设计是整个系统能不能做深的地基。我讲几个核心表建表语句你照着思路写就行。第一张是用户健康档案表。个性化推荐的所有输入数据最终都汇聚到这里CREATE TABLE health_profile ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID, age int(11) DEFAULT NULL COMMENT 年龄, gender tinyint(1) DEFAULT NULL COMMENT 0男 1女, height decimal(5,2) DEFAULT NULL COMMENT 身高cm, weight decimal(5,2) DEFAULT NULL COMMENT 体重kg, smoke tinyint(1) DEFAULT NULL COMMENT 是否吸烟, drink tinyint(1) DEFAULT NULL COMMENT 是否饮酒, family_history varchar(255) DEFAULT NULL COMMENT 家族史多选逗号分隔, past_history varchar(255) DEFAULT NULL COMMENT 既往病史, occupation_type tinyint(2) DEFAULT NULL COMMENT 职业类型 1办公室 2体力劳动 3其他, exercise_freq tinyint(2) DEFAULT NULL COMMENT 运动频率 1几乎不 2每周1-2次 3每周3次以上, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第二张是体检项目库表和套餐关系表。这里要特别注意套餐和检查项目是多对多的关系一定要拆出关系表否则后面做推荐拼接项目列表时会非常痛苦。CREATE TABLE exam_item ( id bigint(20) NOT NULL AUTO_INCREMENT, item_code varchar(32) NOT NULL COMMENT 项目编码, item_name varchar(64) NOT NULL COMMENT 项目名称, category varchar(32) DEFAULT NULL COMMENT 分类血常规/影像学/肿瘤标志物等, price decimal(10,2) DEFAULT NULL COMMENT 参考价格, description varchar(512) DEFAULT NULL, risk_factors varchar(255) DEFAULT NULL COMMENT 关联风险因子, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE package_item ( id bigint(20) NOT NULL AUTO_INCREMENT, package_id bigint(20) NOT NULL, item_id bigint(20) NOT NULL, PRIMARY KEY (id), KEY idx_package_id (package_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;第三张是预约单表这表是整个系统业务流转的中枢CREATE TABLE appointment ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 预约单号, user_id bigint(20) NOT NULL, package_id bigint(20) DEFAULT NULL COMMENT 选定的套餐, items_detail text COMMENT 实际检查项目快照JSON, appoint_date date DEFAULT NULL COMMENT 预约日期, appoint_time_slot varchar(16) DEFAULT NULL COMMENT 时段 如09:00-10:00, status tinyint(2) NOT NULL DEFAULT 0 COMMENT 0待支付 1待确认 2已确认 3已完成 4已取消, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;还有报告表和结果表报告表存报告元信息结果表存每一个检查项目的指标值。设计原则就是红线都不能断只要能表达出表之间的关联关系在ER图和数据库设计说明里拿到印象分你就成功了一半。3. 把个性化推荐说透评分规则引擎怎么设计这部分是整个系统的灵魂也是答辩时最能镇场的内容。3.1 规则怎么来体检领域的知识整理个性化推荐不是凭空拍脑袋它的行业根基是不同年龄、性别、职业、病史的人患某些疾病的风险不同所以需要做的检查项目也不同。我整理一套简化的规则体系足够支撑毕业设计的逻辑35岁以上建议增加空腹血糖、糖化血红蛋白糖尿病风险。吸烟人群建议增加低剂量螺旋CT肺癌早筛而非普通胸片。女性增加乳腺超声、妇科检查宫颈TCT/HPV。男性45岁以上增加前列腺特异抗原PSA。办公室久坐人群增加颈椎/腰椎影像学检查。肥胖人群BMI≥28增加血脂、肝功能、颈动脉超声。有胃部不适或家族胃癌史增加胃镜或幽门螺杆菌检测。高血压家族史增加同型半胱氨酸、心脏彩超。这些规则如果只是前端表单里写死就体现不出后端设计。正确的做法是把规则抽象成一组“风险因子”每个风险因子对应若干检查项目风险因子命中后向用户推荐对应的项目。3.2 评分模型把“风险因子”变成可计算的数值推荐结果不能只给一个“是/否”的二元答案那样不灵活。我采用评分维度阈值筛选的方式每个检查项目有一个基础分比如血常规20分腹部超声30分心脏彩超40分。每个风险因子命中后会对关联的检查项目增加权重分。遍历用户的健康档案计算每个候选项目最终得分得分超过阈值的项目进入“推荐列表”。可选套餐生成逻辑推荐列表和系统预设套餐做“重叠度匹配”重叠度最高的预设套餐作为推荐套餐如果某项风险因子极强比如家族史阳性则单独追加单项检查。这套逻辑用一句话概括就是基础套餐保底风险因子动态叠加最终形成一份“一人一策”的体检方案。3.3 代码实现推荐引擎核心逻辑推荐引擎我放在service层单独封装一个RecommendEngine类不跟Controller耦合。核心结构大致如下Component public class RecommendEngine { private final ListRiskRule rules; private final ExamItemMapper itemMapper; public RecommendResult recommend(HealthProfile profile) { // 1. 加载全部体检项目 ListExamItem allItems itemMapper.selectList(null); // 2. 初始化得分表 MapLong, Integer scoreMap new HashMap(); for (ExamItem item : allItems) { scoreMap.put(item.getId(), item.getBaseScore()); } // 3. 命中风险规则累加权重分 for (RiskRule rule : rules) { if (rule.matches(profile)) { for (Long itemId : rule.getRelevantItemIds()) { scoreMap.merge(itemId, rule.getWeight(), Integer::sum); } } } // 4. 过滤阈值 return buildResult(allItems, scoreMap); } }RiskRule是一个接口public interface RiskRule { boolean matches(HealthProfile profile); ListLong getRelevantItemIds(); int getWeight(); String getReason(); }每个具体规则就是一个实现类比如抽烟规则Component public class SmokeLungCancerRule implements RiskRule { Override public boolean matches(HealthProfile profile) { return Boolean.TRUE.equals(profile.getSmoke()); } Override public ListLong getRelevantItemIds() { // 返回低剂量螺旋CT、肺功能检测的项目ID return List.of(101L, 205L); } Override public int getWeight() { return 30; } Override public String getReason() { return 长期吸烟肺癌风险升高建议进行低剂量螺旋CT等肺部筛查; } }这种策略模式的设计好处是极其便于扩展。以后想加“熬夜人群加查心肌酶”之类的规则新增一个类就完事了不碰任何已有代码。论文里写“采用策略模式解耦推荐规则系统具备高度可扩展性”这句话在答辩时是能撑住追问的。推荐结果里除了套餐列表还要返回每个推荐项目的推荐理由比如“因为您有吸烟史”。这就是可解释性——毕业设计里能体现你考虑问题很全面。4. 预约与结果管理状态机是这类系统的命根子4.1 预约流程的状态设计预约模块最怕的就是状态乱。一个预约单从创建到结束中间经历哪些状态必须在设计阶段用状态机定死。我给这套系统定义的状态流转待支付用户确认套餐后创建预约单此时锁定名额但未付款。已支付/待确认支付成功后等待体检机构确认排期。已确认机构确认预约成功用户收到通知。已完成用户到检报告上传后状态置为完成。已取消用户主动取消或机构拒绝。状态机用Java怎么实现我建议不要用一堆if-else硬写而是用枚举把允许的流转写清楚public enum AppointmentStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), CONFIRMED(2, 已确认), COMPLETED(3, 已完成), CANCELLED(4, 已取消); public boolean canTransitTo(AppointmentStatus target) { switch (this) { case PENDING_PAYMENT: return target PAID || target CANCELLED; case PAID: return target CONFIRMED || target CANCELLED; case CONFIRMED: return target COMPLETED || target CANCELLED; default: return false; } } }改状态时统一走服务入口public void changeStatus(Long appointmentId, AppointmentStatus target) { Appointment appointment appointmentMapper.selectById(appointmentId); if (!appointment.getStatusEnum().canTransitTo(target)) { throw new BusinessException(非法状态流转 appointment.getStatus() - target); } appointment.setStatus(target.getValue()); appointmentMapper.updateById(appointment); }这个设计一出来答辩老师基本就不会在业务逻辑层面难为你了因为最容易被挑刺的“状态不合法”问题你用状态机从底层就堵死了。数据库设计里可以把状态更新做成乐观锁update_time跟着刷新并发问题再降一档。4.2 结果管理的闭环体检结果管理核心不在于“增删改查”而在于两个点第一个点是报告归属校验。用户只能查看自己的报告不能越权看别人的。这种接口是最容易被攻击的URL里传个reportId就能看到别人报告这在真实系统里属于重大漏洞。实现上哪怕是毕设也要写权限校验public ReportVO getReportDetail(Long reportId, Long currentUserId) { Report report reportMapper.selectById(reportId); if (!report.getUserId().equals(currentUserId)) { throw new AccessDeniedException(无权访问该报告); } // 继续返回数据 }第二个点是指标异常标注。报告不能只是把数值罗列出来这样和Excel没区别。你要做的核心是根据性别、年龄自动标注哪些指标超出参考区间并给出对应的解读建议。我在exam_item表里加了gender_reference字段和age_range字段存的是JSON格式的参考区间。读取指标后跟参考区间做比对结果返回一个status字段分normal正常、high偏高、low偏低前端就能渲染成红黄绿三色列表。配合历史报告还能画折线图展示同一指标趋势。这个功能点一旦做出来系统整体层次就不只是“毕设”了甚至可以直接写“连续健康管理”的亮点。4.3 关键业务节点的代码示例预约模块基于Redis数据库双写设计排班表先占用再确认。防止同一天同一时段被重复预定可以给appointment表加一个唯一约束appoint_date、appoint_time_slot、user_id同一个用户不能在一个时段重复下单。创建预约时要用Transactional保证事务一致Transactional(rollbackFor Exception.class) public Appointment createAppointment(CreateAppointmentRequest request, Long userId) { // 1. 查询套餐并校验有效性 PackageEntity pkg packageMapper.selectById(request.getPackageId()); if (pkg null || !上架.equals(pkg.getStatus())) { throw new BusinessException(套餐不存在或已下架); } // 2. 生成预约单号 String orderNo AP System.currentTimeMillis() RandomUtil.randomNumbers(4); // 3. 构建预约快照这里的关键套餐内容可能后续被修改必须快照 String itemsSnapshot JSON.toJSONString(loadPackageItems(pkg.getId())); // 4. 保存预约单 Appointment appointment new Appointment(); appointment.setOrderNo(orderNo); appointment.setUserId(userId); appointment.setPackageId(pkg.getId()); appointment.setItemsDetail(itemsSnapshot); appointment.setAppointDate(request.getDate()); appointment.setAppointTimeSlot(request.getTimeSlot()); appointment.setStatus(AppointmentStatus.PENDING_PAYMENT.getValue()); appointmentMapper.insert(appointment); return appointment; }一个很实用的小细节是快照。套餐内容不是永久不变的运营人员可能哪天把某个项目的价格或内容改了。如果预约单不存快照那用户体检那天看到的项目和下单时可能不一样售后问题就来了。存JSON快照一张表把该锁定的信息锁死这是我在实际项目里用过的习惯放到毕设里完全站得住脚。5. 实操过程与核心环节实现5.1 项目搭建与初始化这个段落专门写给可能还卡在“怎么搭环境”的同学。如果你连Spring Boot项目都还没建起来过建议按这个顺序走不会乱。第一步是JDK和开发工具。JDK装8或者11都行但注意Spring Boot版本跟JDK版本要匹配。Spring Boot 2.7对应JDK8和11都没问题Spring Boot 3.x则必须JDK17以上。很多同学控制台报错“无效的发行版本”“源选项8已过时”十有八九是IDE编译级别、项目JDK、maven配置的JDK三者不一致。装JDK时要额外注意把Path变量配对——很多刚上手的人配到一半配了JAVA_HOME又忘了配Pathcmd里java -version一直报错其实只是一个变量的问题。第二步是Maven仓库。国内直接用中央仓库下载依赖速度慢到你怀疑人生肯定要换阿里云镜像。在settings.xml里加这段mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror第三步是项目的基本骨架。一个合理的包结构是这样的com.example.physicalexam ├── controller │ ├── UserController.java │ ├── AppointController.java │ └── ReportController.java ├── service │ ├── RecommendService.java │ ├── AppointmentService.java │ └── ReportService.java ├── engine │ ├── RecommendEngine.java │ ├── RiskRule.java │ └── rule │ ├── SmokeLungCancerRule.java │ ├── DiabetesRule.java │ └── ... ├── mapper ├── entity ├── dto ├── vo ├── config └── common5.2 推荐引擎实现从规则到套餐的串联代码上的核心链路我在第3节已经给了一部分这里补充前端怎么展示推荐理由和如何做套餐匹配。从前端交互看用户进入“定制套餐”页面时第一步填写健康问卷第二步点击“生成推荐方案”。页面拿到后端返回的RecommendResult里面大致是下面这样的结构{ recommendedPackage: { packageId: 3, packageName: 职场精英升级体检套餐, items: [ {itemName: 血常规, reason: 基础项目}, {itemName: 低剂量螺旋CT, reason: 长期吸烟肺癌风险升高} ], totalPrice: 680.00 }, riskAlerts: [ {riskName: 吸烟史, level: 中, advice: 建议戒烟每年进行低剂量螺旋CT检查} ] }后端在buildResult方法里做套餐匹配时我用了“重合度优先”的简单算法遍历所有预设套餐计算推荐项目集合与套餐项目集合的交集占并集的比例比例最高且不低于50%的套餐就是推荐套餐。重合度低于50%时不推荐任何预设套餐而是允许用户自选推荐项目生成“定制套餐”。这样用户永远有一个兜底方案系统的选择自由度也很高。写推荐逻辑时一定要记得每个规则都要能向前端返回reason这是可解释性的核心。答辩时老师只要问“你这个推荐凭什么成立”你把reason调出来展示一遍这个问题的杀伤力就归零了。5.3 预约流程实现排班与状态推进的配合预约排班我是在admin端维护一张schedule表字段包括机构ID、日期、时段、总名额、已约名额。用户提交预约时后端先扣减名额再创建订单Transactional public Appointment createAppointment(CreateAppointmentRequest request, Long userId) { // 使用selectForUpdate锁住排班记录防止并发超卖 Schedule schedule scheduleMapper.selectForUpdate(request.getScheduleId()); if (schedule.getBookedCount() schedule.getTotalCount()) { throw new BusinessException(该时段名额已满); } // 预约时间必须在未来且不能早于当天 if (schedule.getDate().isBefore(LocalDate.now())) { throw new BusinessException(预约日期不能早于当前日期); } schedule.setBookedCount(schedule.getBookedCount() 1); scheduleMapper.updateById(schedule); // 然后创建预约单逻辑同4.3 }这里用了selectForUpdate在毕设场景里完全够用。虽然在高并发下性能不是最好但能保证正确性而且像这种真实系统里预约操作并发量本来就远低于秒杀场景。答辩时如果被问到并发问题你能说出“用数据库悲观锁保证同一时段的预约请求串行化避免超卖”这已经比大多数同水平同学强了。管理端确认预约的操作也比较直接管理员看到已支付的预约单点击确认后系统发送站内通知给用户。毕设里做站内通知就够了不用真的接短信。确认操作也走状态机的changeStatus方法不允许跳过中间的流转。5.4 报告结果模块实现从录入到查看的完整链路报告结果是整个体检链条的最后一段。管理员端的操作是根据预约单号找到已完成体检的预约录入该用户的各项检查指标值上传附件形式的PDF报告然后发布。用户端看到的是指标列表。我在实现时做了一件事前端传历史报告条数时后端会统计该用户最近三次同项指标的历史值让前端可以渲染趋势图。这一步就能把“结果管理”讲成“健康趋势管理”内容价值一下就不一样了。报告表关键字段report_id、appointment_id、user_id、report_no、status草稿/已发布、pdf_url、metric_dataJSON、create_time。metric_data是核心里面存的是检查项目的完整结果{ itemId: 102, itemName: 空腹血糖, value: 6.3, unit: mmol/L, referenceRange: 3.9-6.1, status: high, advice: 空腹血糖偏高建议内分泌科进一步检查排除糖尿病可能 }参考区间判定我单独封装了一个方法public String judgeMetric(String value, String referenceRange) { // 解析3.9-6.1格式的参考范围 String[] parts referenceRange.split(-); BigDecimal val new BigDecimal(value); BigDecimal low new BigDecimal(parts[0]); BigDecimal high new BigDecimal(parts[1]); if (val.compareTo(low) 0) return low; if (val.compareTo(high) 0) return high; return normal; }注意有的人直接拿字符串比较“6.3 6.1”一旦数值有整数位就出错。体检指标全是小数和单位必须用BigDecimal。6. 常见问题与排查技巧实录6.1 预约并发超卖问题现象同一时间多个用户抢同一个时段的最后一个名额系统里出现已预约人数大于名额数。原因先查空余名额再插入预约数据两步操作之间没有做并发控制后一个请求读到的还是旧数据。解决办法用selectForUpdate对排班记录加锁让同一时间的预约请求串行执行。另外可以在预约单表加唯一索引schedule_id、user_id用户不能重复预约同一时段双保险。排查思路先看预约表里是否出现同一个人同一个时段的多条记录再看是否有并发日志。我建议在开发阶段就把预约服务加上接口耗时日志方便排查。6.2 慢SQL与N1查询问题现象报告列表页打开极慢数据库CPU飙高。原因查询报告列表时在循环里逐条查用户表、预约表、套餐表。这种N1问题在MyBatis Plus里特别容易踩——查出来的10条报告记录每条都去数据库再查一次关联表。解决办法用MyBatis Plus的selectBatchIds一次性查出关联数据或者直接用自定义SQL连表查询。代码走查时看到循环里嵌套查询一律改掉。排查技巧开启MyBatis的SQL日志看一条列表接口到底打了多少条SQL。多次查询的“database traffic”模式一眼就能定位到循环内查询。6.3 Redis缓存与数据库一致性问题现象管理员修改了套餐价格用户端看到的还是旧价格。原因套餐列表缓存了Redis但管理员端更新操作只改了数据库没有删除缓存。解决办法设置缓存的过期时间比如30分钟管理员端更新后主动删除缓存。简单粗暴但好用。不要在写操作时同步更新缓存删除缓存让下次查询重建更可靠。实践心得缓存里只放套餐列表、体检项目分类这些读多写少的数据。预约单状态实时性高不要进缓存走数据库查询就行。6.4 环境与部署类的常见坑这类问题不写代码但特别花时间。Spring Boot启动时端口被占用用netstat -ano | findstr 8080查占用进程任务管理器杀掉就行。前后端联调出现跨域后端加CrossOrigin或者写一个CORS配置类。Redis连不上先看6379端口是不是通的再确认Redis配置密码是否正确最后看Spring Boot的配置文件是否用了正确的host。本机跑得动、部署到服务器上配置不一样云服务器上启动后端的时候注意改数据库地址和密码。Windows本机和Linux服务器路径写法不同PDF报告路径用相对路径存数据库别用D:\这种写死路径。6.5 答辩时最容易被追问的三个问题第一个是“为什么使用推荐引擎而不直接用现成套餐”回答要点现成套餐不能满足个性化需求规则引擎可以根据个人风险因子动态生成方案这是系统的核心创新点。第二个是“规则冲突时怎么处理”比如用户既年轻又吸烟按年龄不该做CT按吸烟史该做CT。回答要点风险因子采用“或”的逻辑叠加权重分加和只要有强风险因子命中就推荐相关项目评分数值本身已经体现了优先级。第三个是“系统如何保证数据安全”回答要点用户数据按用户ID隔离任何跨用户查询都做归属校验管理端接口单独加管理员权限拦截器数据库密码用环境变量配置不硬编码在代码里。这三个问题答上来答辩基本稳了。我个人的经验是体检套餐定制系统这种题目成败不在技术多花哨而在业务闭环是否完整、设计思路是否讲得通。你把“健康问卷采集 → 规则引擎推荐 → 套餐快照锁定 → 预约状态推进 → 报告指标分析”这条主线贯穿下来再用策略模式、状态机、事务控制这几个技术点往细节里填论文和系统就同时活了。代码写不完可以慢慢写但设计思路一定先想透——这是我把这个项目做完之后最想说的一句话。