最近后台经常有人问社区家庭医生在线问诊系统怎么做,正好我前阵子完整跟过一个类似项目,从需求梳理到部署上线走了不少弯路。我干脆把这套系统的技术拆解和踩坑记录整理成一篇长文,一方面给自己做个复盘,另一方面给正在做毕业设计,或者想拿这个题目做开源练习的同学一个可以直接参照的思路。先说结论:这个项目用 Spring Boot 做后端框架,MySQL 做数据存储,核心流程其实就是患者、医生、管理员三种角色把线下问诊搬到线上。听起来不复杂,但真正动手之后你会发现,难点全在流程状态、权限边界和数据一致性上,而不是简单的增删改查。
1. 这个项目到底在解决什么问题:社区医疗的“最后一公里”问诊场景
1.1 线下问诊流程里的真实痛点
社区卫生服务中心覆盖的是常见病、多发病和慢性病随访,但实际运转中经常出现几个让人头疼的问题。首先是患者复诊效率低,一个高血压老人想找同一个医生看化验单,往往要重新排队挂号,医生抽屉里翻纸质病历要花十几分钟。其次是医生时间碎片化,社区医生半天门诊拥塞,下午又可能要出诊随访,线上咨询需求没处承接。还有一个容易被忽视的问题是健康档案割裂,体检数据、门诊记录、家庭随访记录散落在不同表格里,没有统一入口。
我之前在梳理某社区的项目时,专门统计过半个月的门诊记录,发现超过四成的挂号其实是复查和续方,真正需要转诊的只有不到一成。这意味着只要能解决复诊和续方这两个场景的线上化,就能释放掉社区医生将近一半的重复劳动。于是这套系统的定位就很清晰:医生可以提前看到患者的申请,患者不用到场就能完成开药申请和结果解读,管理员则能在后台看到整个问诊记录。
1.2 系统的核心价值与目标用户
这套系统取名“社区家庭医生在线问诊系统”,目标用户其实分三类。患者端是微信小程序或网页,最关注的是能不能快速描述症状、上传化验单照片、查看医生回复;医生端是工作台模式,关注的是接诊列表、患者历史病历、处方录入是否顺手;管理员端则是机构运营人员,关注的是医生审核、排班管理和问诊记录审计。
从核心价值上看,系统做了三件线下不太容易做的事。第一,在线问诊单可以携带文字描述和图片附件,医生在接诊前就能了解情况,减少来回追问。第二,电子健康档案自动关联历次问诊记录,医生接诊时不需要翻箱倒柜。第三,问诊有固定状态流转,从待接诊到已回复,每一步都有时间戳,方便后续追溯。这些功能单独拎出来都很简单,但组合在一起就能形成完整业务闭环,这也是这类项目适合作为毕设选题的原因之一。
1.3 为什么这个选题适合做毕设或开源练习
如果是做毕业设计,这个题目正好踩在“难度可控”和“亮点突出”的平衡点上。Spring Boot 的生态非常成熟,网上资料多,遇到报错基本都能搜到答案;业务复杂度适中,既要处理权限,又要处理流程状态,但又不至于像电商秒杀那样需要分布式事务。另一个优势是演示效果直观,问诊单的创建、接诊、返回结果,整个过程可以用浏览器完整操作一遍,答辩时不需要太复杂的现场环境。
从技术学习的角度,这个项目几乎覆盖了后端开发里最常遇到的几个问题:用户登录态管理、角色权限控制、文件上传、状态机流转、MyBatis-Plus 条件查询。就算你不是为了毕业设计,而是想通过一个完整项目来补足开发经验,这套系统的性价比也很高。项目代号可以用 ba74d 之类的内部编号,方便区分不同的交付版本。
2. 技术选型与架构设计:Spring Boot 生态下的合理组合
2.1 为什么是 Spring Boot + MyBatis-Plus + MySQL
我记得很多新手和老手都会为选型争论,SSM 和 Spring Boot 其实都能做,但实际开发效率差别很大。Spring Boot 内置 Tomcat,自动配置大大减少了 XML 配置量,起步和调试都更快。MyBatis-Plus 是我个人比较推荐的一层,它在 MyBatis 的基础上提供了单表 CRUD 封装、分页插件和条件构造器,最典型的场景就是后台管理列表页的模糊查询,直接通过 LambdaQueryWrapper 就能拼出复杂的 where 条件,省掉了大量样板 SQL。
数据库选 MySQL 是基于通用性和易用性。MySQL 的面试题和资料多,安装维护简单,部署到云服务器也方便。如果你的数据量真的很大,比如几十万问诊记录,MySQL 加索引和后期的分库分表也够用,这个系统阶段不会遇到真正的性能瓶颈。
项目本身的依赖也值得说一下。除了核心的 spring-boot-starter-web,还会用到 spring-boot-starter-validation 做参数校验,spring-boot-starter-logging 做日志输出,以及 JWT 相关的 java-jwt 或 jjwt 库。文件上传一般就存在本地目录,配置一个 upload 路径,没必要一上来就接 OSS。如果你要演示消息通知,可以再引入 WebSocket 或者轮询接口,但第一版通常用数据库轮询就能模拟。
2.2 后端分层结构如何组织
项目结构上我是按典型的三层架构来组织的,但略微做了一些调整。Controller 只负责参数接收和响应封装,Service 层承载业务逻辑,Mapper 层负责数据库交互。实体模型会拆成实体类、DTO、VO 三层,避免直接把数据库实体暴露给前端。比如问诊单实体里有个 status 字段,数据库里存 Integer,前端需要的是文字描述,那就由 VO 来输出转换后的值。
代码组织的惯例是用一个模块名做顶层包名,然后按结构分包。com.example.telemedicine 或者你自己定义的域名都可以。我通常这样区分:
- vo:患者提交问诊单的请求体,字段只有 patientId、description、images,不会把数据库自增 id 塞进来。
- entity:和数据库表一一对应的持久化对象。
- service:业务接口和实现类。
- mapper:继承 BaseMapper 的接口。
- config:放 WebMvcConfigurer 配置、Interceptor 注册和跨域配置。
- utils:JWT 工具类、日期工具类、响应结果封装。
有人会质疑说 DTO 和 VO 分层太麻烦,但实际项目里确实遇到过一次“患者信息泄漏”的隐患。如果不做字段隔离,直接把 User 实体返回给前端,用户的密码哈希和手机号就会被一起带出去。哪怕前端不展示,在浏览器开发者工具里也能看到,这在校验系统安全性时是扣分项。所以我会强调,Controller 方法一律返回 VO 或者 Result 包装对象,而不是裸实体。
2.3 前后端接口约定:RESTful + JSON
前后端分离已经是标配,所以后端接口必须固定一套约定。我习惯统一返回一个 Result 结构,包含 code、message、data 三个字段。code 为 0 表示成功,非 0 表示具体错误码,比如 40100 代表未登录,40101 代表 token 过期,40300 代表权限不足。这样做的好处是前端 axios 拦截器可以统一处理,不用每个接口单独判断。
路径设计上,以问诊单为例:
- POST /api/consultation 创建问诊单
- GET /api/consultation/list 查询我的问诊单列表
- PUT /api/consultation/accept 医生接诊
- PUT /api/consultation/finish 完成问诊并提交电子病历
RESTful 风格不需要太死板,重点是一看就知道这个方法在做什么,不要出现 /queryConsultationListById 这种又臭又长的路径。参数校验用 Spring Validation 的 @Validated 和 @NotBlank,避免在 Controller 里堆一堆 if else。文件上传单独用 /api/upload,返回文件 URL 或相对路径。
3. 三大核心业务模块的流程拆解与代码设计
3.1 患者端:创建问诊单、上传描述与历史病历
患者端整个流程可以拆成四个步骤。第一步是注册登录,使用手机号加密码,密码存储用 BCrypt 哈希。第二步是填写问诊单,核心字段是症状描述、发病时间、历史诊断记录,同时支持上传化验单图片。第三步是查看医生回复,医生回复后,患者能看到问诊状态变更为“已回复”,并且可以继续追问。第四步是查看自己的健康档案,这个档案是系统根据历史问诊记录自动生成的。
从代码层面看,创建问诊单是整个系统里隐患最多的一个入口。比如一个患者同时提交两个问诊单,如果系统没有校验,就可能出现同一时间提交多条超高并发的问题。所以我在 Service 里加了一个防重判断,查询这个患者是否已经存在“待接诊”状态下的问诊单,如果存在就直接提示“您有进行中的问诊,请等待医生处理”。这个校验虽然简单,但非常实用。
患者上传图片的接口也要做大小限制。我常用的方案是限制单个文件不能超过 5MB,文件名用 UUID 重命名,避免中文文件名带来的编码问题。图片保存后返回相对路径,比如 /upload/20240612/uuid.jpg,前端拼接上服务器地址即可。
3.2 医生端:接诊、填写病历与电子处方
医生登录后看到的是待接诊列表,列表里的核心信息不应该是完整的问诊描述,而应该做脱敏处理。比如只显示年龄、既往病史摘要、当前症状关键词,手机号要隐藏中间四位。当医生点击“接诊”时,系统需要将问诊单状态置为“诊疗中”,并记录接诊时间。这个操作要带上乐观锁的版本号,防止两个医生同时抢同一个单子。
接诊之后就是经典的电子病历流程。病历表单包括主诉、现病史、查体信息、诊断结果和电子处方。电子处方模块在设计时要考虑一个场景:患者可能同时开了三种药,所以处方表是一对多的结构,主表存问诊单 id、医生 id、诊断结果,子表存药物名称、用量、用法和天数。我见过很多初学者把这个设计成一张表,用逗号分隔药物,这会给后续统计带来极大困扰。
处方提交后,问诊单状态变为“已完成”,同时给患者发送一条通知。通知在数据库里就是一张 notification 表,包含接收者 id、标题、内容和是否已读。为什么不直接用 WebSocket?因为第一版把 WebSocket 和业务逻辑耦合在一起,后期维护很痛苦,尤其是断线重连的消息丢失问题。数据库通知表配合前端轮询,稳定性和调试成本都更可控。
3.3 管理端:医生审核、排班与问诊记录审计
管理端的核心职责是入口审核。社区医生入驻时需要提交执业资质信息,管理员在后台审核通过后,该账号才拥有医生角色权限。这个审核流程会体现在两张表上:医生申请表和医生信息表。申请时写入医生申请表,状态是“待审核”,审核通过后复制一份去操作医生信息表,并给用户表更新角色标识。
排班功能可以做得复杂也可以做得简单。如果只是展示“今天哪位医生在线”,直接在医生表加一个在线状态字段即可,但要在登录和退出时修改状态。更靠谱的是建一张 schedule 表,记录医生工作日和上午/下午时段,前端根据排班表显示“可预约”还是“休息中”。考虑到毕设的演示时间有限,排班表可以预先写入一批模拟数据。
问诊记录审计是我特别提的一个功能,因为健康类系统必须做到操作可追溯。管理员可以按时间、医生、患者三个维度查询问诊记录,但不能修改。另外需要提供导出 CSV 的接口,方便线下归档。这个功能虽然不抢眼,但在系统介绍里可以当作亮点讲,评委也会比较认可。
4. 数据库设计:一张表关系图背后的事务边界
4.1 核心表职责与归属
这套系统的数据库表并不需要设计得很多,常见的一组核心表我整理了一下,大家可以对照着建:
| 表名 | 职责 | 核心字段 |
|---|---|---|
| user | 用户基础信息 | id, phone, password, name, role, avatar |
| patient_profile | 患者健康档案扩展 | id, user_id, birth_date, gender, allergy_history |
| doctor_info | 医生资质与机构信息 | id, user_id, license_no, specialty, hospital, status |
| consultation | 问诊单主表 | id, patient_id, doctor_id, title, description, status, create_time |
| consultation_image | 问诊图片附件 | id, consultation_id, image_url, create_time |
| medical_record | 病历信息 | id, consultation_id, doctor_id, complaint, diagnosis, suggestion |
| prescription | 电子处方主表 | id, record_id, doctor_id, patient_id, diagnosis |
| prescription_item | 处方明细表 | id, prescription_id, drug_name, usage_dosage, days, quantity |
| notification | 站内通知 | id, user_id, title, content, is_read, create_time |
| admin_log | 管理审计日志 | id, admin_id, action, target_id, create_time |
这个表结构覆盖了患者端主流程和医生端主流程,管理端复用 user、doctor_info 和 admin_log。值得注意的是,user 表里的 role 字段建议用 String 类型,比如 ROLE_PATIENT / ROLE_DOCTOR / ROLE_ADMIN,而不要用 0、1、2 这种纯数字。因为权限注解和前端判断都需要可读性。
4.2 问诊状态机:待接诊、诊疗中、已完成
问诊状态是整个系统的核心状态机。我把 consultation.status 设计成四个值,实际上完整流程是五段式,但第一版可以合并中间过程:
- 0:待接诊,患者刚提交,医生还没操作。
- 1:诊疗中,医生已接诊,正在填写病历。
- 2:已完成,病历和处方都已提交,本次问诊结束。
- 3:已取消,患者在待接诊状态下主动取消。
状态迁移规则必须写清楚,不能随意跳转。比如“已取消”只能由“待接诊”触发,医生接诊后就不能取消。“已完成”之前必须存在病历记录,否则要拦截。我把这个迁移逻辑放在 Service 层一个专门的方法里,哪怕是简单的 switch 判断,也比把这个逻辑散落在多个 Controller 方法里好。
有人会问,为什么不用枚举类型?Java 枚举做状态管理确实更优雅,但 MyBatis 默认处理枚举需要额外配置 type-handler,如果不熟悉反而麻烦。我的习惯是在代码里用常量类定义状态值,并配一个 getStatusDesc 方法。这样既简单又能保证可读性。
4.3 三张角色表与权限控制的数据支撑
权限控制是几乎所有毕设项目的考察重点。数据层面其实就三个核心名词:用户表里的 role 字段、用户表和医生信息表的关联关系、接口维度的角色校验。
我在项目里不会用 Spring Security 的复杂过滤器链,而是自己写一个拦截器 + 自定义注解。登录后颁发 JWT,JWT 里携带 userId 和 role。拦截器先从 Header 里解析 token,校验签名,然后根据当前请求路径的注解判断是否允许该角色访问。例如 @RequireRole("ROLE_DOCTOR") 就只允许医生访问。
这种方式的优点是透明,理解起来快。缺点是安全实现完全依赖业务代码,对并发和密钥管理要求比较高。但对这个量级的项目来说,已经足够了。假如将来要升级成 Spring Security,只需要替换认证过滤器和权限注解即可,业务代码不用大改。
5. 从零跑通项目:环境准备、数据库初始化与本地调试部署
5.1 环境版本选择和准备
我先说说我习惯的一组稳定版本,这套组合我在多个环境里跑过都没有遇到兼容性问题。
| 依赖 | 版本 |
|---|---|
| JDK | 1.8 或 11 |
| Spring Boot | 2.7.x |
| MyBatis-Plus | 3.5.x |
| MySQL | 5.7 或 8.0 |
| Redis | 可选,不用也能跑 |
| Maven | 3.8.x |
| Node.js | 可选,若前端要做 Vue 则 16.x |
JDK 8 仍然是最稳的选择,很多云服务器默认 JDK 8 运行环境。JDK 11 也没问题,Spring Boot 2.7 对这两个版本的兼容性都很好。如果有同学用的是 JDK 17,需要把 Spring Boot 升级到 3.x,但 Spring Boot 3.x 的 javax 包名变成了 jakarta,很多老代码要跟着改,所以我建议不要为了新特性而增加迁移成本。
5.2 导入项目、改配置、启动后端
拿到源码后第一步是把项目导入 IDE。我用的是 IntelliJ IDEA,导入 Maven 工程后等待依赖下载。接下来一定要改配置文件,最常见的坑就是 application.yml 里的数据源地址和密码。
配置文件的几个关键项:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/telemedicine?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true这里要特别提醒一下 MySQL 8.0 和 5.7 的驱动差异。MySQL 8.0 需要使用 com.mysql.cj.jdbc.Driver,5.7 用 com.mysql.jdbc.Driver 也能连,但部分版本会有弃用警告。另外 url 里的 serverTimezone 必须配置,否则默认时区会让时间字段偏移 8 小时,这个坑我在后面单独讲。
数据库脚本一般在项目的 sql 目录下,文件名可能是 init.sql 或 telemedicine.sql。用 Navicat 或命令行执行时要注意脚本里的编码格式。如果脚本是 UTF-8,但 mysql 客户端默认是 GBK,导入后中文会乱码。建议执行前先执行 set names utf8mb4。
启动方式有两种。第一种是直接运行启动类,在类上找到 @SpringBootApplication 注解的类,运行 main 方法。第二种是用 Maven 命令打包部署。调试阶段直接用第一种方式即可,看到日志里有 Tomcat started on port(s): 8080 时说明后端已经跑起来。
5.3 用 Postman 自测接口的验证清单
后端启动后先别急着连前端页面,用 Postman 把核心接口挨个验证一遍才是稳妥的做法。我建议按这个顺序自测:
- 注册接口:POST /api/user/register,传入手机号、密码,返回成功。
- 登录接口:POST /api/user/login,拿到 token。
- 携带 token 访问个人信息接口:GET /api/user/info,在 Header 里设置 Authorization: Bearer xxx,确认返回 200。
- 创建问诊单:POST /api/consultation,表单里加图片文件,确认返回问诊单 id。
- 查询问诊单列表:GET /api/consultation/list,分页参数 page=1&size=10。
每次请求后查看响应里的 code 字段,非 0 就要看日志。这里遇到最多的问题有:token 没传对、跨域配置没生效、数据库表名字段名对不上。表名字段名对不上是最让人头大的,MyBatis-Plus 默认是把实体类驼峰字段转成下划线字段,如果数据库字段命名不统一,就会报“Unknown column”。
6. 我调试这个系统时踩过的坑:跨域、时区、事务与 JWT
6.1 JWT 过期时间不是越长越好
第一次做这个系统时,为了让演示期间不出幺蛾子,我把 JWT 过期时间设置成了 7 天。结果发现患者手机号换绑后,旧 token 依然有效,用户都注销了还能访问接口。这给我一个很重要的教训:token 过期时间要跟业务敏感性匹配。医疗数据的 token 有效期最多不要超过 24 小时,演示时可以临时延长,但要在代码里留一个参数。
同时还要区分 token 过期和 token 无效。我的拦截器逻辑是先从 Header 取 token,解析失败就返回 40101;解析成功但系统时间不在有效期内,返回 40101;有效但用户已经在数据库被删除,返回 40102。这几个状态前端要分别提示:重新登录、登录已过期、账号异常。
下面是一个简化版的 JWT 工具实现,核心就两个方法:生成 token 和验证 token。
public class JwtUtil { private static final String SECRET = "your-secret-key"; public static String createToken(Long userId, String role) { return JWT.create() .withClaim("userId", userId) .withClaim("role", role) .withExpiresAt(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) .sign(Algorithm.HMAC256(SECRET)); } public static DecodedJWT verifyToken(String token) { return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token); } }6.2 MySQL 时区导致的时间错乱恶心了我一下午
有段时间创建问诊单后,前端显示的时间比实际时间慢了 8 个小时。查了半天,发现是因为 MySQL 连接串里没有加时区参数,JDBC 驱动用了服务器本地时区,而我的云服务器默认时区是 UTC。
解决办法很简单,就是在 jdbc url 上加 serverTimezone=Asia/Shanghai。另外,如果数据库表格里的 create_time 是 datetime 类型,查询出来后还要考虑 Jackson 序列化的时区。Spring Boot 的 application.yml 里也需要设置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这样设置后,前后端传过来的时间字符串就不会再有偏差。这里也建议创建时间字段统一由数据库默认值 CURRENT_TIMESTAMP 填充,而不是在 Java 代码里 new Date(),避免应用服务器和数据库服务器时钟不同步。
6.3 同方法内调用导致事务不生效
在写问诊单创建逻辑时,我最初在 Service 里写了一个方法,里面先插入 consultation,再插入 consultation_image。如果插入图片失败,问诊单应该回滚,可我测的时候发现问诊单已经保存了。后来才意识到,这两个操作在同一个类的不同方法里,外层方法没有加 @Transactional,内部方法即使加了 @Transactional,也会因为 Spring AOP 代理问题导致事务不生效。
正确的做法是把事务注解加在 Public 方法上,并且要确保外部通过代理对象调用。如果你用的是同类内部调用,事务注解会被忽略。更保险的是把数据库操作拆成独立的私有方法,由外层方法统一控制。
示例:
@Service public class ConsultationServiceImpl implements ConsultationService { @Override @Transactional(rollbackFor = Exception.class) public Long createConsultation(ConsultationCreateDTO dto) { Consultation entity = new Consultation(); // 填充数据 consultationMapper.insert(entity); // 批量插入图片 for (String url : dto.getImageUrls()) { consultImageMapper.insert(...); } return entity.getId(); } }rollbackFor 一定要设置成 Exception.class,否则运行时异常可以回滚,而 IOException 等受检异常不会触发回滚。很多人以为加了 @Transactional 就万事大吉,实际上还依赖异常类型。
6.4 跨域配置放行后依然登录失效
前端用 Vue 开发时,本地端口一般是 9010,后端是 8080,这就涉及跨域问题。我在后端加了 CorsFilter,允许了所有来源,按理说应该没问题。但测试时发现,每次预检请求都通过了,真正携带 Authorization 头请求时却拿不到当前登录用户。
排查后发现,问题出在拦截器的 order 排序上。跨域预检请求 OPTIONS 应该直接放行,但我的拦截器优先于 CorsFilter 执行,导致 OPTIONS 请求带着 header 被拦截器截住了。解决方法是修改 WebMvcConfigurer 里的 addInterceptors,对路径放行时要把 HTTP 方法也判断一下:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } // 继续验证 token }另外,如果使用 Spring Security,需要调用 cors().and() 配置,但我们现在是自定义拦截器,注意放行顺序就行。前端 axios 里要配置 withCredentials: false,因为我们用的是 token,不是 Cookie 会话。
7. 从毕业设计到可落地系统,还需要补哪些课
7.1 医疗数据的合规存储与加密
如果我们只是拿来做毕业设计或者学习,数据库里存明文手机号和真实姓名没问题。但一旦想上线,医疗数据合规就是个绕不开的话题。我建议至少做到三点:用户密码用 BCrypt 加密;患者的健康档案字段在数据库加密存储,最简单的做法是 AES 加密后落库,解密在 Service 层完成;像身份证号、电话这样的敏感字段,前端展示时要脱敏,后端日志里禁止打印完整值。
还有一个容易被忽略的点是文件上传目录的权限。很多人把图片放在项目的 static/upload 下,这在开发环境没问题,但生产环境会导致用户可能通过 URL 遍历查看患者上传的化验单。比较稳妥的做法是把上传目录放在 web 根目录之外,然后通过一个受权限控制的 Controller 来读取图片,比如 GET /api/file/{filename},先校验 token 再返回文件流。
7.2 问诊高峰时的排队与限流
在线问诊一个很现实的问题是,社区每天有几百个复诊患者同时提交问诊单,医生却只有几个。如果不做限制,医生端列表会瞬间堆积几十个待接诊单,用户体验和系统压力都不好。第一版可以简单用 Redis 做固定窗口限流,比如每个患者每分钟只能提交一次问诊单。想再进阶一点,可以在数据库层面给 patient_id 和 status 加唯一索引,代替应用层的防重校验。
如果要做排队机制,思路是问诊单新增一个状态“排队中”,医生结束当前问诊后,系统自动把队列里最早的一条推给医生。演示版本不一定需要做实时推送,前端每隔 5 秒轮询一次接诊队列即可。这个轮询和 WebSocket 相比虽然高效性差一些,但逻辑简单、不容易出错,也是很多小诊所系统实际在用的方案。
7.3 操作日志与可观测性
操作日志和可观测性是“看起来不起眼、实际很重要”的模块。操作日志要记录谁在什么时间做了什么操作,比如何时接诊、何时完成处方。我建议在管理端单独用一张表保存管理员日志,但对医生接诊这种关键业务操作,可以简单复用 notification 表和 audit_log 表。
调试阶段,日志输出要合理配置。开发环境用控制台日志,生产环境建议输出到文件并按大小滚动。日志里一定不要出现敏感信息,我见过有项目在日志里打印 SQL 时把用户手机号一起打出来,这很危险。可以配置 MyBatis-Plus 的 SQL 日志输出为 DEBUG 级别,但生产环境要调成 INFO。
ProMetheus + Grafana 这类监控体系对这个体量的项目来说有些超前,但可以了解一下。日常开发时,用 Spring Boot Actuator 暴露健康和指标信息就足够了,前端可以定时请求 /actuator/health 来判断后端是否崩溃。
7.4 我的一点个人落地经验
如果时间允许,我强烈建议按“最小可用版本”的思想来做这个系统,而不是一上来就想把所有功能都做完。先把登录注册、问诊单创建、医生接诊、病历提交这四个核心链路跑通,再去补排班、统计、通知这些外围功能。很多同学做到一半发现时间不够,就是因为第一阶段就纠结了一些非核心细节,比如头像裁剪、主题颜色。
代码风格上,接口命名、常量统一、数据库字段统一,这三件事一定要从一开始就做好。我做这个项目时,前期因为字段命名不统一吃了不少亏,一会儿是 createTime 一会儿是 create_time,导致 MyBatis-Plus 自动映射经常报错。后来干脆规定所有数据库字段都用 snake_case,实体类都用 camelCase,并在全局配置里开启 map-underscore-to-camel-case,这个问题才彻底解决。
还有部署环境,项目完成后建议直接部署到一台 Linux 云服务器上,用宝塔面板或者 Docker 都比较方便。Docker 部署时把 MySQL 和 Spring Boot 容器放进同一个 Docker Compose 网络里,数据库文件挂载到宿主目录,这样以后备份和还原都方便。如果你只是跑给老师看,那在本地 IDEA 启动就行,但打包成 jar 后在服务器上跑一次,对理解生产环境的差异帮助很大。