1. 为什么医疗管理系统成了毕业设计的“硬骨头”
每年一到毕设季,Spring Boot 医疗管理系统就扎堆出现,很多同学上来就问“这东西到底难不难”。我的看法是:医疗管理系统算 Spring Boot 项目里最典型的业务型系统,它难不在技术,而在业务逻辑的密集程度。管理员、医生、护士、药房、收费处、患者,光角色就六七个,而且每个角色关心的数据完全不一样,这就逼着你在项目里考虑权限、状态流转、数据一致性这些真实工程问题。换句话说,做完这个项目,你对 Spring Boot 的综合掌握程度会明显上一个台阶。
从技术栈上看,Spring Boot + MyBatis Plus + MySQL 是现阶段最主流的毕设组合。Spring Boot 2.7.x 是目前兼容性和稳定性最平衡的版本,太高了(比如 3.x 以上)需要 JDK 17,太低了很多新特性又用不上。有些同学纠结“Spring Boot 版本太高”怎么办,实际开发中不必追新,选一个自己熟悉的稳定版,把精力花在业务逻辑上更划算。
这篇内容我按自己的实际开发思路来拆解:先讲系统整体架构和模块划分,再深入到数据库设计、核心代码实现、常见问题排查。不管你是打算直接照着做一个,还是想理解整个系统的设计思路,这篇文章都可以当作一份比较完整的设计参考。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统整体设计与技术选型思路
2.1 系统能做什么:业务模块全景拆解
医疗管理系统不是简简单单的“增删改查”,它背后是一条清晰的业务主线:患者进门 → 挂号 → 医生看诊 → 开检查/开药 → 缴费 → 取药/治疗 → 离院。所有功能模块都是围绕这条主线的。
一个典型的 Spring Boot 医疗管理系统,至少包含这些模块:
| 模块 | 核心功能 | 涉及角色 |
|---|---|---|
| 系统管理 | 用户管理、角色管理、菜单权限 | 管理员 |
| 门诊管理 | 科室管理、医生排班、挂号管理 | 管理员、护士、医生 |
| 患者管理 | 患者建档、病历查询、历史就诊记录 | 护士、医生 |
| 医生工作站 | 看诊、开处方、开检验检查、诊断录入 | 医生 |
| 药品管理 | 药品字典、库存管理、入库出库记录 | 药房管理员 |
| 收费管理 | 挂号收费、处方收费、退费 | 收费员 |
| 住院管理 | 住院登记、床位分配、医嘱执行 | 护士、医生 |
| 数据统计 | 门诊量统计、药品消耗统计、收入统计 | 管理员 |
我第一次做类似项目的时候踩过一个很深的坑:一开始只顾着把 CRUD 写完,结果到了“开处方”这个环节才发现,处方里既要有医生开的药品列表,又要关联到收费记录,还要在收费后扣减库存。如果前期没把模块关系理清楚,后面代码会越写越乱,改一处崩一处。所以奉劝各位,拿到题目后第一件事不是写代码,而是先画业务流程图,把数据流向搞清楚。
2.2 技术栈选择:为什么不推荐更“花哨”的组合
有人喜欢在毕业设计里堆技术:Spring Cloud、Redis、RabbitMQ、Elasticsearch,看起来高大上,但我要泼一盆冷水:毕业设计的评分核心是“完整度”和“逻辑严谨性”,不是技术数量。
技术栈建议这样搭:
- JDK 1.8 或 JDK 11(稳定,适配大部分开发环境)
- Spring Boot 2.7.13(目前最稳的 2.x 版本之一)
- MyBatis Plus 3.5.x(单表操作省时省力,分页好用)
- MySQL 5.7 或 8.0(5.7 轻量,8.0 性能更稳)
- Redis(可选,用来做验证码缓存、Token 存储)
- JWT(登录鉴权,前后端分离必选)
- Vue 2/3 + Element UI / Element Plus(前端界面)
有的同学会问:“Spring Boot 3.x 都出了,为什么不用?”我承认 3.x 是一个大版本更新,但它要求 JDK 17,而且有些框架的兼容性还没完全跟上,比如部分旧版 MyBatis Plus 的 mapper 扫描方式会有小问题。对一个 3~5 个月开发周期的毕设来说,2.7.x 是最稳的选择,没必要替官方踩兼容性的坑。
2.3 前后端分离还是服务端渲染
这个决策要看你自己的情况。如果你前端比较熟,推荐用前后端分离:Vue 打包之后放到 Spring Boot 的 static 目录下,或者单独开一个前端服务,通过 nginx 或直接通过后端 CORS 配置访问。这样你既展示了后端接口设计能力,又能体现前端页面开发能力。
如果你一个人时间紧,前后端分离反而可能增加工作量,因为你需要考虑跨域、Token 传递、Axios 封装这些额外问题。这种情况下可以直接用 Thymeleaf 做服务端渲染,页面由 Spring Boot 直接渲染返回,开发调试更直接,也不用担心跨域问题。
我个人建议是:只要是毕设,尽量做前后端分离。不只是因为答辩时更好展示,更因为企业项目基本都是前后端分离,你在项目里提前接触这套模式,面试时也有的聊。
3. 数据库设计:这个系统的“地基”怎么打
3.1 核心表结构和字段规划
数据库是医疗管理系统最容易翻车的地方,表设计不合理,后面写 SQL 想哭都来不及。我按业务模块给出核心表的设计思路。
第一张是用户表 sys_user,这几乎是每个系统都有的。字段上注意区分用户类型,医生、管理员、收费员都是用户,但角色不同能访问的菜单完全不同。最简单的做法是加一个 role_id 字段关联角色表,角色表再用一个 menu_ids 字段存权限范围。你也可以用 user_type 字段直接区分角色,好处是查询方便,坏处是扩展性比较差,新增一个角色类型就要改代码。
第二张是患者表 patient,核心字段包括 patient_no(病历号)、name、gender、id_card(身份证)、phone、birthday、address。病历号建议用时间戳加随机数生成,如 202501011030456789,这样既不会重复,看上去也比较专业。
第三张是门诊挂号表 registration,这是核心中的核心。字段至少要有:patient_id、dept_id(科室)、doctor_id(接诊医生)、reg_date(挂号日期)、reg_time(挂号时间)、visit_time(看诊时间段)、status(挂号状态:待诊、已诊、已退号)、fee(挂号费)。如果你的系统支持线上挂号,还要考虑一个场景——同一个医生同一天同一时间段只有一个号源,数据库层建议加唯一索引,比如 (doctor_id, reg_date, visit_time) 联合唯一。
然后是处方表 prescription 和处方明细表 prescription_item。主表存 doctor_id、patient_id、diagnosis(诊断结果)、create_time、total_amount;明细表存 prescription_id、drug_id、drug_name、price、quantity、amount。药品名称存一份到明细表里,是为了在药品字典被修改后,处方记录仍然能保留当时的价格和名称,这是符合业务规范的冗余设计。
药品库存表 drug_stock 也需要单独设计,字段包括 drug_id、stock_quantity、safety_stock(安全库存线)、updated_time。这里有个推荐做法:在药品入库时记录批次号 batch_no,因为药品是有有效期管理的,同样一种药不同批次的到期时间不同,如果库存表里只有总数,后面做效期预警会很被动。
3.2 为什么需要状态机设计
医疗系统里最典型的状态是“挂号单”的流转:刚挂号是“待诊”,医生点击叫号后变成“就诊中”,医生完成诊断并开完处方后变成“已完成”,患者未就诊时可以在线退号变“已退号”。这些状态之间不是随便跳的,比如退号只能是“待诊”状态下才能退,看诊中的单子不能退。
这种场景就要求开发者在设计表时加上 status 字段,并且在代码里做状态入参校验。我见过很多同学只加一个字段,但不做合法性校验,结果用户通过接口把“已完成”直接改成“待诊”,数据乱成一团。正确的做法是在 Service 层写一个状态变更的判断逻辑,比如:
java复制public boolean cancelRegistration(Long regId) {
Registration reg = registrationMapper.selectById(regId);
if (!"待诊".equals(reg.getStatus())) {
throw new BusinessException("当前状态不可退号");
}
reg.setStatus("已退号");
return registrationMapper.updateById(reg) > 0;
}
把状态流转管理好,你的系统就不只是“演示版”,而是具备基本业务规则的“可用版”,答辩时这一条就明显高于大多数同学。
3.3 数据库索引设计经验
除了主键索引,我建议至少在这些字段上加索引:
- 用户表的
username(登录查询) - 患者表的
id_card(身份证检索) - 挂号表的
doctor_id + reg_date(医生排班和当日患者查询) - 处方表的
patient_id(查历史处方)
索引不是越多越好,因为索引占用额外存储空间,还会拖慢写入速度。毕设项目数据量很小,索引差异体现不明显,但如果在面试中被问起,你能说清楚“这里建联合索引是为了覆盖高频查询场景”这个理由,就很加分。
4. 核心代码实现:能直接“抄作业”的关键逻辑
4.1 Spring Boot 项目初始化和目录结构
新建项目时直接用 IDEA 的 Spring Initializr 即可。如果创建的是 Spring Boot 2.7.13,要注意在 pom.xml 里显式指定版本号,不要用 parent 的默认依赖管理。包结构建议按“模块化”分层:
code复制com.example.medical
├── config
│ ├── WebMvcConfig.java
│ ├── MybatisPlusConfig.java
│ └── CorsConfig.java
├── controller
│ ├── SystemUserController.java
│ ├── PatientController.java
│ ├── RegistrationController.java
│ ├── PrescriptionController.java
│ └── DrugController.java
├── service
│ ├── PatientService.java
│ ├── RegistrationService.java
│ └── ...
├── mapper
│ ├── PatientMapper.java
│ └── ...
├── entity
│ ├── Patient.java
│ ├── Registration.java
│ └── ...
├── common
│ ├── Result.java
│ ├── ResultCode.java
│ └── BusinessException.java
└── MedicalApplication.java
入口启动类最好加上 @MapperScan 注解,扫描 mapper 包,避免每个 Mapper 上都加 @Mapper 注解,代码能整洁不少。
java复制@SpringBootApplication
@MapperScan("com.example.medical.mapper")
public class MedicalApplication {
public static void main(String[] args) {
SpringApplication.run(MedicalApplication.class, args);
}
}
4.2 登录鉴权:JWT 还是 Shiro
毕设项目的登录方案,我推荐直接使用 JWT,不用引入 Spring Security 那一套复杂机制。Spring Security 功能确实全,但是配置门槛高,尤其是过滤链的配置,对新手不友好,而且答辩时你自己也未必能讲清楚里面的逻辑。
JWT 的核心思路是:用户登录成功后,服务端通过 jjwt 库生成一个 Token,返回给前端。前端每次请求在 Authorization 请求头中携带该 Token。服务端通过一个拦截器(HandlerInterceptor)校验 Token 是否有效,并在 HandlerMethod 上加上自定义 @RequireRole 注解判断权限。
简易 JWT 工具类核心代码如下:
java复制public class JwtUtils {
private static final String SECRET = "your-secret-key";
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000L;
public static String generateToken(String username, Long userId, String role) {
return Jwts.builder()
.setSubject(username)
.claim("userId", userId)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}
}
这里有一个小小的经验:JWT 的 SECRET_KEY 不要写死在代码里,可以放到 application.yml 中,用 @Value 或 @ConfigurationProperties 注入。虽然毕设不像企业项目那样严格要求,但至少能体现你有配置管理意识。
4.3 挂号模块的并发问题
挂号是医疗系统里技术含量最高的功能,因为它涉及“号源扣减”,一个典型的 Token Bucket 场景。很多同学写出来的代码如下:
java复制@Transactional
public void register(Long doctorId, Long patientId, String visitTime) {
// 1. 查询当前号源是否充足
Registration reg = new Registration();
reg.setDoctorId(doctorId);
// ...
// 2. 直接插入挂号记录
registrationMapper.insert(reg);
}
问题是:如果两个用户同时抢同一个医生的最后一个号,两个人同时查都能查到余号,同时插入就超卖了。用 MySQL 的话,最简单的解决办法是在医生排班表里加一个剩余号源字段 remain_count,在真正的扣减语句上用乐观锁:
java复制@Update("UPDATE doctor_schedule SET remain_count = remain_count - 1 " +
"WHERE id = #{scheduleId} AND remain_count > 0")
int decreaseRemainCount(Long scheduleId);
UPDATE 语句本身是行锁的,多个事务同时执行时,只有一个会成功,返回值大于 0 才代表扣减成功,否则提示“号源已满”。这种基于数据库行锁的做法比在代码里先查再改要可靠得多。我在这个项目里给多个同学排过并发测试,这个方法实测是有效的。
4.4 药品库存的事务管理
处方生成的流程是:医生录入药品明细 → 提交 → 系统自动计算总价 → 同时锁定库存。这里“锁定库存”和“扣减库存”概念不一样,简单做的话可以直接扣库存,但严谨的流程是“先锁定,后扣减”:医生提交处方但患者还没缴费时,库存先预占,患者缴费后再真正扣减;如果患者放弃缴费超过时限,则释放锁定。
毕设阶段不用把锁库存做得太复杂,但“事务”是必须的。我的建议是:在 Service 层方法加 @Transactional,保证插入处方主表、插入处方明细、更新库存三者要么全部成功,要么全部回滚。注意 @Transactional 默认只在 RuntimeException 时回滚,如果你在业务代码里 try-catch 掉了异常,事务会失效,这点非常关键。
java复制@Transactional(rollbackFor = Exception.class)
public Long createPrescription(PrescriptionDTO dto) {
// 1. 插入处方主表
// 2. 插入处方明细列表
// 3. 循环调用 drugService.deductStock(drugId, quantity)
// 4. 返回处方号
}
rollbackFor = Exception.class 这个参数我强烈建议加上,它让事务回滚覆盖到所有异常类型,否则你 catch 住 Exception 后事务悄悄提交,库存就扣错了。
5. 集成功能与功能亮点设计
5.1 集成 Redis 做验证码缓存
登录页的图形验证码或短信验证码,可以存在 Redis 里,设置过期时间 2 分钟。用户提交时对比 Redis 中的值,一旦验证成功立即删除,防止同一验证码重复使用。这个逻辑并不复杂,却能给系统增加“缓存中间件”的亮点。
Redis 缓存代码示意:
java复制public boolean verifyCode(String key, String code) {
String cachedCode = redisTemplate.opsForValue().get(key);
if (cachedCode != null && cachedCode.equalsIgnoreCase(code)) {
redisTemplate.delete(key);
return true;
}
return false;
}
很多同学担心 Redis 装起来麻烦,其实 Windows 或 Linux 上都有免安装版本,解压之后运行 redis-server 即可,前后端联调的时候非常爽。不过要注意,Redis 在当前版本中做缓存是可以的,但不要把核心业务数据放到 Redis 而不落库,毕竟你这是一个有“持久化需求”的管理系统。
5.2 与 MyBatis Plus 的分页和条件查询
MyBatis Plus 是我在这个项目里的首选持久层框架,单一表操作基本不用写 SQL,分页更是爽到起飞。
配置分页插件:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
查询患者列表并分页:
java复制public Page<Patient> queryPatientPage(int pageNum, int pageSize, String keyword) {
LambdaQueryWrapper<Patient> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(keyword), Patient::getName, keyword)
.or()
.like(StringUtils.hasText(keyword), Patient::getPhone, keyword)
.orderByDesc(Patient::getCreateTime);
return patientMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
}
LambdaQueryWrapper 比 QueryWrapper 更安全,因为字段名用的是方法引用,编译期就能发现错误。这里面的 StringUtils.hasText 判断比较常用,条件为空时不拼接该条件,避免不必要的全表模糊查询。
5.3 使用 EasyExcel 做数据导出
很多毕设要求支持“导出数据到 Excel”,实际操作中最推荐阿里开源的 EasyExcel。相比 Apache POI 而言,EasyExcel 的 API 更直观,内存占用也更低。
加依赖:
xml复制<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.4</version>
</dependency>
导出患者列表到 Excel:
java复制public void exportPatient(HttpServletResponse response) throws IOException {
List<Patient> list = patientService.list();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
EasyExcel.write(response.getOutputStream(), Patient.class)
.sheet("患者信息")
.doWrite(list);
}
在 Patient 实体类的字段上加上 @ExcelProperty("姓名")、@ExcelProperty("身份证号") 这类注解,导出时就能自动生成表头,28 行业务代码搞定一个听起来“高级”的功能。
5.4 静态资源与上传文件处理
医疗系统中会涉及患者检查报告的图片、医生头像等上传场景。Spring Boot 中做个文件上传是很常规的功能,但要注意几个点:文件上传路径不要放在项目源码目录下,而是放到一个独立的目录,比如 D:/medical/upload/ 或者 Linux 下的 /data/medical/upload/,然后把该目录映射成虚拟路径,方便前端直接访问。
配置虚拟路径的代码:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceHandler("file:" + uploadPath);
}
}
这样前端访问 http://ip:8080/upload/xxx.jpg 就能直接看到上传的图片,不需要额外写静态目录映射。
6. 前端界面设计与接口对接的经验
6.1 页面结构设计建议
前端建议使用 Vue + Element Plus 做后台管理系统,页面结构通常分为:
- 登录页面(带验证码)
- 系统布局(左侧菜单 + 顶部导航 + 主内容区)
- 管理页面(患者列表、医生排班表、挂号记录、处方录入、库存表格、收费结算单等)
模板可以找一个开源的后台管理系统脚手架,比如若依的前端部分或者 Vue Element Admin,在上面做二次开发。这里有个经验:直接拿一个后台模板改造,比自己从零写布局省一半时间,尤其是菜单权限、路由守卫这些,模板都处理好了,你只需要关注业务页面。
6.2 Axios 请求封装和 Token 携带
前后端分离开发时,前端需要统一封装 Axios 请求。核心代码建议这样写:
javascript复制import axios from 'axios'
const request = axios.create({
baseURL: '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = token
}
return config
})
request.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
Message.error(res.message)
return Promise.reject(new Error(res.message))
}
return res
},
error => {
Message.error('服务器内部错误')
return Promise.reject(error)
}
)
如果后端接口路径统一加了 /api 前缀,记得后端也要做统一处理。常见做法是在配置文件中设置 server.servlet.context-path: /api,这样 Controller 里的路径就不用每个都加前缀。这个设计前后端联调时不会出现路径不一致的尴尬。
6.3 接口设计规范和统一返回格式
后端接口返回格式一定要统一,这是团队协作和后续调试的基础。我习惯用一个 Result<T> 类统一包装:
java复制@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
Controller 中只需要调用 Result.success(data) 或 Result.error("xxx"),前端判断 code === 200 即可。不要在这里设计太多状态码,成功、失败、未登录三种够用就行,状态码太多前端判断起来也麻烦。
6.4 联调常见问题:跨域和会话问题
前后端分离联调时,跨域(CORS)问题几乎必遇到。后端加上一个全局跨域配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600);
}
}
实际上,跨域问题如果配合 JWT 一起处理,相对比较省心。因为 JWT 是无状态的,服务端不保存 Session,Cookie 的安全性顾虑就小一些,跨域时只要在后端放行即可。但如果后端用了 Session,跨域中的 Cookie 携带会很麻烦,这也是我推荐 JWT 的底层原因之一。
7. 部署与答辩准备:从开发机到演示环境
7.1 本地打包和环境部署
打包前,需要确认 application.yml 中数据库连接、Redis 连接等配置都改成服务器环境对应的值,然后把文件打包:
bash复制mvn clean package -DskipTests
得到 target/medical-system.jar 后,上传到服务器或本机演示环境,运行:
bash复制java -jar medical-system.jar --spring.profiles.active=prod
如果你想用更有“项目感”的部署方式,可以写一个 Dockerfile,把应用打成 Docker 镜像。这不仅是加分项,也能避免在答辩现场因为环境差异导致项目跑不起来——这种尴尬很多人都遇到过。
Dockerfile 示例:
dockerfile复制FROM openjdk:8-jdk-alpine
COPY medical-system.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
7.2 答辩常见提问和应对思路
答辩时老师大概率会围绕以下问题提问,提前准备答案:
- “你的系统有哪些角色?权限是怎么实现的?”——结合用户表、角色表和拦截器解释。
- “药品库存扣减如何保证一致性?”——说清楚
UPDATE ... WHERE stock > 0和@Transactional的配合。 - “如果并发挂号,会不会超卖?”——讲一下乐观锁或行锁方案。
- “为什么选 Spring Boot 而不是 SSM?”——答 Spring Boot 的自动装配、内嵌 Tomcat、起步依赖,以及配置简化带来的开发效率提升。
- “项目的难点在哪里?”——别只说是“写代码”,可以从“业务状态流转设计”“多角色权限控制”“并发号源控制”三个角度展开,哪怕是提前背熟的也要能讲顺。
如果你在项目里使用了 JWT,还要能清楚讲出 JWT 由 Header、Payload、Signature 三部分组成,Signature 是通过 Header 里的算法和 SECRET 计算出来的,服务端在验证时要重新计算比对,防止 Token 被篡改。这部分能讲明白,老师会认定你是真的自己写的。
7.3 项目演示准备:一定要做“用户剧本”
很多同学答辩翻车不是因为项目不好,而是现场操作太乱。我的建议是准备一份“演示剧本”,按角色切换操作演示路径,比如:
- 管理员登录,创建医生账号,分配角色权限
- 护士登录,新建患者档案
- 护士为患者挂号,选择科室和医生
- 医生登录,接诊,开处方
- 收费员登录,收费确认
- 药房登录,出库发药
- 管理员进入统计页面,查看门诊量、药品消耗数据
这个流程贯穿了全部核心模块,每一步都能看到对应的数据变化,老师看完会觉得系统“很完整”,这是比你口头说“我做了很多功能”更有说服力。
8. 开发周期规划和常见问题速查
8.1 时间分配建议
一个完整的医疗管理系系统建议按 4~6 周规划,每周目标明确:
| 阶段 | 时间 | 核心任务 |
|---|---|---|
| 需求分析 | 3~4天 | 画流程图,写功能清单,确定角色和权限 |
| 数据库设计 | 3~4天 | 建表,写模型文档 |
| 后端基础 | 5~7天 | 项目搭建、登录鉴权、统一返回格式 |
| 核心业务 | 10~14天 | 挂号、处方、收费、库存 |
| 前端页面 | 7~10天 | 主要页面 + 接口联调 |
| 收尾 | 3~5天 | 项目部署、演示数据、答辩PPT |
不少同学习惯先写前端再写后端,我建议反过来:先把后端的接口定义清楚,前端页面往接口上“贴”。后端先行能让你在开发初期就固定好数据模型和接口契约,避免前后端各改各的,最后谁也说不清哪里出问题。
8.2 我整理的高频 Bug 和排查方法
举几个我最常被问的、也最典型的问题:
1. 数据库连接失败,Access denied for user 'root'@'localhost'。
原因基本都是密码错误或用户的 host 限制。排查:先在命令行用 mysql -u root -p 测试能否连上,再看 application.yml 里的用户名密码、URL 的 serverTimezone 参数是否正确。
2. 接口返回中文乱码。
优先检查后端统一的编码配置。在 Spring Boot 的配置文件里加:
yaml复制server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
另外前端 Axios 请求头要带 Content-Type: application/json;charset=UTF-8。
3. 前端请求接口报 401 或 403。
排查 Token 是否过期、是否在请求头中被正确携带、自定义拦截器是否放行了登录接口和静态资源。一个很实用的做法是给拦截器添加白名单数组,比如 /api/login、/api/captcha、/upload/**,防止登录接口本身也走鉴权导致死循环。
4. 使用 @Transactional 后库存仍然没回滚。
常见原因有:方法被同类内部调用,事务没有生效;事务方法不是 public;数据库表引擎是 MyISAM 而不是 InnoDB。排查优先看表引擎和执行日志,打印 SQL 能直观看到事务是否开启。
8.3 “抄”代码的正确姿势
我没有办法否认,很多同学毕设都会参考网上现成代码。但“抄”要讲究策略,我建议是:把一个开源项目从启动到跑通,然后顺着它的代码逻辑读一遍,再根据需求做定制化修改。如果你只是把所有文件复制过来、改个数据库名就交差,答辩时老师随便问一个数据表字段为什么这么设计,你就很难答上来。
比较好的套路是:找一个结构清晰、注释完整的 Spring Boot 医疗系统开源项目(GitHub 上搜索 medical-management-system 或 hospital-management-system),克隆下来,先删掉一半的表和页面,理解主流程代码,再按自己的方案把它们补回来。整个过程像改作文一样,比从零开始容易得多,理解程度也远高于“直接复制”。
我的经验是,最值得自己花时间写的代码只有三个地方:登录鉴权过滤器、挂号扣减号源、药品出入库事务。这三处是毕设的核心亮点,也是面试官或答辩老师最容易追问的地方。自己把它们写熟、讲透,有这一两手硬功夫,就不用担心项目被质疑了。
9. 一些项目演示和扩展建议
项目做完别急着提交,可以在演示层面多下一点功夫。建议提前准备好一些模拟数据:几个科室、几十个患者、几位医生、若干药品库存,以及对应当日的挂号记录。演示时用这些数据操作,页面不会空空如也,视觉效果和专业感都会好很多。
如果还想让项目更有竞争力,可以在现有基础上增加两个扩展功能:一是医生排班日历,按时段展示每个医生一周的出诊计划,可视化程度更高;二是药品近效期预警,以列表形式展示三个月内到期的药品,提醒库房处理。这两个功能实现难度不高,但能明显提升系统的“业务完整感”。
还有一点值得考虑:项目完成后把数据库脚本、接口文档、演示截图整理成一个 README.md 或独立文档目录。这不仅是给自己留底,也是答辩现场的重要辅助材料。老师如果看到你的项目文件规范整洁,第一印象分就会不一样。
按照我自己的开发体验,医疗管理系统这个题目放在 Spring Boot 项目里,不算最难的,也绝对不简单。难在它业务链路长、角色多、状态复杂,但这恰恰是做毕设最值得投入的部分。你把这个系统的思路理清楚,很多后台管理类项目到了你手上,都可以快速套出模型来。把上面这些环节按部就班地过一遍,你会发现原来一头雾水的“医疗管理系统”,其实也就这么回事。
