SpringBoot+Vue+MySQL这套组合做社区医院管理系统,我太熟了。每年到毕设选题的时候,总有人来问“学长有没有合适的管理系统项目”,十个里面至少六七个最后都落在这类“XX管理系统”上——技术栈经典、业务场景常见、工作量可控,用来做毕业设计或课程设计刚好合适。Java后端配MySQL数据库是很多高校课程教的那个路线,SpringBoot负责接口和业务逻辑,Vue负责页面和交互,两个人各司其职,前后端分离的架构也正好踩中目前企业开发的主流模式。这篇博文我就以“社区医院管理系统”这个具体项目为例,从需求拆解、技术选型、数据库设计到核心业务实现、本地运行与部署,把整套东西掰开了讲清楚,顺带把答辩时老师爱问的那些问题也给你理一遍。
这个系统的业务主线其实不难理解:患者建档挂号,医生按号接诊、写病历、开处方,收费员收费,药房发药扣库存,管理员维护用户、科室、药品等基础数据,最后再用几张统计图表把数据串起来。整个流程是一个完整闭环,从业务上讲得通,从技术上又不会复杂到做不完,非常适合拿来当毕设或者课设。适合的人群也很明确:想用Java全栈完成一个像样项目的在校学生、准备转行Java开发需要简历项目的新手、以及帮学生选题或带项目的人。下面直接上干货。
1. 为什么社区医院这个选题适合做毕设/课设
1.1 业务规模不大不小,拿来练手刚刚好
很多人一开始喜欢挑“三甲医院管理系统”“智慧医疗平台”这种名字听着很高级的题目,三甲医院那个业务复杂程度,光一个医保结算就能让人研究两三个月,再算上预约挂号、分诊叫号、住院管理、检查检验、手术排期,做出来基本就是一个半成品。社区医院的定位就聪明多了:麻雀虽小,五脏俱全。它有三甲医院一样的核心流程——挂号、看诊、开药、收费、发药,但没有医保对接、没有复杂住院管理、没有大型设备对接,业务边界非常清晰。
我说得直白一点,毕设项目的核心目标不是“做出一个上市产品”,而是“证明你掌握了一整套软件开发流程”。社区医院管理系统恰好能覆盖这个目标:需求分析、数据库建模、后端接口开发、前端页面开发、前后端联调、部署演示,每个环节都能在项目里体现。我见过太多人做一个“XX管理系统”结果只有两个页面三个接口,那种项目答辩时一眼就会被看穿。社区医院这个场景天然逼迫你设计出七八张业务表、十几个接口,工作量正好卡在“能独立完成”和“有含金量”之间。
1.2 角色和功能模块怎么拆才合理
社区医院相比大医院,用户角色也比较简单。我给项目定了四种核心角色:
| 角色 | 职责范围 | 常用功能 |
|---|---|---|
| 管理员 | 维护系统基础数据、管理账号 | 用户管理、科室管理、药品管理、数据统计 |
| 医生 | 处理患者就诊业务 | 查看排班、接诊患者、写病历、开处方 |
| 收费员 | 处理收费业务 | 挂号收费、处方收费、收费记录查询 |
| 药房管理员 | 管理药品和发药 | 药品库存管理、发药登记、库存预警 |
模块划分上,我个人建议做成下面这样一个清单,照着这个清单做需求文档和功能拆分就很顺:
- 系统管理模块:登录认证、用户管理、角色管理、密码修改
- 基础档案模块:患者档案管理、科室维护、药品目录维护
- 门诊业务模块:医生排班、预约挂号、就诊接诊、病历录入、处方开立
- 收费药房模块:挂号收费、处方收费、发药出库、库存查询与预警
- 统计展示模块:挂号量统计、科室营收统计、药品消耗排行
这样做有一个很大的好处:每个模块都有明确的页面和接口归属,分工清楚,写代码的时候不容易乱。而且无论论文里的“系统功能结构图”还是答辩演示,都能直接按照这个框架来讲,逻辑感很强。
2. 技术栈选型:SpringBoot + Vue + MySQL这套组合好在哪
2.1 后端为什么用SpringBoot而不是SpringMVC或SSH
很多人纠结后端框架怎么选,我的态度很直接:现在做毕设还去手写SSH(Struts+Spring+Hibernate)属于给自己找罪受,SpringBoot几乎已经是企业级Java开发的事实标准。它最大的价值就是把Spring的繁琐配置全部自动化了,以前要写一堆XML的地方现在一个注解就搞定。内嵌Tomcat的机制也让部署变得简单,打包成jar文件,服务器上有JDK就能跑,不需要单独装Tomcat。
版本方面,我推荐SpringBoot 2.7.x搭配JDK 1.8,这是目前兼容性最稳的组合。SpringBoot 3.x虽然已经发布很久了,但它强制要求JDK17及以上,很多学校的教学环境和实验室电脑还在用JDK8,而且网上搜到的教程、遇到问题时能搜到的解决方案,绝大多数还是围绕2.x的。课设、毕设求的就是一个稳,没必要在这上面冒险。
至于持久层选型,MyBatis Plus是现在的绝对主流,比原生MyBatis少写大量XML,比Spring Data JPA更容易理解和排查问题。BaseMapper直接提供单表的增删改查,分页插件一条配置就能用,代码生成器还能帮我们把实体类、Mapper、Service全部生成出来。这些能力对于快速完成毕设简直是降维打击。我带过很多学生,用MyBatis Plus写CRUD基本就是抄模板,比手写JDBC爽太多。
2.2 前端为什么选Vue + Element UI
前端的技术选型比后端更讲究“投入产出比”。Vue作为渐进式框架,学习曲线相对平缓,一个新手花两三天理解组件、路由、状态管理这些概念,基本就能上手写管理后台。Element UI组件库又专门为后台管理系统优化过,表格、表单、弹窗、分页都是现成的,拖上去就能用。对整个项目来说,前端的开发效率直接决定你能不能在两周内把页面部分写完。
Vue2还是Vue3,我建议大多数做毕设的人选Vue2。别急着喷,这不是说Vue3不好,而是Vue2 + Element UI这套生态已经成熟到了“网上随便一搜全是案例”的程度。你前端遇到任何问题,搜一下基本都有答案。Vue3 + Element Plus当然也可以,但如果你对组合式API不熟练,边学边写可能会拖慢进度。如果你本身对Vue3比较熟,那直接用Vue3也没问题,项目本身不挑版本,业务设计都是一样的。
2.3 前后端分开部署,靠什么连起来
这个项目的前后端分离结构,需要一套清晰的数据交互方案。后端统一返回一个Result对象,结构长这样:
{ "code": 200, "message": "操作成功", "data": {} }前端所有请求都经过Axios封装,请求拦截器自动带上JWT token,响应拦截器统一处理后端返回的数据。这里有几个关键设计点:
- 登录接口成功后,后端返回一个token字符串,前端存进localStorage
- 每次请求,前端在Header里带上
Authorization: token值 - 后端用一个拦截器拦截需要登录的接口,校验token的有效性
- 如果token失效,后端返回401,前端跳到登录页重新登录
写后端接口的时候,还要给自己定一个规范:路径统一以/api开头,业务接口按模块分组。比如/api/register是挂号相关,/api/prescription是处方相关,/api/drug是药品相关。前端开发的时候通过代理把/api转发到后端的8080端口,这样本地联调完全不会遇到跨域问题。这个规范我在后面第5章还会细说。
3. 数据库设计:关键表和字段一次讲清楚
3.1 用户、医生、患者三类基础档案表
数据库设计是毕设答辩时老师一定会深挖的部分,一个个字段问你“为什么这么设计”都有可能。我按业务模块把表分成几组,咱们一组一组说。
第一组是用户和权限相关的表,至少要有sys_user(用户表)和sys_role(角色表)。sys_user的核心字段要包含:
id主键,自增username登录账号,唯一password密码,存BCrypt加密后的密文real_name真实姓名phone手机号role_code角色标识,比如ADMIN、DOCTOR、CASHIER、PHARMACISTstatus状态,0禁用,1启用create_time创建时间
这里要提醒一句,不要把数据库表和业务表混在一个表里。我们用一个role_code字段区分角色就够了,不需要真的把用户、医生、收费员拆成三张表,否则登录认证会写得非常别扭。医生表单独建一张doctor表,存医生的专业职称(主任医师、主治医师之类)、所属科室、擅长领域、个人简介,用user_id和用户表关联。这样做的核心价值是:账号信息关注“能不能登录”,业务信息关注“这个医生擅长什么”,两者职责分离。
第二组是患者档案表。patient表重点字段:
id_card身份证号,设置唯一约束gender性别birth_date出生日期phone联系电话address联系地址allergy_history过敏史,这个字段很重要,医生开药时一定会看remark备注
患者不直接关联用户表,因为患者通常是前台登记建档的,不需要登录系统。这个设计在答辩时容易被问到,提前想好怎么解释:患者是业务对象,用户是系统账号,两者维度不同。
3.2 业务核心表:排班、挂号、处方、药品、收费
第一张核心业务表是schedule(排班表),记录医生在某一天某个时段是否出诊。字段包括doctor_id、dept_id(科室)、schedule_date(日期)、time_slot(上午/下午)、total_count(总号数)、remaining_count(剩余号数)、fee(挂号费)。这里必须要有remaining_count,因为挂号业务的实质就是“消耗号源”,后续在代码里要用乐观锁的方式保证不超卖。
第二张是registration(挂号表),字段包括patient_id、doctor_id、schedule_id、reg_date、time_slot、serial_no(序号)、status(挂号状态)、fee、create_time。挂号表的状态设计是整个门诊流程的关键:
- 1 待就诊
- 2 就诊中
- 3 已完成
- 0 已取消
第三组是处方和药品。prescription(处方主表)记录doctor_id、patient_id、total_amount、status(1未收费,2已收费待发药,3已发药,4已取消);prescription_item(处方明细表)记录drug_id、quantity、price、amount、usage_notes(用法用量,比如“一日两次,一次一粒”);drug(药品表)记录name、specification(规格)、manufacturer(生产厂家)、unit(单位)、price(零售价)、stock(库存)、warning_stock(库存预警线)、category(药品分类)。
第四组是收费记录表charge_record,字段包括charge_type(收费类型:挂号费/处方费)、biz_id(关联的业务单号,挂号ID或处方ID)、amount(金额)、pay_method(支付方式:现金/微信/支付宝)、operator_id(操作人)、create_time。
数据库设计里还有一个通用原则必须强调:所有金额字段一律用decimal(10,2),严禁用float或double,二进制浮点数存金额会有精度误差,做结算时一分钱对不上就是个大事故。状态字段建议用tinyint加注释,不要用字符串,既省空间又直观。
3.3 几张表之间的关系怎么梳理
表和表之间其实就是业务关系。抽掉业务外壳来看:
- 医生和排班是一对多:一个医生有多条排班记录
- 排班和挂号是一对多:一个排班对应多个挂号的患者
- 患者和挂号是一对多:一个患者可以多次挂号
- 患者和处方是一对多:一次就诊可开一个处方
- 处方和处方明细是一对多:一个处方包含多个药品条目
- 药品和处方明细是多对多:一个药品出现在多个处方里,一个处方包含多个药品
我建议在写代码的时候使用“逻辑外键”,也就是只维护关联字段,不一定要在数据库层面建物理外键约束。MyBatis Plus的关联查询本来就是靠代码控制的,物理外键反而会给初始化数据和测试带来麻烦。但要记得给常用查询字段加索引,比如registration表的patient_id、schedule_id,prescription表的patient_id,drug表的name。数据量小的时候感觉不到索引的价值,答辩时能说出来“我在高频查询字段上建了索引”本身就是加分项。
4. 核心业务链路怎么实现:挂号、开方、发药全流程
4.1 登录认证与权限控制:JWT + 拦截器足够用
很多人在登录模块纠结要不要用Spring Security。我直接给结论:不用。Spring Security那套过滤器链和配置对于毕设来说太沉重了,理解成本高,调试也麻烦。用JWT加一个HandlerInterceptor就能实现全部需求,而且逻辑清晰,答辩时也好讲。
登录接口的核心逻辑其实只有三步:查用户、验密码、发token。代码写出来大概是这个意思:
@PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userMapper.findByUsername(dto.getUsername()); if (user == null) { return Result.error("用户名不存在"); } if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) { return Result.error("密码错误"); } if (user.getStatus() == 0) { return Result.error("账号已被禁用"); } String token = JwtUtil.createToken(user.getId(), user.getRoleCode()); return Result.success(token); }注意这里的passwordEncoder.matches(),密码不是明文比对。注册或初始化账号时用BCryptPasswordEncoder对密码加密入库,登录时把用户输入的明文和数据库里的密文做匹配。BCrypt每次加密都会生成不同的密文,所以哪怕两个用户密码一样,密文也不一样,这能防止彩虹表攻击。这个点答辩时被问到的概率极高,一定要能解释清楚。
JWT的token里只放必要信息(用户ID和角色),不放密码等敏感信息。后端拦截器在请求进入Controller之前校验token:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } // 解析出用户ID放入request,后续业务直接取 Integer userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; }前端配合在Axios请求拦截器里自动带上token:
axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config })再配合Vue Router的路由守卫,没登录就跳登录页,登录了但访问无权限页面就提示,这一套下来权限控制就算完整了。
4.2 挂号扣号:用事务和乐观锁解决并发超卖
挂号业务是整个项目里最有“技术含量”的一个点,也是答辩时最容易被追问的部分。很多人都能写出“先查询剩余号数,大于0就插入挂号记录”,但这里有个隐藏问题:如果两个人同时挂号,都查到了剩余号数是1,然后同时插入记录,结果就变成两个人都挂上了,号源成了负数。
正确的做法是让“扣减号源”这一个操作成为不可拆分的原子操作。我用一个带条件的更新语句来实现:
@Transactional public Registration createRegistration(RegisterDTO dto) { // 1. 乐观锁扣减号源 int rows = scheduleMapper.decreaseRemaining(dto.getScheduleId()); if (rows == 0) { throw new BizException("该时段号源已满,请选择其他时段"); } // 2. 生成挂号记录 Registration reg = new Registration(); reg.setPatientId(dto.getPatientId()); reg.setScheduleId(dto.getScheduleId()); reg.setDoctorId(dto.getDoctorId()); reg.setStatus(1); reg.setCreateTime(new Date()); registrationMapper.insert(reg); return reg; }对应的SQL是:
UPDATE schedule SET remaining_count = remaining_count - 1 WHERE id = #{scheduleId} AND remaining_count > 0这个语句最关键的地方在AND remaining_count > 0。数据库会锁住这一行,保证同时只有一个请求能成功把remaining_count扣到非负数。affected rows如果返回0,就说明号源已经没了,直接提示用户就行。别看这只是一行SQL,它其实就是乐观锁思想在库存业务里的落地。
为什么这个方法要加@Transactional?因为“扣号源”和“插入挂号记录”是两个操作,要么都成功,要么都失败。如果扣了号源但插入挂号记录失败,用户会莫名其妙丢一个号;如果插入了记录但没扣号源,下一个用户就会被超卖影响。事务保证了数据一致性。这个思维模式非常重要,后面所有涉及多表写入的接口都要沿用。
4.3 医生开方与药房发药:状态机让数据流转清晰
医生接诊的逻辑是:确认患者挂号记录状态从“待就诊”变为“就诊中”,然后写病历(可以选择单独建一张medical_record表,也可以简化成在挂号记录里加一个诊断字段),再选药品生成处方。提交处方时,主表和明细表在一个事务里写入:
@Transactional public void createPrescription(PrescriptionDTO dto) { // 1. 插入处方主表,状态默认为“未收费” Prescription presc = new Prescription(); presc.setPatientId(dto.getPatientId()); presc.setDoctorId(dto.getDoctorId()); presc.setTotalAmount(dto.getTotalAmount()); presc.setStatus(1); prescriptionMapper.insert(presc); // 2. 循环插入明细 for (PrescriptionItemDTO item : dto.getItems()) { PrescriptionItem detail = new PrescriptionItem(); detail.setPrescriptionId(presc.getId()); detail.setDrugId(item.getDrugId()); detail.setQuantity(item.getQuantity()); detail.setPrice(item.getPrice()); detail.setAmount(item.getPrice() * item.getQuantity()); detail.setUsageNotes(item.getUsageNotes()); prescriptionItemMapper.insert(detail); } }处方状态的设计我前面已经列过:1未收费 → 2已收费待发药 → 3已发药。为什么要加“收费后待发药”这个中间状态?因为收费和发药很可能是两个人在不同时间操作的。收费是确认“患者付了钱”,发药是确认“药已给出”,状态区分开,哪个环节出了问题都能一眼定位。
发药扣库存有个关键顺序问题:必须在收费成功之后扣库存,不能在医生开方时就扣。原因是患者可能开了药但不来缴费,如果你在开方时就扣库存,会造成大量库存被无效冻结。收费成功后再扣,患者是确定要买这批药的,库存扣除才有意义。扣库存的接口同样要用“库存充足才更新”的条件UPDATE,和挂号扣号源是同一个套路。
5. 本地环境搭建与前后端联调实操
5.1 先搭好开发环境:版本搭配决定后续是否踩坑
环境版本这件事,我见过太多人栽跟头。打开IDEA、安装MySQL、拉取代码、npm install、启动,每一步都可能有版本问题。我直接把推荐版本整理成表格:
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 | 与SpringBoot 2.x兼容最好 |
| Maven | 3.6+ | 管理后端依赖 |
| MySQL | 5.7或8.0 | 本教程针对8.0,5.7注意驱动差异 |
| SpringBoot | 2.7.x | 稳定,教程多 |
| Node.js | 14-16 | 太新的Node版本可能导致Vue CLI编译报错 |
| Vue CLI | 4.x或5.x | 对应Vue2项目 |
| IDEA | 任意较新版本 | Ultimate版自带SpringBoot插件 |
如果用的是MySQL 8.0,JDBC驱动要选com.mysql.cj.jdbc.Driver,连接URL里建议带上参数useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true。前两个参数保证中文不乱码,serverTimezone解决时差8小时问题,allowPublicKeyRetrieval=true解决MySQL 8的认证失败问题。这些不是可有可无的配置,少了真的会报错。
5.2 后端项目从导入到跑通完整步骤
拿到一份SpringBoot源码后,启动流程其实很固定:
- 用IDEA以Maven项目方式导入,等待依赖下载完成
- 在MySQL中新建数据库,比如
community_hospital,导入项目里自带的sql脚本 - 修改
application.yml里的数据库连接信息(URL、用户名、密码) - 直接运行主类
CommunityHospitalApplication,观察控制台日志 - 看到“Tomcat started on port(s): 8080”就说明启动成功
一个很容易踩的坑是数据库字符集。建库的时候记得指定字符集:
CREATE DATABASE community_hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;utf8mb4比utf8多了对emoji和更多特殊字符的支持,也能存正常的生僻字。如果已经是utf8也没什么大问题,但新建库还是建议直接用utf8mb4。
启动后建议给自己配一个接口文档工具。如果项目里已经集成了knife4j或Swagger,直接访问对应地址就能看到所有接口的文档,前端联调时对照文档开发非常高效。即便项目没集成,临时加一个也很简单,一个依赖加一个注解配置的事,值得花这个时间。
5.3 前端项目启动与联调配置
前端拿到代码后,目录下打开终端,执行:
npm install npm run servenpm install如果特别慢或者报错,大概率是镜像源的问题。国内环境下先切换镜像再装:
npm config set registry https://registry.npmmirror.com前端启动起来后默认会在http://localhost:8081或其它端口。注意,前后端端口不要撞成同一个。后端的application.yml里如果配了server.port=8080,前端开发服务器就用8081。同时,前端项目的vue.config.js里配置代理,把所有/api开头的请求转发到后端:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这个配置的价值在于:前端代码里请求路径写作/api/login,浏览器访问的是http://localhost:8081/api/login,开发服务器收到后自动转发给http://localhost:8080/api/login。因为浏览器看到的请求始终是同源的,所以根本不会触发CORS跨域问题。很多同学在本地联调时被跨域折磨到崩溃,就是因为没用代理而直接在前端写死http://localhost:8080。
5.4 部署到服务器:把项目真正“交出去”
毕设如果能现场演示部署到服务器上的版本,观感会提升很多,而且这本身也是答辩加分项。后端打包很简单:
mvn clean package -DskipTests生成的target目录下会有community-hospital-0.0.1.jar文件,在服务器上执行:
nohup java -jar community-hospital-0.0.1.jar > app.log 2>&1 &前端打包生成静态文件:
npm run builddist目录下的文件上传到服务器的Nginx静态目录,再配置一下反向代理:
server { listen 80; server_name your-domain.com; location / { root /var/www/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里try_files $uri $uri/ /index.html这行非常关键。Vue用history路由时,用户访问/register其实没有真实文件,Nginx默认会返回404,加上这行配置就能让所有路径都回退到index.html,由前端路由接管。
数据库也要迁移:本地MySQL导出sql文件,服务器上导入。这里提醒一句,生产环境不要再把数据库密码裸写在application.yml里,至少要用环境变量去引用,这是我一直强调的习惯。
6. 毕设/课设专属:问题排查清单和答辩高能问答
6.1 本地运行时最常见的几个报错与解法
我做这个项目过程中帮人排查过上百次启动问题,整理出一张高频报错速查表,遇到问题时直接对号入座:
| 报错现象 | 根本原因 | 解决方案 |
|---|---|---|
前端请求/api/login报404 | Vue代理没配或后端context-path不一致 | 核对vue.config.js代理路径,确认后端接口前缀 |
| 前端请求报CORS error | 前端直接访问了后端地址 | 用代理方式访问,或后端加CORS配置 |
| 连接数据库报Public Key Retrieval is not allowed | MySQL 8的认证机制问题 | URL加allowPublicKeyRetrieval=true |
报错Access denied for user 'root'@'localhost' | 数据库账号密码配置错误 | 核对application.yml里的username/password |
| 前端启动后页面白屏 | Vue编译报错或入口文件路径问题 | 打开浏览器控制台看报错,检查main.js |
npm install报Node版本过高错误 | Node版本与Vue CLI不兼容 | 用Node 14或16版本重装依赖 |
| 刷新页面404 | history路由没有fallback | 改用hash模式或配置Nginx try_files |
| 接口返回401 | token没传到后端或token过期 | 检查Axios拦截器,确认请求头带了Authorization |
| 时间字段差8小时 | MySQL连接时区未设置 | URL加serverTimezone=Asia/Shanghai |
| 前端调用接口后一直弹登录页 | 响应拦截器把业务错误当成未登录 | 检查拦截器判断逻辑,只看HTTP 401状态码 |
排查问题的思路也很重要:先看浏览器Network面板里请求是否发出、返回什么状态码和响应体,再看后端控制台有没有报错堆栈。大多数前后端联调问题都能在这一步定位。不要在没确认问题出在前端还是后端的情况下瞎改配置,越改越乱。
6.2 演示的时候按什么顺序讲才显得逻辑清晰
答辩演示不是把所有功能点一遍,而是要有叙事逻辑。我强烈建议你按业务闭环来演示,让评委跟着业务走而不是跟着功能菜单走。
推荐的演示顺序:
- 登录系统,展示三种不同角色进入系统后的不同菜单(证明权限控制生效)
- 管理员进入基础数据维护,展示添加患者档案、维护药品目录(证明基础功能完整)
- 医生排班,创建一个明天的出诊排班(对应schedule表)
- 模拟患者挂号,此时能看到号源减一(演示核心业务)
- 医生账号接诊,打开待就诊列表,写诊断、开处方(演示处方生成)
- 收费员账号收费,处方状态从“未收费”变为“待发药”(演示状态流转)
- 药房账号发药,药品库存减少(演示库存联动)
- 最后打开统计报表,展示刚才的业务数据出现在图表里(演示数据综合)
这样一套下来,评委能很直观地看到数据在系统里流转,每一步操作都有上一步做铺垫。演示过程中最好准备一套完整的测试数据:几个患者、几种药品、几个医生、几条挂号记录,不要让评委看到一个空荡荡的系统。我见过太多人现场临时录入“测试用户”“测试药品”,体验很差。
6.3 答辩高频问题和加分点提前准备
结合我带项目的经验,评委最爱问的问题基本围绕四个方面:为什么选这个技术、业务怎么实现的、数据库为什么这么设计、项目有什么不足。下面这些问题提前准备好回答思路:
问:为什么用SpringBoot而不用SSH?
答:SpringBoot简化配置、内嵌Tomcat、生态好、上手快,是当下企业主流选择,能证明我接触的是目前行业主流的开发方式。
问:JWT和Session有什么区别?为什么选JWT?
答:Session存储在服务器内存,需要维护会话状态;JWT是自包含的无状态token,适合前后端分离和横向扩展。JWT的缺点是无法主动失效,所以到期时间不能设置太长。
问:密码是怎么存储的?
答:BCrypt加盐哈希存储,就算数据库泄露,也不能直接拿到明文密码。
问:挂号并发超卖怎么解决?
答:用带条件UPDATE扣减号源,remaining_count > 0保证不超卖,配合事务保证扣号和插入记录的一致性。
问:如果客户反馈系统很慢,你会怎么优化?
答:先从SQL和索引入手,查询频繁的表加索引;引入Redis缓存热点数据;静态资源走Nginx缓存;接口层面做限流。这个问题能答出这几点就说明有工程思维了。
问:你觉得项目最不满意的地方是什么?
答:比如当前没有使用Redis存储验证码、药品库存扣减没有用消息队列削峰,后面可以优化。重点不是真的贬低自己,而是展示你对技术边界有认知。
除了上述问题,还可以主动加一些亮点代码:全局异常处理器统一处理业务异常、参数校验用@Valid注解、逻辑删除代替物理删除、操作日志通过AOP切面记录。这些点每加一个都能让代码“看起来”更像企业级项目,答辩时主动介绍,评委印象分直接拉高。
最后再说几句实在话
这个项目我带过不少同学完整跑通,我自己最大的体会是:代码量其实不是最值钱的部分,值钱的是你能把每个设计决策背后的“为什么”讲清楚。很多人下载源码跑通后就去答辩,结果老师问一句“你这个排班扣号怎么做的”就哑了,那等于白做。所以拿到源码之后,强烈建议自己手动把核心代码敲一遍,哪怕一边抄一边理解,也比直接复制粘贴然后装作会了强得多。
最后分享一个实用小技巧:正式答辩之前,把数据库清空重新初始化一遍,按我上面说的演示顺序完整走两遍流程,确保数据和页面状态都是干净的。中途如果发现哪一步操作报错,提前修掉,别把修复问题的过程留到答辩现场直播。答辩现场演示项目的流畅程度,很多时候比项目本身的功能复杂度更影响成绩。祝各位都能顺利过关。
按着这套思路去跑通一个SpringBoot + Vue + MySQL的社区医院管理系统,并不难,难的是过程中真的把每一张表、每一个接口、每一次状态流转琢磨明白。项目本身只是一个载体,你从里面带走的那套“怎么分析业务、怎么设计系统、怎么排查问题”的能力,才是真正值钱的东西。