
做毕业设计最怕的不是写代码而是题目选完发现做不下去。SpringBoot在线病患管理系统这种题目看着满大街都是好像谁都能写但真正动手的时候从功能划分到表结构设计再从权限控制到工作流集成每一个环节都有不少坑等着你。这篇文章我不打算给你复述一遍SpringBoot基础教程而是把我自己从选题、设计到编码、调试的完整思路以及那些真正容易踩坑的地方一次性写清楚。这套系统说白了就是给医院做一个数字化的门诊住院一体化平台患者能在线挂号、查报告医生能写病历、开处方、下医嘱住院部能管床位、管医嘱执行、管出院结算。它的核心价值就是把医院里原本靠纸质单据和人肉传递的那套流程搬到线上来让每个环节的数据都可追踪、可统计。如果你准备拿这个题目当毕业设计或者你正处于学习SSM/SpringBoot阶段想找一个综合性强的练手项目这篇文章值得你认真看到最后。1. 项目整体设计与思路拆解1.1 为什么要做“门诊住院一体化”很多同学做医院系统容易把范围铺得太大什么在线问诊、药品库存、医保报销全塞进去结果每一个模块都做不深论文写出来全是流水账。我做这个题目的时候给自己划了一条清晰的边界只做“门诊住院”两条主线外加一个在线服务平台。医院的核心业务其实可以拆成两条线。门诊线是“患者到院-挂号-医生看诊-开处方/检查-缴费-取药/检查”住院线是“医生开住院单-办理入院-分配床位-下达医嘱-护士执行-出院结算-病案归档”。两条线之间有交叉比如门诊医生开的住院单要流转到住院部住院期间做的检查项目又要回到门诊检验科室去但这些交叉点在系统里都体现为“状态流转”和“数据引用”而不是另起炉灶做新模块。这个设计思路解决了一个很现实的问题如果只做“在线挂号病历管理”系统看起来就像一个信息录入工具技术含量不高如果做“门诊住院一体化”你就必须面对流程引擎、状态机、多角色权限、复杂关联查询这些真实业务难题无论是代码量还是论文素材都一下子丰富起来。所以我在答辩PPT里写的第一句话就是本系统不是医院的信息孤岛而是贯穿门诊与住院全流程的数字化连接器。1.2 角色权限到底该怎么划分权限设计做得好不好直接决定你的系统是“玩具”还是“能用的系统”。病患管理系统的角色我最终划分成六类系统管理员、门诊医生、住院医生、护士、药房/收费人员、患者。角色之间不是平级关系而是围绕业务流程形成协作关系。门诊医生和住院医生虽然都叫医生但他们的工作台完全不同门诊医生主要处理挂号队列、写门诊病历、开处方住院医生面对的是自己管辖的病区病人列表、下达长期和临时医嘱。护士只拥有“执行医嘱”和“护理记录”的权限没有修改诊断的权限。药房人员只能看到待发药和已发药的处方记录不能查看患者的完整病历。这里有一个很容易被忽略但很关键的细节患者角色不应该一个用户表就搞定所有信息。我是用user表存登录账号再用patient表存患者的实名信息两张表通过user_id关联。因为系统的管理员、医生、护士也需要登录他们的身份信息结构和患者差别很大强行塞在一张表里会导致大量字段为空既浪费存储又让代码写得别扭。1.3 前端技术选型模板引擎还是前后端分离关于前端我一开始纠结过用Thymeleaf模板引擎还是Vue前后端分离。后来综合考虑毕业设计的工作量和答辩展示效果最终选了Vue3 Element-Plus Axios后端提供纯JSON接口。原因很简单前后端分离的项目结构清晰前端页面组件化答辩的时候展示页面效果比传统模板渲染要好看得多而且简历上写“前后端分离开发经验”也是一项加分项。但如果你对Vue不熟或者时间非常紧张用Thymeleaf加Bootstrap也完全能做出漂亮的管理界面。这里我不劝导你一定要用哪种想清楚自己时间是否够用就行。如果是前后端分离要注意处理好跨域问题后端的CORS配置、前端Axios的baseURL统一配置这些都要在一开始就搭好框架不然后面联调会非常痛苦。2. 核心技术选型与项目初始化2.1 SpringBoot版本与配套组件版本搭配我选的是SpringBoot 2.7.x版本而不是最新的3.x。原因很实在3.x要求JDK17及以上而且很多第三方组件的兼容性在毕业设计阶段会给你添麻烦。2.7.x用JDK8就能跑起来学校机房电脑普遍装的就是JDK8答辩演示的时候不用为环境折腾半天。配套组件我列一个清单你们可以直接抄作业组件推荐版本用途SpringBoot2.7.6基础框架MyBatis-Plus3.5.3ORM减少SQL编写MySQL8.0关系型数据库Redis5.x以上验证码缓存、Token刷新Spring SecuritySpringBoot自带依赖认证与授权JWTjjwt 0.9.1无状态TokenFlowable6.7.2流程引擎住院审批、请假审批Swaggerspringfox 3.0.0接口文档Hutool5.8.x工具箱Excel导入导出等这里单独说一下为什么在毕业设计里用Flowable工作流引擎。很多同学看到工作流这仨字就劝退了觉得太重。但仔细想一想医院的住院审批、手术审批、请假审批本质上就是流程审批如果全靠if-else硬写业务一旦变化代码就要跟着改流程状态多了以后代码会变成一团乱麻。用Flowable流程定义是一个BPMN文件节点和流转条件可视化配置审批记录也自动归档到引擎的历史表里代码量反而变少了。答辩的时候还能多讲一张流程图怎么算都不亏。2.2 项目工程结构建议我的工程结构是按模块分包而不是按层分包。按层分包就是controller、service、mapper各建一个包所有类都往里面扔项目小的时候很清晰项目一复杂就全乱了。按模块分包的意思是系统登录认证单独一个包患者门户一个包门诊管理一个包住院管理一个包工作流一个包。每个包内部再分controller、service、mapper等子包。这种结构的好处是你改动一个模块的功能时只在这个包里动刀不影响其他模块。com.hospital ├── common // 通用返回结果、通用常量、异常处理 ├── config // 配置类如WebMvc、CORS、Swagger、Flowable ├── security // Spring Security JWT 相关 ├── module │ ├── system // 用户、角色、菜单管理 │ ├── patient // 患者端门户 │ ├── outpatient // 门诊模块 │ ├── inpatient // 住院模块 │ ├── pharmacy // 药房药库模块 │ └── workflow // Flowable集成 └── utils // 工具类统一返回结果类我用的是Result 里面包含code、message和data三个字段。code为200表示成功500表示业务异常401表示认证失败。全局异常处理器用RestControllerAdvice捕获所有异常业务异常走BusinessException系统异常打印日志后返回兜底提示。这一套东西虽然花不了几小时但会让整个项目的代码规范度提升一档答辩老师看代码的时候印象分会好很多。2.3 开发环境与数据库准备开发工具我建议用IDEA社区版就够用数据库用Navicat或者MySQL Workbench都行。建库的时候注意字符集一定要用utf8mb4不要用默认的utf8。这个坑我踩过数据表里一旦有表情符号比如患者姓名旁边加个emoji备注utf8会直接报错而utf8mb4能正常存取。还有排序规则用utf8mb4_general_ci就好不是越大越好。3. 核心功能模块拆解与实现3.1 用户认证与JWT无状态登录登录逻辑看起来简单但要做好还是有细节。我用的方案是Spring Security JWT。流程是这样的用户提交账号密码后端认证成功后生成一个JWT Token返回给前端前端存在localStorage里每次请求都把这个Token放到请求头的Authorization字段里后端通过一个过滤器校验Token的合法性并从中解析出用户ID和角色。这里有一个特别值得注意的细节JWT的有效期设置。我设置了两小时过期但用户可能操作不止两小时所以我还做了一套简单的Token续期机制。具体做法是后端在响应头里返回一个新的Token当前端检测到新Token时自动更新本地存储。这样用户体验好代码又不复杂。如果你不做续期到答辩演示时评委老师多看了几分钟你自己的系统就401了场面会很尴尬。密码存储上我用的是BCrypt加密注册时加密登录时校验。Spring Security框架自带BCryptPasswordEncoder不用自己写加密算法也不要用MD5MD5彩虹表破解太容易了答辩时被追问起来不好解释。3.2 门诊模块号源排班与在线挂号门诊模块是这个系统最体现业务逻辑的地方。挂号的前提是号源充足号源哪里来医生排班产生号源。所以我在设计上拆了两张表doctor_schedule表存排班记录比如某个医生在某个日期上午出诊放号30个registration表存患者的挂号记录每挂一个号就把对应排班记录的已约数加一当已约数大于等于总号数时这个时间段就自动约满。这个“号源扣减”操作在并发情况下会出问题但在毕业设计里不需要引入消息队列那么重的方案只需要在数据库层面把扣减写成原子操作。我用的是乐观锁的方案在doctor_schedule表里加一个version字段更新已约数时检查version如果version变了就说明有人抢先操作重新计算后再试。SQL大概是这样的UPDATE doctor_schedule SET booked_count booked_count 1, version version 1 WHERE id #{scheduleId} AND booked_count total_count AND version #{oldVersion}这里的关键是执行update后检查影响行数如果影响行数为0说明号源已经被抢完或者版本冲突需要提示用户重新选择。虽然毕业设计不用面面俱到但把这个细节讲清楚你自己的代码逻辑会更严密答辩时也能多聊几句高并发场景下的数据一致性。挂号成功后系统自动生成排队序号。这里我用了Redis的自增命令以排班记录ID作为key每挂一个号INCR一次得到的就是排队号。Redis的INCR是原子操作比从数据库查询max序号再加一靠谱得多也不用担心并发问题。3.3 门诊医生工作台病历、处方与检查申请门诊医生端的主界面我做成一个“工作台”的概念。医生登录后第一眼看到的是当前正在等待就诊的患者列表来自挂号记录状态为“待就诊”。点击“开始诊疗”这个患者的状态改成“就诊中”医生就可以在这个界面上写主诉、现病史、既往史、诊断结果开处方或者开具检查检验申请单。这里有一个设计上的取舍处方和检查申请不直接绑死到挂号记录上而是先写入一张“就诊记录”表也就是一次就诊的主记录处方、检查、诊断都挂在就诊记录下面。这样做的原因是一次挂号就诊可能开多张处方也可能开多种检查如果直接挂在挂号记录下数据会混乱后期统计也不方便。这个“一次就诊-多条业务子记录”的模型其实是很多医疗系统的通用设计理解了它对后面住院模块的设计也有帮助。处方这块我用的是主从表结构。处方主表存开单医生、患者、就诊记录ID、总金额、状态处方明细表存具体的药品名称、规格、数量、用法用量。药品信息要从药品目录表里选择不能手输药品名否则药房那边没法对应药品库存发药。我在前端做了一个搜索选择器后端提供药品模糊查询接口同时能查到库存数量库存不足时前端直接禁止添加。这个细节看似小但实实在在地避免了“处方开了却没药”的业务错误。3.4 住院模块入院登记、医嘱执行与出院结算住院模块是另一个重头戏。流程从门诊医生开住院单开始住院单上有建议入住的科室、初步诊断和病情描述。患者拿着住院单到住院部护士站办理入院手续选择病区系统自动分配床位。这里的关键是一张bed表每张床有状态字段空闲、占用、消毒中。分配床位时只能选空闲的床选中后立刻把状态改成占用出院时再释放。这一步的逻辑可以用一个很简单的状态机来描述但一定要保证状态变更的原子性防止两个护士同时分配同一张床。医嘱管理是住院模块里最核心的功能。医嘱分为长期医嘱和临时医嘱长期医嘱比如“每日8点口服降压药一片”临时医嘱比如“今晚急诊抽血查电解质”。医嘱由住院医生下达护士在审核后执行。每一条医嘱都要求有一个执行记录说明什么时候由哪位护士执行执行后患者状态有无异常。这套逻辑本质上是对医院实际业务流程的模拟如果只做简单的增删改查住院模块就没有灵魂了。出院结算这里我建议做成两个步骤先由医生在医生端发起“出院申请”填写出院诊断和出院小结护士核对患者住院期间的医嘱执行情况和费用明细后办理结算。结算时系统自动汇总住院费、药品费、检查费、治疗费生成结算单患者确认后点击“结清”住院状态变更为“已出院”床位释放。这里最容易被忽略的是费用明细和结算单之间的数据一致性我直接用事务控制先插入结算单主表再批量插入费用明细子表全部成功才提交事务保证不会出现结算单金额和费用明细对不上的情况。3.5 Flowable工作流把住院审批流程做成可配置我之所以单独把工作流引擎拿出来讲是因为很多同学在书上看了概念但不知道怎么和SpringBoot结合。我这里给你一个最小可用方案。首先在pom.xml里引入flowable-spring-boot-starter然后准备一个BPMN文件。最简单的流程是开始节点 - 医生提交申请 - 科主任审批 - 结束。这个BPMN文件放在resources/processes目录下Flowable会在项目启动时自动部署。流程实例由业务代码触发。比如“住院审批”流程当医生点击“提交住院申请”按钮时后端调用runtimeService.startProcessInstanceByKey(hospitalization_approval, businessKey, variables)启动一个流程实例businessKey就存业务表的主键IDvariables里放审批需要的参数比如申请医生ID、患者ID、科室ID。科主任登录后通过taskService.createTaskQuery().taskAssignee(userId).list()查出待办任务点击同意时调用taskService.complete(taskId, variables)完成任务。如果你想更灵活一点可以用taskCandidateGroup来设置候选组比如“某科室的科主任”是一组人组内的任何一个人都可以领取任务并处理。这个模式在医院场景下更贴合实际因为科主任可能有正副主任两个人谁有空谁审批这个细节在答辩时讲出来很加分。使用Flowable需要注意瞬态数据管理和历史数据归档是它自动处理的。比如流程跑到哪个节点由act_ru_*表实时记录流程结束后数据转入act_hi_*表。你不需要自己去建待办任务表来管理流程节点只需要在业务表里冗余一个“流程状态”字段用于普通列表查询时快速显示当前走到哪一步而详细的流程轨迹用historyService去查历史数据。这就是“业务数据和工作流数据分离”的思想理解了这个你的系统设计层次会明显比纯CRUD高一级。3.6 患者服务门户查询、支付与报告查看患者端的门户页面我做了几个入口在线挂号、挂号记录、缴费记录、检查报告、住院信息。在线挂号的逻辑前面讲过了这里重点说一下报告查看。检查检验的报告来源是医生在门诊或住院模块里开具的检查申请单。检查科室上传报告后这里我用一个简单的文件上传接口模拟支持PDF和图片报告状态从“待出报告”变成“已出具”。患者登录系统后在报告列表里能看到自己名下所有已出具的报告点击进入查看详情。如果报告还没出显示“报告出具中”的状态提示。这里有一个上传文件路径的处理细节不能存绝对路径比如C:/upload/xxx.pdf因为系统一旦部署到别的服务器或者容器里路径就会失效。我这边是把文件存在项目配置的upload.dir相对路径下数据库里只存相对路径下载时通过接口拼装完整URL去访问。在支付这块我没有对接真实的微信支付或支付宝而是做了一个模拟缴费收银台。用户确认费用后选择“在线支付模拟”系统直接把缴费状态置为已支付并生成票据记录。这个方案在毕业设计范畴里是完全可以接受的毕竟真实支付需要商户号、证书等一堆资质材料。但如果你想把这块做得更有说服力可以引入一个支付沙箱环境的对接或者用代码模拟一个支付回调接口展示你对支付流程的理解。我在论文里是明确写了“本系统的支付模块采用模拟实现真实环境中可替换为第三方支付网关”这个说明很重要答辩老师看到你清楚自己的设计边界反而不会在这个地方为难你。4. 数据设计与核心表结构4.1 核心表一览我挑了十张最核心的表画一下它们的职责表名职责关键字段sys_user登录账号username, password, role_type, statussys_role角色role_code, role_namepatient患者信息user_id, real_name, id_card, phone, medical_record_nodoctor医生信息user_id, real_name, dept_id, title, schedule_enableddepartment科室dept_name, dept_code, parent_iddoctor_schedule医生排班doctor_id, dept_id, work_date, period, total_count, booked_count, versionregistration挂号记录patient_id, schedule_id, doctor_id, status, queue_no, feemedical_record就诊记录registration_id, patient_id, doctor_id, chief_complaint, diagnosisprescription处方主表medical_record_id, patient_id, doctor_id, total_amount, statusprescription_item处方明细prescription_id, drug_id, quantity, dosage, amounthospitalization住院记录patient_id, dept_id, bed_id, attend_doctor_id, status, in_time, out_timemedical_order医嘱表hospitalization_id, doctor_id, order_type, content, status, execute_time, executor_iddrug药品字典drug_name, specification, unit, stock, price这个表结构不是一次性定死的而是我在开发过程中慢慢完善出来的。比如排班的version字段就是做并发扣减号源时加的医嘱的executor_id是在做护士执行功能时加的。刚开始先把主体字段定下来后面根据业务逻辑一点点补这是正常的迭代节奏。4.2 表关系设计的几个关键点第一点是sys_user和业务人员表patient、doctor、nurse之间的关联。我的方案是sys_user表只负责认证业务人员表存各自的业务属性通过user_id关联。这样角色扩展非常灵活比如一个用户既可以是医生又可以是科室主任在业务表里加个管理范围的字段就行不用改认证逻辑。第二点是门诊和住院之间的数据衔接。患者从门诊就诊后被收治入院在数据库中体现为一条住院记录关联到一张就诊记录的ID。这样患者的历史门诊记录、检查报告、处方记录都能通过住院记录反查得到在医生写住院病历时可以直接引用门诊的诊断信息和检查结果很符合真实医院的场景。第三点是钱和数据的一致性。处方金额和结算金额不能靠前端算好后传给后端必须后端根据药品单价乘以数量计算。我在ServiceImpl层每次创建订单、处方、结算单时都重新从数据库查药品价格或费用项目价格来计算金额前端传过来的金额只用于展示后端一律重新计算。这个做法在答辩时是一个非常好的“系统安全性”答辩点。5. 权限与安全实现细节5.1 Spring Security配置中最容易忽略的几个点Spring Security的配置毕业设计里最常用的就是让所有请求都经过认证但放行登录接口、Swagger文档接口。有两个细节值得注意。第一个是BCryptPasswordEncoder的bean一定要显式声明否则注入会报错。我在一个config包里放了一个SecurityConfig配置类同时把PasswordEncoder也定义在里面不要分散到不同类。第二个是自定义过滤器注册到Spring Security过滤链的顺序。JWT校验过滤器必须加在UsernamePasswordAuthenticationFilter之前因为JWT校验的结果要先于框架自带的认证检查。代码这样写http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);这个顺序一旦写错你会看到请求进不了控制器老是提示未认证排查半天也找不到原因。其实原理很简单过滤器链是有先后顺序的JWT把用户信息塞进SecurityContext后后面的过滤器才知道当前是谁在请求。5.2 越权访问一个常见但很严重的漏洞很多同学的毕业设计里都有越权问题最典型的就是患者A登录后通过修改URL里的ID能查看到患者B的病历或报告。这是因为后端接口只验证了“是否登录”没验证“数据是否属于当前用户”。我在写患者端的查询接口时做了一个统一的处理当前登录用户从SecurityContext里取拿到user_id之后再根据关联关系去查询。比如患者查看报告列表的接口不管前端传什么patientId后端一律先用user_id去patient表反查真实的patient_id再拿着这个patient_id查询报告。这样就算前端恶意篡改参数后端也会按当前登录人的身份去过滤数据。医生的越权也要防。一个门诊医生只能看到自己名下的挂号队列和已就诊记录不能通过修改doctor_id参数查看其他医生的患者。这个约束在一个“医生工作台”查询接口里用到了JPQL或者MyBatis-Plus的LambdaQueryWrapper里都强制加上doctor_id等于当前用户的条件。RESTful风格的接口里尽量少用那种“传一个id从头穿到尾”的设计多用当前上下文里的身份信息来约束数据范围这是后端安全一个很重要的意识。5.3 日志与操作留痕医院系统的合规性要求比较高所以我对关键操作都做了日志记录。不是简单用Logback打印日志而是建了一张operation_log表把患者的ID、操作类型、操作人、操作时间、操作详情存进去。挂号、开处方、医嘱下达、医嘱执行、出院结算这些都算关键操作。日志记录我用了Spring AOP的机制自定义了一个OperationLog注解标在需要记录的Controller方法上然后在切面里统一记录。切面里通过HttpServletRequest获取操作人ID通过OperationLog注解里的value获取操作描述再通过方法参数或者返回结果里提取业务主键。这个方法比每个方法里手动写日志代码干净得多也很容易扩展新的日志点。答辩的时候你可以说这是从“输出系统日志”到“业务审计日志”的一种进阶设计格局一下子就不一样了。6. 常见问题与排查技巧实录6.1 MyBatis-Plus分页查不出数据或者总数为0这个问题的根源多半是分页插件没有配置成功。MyBatis-Plus的分页需要手动注入一个分页插件不配置的话你调用Page对象时SQL里不会自动带上LIMIT返回的数据就会是全部数据或者在某些版本里直接返回总数和列表都是空的。我自己在3.5.3版本里配置这样一个Bean就行Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }配置好后分页查询统一用Page 作为第一个参数传入Mapper方法返回IPage 。还要注意一个细节分页查询返回的记录数total默认会执行一次COUNT查询如果SQL里有复杂的多表JOINCOUNT可能不准你可以自定义COUNT查询SQL或者用优化器去解决但毕业设计里直接让分页插件自动生成COUNT就行不用过度优化。6.2 日期时间类型序列化后变成时间戳这种情况出现在后端返回LocalDateTime类型的字段前端收到的是一个类似于“1714560000000”的长整型数字。解决方式有两种第一种是在application.yml里配置全局的Jackson序列化格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第二种是在实体类的日期字段上加上JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解。这两种我会选择全局配置因为项目中日期字段太多了一个个加注解容易漏。全局配好之后前端接收到的就是格式化好的字符串不用再做转换。有一个跟时区相关的坑需要特别注意如果服务器的时区没配好存库时间会比真实时间早8小时。在MySQL连接串上最好显式加上serverTimezoneAsia/Shanghai参数jdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai少了serverTimezone这个参数有些环境下会直接报错或者时区不对导致后续排班日期判断全部错乱。6.3 事务不生效的三种典型场景病患管理系统里事务用得非常频繁挂号扣号源、创建结算单、提交住院申请这些操作都涉及多表写入没有事务会出现数据不一致。我总结了自己踩过的三种事务不生效的场景你们写代码时一定要避开。第一种是在同一个类内部方法A调用方法BB上有Transactional注解但事务不生效。原因是Spring的声明式事务基于AOP代理同一个类内部调用走的是this.func()不会经过代理对象所以注解无效。解决方式是把需要事务的方法放到另一个Service类里或者自己注入代理对象再调用。第二种是Transactional只标注在private方法上。Spring的AOP无法拦截private方法标注了也没有意义必须用public方法。第三种是异常被捕获了但没抛出去。比如你在Service方法里用try-catch包住了数据访问代码catch里只打印日志而没有继续抛出运行时异常事务框架不知道出了错就会直接提交最终数据写了一半。正确做法是捕获异常后要么记录日志后重新throw new RuntimeException(e)要么吞掉这次异常并返回失败提示。6.4 Flowable引擎启动变慢与自动部署问题Flowable引擎在项目启动时会对引擎配置进行初始化并且扫描resources/processes目录下的BPMN文件自动部署。首次启动会比较慢因为要创建几十张act_*表这是正常现象。但如果每次启动都重新部署一遍流程会影响开发调试效率你可以在application.yml中设置spring: flowable: check-process-definitions: true database-schema-update: true这里的database-schema-update设为true可以让Flowable自动检查表结构如果缺少表就自动创建。你还可以在Flowable的配置里关闭流程定义的自动部署改成手动部署或者指定一个更精确的扫描路径来避免不必要的文件扫描。但注意在校期间做项目这个默认操作就够了不用过度配置。6.5 跨域问题前端请求通通被拦截前后端分离模式下跨域是我每个项目都会遇到的经典问题。前端跑在localhost:8080后端跑在localhost:8081浏览器默认会拦截跨域请求。最简单的解决方式是在后端写一个CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里有两个坑第一个是allowedOrigins配置成的时候不能和allowCredentials(true)同时使用浏览器会报错。改成allowedOriginPatterns()就可以解决。第二个是如果使用Spring Security预检请求OPTIONS必须放行否则前端发预检请求时就直接被Security拦截了根本到不了后端业务接口。需要在SecurityConfig里加上http.cors().and().csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, /**).permitAll()这两个坑基本都是连在一起的只配CORS不配Security或者只配Security不配CORS都会导致前端联调的时候莫名其妙失败。7. 项目优化与答辩准备建议7.1 给系统加一个“数据看板”提高完成度我的建议是答辩前花两三天时间给系统加一个首页数据看板不要小看这个功能它能让系统完成度看起来高一个档次。我在首页放了几个统计卡片今日挂号人次、门诊收入、住院在床人数、待处理医嘱数量。下面再放一个近7天门诊人数的折线图和一个科室挂号占比的饼图。数据来源全部是本系统真实业务表中通过SQL聚合查询出来的前端用ECharts图表库渲染。实现上不复杂就是几个统计SQL加一个DashboardController。这几个图给答辩老师看到能直观地证明系统真的在跑业务数据而不只是几个孤立的CRUD页面堆在一起SELECT DATE(create_time) AS day, COUNT(*) AS cnt FROM registration WHERE create_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE(create_time) ORDER BY day7.2 性能优化能讲出来的几个点答辩时老师可能会问“你的系统有什么优化”。你不需要说自己用了微服务或者消息队列这种明显过度的架构就老老实实讲几个扎实的小点就行。我用缓存缓存了科室列表、药品字典这类变化极少的数据每次查询先查Redis没有的话再查数据库并回填Redis有效减少了对数据库的重复查询。我还给高频查询的字段建了索引比如registration表的patient_id和status联合索引medical_order表的hospitalization_id和order_type联合索引。查询量大的列表接口我用了MyBatis-Plus的分页插件做物理分页不一次把全表数据load到内存里。还有一个比较容易讲清楚的是懒加载。医生工作台里的患者列表加载时不加载全部的病历详情只有医生点击“查看详情”时才通过接口异步加载详细数据前端展示速度会快很多。这些优化点在代码里都是一两行的事但答辩的时候说出来逻辑清晰很有说服力。7.3 论文写作与演示准备的三个心得论文结构上我按照“选题背景与意义-关键技术介绍-需求分析-系统设计-系统实现-系统测试-总结展望”的顺序写。这里有一个重要提醒论文里的核心代码不要大段贴Controller层代码而要贴有设计感的底层代码比如统一返回结果Result类的实现、JWT过滤器、自定义注解和切面、Flowable流程的Service调用。这类代码有设计感老师看了会觉得你有工程意识。演示准备上提前准备好演示用的测试账号非常重要。我建了五类账号分别对应管理员、门诊医生、住院医生、护士、患者每一种账号的登录密码我都用便签记在演示文档里。演示流程也提前演练了好几遍用患者账号挂号切到医生账号看队列并开处方再切到住院医生账号开住院单用护士账号执行医嘱最后患者端查看报告。一条完整的业务串联比零散地展示每个页面有效得多。还有一个答辩技巧是主动说出系统设计的边界和不足。比如支付是模拟的真实场景下需要对接支付网关没有做消息推送医生待办的新单据需要刷新页面才看得到没有做复杂的医保报销规则引擎只做了基本的自费结算。提前把不足都说清楚老师反而觉得你对自己的工作有清晰的认知总比硬撑说“我的系统没有问题”要好得多。8. 结语与后续扩展建议做这个病患管理系统我个人最大的感受是毕业设计不是写一堆代码交差而是通过一个完整项目把你对软件工程的理解整体走一遍。这套系统从前期的需求调研、角色划分、数据库设计到中期的SpringBoot后端接口开发、Vue前端页面联调再到后期的Spring Security权限加固、Flowable工作流集成、Redis缓存优化每一个环节都有让人卡壳的地方。但正是这些卡壳和排查的过程让我真正理解了为什么企业项目要分层、为什么要做统一异常处理、为什么事务控制这么重要。在学校里写那些几百行的算法题和做一个完整的业务系统确实是两种完全不同的成长路径。后续想扩展的话可以从这几个方向考虑一是引入WebSocket做待办消息实时提醒医生端能实时收到新挂号患者和新的审批待办二是用定时任务框架做预约挂号的号源自动释放患者过了预约时间没有就诊就自动把号源释放回池子里三是把Excel导出做进去门诊日常报表、药品库存报表都可以按时间范围导出。这些扩展点都不难但每加一个系统的完整性和你的技术增量都更上一个台阶。如果你正在头疼那个“在线病患管理系统”的题目按照这套思路把门诊、住院、工作流、权限四条主线搭起来再补上测试账号和统计看板认真走一遍你一定会发现这个题目能学到的东西比想象中多得多。