
做社区卫生服务中心的信息化改造最头疼的不是没有需求而是需求散、用户杂、预算少。我最近刚整理完一套社区医院管理系统SpringBoot做后端、Vue做前端、MySQL存数据源码可以直接跑通。对做毕业设计的同学、刚入行的Java开发、以及想给小诊所或社区卫生站快速搭一套信息化系统的朋友来说这套源码是一个很省事的起点。你可以先跑起来看效果再按自己的业务需求改成想要的样子比从零开始写省下大量时间。这套系统不是那种只有几个CRUD页面的拼凑货而是把社区医院最核心的几条业务链路都串起来了患者建档、医生排班、挂号分诊、门诊开方、药房库存、收费结算、系统权限前后端分离接口规范数据库脚本齐全。我更想做的是带你把它彻底吃透每一层代码在做什么数据库为什么要这么设计部署时会遇到哪些坑后续怎么扩展成真正能上线的产品。1. 项目定位与整体架构社区医院管理系统圈内叫法很多社区卫生服务中心HIS、基层门诊信息系统、一体化诊疗管理平台。不管叫什么核心就是干三件事挂号分诊、处置记录、收费统计。市面上的商用HIS动辄几十万对社区卫生站、校医院、小型诊所完全不现实于是就有了这种基于开源技术的轻量替代方案。我整理的这套源码技术栈锁定在SpringBoot 2.x Vue 2.x MySQL 5.7/8.0前后端分离拿到IDEA里导入后端、VSCode里跑前端、本地起一个MySQL按文档初始化数据几分钟就能看到登录页属于“可直接运行”的典型项目模板。选择SpringBoot而不是SSH或SpringMVC是因为SpringBoot把配置简化到了“约定大于配置”的极限。内嵌Tomcat一个Application类就能启动不需要外置容器起步依赖管理了版本冲突MyBatis-Plus和数据源也不需要像以前那样写一堆XML。Vue这边选2.x而不是3.x不是3不好而是社区医院系统的使用者要的是稳定ElementUI生态成熟组件文档齐全做医疗表格、弹窗、表单这类密集交互场景效率极高。MySQL做关系型存储事务和ACID特性在挂号、收费、药品库存扣减这些场景里依然不可替代Redis缓存可以做性能增强但绝不是必须。这套系统不是一个简单CRUD的堆砌而是按社区医院的实际业务流划分模块。患者档案、医生排班、门诊挂号、收费结算、药房库存是主链路系统管理、权限控制、公告配置、数据统计是支撑链路。数据上以患者ID为主线把挂号记录、处方记录、收费记录、库存流水串起来形成可追溯的业务闭环。架构上采用经典的前后端分离前端通过HTTP调用后端RESTful接口后端通过Spring Security JWT做认证接口响应统一封装为Result对象这样方便前端统一处理业务异常。为什么不推荐做成单体JSP项目社区医院系统最烦的是需求频繁变化今天要新增一个体检模块明天要调整收费项目后天报表格式要改。前后端分离后前端和后端可以独立迭代、独立部署涉及页面调整的改动完全不用重启Java服务。而且这套代码如果以后要接第三方平台比如医保接口、公共卫生数据上报后端接口就是天然的数据出口这比改造传统JSP页面要轻松得多。1.1 模块划分与业务边界我先按业务模块拆一下让你在跑通之前就有全局地图。系统管理模块管用户、角色、菜单、部门采用RBAC模型避免每个接口都写死权限逻辑挂号管理模块负责号源维护、现场挂号、退号门诊模块包括接诊开方、检查检验申请、处方管理药房模块覆盖药品字典、入库、出库、库存盘点、效期预警收费模块处理挂号费、药品费、检查费以及结算退费。还有一个容易忽略但很关键的模块是统计报表。社区医院要应对上级考核、基本药物使用统计、门诊量月报这些数据如果靠人工Excel汇总月底能加三天班。源码里预置了简单的按日期聚合汇总接口给前端图表组件提供数据后续接ECharts或者大屏都很方便。每个模块都有独立的Controller、Service、Mapper层包路径清晰非常适合作为二次开发底座。1.2 技术栈选型对比如果你在纠结为什么不是SpringCloud微服务、为什么不用Vue3TS我的回答很简单给社区医院用的系统性能瓶颈从来不是高并发而是业务复杂度和维护成本。SpringCloud全家桶引入Nacos、Gateway、Feign对一台普通PC就能跑起来的项目是负资产。Vue3TS当然更现代但对于大部分护理人员、收费员来说界面的稳定性远比语言特性重要。技术选型的核心逻辑是用最成熟的技术完成最稳定的业务让接手的同事三天内能看懂让系统五年内不用大改。层面这套源码采用常见替代方案选择理由后端SpringBoot 2.x MyBatis-PlusSpringMVC JPA开发效率高SQL可控性强前端Vue 2.x ElementUIReact / Vue3生态成熟组件稳定数据库MySQL 5.7/8.0PostgreSQL / SQL Server部署普及运维资料多认证JWT Spring SecurityShiro / Session无状态适合前后端分离构建Maven / npmGradle / Yarn默认工具链排错成本低当然如果你所在团队主要用PostgreSQL或者你已经习惯了Vue3的组合式API也可以在这个基础上迁移但第一次做建议先用原技术栈跑通再谈替换。2. 后端核心模块与数据库设计后端代码的阅读顺序我建议先看启动类确认启动路径再看pom.xml了解依赖范围然后走一遍登录接口的Controller-Service-Mapper调用链最后看数据库初始化脚本。这样的顺序最快建立代码地图。很多新手拿到代码直接翻业务代码结果被各种配置绕晕其实后端的启动过程和依赖关系是最先要摸清的。2.1 数据库表结构设计脚本通常在项目根目录的sql文件夹下文件名类似community_hospital.sql。建议不要直接在Navicat里一键执行先打开脚本确认字符集和存储引擎设置。我用到的核心表大致是这样的sys_user系统用户表主键idaccount账号password密码BCrypt加密存储name真实姓名tel电话dept_id部门status状态。sys_role角色表、sys_menu菜单表、sys_user_role、sys_role_menu关联表组成RBAC权限体系。patient患者档案表idcard_no就诊卡号namesexagephonemedical_history既往史allergy过敏史。doctor_schedule排班表iddoctor_iddept_idschedule_dateperiod时段max_count号源上限。registration挂号记录表idpatient_iddoctor_idschedule_idreg_type挂号类型fee挂号费status状态。prescription处方表和prescription_item处方明细表记录开药信息、药品数量、用法用量。drug药品表drug_code药品编码name名称spec规格unit单位stock库存量price单价expiry_date效期。charge_record收费记录表idregistration_idcharge_type收费类型amount金额pay_method支付方式operator开单员。inventory_log库存流水表记录药品入库、出库、盘点操作方便追溯。关系上registration表同时关联patient和doctor_scheduleprescription表关联registrationprescription_item关联prescription和drugcharge_record关联registration。这样设计的好处是“以号找人、以方带药、以费结诊”每一笔业务数据都有明确的指路人查问题不需要翻纸质台账。2.2 登录认证与权限控制这套系统的登录接口走的已经不是简单的密码比对而是Spring Security JWT的标准流程。用户在登录页输入账号密码后端用BCryptPasswordEncoder校验密码成功后签发一个包含用户ID、角色编码、过期时间等信息的JWT字符串。前端后续请求在Authorization头带上这个Token后端通过JwtAuthenticationTokenFilter过滤器解析并放行。需要特别提醒JWT虽然无状态、方便横向扩展但注意签发有效期不能太长。社区医院收费员的电脑可能会放在公用位置如果Token一个月不失效安全风险非常大。建议把过期时间设为8到12小时用户在下班后自动重新登录。源码里通常可以在application.yml中配置过期时间我一般还会加上刷新逻辑白名单机制等但这些属于进阶改造后面再展开。RBAC权限模型的作用是让菜单和接口权限“按角色走”。管理员分配角色角色绑定菜单用户通过角色拿到菜单列表。后端接口层面方法上打PreAuthorize(hasAuthority(system:user:add))这类注解前端根据用户能访问的菜单动态渲染侧边栏。这样不会出现一个收费员误点进系统管理页面改了自己权限的尴尬。当前端路由通过addRoutes动态注册后还需要配合路由守卫判断Token和权限否则刷新页面容易直接跳回登录页。2.3 核心业务接口设计我用一个挂号的请求链路来演示接口设计。前端POST /api/registration请求体带着patientId、doctorId、scheduleDate、regType、fee等字段。后端RegistrationController接收校验号源是否已满判断时间段是否已经被约走若是现场号再查一次档案是否存在然后调用RegistrationService生成挂号记录更新doctor_schedule表的剩余号源数最后异步记录一条操作日志。响应统一为Result.success(data)data里包含挂号流水号。收费接口ChargeController类似但更强调事务性。一次收费可能包含药品费、检查费、诊查费前端会把多笔费用打包提交后端在Transactional注解下先查处方明细算总额再生成charge_record主记录和明细流水同时减少药品库存并写入inventory_log。任何一个环节抛异常事务回滚不会出现钱收了库存没减或者库存减了收费记录没落库的问题。这一点是医院系统绝对不能出bug的地方。接口路径的版本管理我也建议保留。源码中可能只有/api/v1前缀如果你要长期维护把路径改成/api/v1/registration或/api/v1/charge这样后面升级接口时不会影响已经在用的客户端。RESTful命名上GET用于查询、POST用于创建、PUT用于更新、DELETE用于删除虽然老手都懂但接手源码的人需要统一遵循避免一个项目里出现四种风格。2.4 MyBatis-Plus使用技巧MyBatis-Plus在这套源码里的角色是简化单表CRUD。实体类上标TableName注解Mapper接口继承BaseMapperService继承IService然后几乎不需要写XML就能完成基础增删改查。比较复杂的报表聚合、动态条件查询可以通过lambdaQueryWrapper或者自定义SQL完成。实际开发中多条件分页查询很容易写成无限拼接字符串的灾难代码。用MyBatis-Plus后可以这样PageRegistration page new Page(current, size); LambdaQueryWrapperRegistration wrapper Wrappers.lambdaQuery(); wrapper.eq(StringUtils.hasText(patientName), Registration::getPatientName, patientName) .eq(regDate ! null, Registration::getRegDate, regDate) .eq(regStatus ! null, Registration::getStatus, regStatus) .orderByDesc(Registration::getRegDate); registrationMapper.selectPage(page, wrapper);这段代码的要点是条件判断放在eq的第一个参数里为空时自动忽略该条件避免用户不输入任何条件时SQL变成一个全表扫描。selectPage会自动补limit和count分页结果是IPage对象能直接取出total、records。如果你需要联表查询且不想写复杂XML可以先单表查出符合条件的registration再用in查询关联表补数据两种方式各有利弊优先考虑性能。3. 前端结构与关键实现前端代码放在vue目录或frontend目录启动方式通常是npm install然后npm run dev。拿到源码后第一步不是点运行而是看package.json里的scripts搞清楚dev、build、serve这些命令分别对应什么。然后看src目录结构确认api、router、store、views、components这几个标准目录是否存在。如果目录结构混乱后期维护成本会成倍增加所以切包前一定要检查。3.1 页面结构与路由设计典型页面结构是登录页、系统管理下的用户管理/角色管理/菜单管理、患者档案管理、排班管理、挂号收费、处方管理、药房管理、统计报表。这些页面共用一套后台布局左侧菜单树、顶部用户信息、中间内容区以router-view渲染。路由在router/index.js中定义meta里会带上title、icon、roles等信息用来区分菜单显示和权限控制。对于社区医院的特殊使用场景页面交互要尽量少点几次鼠标。挂号收费页面我会建议做成“一个界面三步走”先搜索患者再选医生时段最后收费结算并打印小票。不要把挂号做成需要跳转三个页面的流程收费员一忙起来会疯。前端的表单验证用ElementUI的rules即可必填项、年龄范围、手机号格式、数量不能为负这些规则放在forms里既保证前端体验也减轻后端压力。3.2 Axios封装与请求拦截前端请求就必须封装Axios否则每个组件都要手动处理token、错误码、加载状态代码会乱到无法维护。常见的封装方式是创建src/utils/request.jsimport axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.msg || 系统异常) return Promise.reject(new Error(res.msg)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } Message.error(error.message || 请求失败) return Promise.reject(error) } ) export default service这段代码就是前后端协作的“合同”后端统一返回code、msg、data前端在响应拦截器里判断code非200直接弹错误提示。401表示Token失效或未登录拦截器统一跳登录页这样业务页面不用反复写“如果接口报错就跳登录”的判断。3.3 动态菜单与权限控制动态菜单的实现本质是把后端返回的菜单列表转换成Vue Router的路由结构。用户登录成功后调GET /api/sys/menu/current拿到当前用户可见的菜单树然后在前端递归构造路由对象用router.addRoute批量注册。注意addRoute在Vue Router 4和Vue Router 3的用法略有差异Vue2项目里通常是this.$router.addRoutes(routes)一次性添加。踩过的坑是刷新页面后动态路由丢失。因为Vue是单页应用刷新后内存中的路由重置如果只在登录页存了Token刷新后在全局守卫里发现没加载菜单就会误判为未登录。解决办法是在全局路由守卫里通过判断store中是否有用户信息来决定是否重新拉取菜单并addRoutes或者在main.js里启动时主动拉取一次。现在很多低代码后台已经把这套逻辑封装成公共模块但理解原理仍然重要。3.4 关键页面交互实现处方录入页是全系统交互密度最高的地方我的实现思路是左侧药品字典列表右侧当前处方明细点击某药品自动添加到明细并带上默认用法用量医生可以在明细中修改数量、频次、备注保存时一次性提交分页数据。这里前端要处理的一个细节是用量用法的文本字段要支持模板规则比如“每日3次每次1片”如果让医生自由输入统计报表就没法按药品维度分析。库存管理页的提醒逻辑也值得说。药品有效期预警主要靠后端提供接口前端每个半天轮询一次或者打开页面时拉一次然后展示在顶部公告栏。表格里对库存低于阈值的行标红对七天之内过期的药品显示“过期预警”标签。ElementUI的表格列渲染用template插槽在scoped-slot里根据字段值切换样式这个不需要复杂组件纯前端就能搞定。4. 运行部署与踩坑实录这部分是大家最容易卡住的地方。很多同学下载源码后第一反应是“直接启动试试”结果报错千奇百怪。我按能跑通的顺序捋一遍配合一些常见的坑。如果你按这个顺序操作基本半小时内能把系统在本地跑起来。4.1 本地环境准备必须安装的软件按优先级排JDK 1.8推荐8u201以上、MySQL 5.7或8.0、Node.js推荐14.17或16.xVue2项目Node版本太高会有冲突、Maven3.6、IDEA后端开发、VSCode或IDEA自带前端插件。逐一确认版本打开终端输入java -version、mysql --version、node -v、mvn -v版本正常后再动手。MySQL初始化是最关键的步骤。我建议先创建专用数据库而不是用root直连业务库避免权限过大。命令如下mysql -uroot -p CREATE DATABASE community_hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE community_hospital; SOURCE /path/to/community_hospital.sql;编码不能用utf8一定用utf8mb4覆盖emoji和生僻字避免药品名称或患者姓名中偶尔出现特殊字符导致乱码。SOURCE导入完成后用show tables看看表数量再select * from sys_user查一下users表确认有初始账号。4.2 后端启动步骤先用IDEA打开后端目录等待Maven下载依赖。pom.xml里的spring-boot-starter-parent版本如果是2.5.x或2.6.xJDK1.8完全兼容如果看到3.x版本就要高版本JDK17这是新手最常踩的坑。然后在application.yml中确认数据库连接、账号密码、端口和JWT密钥。配置示例server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/community_hospital?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: trueurl里的serverTimezoneAsia/Shanghai一定要配不然MySQL 8的时区警告会让你怀疑人生。启动后访问http://localhost:8080/doc.html或接口地址看到Swagger或某个健康检查接口返回正常后端就算跑通了。如果没有Swagger直接访问登录接口用POST工具测试也可以。4.3 前端启动步骤进入前端目录后依次执行npm install和npm run dev。npm install卡在node-sass上是老生常谈的问题对于Vue2项目如果package.json里写着node-sass建议先卸载换成sass或者直接升级到dart-sass。也可以直接设置淘宝镜像源然后删除node_modules和package-lock.json重新安装实测是解决依赖问题的最快路径。随后确认vue.config.js里的devServer.proxy配置。因为前端开发服务器默认8080后端也是8080需要把前端端口改成8081并通过代理转发/api请求devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这个配置的原理浏览器访问8081遇到/api前缀的请求Node服务转发给后端8080。如果不用代理你就要在axios里写全路径然后被跨域问题折磨。代理配好后本地开发完全不需要开启后端CORS。4.4 常见问题速查表我把碰到频率最高的几个问题汇总成表建议直接收藏问题现象可能原因解决操作后端启动报Unknown database数据库没创建或名称不对按脚本中CREATE DATABASE的名称创建库前端npm install报node-sass errorNode版本不兼容或源问题换用sass删除锁文件重装登录接口返回401Token过期或未传检查请求头Authorization格式清除缓存重登数据库中文乱码连接字符集配置错误url加useUnicodetruecharacterEncodingutf8跨域报CORS error未走代理或后端未配CORS优先配置proxy开发阶段不要直接开CORS页面路由空白动态路由没注册检查全局守卫拉取菜单逻辑启动时提示端口被占用8080被其他应用占用换端口比如server.port8082并同步修改proxyMyBatis-Plus报Invalid bound statementmapper.xml路径不对检查mapper-locations确认XML扫描路径4.5 生产环境部署要点本地跑通后如果要部署到服务器思路是前端执行npm run build生成dist目录放进Nginx的html目录后端用mvn package打成jarjava -jar运行。Nginx配置里把location /api/ 反向代理到后端服务同时配置gzip、静态缓存这样一个普通2核4G的服务器就能支撑一个小型社区医院一天的挂号与收费压力。启动脚本建议写成systemd服务别用nohup裸跑。开发时nohup可以生产环境进程崩溃没人管就麻烦。写一个community-hospital.service文件把ExecStart指向jar包路径Restartalways加上配合日志输出路径至少保证系统挂了能自动拉起也能用journalctl查日志。5. 二次开发与功能扩展这套源码最大的价值不只是能跑而是可以当底座继续长出业务。我建议从下面四个方向去延展。每个方向都不难但都需要对业务有一定理解做出来以后系统价值会明显提升。5.1 电子病历模块社区医院现在都要写电子病历完全可以在现有架构上加一个emr_record表字段包括patient_id、doctor_id、visit_date、complaint主诉、diagnosis诊断、doctor_advice医嘱、record_text病历正文。前端新增病历编辑页后端提供保存、查询接口。如果想把数据做得更规范可以引入结构化字段既往史、家族史、检查结果等做成JSON存储方便后续做统计分析和科研检索。电子病历的编辑采用“富文本结构化表单”混合模式最实用。医生主诉和诊断用输入框既往史用多选框加补充文本框体格检查按生理系统分组打勾快节奏场景下比大段自由文本效率高。注意病历修改留痕增加一个emr_record_log表记录每次修改时间和内容这不管在合规方面还是医患纠纷溯源方面都很重要。5.2 预约挂号与微信端很多社区卫生服务中心有公众号预约的需求。扩展方案是在现有registration表里加appoint_source字段标识是现场还是线上预约然后在后端加一个预约接口前端做一个H5页面。H5页面用Vue2也能做只是注意移动端适配和微信JS-SDK的签名逻辑如果只是展示预约成功状态用微信网页授权就够了。排班管理需要加号源池概念。线上号源和现场号源分开医生排班时定义total_num线上号量、scene_num现场号量挂号接口判断对应号源是否超出。这样才能避免线上约满后现场患者完全约不上的情况。这个改造不算重改doc_schedule表和两个查询接口加上事务锁就能满足绝大多数社区的预约需求。5.3 引入Redis提升性能如果希望挂号高峰期扛得住可以引入Redis。用户Token存在Redis实现主动失效药品字典和科室列表缓存到Redis减少数据库压力挂号号源使用Redis的incr原子操作防止并发超卖。SpringBoot整合Redis非常简单引入spring-boot-starter-data-redis在application.yml里配置连接即可。但要控制范围。Redis不是银弹像收费记录这种强一致数据就不应该走缓存必须老老实实写MySQL。项目里的缓存尽量放在“读多写少”的场景比如科室列表、药品字典、排班查询。缓存更新策略用Cache Aside模式先更新数据库再删除相关缓存下次查询重新加载。这个模式在99%的小型系统中够用不需要引入分布式锁和消息队列。5.4 数据统计可视化报表模块可以从以下维度做门诊量趋势、按科室分布、挂号费与药品费收入占比、医生工作量排行。后端写聚合SQL按日期group by前端用ECharts画折线、柱状、饼图。这个功能一旦上线整个系统的价值立刻提升一个档次因为领导们能看到“数据”而不是只看你在忙活。SQL的思路不难比如统计7天内每日挂号人数SELECT DATE(reg_date) AS day, COUNT(*) AS cnt FROM registration WHERE reg_date CURDATE() - INTERVAL 7 DAY GROUP BY DATE(reg_date) ORDER BY day;接口返回数组给前端前端用ECharts初始化图表再定时刷新。更重要的一点是统计口径要写清楚是按挂号时间统计还是按就诊时间统计是收费后算收入还是开单后算收入这些业务规则一定要在接口文档里明确否则不同科室看到的报表数字对不上麻烦就大了。6. 个人经验与后续建议这段时间整理这套系统我最深的感受是医院类系统不是一个“功能越多越好”的系统而是一个“稳定压倒一切”的系统。你用到的技术栈可以不是最新但你得把异常处理、事务边界、权限控制、数据完整性这些基本功做扎实。一次收费事务漏了库存扣减一次跨域配置错误导致医生看不了患者这些都是比“用了微服务”严重得多的故障。给拿到源码准备改造的朋友两个建议。第一先把自动测试跑起来。即使没有完善的测试体系至少把登录、挂号、收费、库存这四个核心接口的集成测试写好改动代码后跑一遍能挡住百分之八十的回归问题。第二数据库变更不能随意改表名和字段名尤其是已有业务数据时建议每张业务表都保留create_time和update_time这对排查问题有巨大帮助。最后医院系统的密码策略、操作日志、数据备份这几样东西上线前就要配好别等出事了再补。这个项目后续还可以扩展的方向很多对接检验科设备、接入医保电子凭证、做检查预约排队叫号、部署到云服务器上做成多租户SaaS。如果你只是需要一个能跑、能答辩、能改造的社区医院管理系统这套SpringBootVueMySQL的源码已经是一个足够扎实的起点。