
简介毕业设计级别的电子病历管理系统基于SpringBoot搭建面向计算机相关专业毕业生、Java后端学习者以及需要快速搭建医院信息管理原型的开发者。系统覆盖病历录入、增删改查、就诊记录管理并区分医生、患者、管理员三类角色支持权限控制、数据安全与统计查询是一套结构完整的可运行项目。压缩包共2011个文件大小49.06MB以1307个Markdown文档、563个JavaScript文件、95个JSON配置及HTML、XML、C头文件等为主其中Markdown可用于阅读说明与设计文档JS/JSON对应前端逻辑与配置便于按目录理解后端与前端分层实现。目前已有69人学习下载适合作为毕业设计参考、课程项目改造或SpringBoot实战练习的起点除源代码和数据库文件外还附带较完整的过程文档能够帮助使用者快速梳理系统模块、角色业务流程与部署思路。1. 基于SpringBoot的电子病历管理系统源码包能跑通什么闭环一个电子病历管理系统最典型的演示场景是这样医生登录 → 选患者 → 录入主诉和诊断 → 暂存或提交 → 病历列表里能查到详情。这套基于SpringBoot的毕业设计源码包把这条闭环完整串下来了后端用SpringBoot框架搭REST接口数据库脚本和预置账号都放在源码包里前端页面也配好了解压以后配好数据库连接串就能启动。它覆盖患者管理、病历录入、用户登录这些模块适合拿来当毕设底子也适合刚入行的Java开发把管理系统的完整链路看一遍建表 → 实体 → Mapper → Service → Controller → 页面渲染。这类源码包的通用价值在于数据库脚本不是摆设预置账号能直接登录答辩前把科室、字典表改成自己学校的场景就能用。2. 先把数据库钉死表结构设计与初始化脚本的使用顺序我拆过太多毕设源码包一个最深的感受是数据库脚本是整包的地基但很多人拿到手第一件事就是双击运行SQL文件结果要么报错要么表对不上后面整个项目都跑不起来。这套电子病历管理系统的数据库脚本核心是围绕“患者医生病历”这三类角色展开的理解清楚表与表之间的关系再动手导数据后面遇到的坑会少很多。2.1 核心实体与关系患者、病历、医生、科室怎么挂拿到SQL文件后先别急着执行打开看一眼里面的建表语句把实体关系摸清楚。这类系统最常见的表结构是这么一组科室表、医生表、患者表、系统用户表加上核心的病历主表和病历明细表。科室表和医生表是一对多关系一个科室下挂多个医生医生表和病历表是一对多关系一个医生可以录很多份病历患者表和病历表也是一对多一个患者多次就诊会产生多份病历。理解了这个关系你就知道为什么病历不能塞在患者表里——如果把主诉、诊断都堆到 patient 表的字段里第二次就诊就把第一次的记录覆盖了这在医疗场景里是完全不可接受的。病历必须单独建表用 patient_id 和 doctor_id 做逻辑外键去关联每次就诊生成一条新记录历史数据才能完整保留。表名核心字段作用sys_userid、username、password、role登录账号区分管理员与医生departmentid、dept_name、description科室信息doctorid、user_id、dept_id、title医生信息关联科室patientid、name、gender、phone、id_card患者档案medical_recordrecord_id、patient_id、doctor_id、status病历主表记录每次就诊record_detaildetail_id、record_id、content_type、content病历明细存放主诉、诊断等| 表格里 record_detail 的 content_type 字段一般用数字或字符串区分内容类型比如 1 代表主诉、2 代表现病史、3 代表诊断结果、4 代表处置意见。2.2 初始化脚本的使用顺序先建库再导数据源码包里的SQL文件一般会拆成两个或者合成一个但执行顺序是有讲究的先建库再建表最后导入预置数据。很多人在Navicat里直接选中整个SQL文件一次性执行如果文件里有CREATE DATABASE语句连接的不是同一个实例就会出问题。我一般用命令行或者Navicat的查询编辑器分步执行先把库建出来再切到对应的库跑建表语句。-- 第一步创建数据库指定字符集 CREATE DATABASE IF NOT EXISTS emr_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 第二步切换到emr_db USE emr_db; -- 第三步创建系统用户表 CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt密文, real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role TINYINT DEFAULT 2 COMMENT 1管理员2医生, status TINYINT DEFAULT 1 COMMENT 1启用0停用, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;这里有三点要说清楚。第一数据库字符集必须用 utf8mb4不是 utf8因为 utf8mb4 才能存 emoji 和生僻字病历里偶尔会出现特殊符号用 utf8 会有概率报“Incorrect string value”的错。第二DEFAULT CHARACTER SET和COLLATE要一起指定只指定字符集不指定排序规则在个别 MySQL 版本下会使用默认的 latin1 规则中文字段排序也会出玄学问题。第三字段注释 COMMENT 一定要写答辩时老师问表结构设计你能对着注释说清楚每个字段的含义比现场翻代码强得多。建完表之后执行预置数据脚本一般会插入一个管理员账号和几个测试患者。注意预置的密码通常是 BCrypt 加密后的字符串直接往表里插明文是登录不上的Spring Security 或者 Sa-Token 的 BCrypt 校验会把明文和密文做比对。想知道某个密文对应什么明文去脚本注释里找源码包通常会在 SQL 文件头部写清楚预置账号。2.3 病历主表和明细表的设计为什么拆成两张病历主表medical_record存的是“这次就诊的身份信息”和“状态”不存具体文字内容。每次就诊只有一条主表记录但主诉、现病史、既往史这些内容可能有好几段如果每个字段都建成主表的一列表就会变成一张超宽表而且不同科室要录的内容还不一样。所以设计上把内容拆到明细表用content_type区分段落类型。CREATE TABLE medical_record ( record_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 病历ID, patient_id BIGINT NOT NULL COMMENT 患者ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, record_no VARCHAR(30) NOT NULL COMMENT 病历编号, status TINYINT DEFAULT 0 COMMENT 0暂存1已提交, visit_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 就诊时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (record_id), KEY idx_patient (patient_id), KEY idx_doctor (doctor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT病历主表; CREATE TABLE record_detail ( detail_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 明细ID, record_id BIGINT NOT NULL COMMENT 病历ID, content_type TINYINT DEFAULT 0 COMMENT 1主诉2现病史3诊断4处置, content TEXT COMMENT 内容, sort_no INT DEFAULT 0 COMMENT 排序, PRIMARY KEY (detail_id), KEY idx_record (record_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT病历明细表;这两张表配合起来就是一套完整的病历结构。status字段是关键0 代表暂存1 代表已提交前端根据这个字段决定显示“编辑”还是“只读”后端也根据这个字段决定能不能再次修改。record_no是病历编号一般用日期加流水号生成答辩时可以展示给老师看证明你考虑过业务编号的生成规则。外键我通常不建物理外键保留逻辑外键就够了——物理外键在删除预置数据时会跑来跑去地报外键约束错误演示的时候很尴尬。参数值说明record_id主键自增每次就诊一条status0 / 10暂存1已提交content_type1~4明细段类型可按需扩展visit_date默认当前时间就诊时间演示时不用手动传数据库这层理清楚之后后面写接口其实就是在给这几张表做增删改查。所以拿到源码包的第一步永远是把表结构读懂再谈启动项目。3. 后端接口链路SpringBoot分层、登录鉴权与病历状态流转数据库设计好了接着看后端代码是怎么组织的。这套系统基于SpringBoot框架包结构基本遵循标准的 controller → service → mapper 三层调用链。这一类源码包最容易迷惑人的地方在于文件太多新手不知道从哪个文件开始读。我一般会按这个顺序给读者讲先看配置再看拦截器最后看一个完整的业务模块。3.1 包结构阅读顺序从 controller 到 mapper 的调用链拿到源码之后先看 Java 目录下的包结构通常是这样排布的src/main/java/com/example/emr ├── controller # 接收前端请求做参数校验 ├── service # 业务逻辑层接口定义 │ └── impl # 业务逻辑实现类 ├── mapper # MyBatis的Mapper接口 ├── entity # 数据库实体类 ├── config # 配置类拦截器、跨域、MyBatis配置 ├── util # 工具类JWT工具、日期工具 └── common # 通用返回体、异常处理阅读顺序建议是entity → mapper → service/impl → controller。entity 对应数据库表一眼能看出字段映射mapper 里能看到 SQL 语句service 里是业务规则controller 是最后的入口。用一套源码包最快的学习方式就是跟着一条业务链路读比如“患者建档”这条链路controller 接收患者信息 → service 校验身份证号是否重复 → mapper 执行 insert → 返回主键。这里有个常见的误解拿到源码就去找 Controller看每个接口写了什么。其实 Controller 里基本没有业务逻辑真正值得读的是 service 的实现类。3.2 数据源与全局配置application.yml 里必须改的四个参数数据库能连上项目才能启动。application.yml是 SpringBoot 项目的配置总入口拿到源码包第一步就是把数据源改成你自己的本地环境。下面是这类项目最常见的一套配置我加了注释说明每个参数的作用。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/emr_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里每一行都值得说。serverTimezoneAsia/Shanghai如果漏了MySQL 8.0 会报时区错误而且查询出来的时间会和本地时间差 8 小时useSSLfalse是为了避免本地连接时 SSL 握手警告allowPublicKeyRetrievaltrue在 MySQL 8.0 使用 caching_sha2_password 插件时必须加否则报“Public Key Retrieval is not allowed”。driver-class-name用的是com.mysql.cj.jdbc.Driver这是 MySQL 8.0 的驱动类名如果你的本地 MySQL 是 5.7可以换成com.mysql.jdbc.Driver。max-file-size和max-request-size控制上传大小。病历系统里可能会有检查报告图片上传默认 10MB 够用但如果演示时上传一个 15MB 的图片就会报FileSizeLimitExceededException。map-underscore-to-camel-case设为 true 后数据库字段create_time会自动映射到实体属性createTime不用写一堆TableField注解。log-impl配成 StdOutImpl 会把执行的 SQL 打印到控制台这是排查问题最好的帮手。3.3 登录鉴权与 JWT 拦截器哪些接口放行哪些必须带Token病历数据属于敏感数据登录鉴权是必有的。这套系统如果用的是 JWT 方案登录成功后后端返回一个 token前端后续请求都带着这个 tokenSpringBoot 侧用一个拦截器统一校验。拦截器的作用是在请求到达 Controller 之前先检查 token 合法性不合法直接返回 401。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求前端跨域时会先发OPTIONS if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); if (JwtUtil.verify(token)) { // 把用户ID放到request属性里后续接口可以直接取 request.setAttribute(userId, JwtUtil.getUserId(token)); return true; } } response.setStatus(401); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } }这里有几个细节容易翻车。第一跨域预检请求是 OPTIONS 方法没有 Authorization 头如果不放行前端调用接口时会一直报 401这个我踩过坑血泪经验。第二token 放在 Authorization 头值是Bearer开头加空格前端封装时要保证格式严格一致。第三JWT 校验失败后要返回统一的 JSON 结构前端才能统一处理不能直接返回一个空的 401。拦截器注册到 WebMvcConfigurer 里时要配好放行规则。登录接口、验证码接口、Swagger 文档接口应该放行其余接口全部拦截。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/api/**) .excludePathPatterns( /api/auth/login, /doc.html, /webjars/**, /v3/api-docs/** ); } Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }放行规则的顺序很重要先加addPathPatterns(/api/**)声明拦截范围再用excludePathPatterns排除不需要拦截的路径。如果把顺序写反拦截器可能不会生效。跨域配置里allowCredentials(true)和allowedOriginPatterns(*)必须配套只配allowedOrigins(*)加上allowCredentials(true)会导致浏览器拒绝响应。3.4 暂存与提交病历状态机的核心业务逻辑病历模块的核心业务不是简单的增删改查而是“暂存”和“提交”两个状态之间的流转。暂存意味着医生没写完随时可以继续编辑提交意味着病历定稿了不能再改。这个逻辑写在哪写在 Service 层Controller 只负责把参数接住。Service public class MedicalRecordServiceImpl implements MedicalRecordService { Override Transactional(rollbackFor Exception.class) public Long saveOrUpdate(MedicalRecordDTO dto) { // 0暂存 1提交提交前强制校验 boolean isSubmit Integer.valueOf(1).equals(dto.getStatus()); if (isSubmit) { Assert.hasText(dto.getChiefComplaint(), 主诉不能为空); Assert.hasText(dto.getDiagnosis(), 诊断不能为空); } MedicalRecord record new MedicalRecord(); record.setPatientId(dto.getPatientId()); record.setDoctorId(dto.getDoctorId()); record.setStatus(dto.getStatus()); record.setVisitDate(LocalDateTime.now()); // 已提交的病历不能再修改前端靠status控制后端也要兜底 if (dto.getRecordId() ! null) { MedicalRecord old this.getById(dto.getRecordId()); if (old ! null Integer.valueOf(1).equals(old.getStatus())) { throw new BusinessException(病历已提交无法修改); } record.setRecordId(dto.getRecordId()); this.updateById(record); } else { record.setRecordNo(generateRecordNo()); this.save(record); } return record.getRecordId(); } }这段代码里有三个点值得细说。一是Transactional保存主表的同时要保存明细表任何一步失败都必须回滚不然会出现主表有记录、明细表没内容的脏数据。二是“提交前强制校验”暂存允许只填一半但提交必须主诉和诊断都填了这个规则后端必须做不能依赖前端校验。三是“已提交的病历不能修改”前端把按钮置灰只是体验问题后端如果不在保存时拦截直接拿接口调就会绕过校验。回到面试和答辩场景老师经常会问“为什么提交后不能修改”答案不是“前端控制了”而是“病历是医疗文书提交后代表当时医生的判断修改会产生法律风险所以业务上只允许新增修订版本”。这句话一出来整个设计的业务思考就体现出来了。4. 前端对接与权限控制Vue页面与接口联调的配合细节后端接口再规范前端接不上也白搭。这套系统的前端页面通常是用 Vue 写的和后端通过接口交互。前端这一块我认为最值得讲的不是某个页面长什么样而是接口约定和权限控制——这两件事直接决定了页面能不能跑通。4.1 接口返回体约定三段式结构前后端联调最怕的就是各写各的。这套源码包的接口返回体基本都是三段式结构code表示业务状态码、message表示提示信息、data表示实际数据。前端只要在全局拦截器里统一判断 code 就可以不需要每个页面重复写错误处理。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这个类的设计很简单但它是前后端协作的契约。code200代表业务成功code401代表未登录code500代表业务异常。前端 axios 的响应拦截器里只需要判断 code 是不是 200不是 200 就弹出 message 里的内容。这样后端抛什么业务异常前端就直接展示什么提示不用每个页面单独处理错误分支。4.2 axios 封装与 Token 注入常见的请求拦截写法前端封装 axios 实例作用就是把 baseURL、超时时间、Token 注入统一处理掉。这样每个页面调用接口时只需要关注业务参数不需要关心 Token 从哪来、错误怎么统一处理。import axios from axios const service axios.create({ baseURL: /api, timeout: 15000 }) // 请求拦截器每次请求自动带上Token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }, error { return Promise.reject(error) }) // 响应拦截器统一处理业务码和登录过期 service.interceptors.response.use(response { const res response.data if (res.code 401) { localStorage.removeItem(token) window.location.href /login return Promise.reject(new Error(登录过期)) } if (res.code ! 200) { return Promise.reject(new Error(res.message || 请求失败)) } return res.data }, error { return Promise.reject(error) }) export default service这里有两个细节要注意。Token 存 localStorage 还是 sessionStorage有讲究。localStorage 不随浏览器关闭而清除用户刷新页面后登录状态还在演示时更方便但安全性上 sessionStorage 更好一点关掉浏览器就失效。毕设系统用 localStorage 是主流做法因为演示中途刷新页面不需要重新登录。另一个细节是baseURL: /api如果前端和后端部署在同一台服务器用相对路径加 nginx 反向代理转发到后端端口最省事如果前端开发时和后端分开跑就需要在 vue.config.js 里配 devServer 的 proxy 转发。4.3 路由守卫与按钮级权限路由守卫解决的是“未登录不能进页面”的问题。前端路由在跳转前先判断有没有 token没有就直接踢到登录页。这套逻辑写在 Vue Router 的全局前置守卫里。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })路由守卫只是第一层控制。真正细粒度的权限控制是按钮级的比如管理员能看到“用户管理”菜单普通医生只能看到“病历录入”“病历查询”。实现方式有两种一种是后端返回菜单列表前端根据菜单动态生成路由另一种是前端按角色字段去判断v-ifrole 1。毕设项目用第二种就够但答辩时如果能说出动态路由的思路会很加分。连调的时候还有一个容易忽略的点接口路径的大小写。Java 后端接口一般用/api/medicalRecord/save这种驼峰命名Vue 里调用时路径大小写必须完全一致Linux 服务器上大小写敏感Windows 本地不敏感所以本地跑通了部署到服务器上反而 404 的情况我也见过不少。建议拿到源码后先把接口路径统一改成小写连字符风格比如/api/medical-record/save一劳永逸。5. 避坑指南从导入到跑通的五个高频翻车现场这套源码包从拿到手到跑起来我总结了五个高频翻车点每个都是我实际遇到过、帮别人排查过的真实案例。这里按“现象 → 原因 → 解决”的方式逐一拆给你看。5.1 MySQL 8.0 驱动名配错导致启动失败运行 SpringBoot 项目时控制台报ClassNotFoundException: com.mysql.jdbc.Driver或者在数据源初始化阶段直接启动失败。原因是 MySQL 8.0 版本把驱动类名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver源码包里如果用的是旧版驱动配置在 MySQL 8.0 环境下就会找不到类。8.0 之前的老驱动包mysql-connector-java版本低于 5.1 时类名也是com.mysql.jdbc.Driver。解决方式很简单要么把 application.yml 里driver-class-name改成com.mysql.cj.jdbc.Driver同时确认 pom.xml 里 MySQL 驱动依赖版本是 8.0.x。如果本地装的是 MySQL 5.7可以继续用旧驱动类名但更推荐统一到 8.0。5.2 时区导致查询出的时间比本地时间少 8 小时病历列表里显示的时间比当前时间慢了 8 个小时或者插入数据库的时间是对的但查询出来不对。原因是 JDBC 连接串里没有指定serverTimezone参数MySQL 8.0 默认使用服务器的时区设置而本地 MySQL 服务时区通常是 UTC和北京时间差 8 小时。解决方式是在 JDBC URL 里加serverTimezoneAsia/Shanghai。如果加了还是不对检查 MySQL 服务端时区执行SELECT global.time_zone如果是SYSTEM再查SELECT NOW()看数据库当前时间必要时在 MySQL 配置文件my.ini里设置default-time-zone08:00。5.3 中文乱码明明表里是中文查询出来是一堆问号数据库表里中文变成“”或者页面上显示乱码。原因要从三层排查。第一层是数据库连接串里没加characterEncodingutf8第二层是表和字段的字符集不是 utf8mb4而是继承了 latin1 的默认字符集第三层是前端页面本身的字符集声明不对。解决方式第一步JDBC URL 里加上useUnicodetruecharacterEncodingutf8第二步把表的字符集改成 utf8mb4执行ALTER TABLE medical_record CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第三步确认前端入口 HTML 有meta charsetutf-8。按这个顺序排查90% 的乱码问题能解决。5.4 登录后请求仍然 401跨域配置和 Token 传递的冲突前端页面能打开登录页但登录成功后调接口还是 401控制台提示跨域问题或者发现请求头里没有 Authorization 字段。原因往往是两个。第一前端开发服务器和后端端口不同比如前端跑在 8081后端跑在 8080浏览器跨域请求先发 OPTIONS 预检后端拦截器没放行 OPTIONS 导致失败。第二前端代码里 Token 存的位置和后端要求不一致比如后端从 Header 取前端却把 Token 放在了 Cookie 里。解决方式是双管齐下。后端在拦截器里放行 OPTIONS 请求同时跨域配置里allowCredentials(true)和allowedOriginPatterns(*)配套前端在 axios 封装里从 localStorage 读取 Token 并注入 Authorization 头。两端都改好之后用浏览器的开发者工具看 Network 面板确认预检请求返回 200正式请求带上了 Authorization 头。5.5 端口被占用导致 SpringBoot 启动失败启动时控制台报Port 8080 was already in use应用起不来。原因很简单本地有别的进程占用了 8080 端口常见的是之前跑过的后端服务没停掉或者其他软件比如某个前端开发服务器抢占了端口。解决方式有两种一是找到占用进程在命令行执行netstat -ano | findstr 8080查到进程 PID然后在任务管理器里结束对应进程二是直接改 SpringBoot 端口在 application.yml 里把server.port改成 8081同时记得改前端代理配置里的 target 地址。我更推荐第二种因为演示现场你不知道那台机器上有什么服务占着端口改端口是可控性最高的方案。坑核心排查方向驱动找不到MySQL 8.0 vs 5.7 驱动类名时间差8小时serverTimezone 参数中文乱码连接串、表字符集、页面字符集登录后401OPTIONS 预检、Token 注入位置端口占用server.port 修改6. 进阶配置日志链路、数据脱敏与备份策略毕设演示能跑通只是第一步如果想让老师觉得你真的理解了工程化有几个进阶配置值得花半小时加上。第一个是日志链路。现在这套系统的日志基本是靠控制台输出但正式一点的做法是在拦截器里记录每个请求的耗时把请求路径、用户ID、耗时打印出来。这样演示时出了问题能很快定位到是哪个接口慢现场给老师展示日志也是一种加分项。第二个是病历数据的脱敏。身份证号、手机号属于敏感信息页面列表里不应该明文展示全部。在后端返回前对字段做处理比如138****1234、110***********0011详情页再展示完整信息。这个逻辑不复杂一个工具类就能实现但它的价值在于体现了数据安全意识——电子病历系统的数据敏感度比普通管理系统高得多答辩时主动提到这个设计比被老师问到时再解释要主动得多。第三个是数据库备份习惯。我自己的习惯是每次改完表结构或者导入完预置数据立刻用 mysqldump 导出一份备份文件命名带上日期。这套系统的数据库脚本虽然给你了但演示前如果手滑删了数据没有备份就只能重跑初始化脚本之前录入的演示数据全没了。mysqldump -uroot -p emr_db backup_$(date %Y%m%d).sql从那以后我每次拿到这类源码包都会强制走一遍这个流程先读表结构再执行脚本启动后先用预置账号登录验证一遍最后立刻导出备份。这套电子病历管理系统的源码和数据库只要按这个顺序走基本能在一个小时内从解压跑到录病历。希望帮到你。本文还有配套的精品资源点击获取