简介:这是面向高校计算机类毕业设计及课程作业的智能导诊系统项目包,借助人工智能技术对用户症状进行解析与匹配,输出可能的疾病方向,可用于学习医疗辅助诊断系统的设计思路,适合具备基础Java与Web知识、希望接触AI落地场景的学生参考。压缩包共14个文件,容量41KB,主要有Java源码、FTL页面模板、XML与YML配置、数据库相关文件,以及JS/CSS/HTML前端资源和README说明文档,结构紧凑,便于快速梳理后端逻辑、页面渲染与配置文件之间的关系。目前已有322人学习。资源虽然轻量,但覆盖了后端服务、前端交互、数据存储与基础算法模块,能帮助读者理解从症状输入到结果展示的完整链路;同时涉及配置管理与项目部署的常见实践,可作为课程答辩的参考实现,也可在此基础上扩展模型与数据。
1. 智能导诊系统的真实构成:不只是把症状丢给算法
做毕设选“智能导诊系统”这个题目的同学,大概率是被两个问题卡住的:第一,导诊逻辑到底怎么写才能显得“智能”,而不是一堆 if-else 硬编码;第二,AI 部分和 Web 部分怎么缝合,才能让答辩老师觉得这是一个完整的系统而不是两个 demo 的拼接。这个压缩包里的项目结构暴露了答案——pom.xml说明后端是 Maven 工程,src/test说明有测试代码,README.md说明作者至少把运行步骤写清楚了。从工程组织来看,这不是一个纯算法的仓库,而是一个从数据到接口再到前端的全栈闭环。
我拆过不少类似的医疗类课程项目,说实话,导诊系统的技术含量不在于模型多深,而在于“症状描述 → 科室推荐”这条链路的工程化完整度。自然语言处理不需要你训练一个 GPT,经典的做法是基于医学词典做分词和关键词匹配,再用规则引擎或轻量级分类器兜底。这个项目把 AI、数据库设计、Web 开发、测试全串在一起,恰好就是计算机专业毕设最标准的“综合应用”打法。下面我按照“从数据到接口再到前端排错”的路径,把每个值得展开的模块、参数和坑位都过一遍。
2. 智能导诊的语义解析层:症状关键词抽取与科室映射
2.1 为什么先做关键词抽取而不是上深度学习
用户输入“我头痛发热咳嗽三天了”,系统要做的第一件事不是预测疾病,而是把这句话拆成可匹配的医学实体。深度学习方案在这个场景里性价比很低:训练数据难获取,标注成本高,而且毕设答辩时你很难解释清楚模型为什么把“头痛”归到神经内科而不是呼吸科。更务实的路径是基于医学词典的最大正向匹配分词,配合词性过滤和同义词归一化。
这个项目里我推测核心用的是 HanLP 或结巴分词加载自定义医学词典,因为pom.xml里如果引入了com.hankcs:hanlp或者org.ansj:ansj_seg,对应的就是这种方案。自定义词典的格式非常简单,每行一个词,可以带词性和频次,例如:
头痛 n 100 偏头痛 nz 80 发热 n 90 咳嗽 v 85 咽痛 n 70加载方式在各个框架里大同小异,以 HanLP 为例,把词典放入resources/dictionary/custom/目录,然后在hanlp.properties中指定路径。实际调用时核心代码就几行:
List<Term> termList = HanLP.segment(userInput); for (Term term : termList) { System.out.println(term.word + " / " + term.nature); }这段代码把用户输入切分为词项,然后根据词性过滤掉代词、语气词等噪声,只保留名词和动词作为候选症状。参数上要注意 HanLP 默认词典是通用的,医疗场景必须把自定义词典的优先级调到最高,否则“心慌”可能被切成“心”和“慌”,直接丢失语义。我在实际项目里一般会把CustomDictionary的覆盖模式设为OVERWRITE,确保医学词条优先命中。
2.2 同义词归一化与症状编码表
分词只是第一步,更关键的在于归一化。用户可能说“头疼”“头痛”“脑袋疼”,这三个表述必须映射到同一个症状编码上,否则后续的科室映射表会膨胀得不可维护。这里要用到一张症状编码表,它可以是 Java 枚举、数据库字典表或者 JSON 文件。我建议放在数据库字典表里,因为毕设的导诊系统往往还需要后台管理功能,管理员需要能动态添加同义词,而不是改代码重新部署。
建表语句长这样:
CREATE TABLE symptom_dict ( id INT PRIMARY KEY AUTO_INCREMENT, symptom_code VARCHAR(32) NOT NULL COMMENT '症状标准编码,如 SYM_HEADACHE', symptom_name VARCHAR(64) NOT NULL COMMENT '标准症状名', alias_name VARCHAR(64) NOT NULL COMMENT '同义词/别名', UNIQUE KEY uk_alias (alias_name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;匹配时先把用户输入分词,再拿每个词去symptom_dict表里查alias_name,命中的记录返回symptom_code。这里有个数据库设计的小技巧:alias_name建唯一索引,因为同一个词理论上不应该映射到两个不同的标准症状,这能在数据层面阻止脏数据进入。
一套标准症状编码表大概维护几百条记录就足够覆盖课程设计场景。每个症状编码再关联一个权重值,因为“发热”对呼吸科的指向性不如“咳嗽”强,权重可以在 0.5 到 1.0 之间浮动。这个权重值在后面计算科室得分时非常有用,先记住这个概念。
2.3 多症状输入的冲突消解规则
当用户同时输入多个症状时,系统要处理症状之间可能出现的矛盾。比如“腹痛”和“胸痛”同时出现,到底去消化科还是心内科?朴素的实现是取得分最高的科室,但更合理的做法是把症状按权重排序后做决策树判断。我见过一个比较稳妥的方案:把症状分为“强指向症状”和“弱指向症状”,强指向症状拥有一票决定权,弱指向症状只做辅助得分。
这段逻辑用 Java 表达就是:
public String matchDepartment(List<String> symptomCodes) { Map<String, Integer> deptScoreMap = new HashMap<>(); for (String code : symptomCodes) { List<DeptMapping> mappings = deptMappingMapper.selectBySymptomCode(code); for (DeptMapping mapping : mappings) { if (mapping.getIsStrong() == 1) { return mapping.getDeptCode(); // 强指向直接短路 } deptScoreMap.merge(mapping.getDeptCode(), mapping.getWeight(), Integer::sum); } } return deptScoreMap.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) .orElse(DEFAULT_DEPT); }注意这个实现里强指向症状直接返回,不走累加流程,这在医疗场景里的语义是“症状 A 强烈提示科室 B,不再考虑其他可能性”。弱指向症状则通过权重累加排序,取最高分科室。这里的DEFAULT_DEPT是全科或者导诊台,用于完全无法匹配的兜底场景。dept_mapping表是导诊系统的核心业务表,联查队列的 SQL 我在下一节展开。
3. 导诊服务端核心链路:科室映射表与规则引擎
3.1 科室映射表的字段设计与检索 SQL
如果只做一个症状匹配一个科室的映射,那用 HashMap 就够了,根本不需要数据库。但实际系统中一个症状往往对应多个科室,比如“头晕”既可能是神经内科,也可能是耳鼻喉科,还可能是心血管内科。所以映射表要设计成多对多的结构,并且为每个映射关系赋予权重和优先级。
我一般这样设计:
CREATE TABLE dept_mapping ( id BIGINT PRIMARY KEY AUTO_INCREMENT, symptom_code VARCHAR(32) NOT NULL, dept_code VARCHAR(32) NOT NULL, weight DECIMAL(3,2) DEFAULT 0.5 COMMENT '权重,0~1', priority INT DEFAULT 0 COMMENT '优先级,数值越小越优先', is_strong TINYINT DEFAULT 0 COMMENT '强指向标记', KEY idx_symptom (symptom_code), KEY idx_dept (dept_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;查询接口设计成批量查询,避免在 Java 代码里循环查库:用户一次输入最多可能包含 5 到 10 个有效症状,如果每个症状查一次库,在高并发场景下数据库连接会被迅速耗尽,虽然毕设不需要撑住高并发,但写成批量查询的习惯在答辩时是一个亮点。对应的 MyBatis Mapper 方法:
List<DeptMapping> selectBySymptomCodes(@Param("codes") List<String> codes);对应 XML 里的 SQL 用foreach拼接:
<select id="selectBySymptomCodes" resultType="DeptMapping"> SELECT * FROM dept_mapping WHERE symptom_code IN <foreach collection="codes" item="code" open="(" separator="," close=")"> #{code} </foreach> ORDER BY symptom_code, priority ASC, weight DESC </select>注意ORDER BY的写法,先按symptom_code分组,再按priority升序、weight降序排列,这样在 Java 侧遍历时可以把同一症状的映射记录按优先级从高到低取用,不需要再做一次内存排序。
3.2 规则引擎:让导诊逻辑从代码中解耦
科室得分计算如果直接写在 Controller 或 Service 里,一旦规则调整就需要改代码、重新编译、重新部署。虽然毕设不追求微服务,但把规则抽象成独立组件会让代码层次更清晰,答辩时也更好讲。这里的规则引擎不引入 Drools 这种重量级框架,而是用策略模式加配置化的规则模板。
规则模板用 JSON 存储,放在resources/rules/目录:
[ { "id": "RULE_001", "name": "发热伴呼吸道症状", "conditions": { "include": ["SYM_FEVER", "SYM_COUGH"], "exclude": ["SYM_RASH"] }, "action": { "dept": "DEPT_RESPIRATORY", "scoreBonus": 30 } } ]这个规则的含义是:当症状集合中同时存在“发热”和“咳嗽”,且不存在“皮疹”时,给呼吸科加 30 分。实现时用一个规则解析器读取 JSON 到Rule对象列表,在计算科室得分时依次匹配。这样做的核心优势是,医学知识的调整不需要动 Java 代码,改 JSON 文件即可,对于后续扩展“儿童版导诊”或“老年版导诊”非常有价值。
规则引擎的匹配核心逻辑:
public void applyRules(List<String> symptoms, Map<String, Integer> scoreMap) { Set<String> symptomSet = new HashSet<>(symptoms); for (Rule rule : ruleList) { if (symptomSet.containsAll(rule.getConditions().getInclude()) && Collections.disjoint(symptomSet, rule.getConditions().getExclude())) { String dept = rule.getAction().getDept(); scoreMap.merge(dept, rule.getAction().getScoreBonus(), Integer::sum); } } }参数说明:include列表是必须全部命中的症状集合,exclude列表是命中任意一个即跳过该规则的禁忌症状集合,scoreBonus是附加分数。这种“正向必需 + 反向排除”的组合能处理掉大多数导诊中的常识问题,比如“发热+皮疹”不能简单导向呼吸科,要排除传染病或皮肤科的可能性。
3.3 科室排序与推荐结果组装
计算出所有科室的得分后,响应给前端的数据结构应该包含科室名称、科室简介、匹配置信度、推荐医生数等信息。置信度不是简单的分数归一化,我见过一个比较合理的做法是用当前科室得分 / 所有科室最高分,得到一个 0 到 1 之间的相对值,然后四舍五入保留两位小数。
响应体的 JSON 结构设计为:
{ "code": 0, "message": "success", "data": { "departmentList": [ { "deptCode": "DEPT_RESPIRATORY", "deptName": "呼吸内科", "confidence": 0.95, "intro": "诊治呼吸系统相关疾病", "doctorCount": 12 } ], "rawSymptoms": ["头痛", "发热", "咳嗽"], "processTimeMs": 36 } }processTimeMs这个字段很重要,前端可以从这里直接拿到耗时做展示,也方便你验证规则引擎优化前后的性能差异。组装这个响应时,注意把doctorCount从doctor表关联查询出来,而不是写死。这同时验证了数据库一对多关系的设计,在答辩时可以展开讲。
4. Maven 工程实战:从 pom.xml 依赖到前后端联调
4.1 依赖选型与版本兼容性排查
打开pom.xml第一件事是核对 Spring Boot 版本和 Java 版本是否匹配。常见坑位是 Spring Boot 2.7.x 配 Java 8 没问题,但如果你电脑装的是 Java 17,需要把 Spring Boot 升到 2.7.8 以上或者直接用 3.0.x。另一个坑是 MyBatis Spring Boot Starter 的版本兼容性,官方文档说得比较清楚的是mybatis-spring-boot-starter2.x 系列适配 Spring Boot 2.x,3.x 系列适配 Spring Boot 3.x 和 Java 17。
核心依赖清单和用途如下:
| 依赖 GAV | 版本建议 | 用途 |
|---|---|---|
org.springframework.boot:spring-boot-starter-web | 2.7.18 | MVC 层与内嵌 Tomcat |
org.mybatis.spring.boot:mybatis-spring-boot-starter | 2.3.2 | 数据持久层 |
mysql:mysql-connector-java | 8.0.33 | MySQL 驱动 |
com.hankcs:hanlp | 1.8.4 | 中文分词 |
org.projectlombok:lombok | 1.18.30 | 简化实体代码 |
org.springframework.boot:spring-boot-starter-test | 2.7.18 | 单元测试与集成测试 |
Spring Boot 2.7.18 是 2.x 系列最后一个版本,比 2.7.17 多修了几个 CVE,对于课程项目来说稳定性优先,不建议去冒险用 Spring Boot 3 测试版。如果你之前装过别的 JDK,启动项目前先用java -version确认当前环境的 Java 版本,再在 IDE 里同步 Project Structure 的 SDK 设置。
4.2 分诊服务接口的完整实现
前端页面调用的核心接口是/api/consult,接收用户输入的症状描述文本,返回导诊推荐结果。Controller 层保持薄,只做参数校验和响应封装,业务逻辑全部下沉到 Service:
@RestController @RequestMapping("/api") public class ConsultController { private final ConsultService consultService; public ConsultController(ConsultService consultService) { this.consultService = consultService; } @PostMapping("/consult") public Result<ConsultResponse> consult(@RequestBody ConsultRequest request) { if (StringUtils.isBlank(request.getText())) { return Result.error("症状描述不能为空"); } if (request.getText().length() > 200) { return Result.error("症状描述过长,请控制在200字以内"); } long start = System.currentTimeMillis(); ConsultResponse response = consultService.doConsult(request.getText()); response.setProcessTimeMs(System.currentTimeMillis() - start); return Result.success(response); } }参数说明:ConsultRequest包含一个text字段,限制 200 字以内,这是为了防止恶意超长文本导致分词算法耗时剧增或内存溢出。Result是统一响应体,包含code、message、data三个字段。如果你看过一些教学项目,会发现很多人把业务逻辑写在 Controller 里,这里刻意把doConsult放到 Service 就是为了体现分层意识。
Service 层内部依次调用分词组件、症状匹配组件、科室评分组件、规则引擎组件和结果组装组件,在编码时建议在关键方法上加上 SLF4J 日志:
log.info("用户输入: {}, 匹配症状数: {}, 推荐科室: {}", text, matchedSymptoms.size(), result.getDeptName());打印日志在开发和接口调试阶段极有用,不然你得靠肉眼比对请求参数和返回结果来定位问题。
4.3 前端集成与跨域配置
前端如果是 Vue 项目,本地开发时跑在 8080 端口,后端跑在 9090 端口,必须解决跨域。一个常见的做法是在后端加一个全局 CORS 配置类:不用在每个@RequestMapping上都写@CrossOrigin,直接注册一个WebMvcConfigurer:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意.allowCredentials(true)和.allowedOrigins("*")不能同时使用,否则浏览器会报错,明确写出前端地址是最稳妥的。如果前端页面用 Nginx 代理后部署,可以删掉这个配置,改为在 Nginx 层处理跨域。前端 Axios 调用后端的示例:
axios.post('/api/consult', { text: this.symptomText }, { headers: { 'Content-Type': 'application/json' } }).then(res => { if (res.data.code === 0) { this.deptList = res.data.data.departmentList; } });这里有一个细节是 URL 路径:如果开发环境前端跑在 Vite 或 Webpack 的 dev server 上,需要在vite.config.js里配置 proxy,把/api前缀代理到后端服务,而不是在前端代码里写死http://localhost:9090/api/consult。写死跨域在本地能跑,但到时候部署到同一台服务器上就成了垃圾代码。
4.4 测试代码设计要点
src/test目录下的测试不能只是走流程,要能证明你的算法正确。我在这种项目里通常会放三类测试:分词测试、匹配测试、接口集成测试。分词测试要覆盖同义词归一化;匹配测试要覆盖多症状输入时科室得分排序的边界条件;集成测试使用@SpringBootTest拉起完整上下文,用 MockMvc 模拟 HTTP 请求:
@SpringBootTest @AutoConfigureMockMvc class ConsultIntegrationTest { @Autowired private MockMvc mockMvc; @Test void testConsultWithTypicalSymptoms() throws Exception { mockMvc.perform(post("/api/consult") .contentType(MediaType.APPLICATION_JSON) .content("{\"text\":\"头痛发热咳嗽三天\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.departmentList[0].deptName").value("呼吸内科")); } }这里jsonPath("$.data.departmentList[0].deptName")断言排名第一的科室是呼吸内科,如果规则引擎的权重调整导致结果变成了神经内科,这个测试会第一时间报红,省去手动 Postman 验证的流程。
5. 数据持久化设计:病历表、队列表与索引优化
5.1 核心表结构设计
导诊系统除了导诊本身,还需要支撑“历史咨询记录查看”和“科室排队人数实时显示”这两个功能点,所以至少需要三张核心表:咨询记录表consult_record、科室表department、科室队列表dept_queue。咨询记录表存每次导诊的用户输入、症状标签、推荐科室、用户是否采纳等信息,这些数据积累起来可以做后续分析和模型迭代,也是答辩时“数据闭环”的证明。
咨询记录表结构如下:
CREATE TABLE consult_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(64) COMMENT '匿名用户ID', raw_text VARCHAR(500) NOT NULL COMMENT '用户原始输入', matched_symptoms VARCHAR(255) COMMENT '匹配到的症状编码,逗号分隔', result_dept VARCHAR(32) COMMENT '推荐科室编码', confidence DECIMAL(3,2) COMMENT '推荐置信度', is_accepted TINYINT DEFAULT 0 COMMENT '用户是否采纳推荐', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_dept (result_dept), KEY idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;科室队列表设计成为“科室 ID + 当前排队数 + 预计等待时间”,实时调整。排队的实现不一定要上 Redis,用一张表加乐观锁就够了:
CREATE TABLE dept_queue ( dept_code VARCHAR(32) PRIMARY KEY, waiting_count INT DEFAULT 0, avg_wait_minutes INT DEFAULT 0, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, version INT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;version字段用于乐观锁控制并发更新,比如用户在导诊结果页点击“取号”,系统执行UPDATE dept_queue SET waiting_count = waiting_count + 1, version = version + 1 WHERE dept_code = ? AND version = ?,如果更新行数为 0 则重试。这个设计能撑住毕设场景的并发量,同时展示了乐观锁的知识点。
5.2 查询性能优化:从慢 SQL 到索引覆盖
课程项目的数据量通常只有几百条,索引对查询速度的改善不明显,但写对索引是加分项。需要注意的一个反直觉坑:consult_record表上的联合索引要按“查询频率”来设计,而不是按字段顺序。最常见的查询是“某用户的历史记录”,那么(user_id, created_at)联合索引要优于单独在user_id上建索引,因为这样可以避免回表查created_at。
如果 MySQL 版本是 5.7 以上,建议通过慢查询日志验证:
SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;然后再跑一次导诊流程,查看慢查询日志定位哪条 SQL 耗时超过 1 秒。医疗类课程项目经常出现的慢 SQL 是在matched_symptoms字段上做LIKE '%SYM_FEVER%',这种模糊匹配走不了索引。解决方案是拆分表,把consult_record和consult_symptom_rel拆成一对多关联表:
CREATE TABLE consult_symptom_rel ( consult_id BIGINT NOT NULL, symptom_code VARCHAR(32) NOT NULL, PRIMARY KEY (consult_id, symptom_code), KEY idx_symptom (symptom_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这样查询“哪些咨询记录包含发热症状”就变成主键索引扫描,而不是全表模糊匹配。
5.3 MyBatis 分页与批量插入
导诊记录列表的展示一般要分页,MyBatis 生态里最常用的是 PageHelper,但注意 PageHelper 的分页原理是在你的 SQL 后面拼接LIMIT语句,所以它必须作用于立即执行的 Mapper 方法上,不能先查了 List 再分页。常见的误用是在 Service 方法里先调用了另一个查询,破坏了 PageHelper 的 ThreadLocal 上下文,导致分页失效。
批量插入采用 MyBatis 的foreach:
<insert id="batchInsertRel"> INSERT INTO consult_symptom_rel (consult_id, symptom_code) VALUES <foreach collection="rels" item="rel" separator=","> (#{rel.consultId}, #{rel.symptomCode}) </foreach> </insert>注意 MySQL 默认的max_allowed_packet是 4MB,单条 INSERT 语句拼接太大可能超出限制,所以分批插入时每批控制在 500 条左右比较稳妥。
6. 部署运行与失败恢复:从本地跑通到 Linux 服务器
6.1 本地启动的完整步骤
本地启动这个项目,需要依次保证 MySQL、Redis(如果有用到的话)、Maven 三个环境就绪。MySQL 初始化脚本一般在src/main/resources/db/目录下,执行完建库建表之后,修改application.yml里的数据源配置:
spring: datasource: url: jdbc:mysql://localhost:3306/intelligent_guide?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver这里有两个小坑:一是serverTimezone必须显式指定,否则 MySQL 8.x 驱动会报时区异常;二是characterEncoding=utf8不能写成utf-8,JDBC 识别不了连字符。然后执行:
mvn clean package -DskipTests java -jar target/intelligent-guide-0.0.1-SNAPSHOT.jar如果想在本地跑测试,去掉-DskipTests即可。
6.2 服务器部署与 Nginx 反向代理
Linux 服务器上部署的核心是把项目打成 jar 包丢上去,用nohup或systemd守护进程跑。nohup的写法是:
nohup java -Xms256m -Xmx512m -jar intelligent-guide-0.0.1-SNAPSHOT.jar > app.log 2>&1 &-Xms256m -Xmx512m限定堆内存,对只有 2G 内存的云服务器很关键,不然默认堆大小会占到物理内存的四分之一,导致其他服务没内存可用。日志重定向到app.log,排查启动问题就看这个文件。
Nginx 反向代理的配置片段:
server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; } }location /api/单独分流,是为了后续如果要把接口服务拆出去扩展时不需要改前端代码。
6.3 前端打包后如何正确配 API 地址
前端项目开发时用 Vite 代理,打包后要改用相对路径,不然部署上线后请求会打到服务器上不存在的 8080 端口。在.env.production文件中设置:
VITE_API_BASE_URL=/api前端代码统一用import.meta.env.VITE_API_BASE_URL拼接请求路径,这样打包后请求会走 Nginx 的/api/反向代理。答辩现场的演示环境经常遇到的问题是,前端页面能打开但接口请求 404,基本就是这里配错了。
6.4 常见启动失败与日志定位策略
启动失败分两类:编译失败和运行失败。编译失败通过 Maven 输出定位,最常见的是 Lombok 插件没配置好导致log.info找不到符号;运行失败要立刻看app.log的前几十行,如果打印了APPLICATION FAILED TO START,说明是application.yml中的配置缺失或数据库连不上。一个实操技巧是启动时加一个--debug参数,Spring Boot 会输出自动配置的匹配和排除日志,能快速看出 MyBatis 的 Mapper 有没有被正确扫描到。
提示:如果启动后接口能调用但导诊结果始终是默认科室,优先检查
dept_mapping表是否插入了数据,很多项目代码没问题,是初始化 SQL 漏执行导致数据表为空。
本文还有配套的精品资源,点击获取