作为一个常年泡在毕业设计和公司内部管理系统里的Java开发,一看到“基于SpringBoot+Vue的社区医院信息平台管理系统”这个标题,我就知道又是一位同学正在经历全栈项目的洗礼。这个题目非常典型,SpringBoot负责后端接口,Vue负责前端页面,MySQL存数据,MyBatis操作数据库,几乎是把Java Web开发的主流技术栈一网打尽。社区医院管理系统这个业务场景也选得聪明,它不像电商、秒杀系统那么复杂,但患者档案、挂号、病历、处方、药品库存、收费这些模块环环相扣,做下来能完整锻炼数据库设计和业务逻辑梳理能力,无论是用于毕业设计、课程项目,还是作为求职简历里的实战项目,都有足够的说服力。
这篇文章我不会只贴一堆源码截图,而是把整套系统的设计思路、核心表结构、后端接口实现、前端页面对接、以及本地部署启动的完整过程都拆开讲清楚。如果你正准备做类似的Java全栈项目,或者已经下了源码但跑不起来、看不懂代码结构,这篇文章就是给你准备的。文末我还会把我实际跑这个项目时踩过的坑、排查问题的方法一并整理出来,方便你少走弯路。
1. 项目概述与核心价值
1.1 这套系统到底在解决什么问题
社区医院和大型三甲医院的信息化需求不太一样。三甲医院有HIS(医院信息系统)、LIS(检验信息系统)、PACS(影像归档系统)等一整套重型软件,而社区医院、乡镇卫生院、校医院这些基层医疗机构,预算有限、人员编制少,业务流程也相对简单,需要的是一套轻量、够用、能快速上手的平台。
传统的手工管理方式,最让人头疼的就是患者档案散乱、挂号记录靠纸质登记、医生开完处方后药房和收费处信息不同步。比如一个患者上午挂了号、看完病开了药,收费处要重新问一遍药名和价格,药房还要再核对一遍库存,整个流程全靠人肉传递,既慢又容易出错。
这套基于SpringBoot+Vue的系统,核心就是把这个流程线上化:患者建档后在系统里挂好号,医生在电脑上接诊,书写电子病历、开电子处方,处方自动流转到收费处和药房,收费完成后药房直接发药并扣减库存。管理员还能在后台查看科室医生安排、统计每天的接诊量和药品消耗。整体上覆盖了一家小型医疗机构最核心的日常运营场景。
1.2 技术选型为什么是这四件套
SpringBoot、Vue、MySQL、MyBatis这个组合,放到今天依然是Java全栈入门最稳、生态最成熟的一套方案,没有之一。选它不是因为赶时髦,而是每个组件都恰好卡在合适的生态位。
- SpringBoot:解决了Spring框架配置繁琐的问题,内嵌Tomcat,一个main方法就能启动Web服务,自动配置机制让开发者不用再写大量XML。对于中小型业务系统,单体架构的SpringBoot完全够用,没必要上Spring Cloud那套微服务全家桶,只会徒增复杂度。
- Vue:作为前端渐进式框架,Vue对新手非常友好。它的响应式数据绑定和组件化开发模式,让页面逻辑变得直观。配合Element UI这类现成的组件库,不需要专业前端工程师也能写出像样的管理后台界面。
- MySQL:开源免费、性能稳定、资料丰富,是中小型系统的首选数据库。社区医院的数据量级,MySQL在合理设计下跑个十年八年都没有压力。
- MyBatis:它最大的优势是SQL由开发者自己掌控,不像JPA那样自动生成SQL。对于医院系统里经常出现的多表联查、统计报表需求,手写SQL反而更容易优化、更直观。MyBatis的SQL映射文件还能实现灵活的动态SQL拼接,一套查询支持多种筛选条件,实用性很强。
这个选型组合还考虑到了学习成本。如果你用SpringBoot + Vue做项目,遇到问题无论是搜CSDN、博客园还是掘金,基本都能找到对应的解决方案。对于毕业设计这种需要兼顾“独立完成”和“演示流畅”的场景,这套技术栈是最稳妥的。
2. 数据库设计:搭好地基才能盖高楼
2.1 核心表规划与字段设计
我在拆解很多同学做的管理系统时发现一个问题:代码能跑,但数据库设计非常随意。表之间的关系理不清,字段命名不规范,导致后面写SQL查询时痛苦不堪。这套社区医院系统的数据库设计,我建议按“基础数据-业务数据-关联数据”三层来规划。
基础数据表:系统用户表(sys_user)、科室表(dept)、医生信息表(doctor)、药品表(drug)、患者档案表(patient)。
业务数据表:挂号表(registration)、病历表(medical_record)、处方表(prescription)、处方明细表(prescription_item)、收费记录表(payment)、库存变动表(drug_stock_log)。
关联数据设计:医生挂在科室下面,通过doctor表里的dept_id关联;挂号记录同时关联患者和医生;一张处方对应多行处方明细,处方明细关联具体药品。这种设计完全符合第三范式,既避免数据冗余,也让后续统计查询有清晰的路径。
以挂号表为例,核心字段应该包括:
CREATE TABLE `registration` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', `registration_no` varchar(32) COMMENT '挂号单号', `patient_id` int COMMENT '患者ID', `doctor_id` int COMMENT '医生ID', `dept_id` int COMMENT '科室ID', `reg_date` date COMMENT '挂号日期', `time_slot` tinyint COMMENT '时段:1上午 2下午', `status` tinyint DEFAULT 0 COMMENT '状态:0待就诊 1已完成 2已取消', `fee` decimal(10,2) COMMENT '挂号费', `create_time` datetime COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='挂号表';2.2 字段设计的三个关键决策
第一个关键决策是状态字段统一用tinyint类型,不要用varchar存“待就诊”“已完成”这些中文描述。原因很简单:第一,数字比字符串占空间小、查询效率高;第二,后端代码里用0/1/2这样的常量做判断比equals字符串更安全,不会出现因为中文输入不统一导致的前端传值与后台不匹配;第三,展示层需要中文时,由前端根据状态码映射成文本即可。我见过不少项目把状态直接存成“正常”“作废”“待审核”,结果统计时各种写法都有,COUNT查询结果千奇百怪,这是很典型的坑。
第二个关键决策是所有业务表都加上逻辑删除标记,比如delete_flag字段,默认值为0。在整套系统中,医生可能会停诊、患者档案可能会注销,但历史业务数据(比如挂号记录、病历、处方)必须保留可追溯。用UPDATE ... SET delete_flag = 1代替物理删除,能防止误删数据,也让统计报表的数据链保持完整。查询时在SQL里统一加上WHERE delete_flag = 0即可。
第三个关键决策是金额字段用decimal而不是float或double。药费和挂号费涉及钱,float和double在计算机中是近似存储,可能产生0.1+0.2不等于0.3的问题,这在财务数据上是完全不能接受的。decimal(10,2)可以精确控制小数位,虽然计算性能略低于浮点数,但业务系统里这点差异可以忽略不计。
2.3 一对多与多对一的映射处理
整个系统里最核心的关联关系就是:患者是中心,围绕患者产生挂号、病历、处方;每个科室有多个医生,每个医生可以接诊多个患者。在代码层面,这种关系的处理最容易出问题。
比如查询挂号记录时,前端页面需要同时展示患者姓名、医生姓名、科室名称。如果用MyBatis的resultType直接映射到VO对象,那SQL就得写成:
SELECT r.id, r.registration_no, p.real_name AS patientName, d.real_name AS doctorName, dept.dept_name AS deptName, r.reg_date, r.time_slot, r.status, r.fee FROM registration r LEFT JOIN patient p ON r.patient_id = p.id LEFT JOIN doctor d ON r.doctor_id = d.id LEFT JOIN dept ON r.dept_id = dept.id WHERE r.delete_flag = 0如果查询条件还要加按患者姓名模糊搜索、按日期范围筛选,就要用MyBatis的动态SQL。这里有个操作要点:多条件查询建议在Mapper层接收一个查询DTO对象,利用<if>标签动态拼接where条件,比在Java代码里手动拼接SQL字符串要安全得多,也能有效避免SQL注入问题。
3. SpringBoot后端架构与实践要点
3.1 项目分层与包结构设计
后端项目建议采用经典的四层结构:Controller、Service、Mapper(DAO)、Entity(实体类)。在此基础上增加一个VO(View Object)层,专门用于前端页面展示的数据封装。不要直接把Entity实体返回给前端,因为实体里的某些字段(比如密码)不应该暴露给前端,而且多表联查的结果在单个实体类里也放不下。
以登录功能为例,完整的调用链是这样的:前端把用户名密码发给AuthController,Controller调用AuthService,Service里调用UserMapper查询数据库,拿到用户信息后校验密码,生成JWT令牌返回给前端。整个项目包结构:
com.hospital.system ├── controller # 请求入口 ├── service # 业务逻辑层,接口+实现类 ├── mapper # MyBatis数据访问层 ├── entity # 数据库实体类 ├── vo # 视图对象 ├── common # 统一返回结果、异常处理、工具类 ├── config # 配置类(拦截器、跨域等) └── interceptor # 拦截器(登录鉴权等)这种分包方式的核心价值是职责单一,互相依赖方向清晰。Controller只处理参数接收和响应封装,Service专注业务规则(比如挂号时检查该医生当天号源是否已满),Mapper只负责数据存取。任何一个环节出问题,排查范围都能快速缩小。
3.2 统一返回体与全局异常处理
前后端联调时最容易出现的尴尬场景就是:接口成功时返回一个结构,失败时返回另一个结构,前端处理起来要写大量if判断。解决这个问题的标准做法是定义统一返回体:
public class Result<T> { private Integer code; // 200成功,500失败,401未登录 private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMsg("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String msg) { Result<T> result = new Result<>(); result.setCode(500); result.setMsg(msg); return result; } }配合@RestControllerAdvice全局异常处理器,业务代码里就不用到处写try-catch了。比如药品库存不足、挂号号源已满这类业务异常,可以直接在Service里throw new BusinessException("库存不足"),由全局处理器捕获后统一封装成Result返回。这样整个接口层的行为是可控的、可预期的,前端处理逻辑也会简单很多。
3.3 MyBatis的SQL映射与分页插件
MyBatis的使用有两条路:注解方式和XML文件方式。我个人建议复杂SQL一律用XML方式。原因是复杂查询的SQL往往很长,注解方式把SQL写在Java代码里,字符串拼接、换行、动态标签混在一起,可读性极差,改一个小条件要重新编译。XML文件则可以在编辑器中获得SQL语法高亮,还能复制到Navicat里直接调试,效率完全不一样。
分页查询是后台管理系统的刚需,手写LIMIT参数既麻烦又容易忘传currentPage和pageSize。这里推荐使用PageHelper分页插件,引入依赖后配置一个拦截器即可。
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</version> </dependency>使用方式极其简单,它通过MyBatis拦截器在SQL执行前自动拼接LIMIT语句:
@Override public PageResult<RegistrationVO> queryRegistrationPage(RegistrationQueryDTO query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List<RegistrationVO> list = registrationMapper.selectRegistrationPage(query); PageInfo<RegistrationVO> pageInfo = new PageInfo<>(list); PageResult<RegistrationVO> pageResult = new PageResult<>(); pageResult.setTotal(pageInfo.getTotal()); pageResult.setList(pageInfo.getList()); return pageResult; }注意一点:PageHelper.startPage()一定要放在Mapper方法调用前一行,并且中间不要再穿插其他数据库查询操作。我见过有人在这个调用前查了一次用户信息,结果PageHelper把分页SQL拦截到了上一次查询上,导致分页不生效,数据量也不对。
3.4 登录鉴权:JWT拦截器实现
因为是前后端分离项目,纯后端管理平台可以采用JWT(JSON Web Token)做无状态登录鉴权。用户登录成功后,后端生成一个带过期时间的token返回给前端,前端存在localStorage里,每次请求时在请求头里带上Authorization: Bearer token,后端通过拦截器统一校验。
JWT拦截器的核心逻辑:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 预检请求直接放行 if ("OPTIONS".equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); } try { Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.setStatus(401); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"登录已过期,请重新登录\"}"); return false; } } }通过WebMvcConfigurer注册拦截器,并配置放行路径,比如/api/auth/login、/api/auth/register不需要登录,其余接口都要校验。这里要注意,如果前端做了跨域请求,拦截器里必须放行OPTIONS预检请求,否则前端会一直报CORS错误,但后台看日志却已经到达了拦截器,这个坑排查起来很费时间。
4. Vue前端构建与接口联调
4.1 前端项目结构与路由设计
前端采用Vue 2.7 + Element UI + Axios的组合,这是目前中文社区资料最丰富、稳定度最高的组合。项目初始化可以直接用官方脚手架vue-cli创建:
npm install -g @vue/cli vue create hospital-web项目目录规划:
src ├── api # 接口请求统一封装 ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex状态管理 ├── utils # 工具类,如request.js ├── views # 页面组件 │ ├── login.vue │ ├── dashboard.vue │ ├── patient │ ├── registration │ ├── medical │ └── ... └── App.vue4.2 Axios封装与请求拦截
登录后的每个业务接口都要带token,不可能在每次请求时手动设置header,所以必须在Axios实例的请求拦截器里统一处理:
import axios from 'axios' import { Message } from 'element-ui' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器 request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => { return Promise.reject(error) }) // 响应拦截器 request.interceptors.response.use(response => { const res = response.data if (res.code === 401) { Message.error('登录已过期,请重新登录') localStorage.removeItem('token') router.push('/login') return Promise.reject(new Error('未登录')) } if (res.code !== 200) { Message.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) }) export default request这段封装的核心价值在于把错误处理统一收口。比如医生端调用“保存病历”接口,后台返回500,前端不用在每个页面里单独写错误提示,响应拦截器自动弹Error消息,代码量大幅缩减。
4.3 动态菜单与导航守卫
社区医院系统有角色区分:管理员、医生、收费员、药房药师。不同角色登录后能看到的菜单不一样。最简单实用的方案是在登录接口返回用户角色,前端根据角色动态生成路由菜单。以管理员和医生为例:
const allMenus = [ { path: '/dashboard', title: '工作台', roles: ['admin', 'doctor', 'cashier', 'pharmacist'] }, { path: '/patient', title: '患者档案', roles: ['admin', 'doctor'] }, { path: '/registration', title: '挂号管理', roles: ['admin', 'cashier'] }, { path: '/prescription', title: '处方管理', roles: ['admin', 'doctor', 'pharmacist'] }, { path: '/drug', title: '药品管理', roles: ['admin', 'pharmacist'] }, { path: '/statistics', title: '统计报表', roles: ['admin'] }, ]路由守卫控制页面访问权限:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') return } const role = localStorage.getItem('role') const menu = allMenus.find(item => item.path === to.path) if (menu && !menu.roles.includes(role)) { next('/dashboard') return } next() })4.4 核心页面与后端接口对接
以挂号页面为例,这个页面的交互流程是:收费员选择科室,系统自动带出该科室的医生列表;选择日期,系统校验该医生当天剩余号源;选择患者,如果患者未建档则先跳转到新建档案页面。前端调后端的接口可能有5个:按科室查询医生列表、查询医生排班、查询患者列表、创建挂号、更新号源。
Element UI的el-select组件里有一个细节:将患者用el-select带远程搜索:
<el-select v-model="registrationForm.patientId" filterable remote :remote-method="searchPatient" placeholder="输入患者姓名或手机号搜索"> <el-option v-for="item in patientOptions" :key="item.id" :label="item.realName + '(' + item.phone + ')'" :value="item.id"> </el-option> </el-select>searchPatient(keyword) { if (!keyword) return patientApi.search(keyword).then(res => { this.patientOptions = res.data }) }这样设计的好处是避免一次性把几千个患者全部加载到下拉框里,数据量一大,页面就会非常卡。远程搜索既减少了后端查询压力,也提升了交互体验。
5. 从挂号到发药:核心业务闭环拆解
5.1 挂号模块:号源与状态的流转
挂号的业务规则听起来简单,但细节很多。患者只能挂未来7天内的号,医生每天上午和下午各限号30个,同一个患者同一天不能重复挂同一个医生。这些校验逻辑写在Service层。
挂号成功时要做两件事:插入一条挂号记录,同时扣减号源余量。这里涉及事务问题,一定要在Service方法上加上@Transactional,否则前一步成功、后一步失败,数据就产生了不一致。第一次开发的伙伴最容易忽略的就是这一点。
挂号的业务流程核心代码:
@Override @Transactional(rollbackFor = Exception.class) public void register(RegistrationDTO dto) { // 1. 校验患者状态 Patient patient = patientMapper.selectById(dto.getPatientId()); if (patient == null || patient.getDeleteFlag() == 1) { throw new BusinessException("患者档案不存在"); } // 2. 校验号源是否充足 int count = registrationMapper.countByDoctorAndDate(dto.getDoctorId(), dto.getRegDate(), dto.getTimeSlot()); if (count >= 30) { throw new BusinessException("该时段号源已满"); } // 3. 校验是否重复挂号 int repeat = registrationMapper.countByPatientAndDoctorAndDate(dto.getPatientId(), dto.getDoctorId(), dto.getRegDate()); if (repeat > 0) { throw new BusinessException("您已在当日挂过此医生"); } // 4. 插入挂号记录 Registration registration = new Registration(); registration.setRegistrationNo(generateRegNo()); registration.setPatientId(dto.getPatientId()); registration.setDoctorId(dto.getDoctorId()); registration.setDeptId(dto.getDeptId()); registration.setRegDate(dto.getRegDate()); registration.setTimeSlot(dto.getTimeSlot()); registration.setStatus(0); registration.setFee(new BigDecimal("10.00")); registration.setCreateTime(LocalDateTime.now()); registrationMapper.insert(registration); }5.2 医生接诊与病历处方录入
医生登录后看到的是待就诊列表。点击接诊,弹出病历编辑窗口。一本电子病历需要包含主诉、现病史、既往史、初步诊断、处理意见等字段。这里有一个设计细节:病历和挂号是一对一关系,一条挂号记录只能产生一份病历,通过registration_id关联。
医生接诊完成后开处方。处方表存的是处方头信息,包括患者ID、医生ID、开方时间、总金额;处方明细表存的是多行药品信息,包括药品ID、单价、数量、小计。一次处方至少包含一盒药,所以一个处方头下面可能有好几条明细。
这里就需要MyBatis处理一对多嵌套查询。用resultMap实现:
<resultMap id="PrescriptionDetailMap" type="PrescriptionVO"> <id property="id" column="id"/> <result property="patientName" column="patient_name"/> <result property="doctorName" column="doctor_name"/> <collection property="items" ofType="PrescriptionItemVO"> <id property="id" column="item_id"/> <result property="drugName" column="drug_name"/> <result property="quantity" column="quantity"/> <result property="price" column="price"/> </collection> </resultMap>开处方时的库存扣减一定要放在事务里同时处理。药品库存表的数量字段,在执行扣减前建议用UPDATE drug SET stock = stock - #{quantity} WHERE id = #{drugId} AND stock >= #{quantity}这种带条件更新语句,而不是先SELECT出来在Java里减完再UPDATE。这样能从数据库层面避免高并发下超卖的问题。
5.3 药房发药与库存扣减
药房药师查看已缴费的处方列表,点击发药后,对应处方的状态从“已缴费”变成“已发药”,同时扣减药品库存并写入库存变动日志。这块逻辑注意一个审计需求:每个月药房对账时需要知道每种药消耗了多少,所以每一笔库存变动都要记录原因。日志表字段可以设计为:药品ID、变动数量(正数入库、负数出库)、变动类型(1入库 2挂号退费退药 3销售出库)、关联业务单号、操作人ID、操作时间。
我建议不要直接在业务表上做无痕的增删改,所有涉及库存的操作都走统一的库存服务,配合日志表记录。后面做月统计、季度报表时,可以直接从日志表聚合数据,查起来非常舒服。
6. 本地部署与注意事项
6.1 环境准备与版本匹配
在跑这个项目之前,首先要把本地的开发环境搭好。JDK建议用1.8,这是SpringBoot 2.x系列最稳妥的版本。如果你不小心用了SpringBoot 3.x,JDK版本至少得17以上,且MyBatis、PageHelper等插件的starter版本兼容性可能会有问题,所以如果是拿现成源码,尽量先看pom.xml里定义的父工程版本再决定本地环境。
MySQL建议安装5.7或8.0均可。8.0的驱动类名是com.mysql.cj.jdbc.Driver,5.7及以下用com.mysql.jdbc.Driver。如果你用的新版数据库驱动连接时一直报时区错误,在JDBC连接串后面加上serverTimezone=Asia/Shanghai即可解决。前端环境需要Node.js,Vue CLI要求node版本不能太老,建议14以上。
6.2 完整启动步骤
第一步,把源码导入IDEA,等待Maven下载依赖完成。相关的核心依赖包括spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、pagehelper-spring-boot-starter、jjwt(JWT工具库)、lombok等。
第二步,在MySQL中创建数据库并导入初始化SQL脚本:
mysql -u root -p CREATE DATABASE hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital; SOURCE /path/to/hospital.sql;第三步,修改application.yml里数据库连接信息:
spring: datasource: url: jdbc:mysql://localhost:3306/hospital?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.hospital.system.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl第四步,启动后端SpringBoot应用,访问http://localhost:8080/api/auth/login能看到JSON返回。第五步,在前端目录执行npm install安装依赖,然后npm run serve启动开发服务器,浏览器访问http://localhost:8081。如果前端和后端端口不一致,需要在vue.config.js里配置devServer的proxy代理,将/api开头的请求转发到后端8080端口。
6.3 常见问题排查表
我把做这个项目时比较常遇到的问题整理成了一张速查表:
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 前端登录后请求接口报401 | token没有传到后端 | 检查Axios请求拦截器是否设置了Authorization头 |
| 后端接口返回401但没走登录逻辑 | 拦截器拦截了OPTIONS预检请求 | 在拦截器preHandle里对OPTIONS方法直接return true |
| MyBatis分页不生效,返回所有数据 | PageHelper.startPage与Mapper查询之间穿插了其他SQL | 确保startPage紧跟在需要分页的查询方法前 |
| 数据库查询结果中文乱码 | 数据库连接串未指定编码 | 在JDBC URL加characterEncoding=utf8 |
| 前端npm install报ESLint版本错误 | node版本过高或过低 | 删除node_modules和package-lock.json后重新安装,或升级脚手架 |
| MySQL 8.0连不上报Public Key Retrieval错误 | 驱动版本兼容问题 | 在JDBC连接串加allowPublicKeyRetrieval=true&useSSL=false |
| 接口返回的时间字段格式不对 | 未配置Jackson日期格式 | 在application.yml配置spring.jackson.date-format |
| 批量插入药品明细时报SQL语法错误 | insert语句写死单条插入 | 用MyBatis的foreach标签写批量插入 |
6.4 关于代码“能跑”和“能答辩”的差距
最后我再多说一句,这也是我带这个项目印象最深的一点。很多人的源码跑起来没问题,功能也都实现了,但一到演示或者答辩环节就露怯:问到一个查询功能为什么这么设计,支支吾吾答不上来;问到数据库表为什么加这个字段,只能说“跟着教程写的”。所以我强烈建议,拿到源码后不要急着启动,先把整体的业务流转捋一遍:患者从哪里进入系统、挂号之后数据流走到哪个表、医生保存病历后数据发生了哪些变化、药房发药时哪些表被更新了。当你能流畅说出一条完整的链路时,这个项目才算真正属于你。
从技术维度看,SpringBoot负责搭好接口骨架,MyBatis负责灵活的数据访问,Vue让操作界面清晰友好,MySQL把整个系统的数据安全地存下来,四者各司其职,组合起来就是一个完整度很高的全栈应用。如果在当前基础上想继续扩展,可以给系统加上微信小程序患者端,让患者在线预约挂号、查看病历报告;也可以引入ECharts图表,把门诊量和药品消耗做成可视化看板,这些都是在现有架构上很容易做增量迭代的方向。