1. 项目概述与技术选型思路1.1 这套社区医疗系统到底解决什么问题先聊聊这套系统的定位。社区医疗服务管理系统说白了就是给社区卫生服务中心、小型诊所、社区健康服务站这类机构用的信息化工具。这类机构和大医院不一样科室少、人员精简、经费有限但又实实在在需要管居民健康档案、门诊挂号、药品库存、收费记录这些事。以前很多社区还在用Excel表格加纸质台账档案散落、数据容易丢、查个历史记录翻半天碰上季度报表更是头疼。这套基于SpringBootVue的社区医疗服务系统正好卡在这个需求上。后端用SpringBoot把接口层和业务逻辑层做了清晰分层搭配MyBatis操作MySQL数据库前端用Vue做单页应用页面交互流畅不用每次点个按钮都刷新页面。整个项目结构清楚功能覆盖了社区医疗最核心的几条业务线居民健康档案管理、门诊挂号与就诊记录、药品入库与库存预警、收费与结算流水。如果你是正在做毕业设计或者刚入行想找个完整全栈项目练手的开发者这套代码的参考价值很高。SpringBootMyBatis的组合是Java后端面试和工作中最常见的配置Vue前端也是目前中小型管理系统的绝对主流把这一套吃透基本就掌握了企业级CRUD项目的标准写法。1.2 为什么是SpringBoot Vue MyBatis MySQL这套组合选这套技术栈不是拍脑袋决定的它背后是一套很成熟、很适合中小型管理系统的工程实践。先看后端。SpringBoot现在的地位不用多说它解决了传统SSH、SSM框架配置繁琐的问题。以前搭一个SpringMVC项目要做XML配置、web.xml配置、扫描器配置光环境就得折腾半天SpringBoot用自动配置把这些全收了开发者只需要关注业务代码本身。它内置的Tomcat也让部署变得非常轻量打一个Jar包扔服务器上就能跑这对社区医院这种没有专职运维人员的场景特别友好——卫生服务中心的老师只需要会双击启动脚本就行。再看持久层。MyBatis和SpringBoot的整合在Java领域是经典搭配。MyBatis的好处是SQL自己写对于社区医疗这种业务逻辑复杂、动态查询条件特别多的系统来说比JPA那种自动生成的SQL更可控。比如居民健康档案查询可能需要按姓名、身份证号、年龄段、慢病类型、建档时间段等任意组合筛选MyBatis的动态SQL就能优雅地处理这种场景。另外MyBatis上手曲线平缓新手学起来压力小出了问题也能直接看到SQL去排查。数据库选MySQL理由就更有意思了。很多社区医疗机构早期用的可能是Access甚至Excel迁到MySQL几乎没有门槛。MySQL开源免费、性能稳定、生态成熟对一台4核8G的普通服务器来说支撑几百个社区居民的日常医疗数据完全没压力。前端用Vue是因为它组件化的开发方式特别适合管理系统的页面组织方式。你说要做好几个页面——登录、首页看板、档案管理、挂号、收费、药品管理——如果用传统JSP或者原生JS写代码会混成一团改一个地方牵动全身。Vue把每个页面甚至每个功能块拆成独立组件数据双向绑定让表单操作在开发效率上有质的飞跃。配一个Element UI之类的组件库表格、弹窗、表单校验这些高频组件直接拿来用开发速度比后端还快。这套组合还有一个隐藏优势社区医疗服务系统的部署环境通常很朴素可能是一台Windows老电脑也可能是一台低配Linux服务器。Jar包加静态文件的部署方式是在这种环境下最稳的方案。我见过太多项目在开发环境跑得好好的一部署到客户机器就各种环境问题而这套技术栈只要把JDK装好、MySQL装好、两个包扔进去基本上就稳了。2. 功能模块全景拆解2.1 居民健康档案模块社区医疗的数据基石健康档案是社区医疗系统里最重要的模块没有之一。大医院看的是“病”社区医院管的是“人”一个人从出生到老年所有的体检记录、慢病随访、疫苗接种、就诊历史都应该沉淀在档案里。这套系统的档案模块核心字段一般包括居民姓名、性别、出生日期、身份证号、联系电话、家庭住址、血型、过敏史、既往病史、家族病史、是否慢病患者、建档医生、建档时间等。在实体设计上对于居民档案表比较合理的字段类型选择是身份证号要建唯一索引因为它是居民的唯一业务标识出生日期用date类型过敏史和既往病史这类可能有多个值的字段可以存逗号分隔的字符串或者单独建关联表看后续是否需要按病种统计。如果是做毕业设计建议用单独的居民病症关联表因为后面做慢病统计分析时一个居民可能有高血压和糖尿病两种慢病单字段存不了。这个模块的难点在“查询”。实际业务中医生经常说“帮我查一下这个小区65岁以上高血压的老人有哪些”这就是典型的多条件组合查询。在后端用MyBatis的 标签加 标签写动态SQL即可实现前端用表单绑定查询参数把居民姓名、年龄段、慢病类型、建档日期范围等条件拼进去。曾经在给一个社区做演示时对方医生要求“能按楼栋查”——这其实就是给档案表加一个“所属网格/楼栋”字段然后加入查询条件列表的事。做社区医疗系统字段设计时一定要留出扩展位真实业务的需求总是比教科书丰富。2.2 门诊挂号与就诊记录打通医生工作台有了档案基础再看日常门诊流程。社区居民来看病第一步是挂号——初诊病人先建档案再挂号复诊病人直接刷身份证或者报手机号调出档案然后选择科室、医生、号别普通号/专家号生成挂号记录。系统需要维护一个号别和挂号费用的配置表不同医生、不同号别的费用不同。就诊环节是整个系统的核心使用场景。医生登录自己的工作台能看到当前待就诊的排队列表点开一个病人左侧显示健康档案摘要右侧是本次就诊的记录表单主诉、现病史、既往史、体格检查、初步诊断、处理意见、是否需要转诊。症状和诊断建议用文本域加关键词辅助输入因为社区医生的书写习惯差异很大过度结构化反而降低录入效率。处方开药和检查检验单也是这个模块的子功能。处方关联药品字典表医生选择药品系统自动带出规格、单位、库存数量录入用量用法。这里有个很关键的联动逻辑提交处方时系统要自动扣减药品库存如果库存不足需要给出提示。这个操作涉及事务问题后面在实操部分详细讲。就诊结束后的收尾动作是收费。社区医疗的收费比较简单挂号费、药品费、检查费费用明细逐条展示支持现金或移动支付登记。收费完成之后这笔就诊记录就闭环了。整套流程走下来社区医生每天的操作都可以在系统里留痕月底统计接诊量、药品消耗量也都有了数据基础。2.3 药品库存管理社区药房的进销存逻辑药品管理是社区医疗里最容易踩坑的模块因为它的业务逻辑涉及“进、销、存”三条线对接的是真实库存。药品字典表包含药品名称、通用名、规格、生产厂家、批准文号、剂型、单位、零售价药品入库单记录每次采购的批次、数量、生产日期、有效期药品库存表维护每种药品当前实物库存与可用库存。这里要特别注意“批次”的概念。医院药房的药品是严格按批次管理的同一种药品不同批次的进货价都可能不同近效期药品要先出库效期管理原则。不过在社区医疗系统里为了简化操作通常只做按药品维度的总库存管理加一个最近效期提醒字段。如果这种设计放到大型三甲医院会被药学部老师直接打回因为必须做到批次级追踪。做毕设的话按“药品ID批号”维度管理库存是更有含金量的做法能在答辩时体现出对业务的理解深度。药品模块还有一个高频功能是库存预警。设定每个药品的最低库存阈值比如50盒当前库存低于阈值时系统在药房工作台醒目位置提示补货并生成采购建议单。这个功能虽然只是简单的大小比较但对于社区药房来说极其实用。曾经就有一个实际案例社区药房阿姨因为降压药断供被居民当面数落过装上这个预警之后药房会提前一周备货阿姨满意度直线上升。采购入库的流程也值得细说采购员填入库单选择药品、填数量、单价、批号、生产日期、有效期提交后库存自动增加同时生成一条入库流水。这里要设计好权限——只有药剂科角色能执行入库操作医生角色只能查看库存而不能修改避免误操作把库存搞乱。2.4 系统管理与权限设计管理系统永远绕不开用户、角色、权限这三件套。这套系统的用户表设计不复杂字段就这些用户名、密码加密存储、真实姓名、角色ID、所属科室、手机号、状态。角色表先区分几个固定角色系统管理员、医生、护士、药房人员、收费员每个角色绑定一组菜单权限和操作权限。权限控制采用的方案是“用户—角色—菜单”这样比较经典的RBAC模型。前端根据登录用户的角色动态渲染菜单后端通过拦截器校验每个请求的权限码。这种设计的好处是很直观新增一个角色只需要在角色管理页面勾选菜单树就行不需要改代码。有一个细节很多项目会忽略——密码存储。明文密码在真实项目中是安全事故级别的错误系统里保存密码必须用BCrypt这类不可逆算法加密。MD5加盐也行但BCrypt已经是行业默认标准Spring Security里直接就有对应实现社区医疗系统可以借鉴这个方案。如果是毕业设计在论文里写清楚“采用BCrypt进行密码加密存储防止数据泄露导致用户密码暴露”这个点是加分项。3. 数据库设计详解3.1 核心表结构与字段设计思路数据库是整个系统的地基。我见过太多项目死在建表随意上后面业务一扩展就要改表结构牵一发而动全身。这套社区医疗服务系统的表结构我从头梳理一遍核心的表和字段方便你对照建库。先给出一个数据库级别的表清单系统用户表、角色表、菜单权限表、居民健康档案表、门诊挂号表、就诊记录表、诊断字典表、药品分类表、药品字典表、药品库存表、药品入库单表、药品出库单表、收费项目表、收费流水表、系统日志表、通知公告表。用户表的核心字段id、username唯一、passwordBCrypt密文、real_name、role_type枚举admin/doctor/nurse/pharmacist/cashier、department_id、status1启用0禁用、create_time。department虽然只有一张表但建议单独建科室表社区中心的科室数量少但架不住领导今天想加个中医科明天加个康复科写死在代码里就是给自己挖坑。居民健康档案表是数据量最大的表建议的字段如下字段名类型说明idbigint主键自增id_cardvarchar(18)身份证号唯一索引namevarchar(50)姓名gendertinyint1男2女birthdaydate出生日期phonevarchar(20)联系电话addressvarchar(200)家庭住址community_idbigint所属社区网格allergy_historyvarchar(500)过敏史past_historyvarchar(1000)既往病史chronic_diseasevarchar(200)慢病标签逗号分隔blood_typevarchar(10)血型doctor_idbigint建档医生create_timedatetime建档时间这里身份证号建唯一索引是因为档案合并时需要用身份证作为匹配依据。曾经有个社区刚上线时居民信息录入了不少重复档案就是因为建档环节没做身份证重复校验后来加了唯一索引和前端查重提示才解决。门诊挂号表关联了患者、医生、科室三类主体字段有id、patient_id关联档案表、department_id、doctor_id、clinic_date就诊日期、clinic_time上下午/具体时段、registration_fee挂号费、status0待就诊 1就诊中 2已完成 3已取消、create_time。这个表的查询频率极高必须建联合索引(clinic_date, department_id, status)否则数据量过万之后统计当日各科室挂号量会明显变慢。药品库存表不是简简单单一个药品一个数量它需要有锁定库存和可用库存的概念。字段设计id、drug_id、batch_no批号、purchase_price进货价、quantity当前实物库存、locked_quantity锁定库存、expiry_date有效期、supplier_id、create_time。提交处方扣减库存的逻辑先去锁定等收费完成再从锁定转扣减如果患者挂号后没缴费药品不会真正被扣掉。这套逻辑在真实药房系统里是基本功。3.2 表关系与常见设计坑点理一下表关系画不出图也给你用文字描述清楚。居民健康档案是中心表它和挂号表是一对多关系一个居民可以多次挂号挂号表和就诊记录是一对一关系就诊记录和处方明细是一对多关系药品字典表和库存表是一对多关系入库单、出库单和药品字典表分别是多对一关系。这里面最常见的坑点是“逻辑删除”和“物理删除”的选择。管理系统里很多删除操作不应该真的把数据删掉比如居民档案如果医院把一条档案物理删除了后续卫健委检查时发现人少了数据就对不上账。更合理的做法是加一个delete_flag字段0正常、1已删除查询时默认过滤掉。体检过期的药品记录也建议留底以后出现医疗纠纷时能追溯。我在自己的项目里统一规定业务主数据一律逻辑删除只有流水表这种允许物理清空。另外表名和字段名的命名规范也必须自始至终保持一致。要么全部下划线风格user_name要么全部驼峰userName建议采用下划线风格MySQL和Java之间通过MyBatis的mapUnderscoreToCamelCase开关自动转换。很多新手项目做了一半发现一半表用驼峰一半用下划线Mapper文件里不得不写一堆繁琐的字段映射维护成本陡增。4. 后端核心实现与关键代码解析4.1 SpringBoot项目结构与分层设计拿到这套源码先从项目结构看你就能看出来作者是讲究人。标准的controller、service、mapper、entity四层结构controller只做参数接收和结果封装service写业务逻辑mapper接口配XML文件操作数据库。这种分层做得好后面改需求时不需要在controller里翻业务代码测试也能写得更干净。后端包结构大概是这样的com.health.community ├── controller # 控制层 │ ├── PatientController.java │ ├── RegistrationController.java │ └── DrugController.java ├── service # 业务层接口 │ ├── PatientService.java │ └── impl/ │ └── PatientServiceImpl.java ├── mapper # MyBatis Mapper接口 │ ├── PatientMapper.java │ └── PatientMapper.xml ├── entity # 实体类 ├── dto # 入参/出参对象 ├── common # 统一返回结果、异常处理 ├── config # 配置类 └── utils # 工具类日期、字符串等统一返回结果的封装是这套代码里值得学习的点。所有的Controller接口返回统一的Result对象里面包含code、message、data三个字段前端axios响应拦截器只需要对code做一次判断成功走成功逻辑失败弹错误提示。做过前后端分离项目的人都懂这是减少联调沟通成本的关键手段。如果没有这套封装每个接口返回的格式都靠程序员自由发挥前端对接时绝对是一场灾难。4.2 登录鉴权模块的实现方式登录鉴权是管理系统的命门社区医疗系统涉及居民隐私数据对鉴权的要求不能含糊。项目采用的方式是登录接口校验用户名密码成功后后端生成一个Token可以用JWT也可以用UUID存Redis前端把Token存在localStorage里每次请求在axios拦截器中带上Authorization请求头后端通过拦截器统一校验Token合法性并在ThreadLocal中存放当前登录用户信息方便后续业务代码取用。具体流程拆解如下用户输入用户名、密码向后端 /login 接口发起请求后端从数据库查到用户记录用BCrypt校验密码是否匹配校验通过后查询用户的角色、菜单权限列表生成Token并返回前端用户进入主界面后前端根据角色渲染对应菜单此后每个请求都带Token后端拦截器校验通过后记录当前操作人ID退出登录时后端将Token置为失效前端同时清空本地存储。有一个实操经验要分享Token的有效期一定要设置毕业设计可以设置为24小时真实项目建议2小时。有些新手写的系统Token永久有效用户在医院公用电脑上登录后忘记退出保安大哥拿着管理员账号就能操作整个系统这在医疗行业是多大的安全隐患想象一下就知道了。4.3 挂号与扣库存的事务处理门诊挂号流程涉及一个经典的事务边界问题。来看具体场景患者挂号时医生同时开出药品处方系统需要完成两步操作——写入挂号记录和就诊记录扣减药品库存。这两步必须在一个数据库事务里完成要么全部成功要么全部失败。在Spring中最简单的做法是在Service方法上加上Transactional注解。但是很多新手在这里犯了一个典型错误在Controller层直接调多个Service方法每个Service方法内部各自有事务结果一个成功一个失败数据就出现了不一致。正确的做法是把业务链路完整封装在一个Service方法里由这个方法承担事务边界。我们用开处方入库这个业务来解释Transactional(rollbackFor Exception.class) public void createPrescription(PrescriptionDTO dto) { // 1. 校验患者档案是否存在且有效 Patient patient patientMapper.selectByIdForUpdate(dto.getPatientId()); if (patient null || patient.getDeleteFlag() 1) { throw new BusinessException(患者档案不存在或已删除); } // 2. 写入处方主单 Prescription prescription new Prescription(); prescription.setPatientId(dto.getPatientId()); prescription.setDoctorId(currentUserId()); prescription.setTotalAmount(calculateTotalAmount(dto.getItems())); prescriptionMapper.insert(prescription); // 3. 逐条写入处方明细并扣减库存 for (PrescriptionItemDTO item : dto.getItems()) { PrescriptionDetail detail new PrescriptionDetail(); detail.setPrescriptionId(prescription.getId()); detail.setDrugId(item.getDrugId()); detail.setQuantity(item.getQuantity()); detail.setUnitPrice(item.getUnitPrice()); prescriptionDetailMapper.insert(detail); // 扣减库存使用乐观锁/行锁防止超卖 int rows drugStockMapper.deductStock(item.getDrugId(), item.getQuantity()); if (rows 0) { throw new BusinessException(药品库存不足 item.getDrugName()); } } }扣减库存的SQL是其中的技术关键点一定不要写成先查库存再更新库存这样两步操作因为并发场景下会出现两个请求同时查到库存100各自扣了10最终库存变成90而不是80。正确的做法是用一条UPDATE语句同步完成判断和扣减UPDATE drug_stock SET quantity quantity - #{quantity} WHERE drug_id #{drugId} AND quantity #{quantity}然后靠受影响的行数来判断是否扣减成功这种方式是典型的乐观锁思想在并发量不高的社区场景里已经完全够用。如果并发很高可以用悲观锁select for update但社区医院一天也就一两百个患者乐观锁的性能和简洁性更好。4.4 MyBatis动态SQL实现多条件组合查询社区医疗里查档案、查就诊记录、查收费流水都是典型的动态查询场景。患者来复诊说“我上次来看病是什么时候开的降压药”护士在系统里勾几个条件就能查出来。用MyBatis的if标签和where标签可以优雅地对这种场景进行拼装SQLselect idpageQuery resultTypecom.health.community.entity.Patient SELECT p.* FROM patient p where if testdto.name ! null and dto.name ! AND p.name LIKE CONCAT(%, #{dto.name}, %) /if if testdto.idCard ! null and dto.idCard ! AND p.id_card #{dto.idCard} /if if testdto.chronicDisease ! null and dto.chronicDisease ! AND FIND_IN_SET(#{dto.chronicDisease}, p.chronic_disease) /if if testdto.minBirthday ! null AND p.birthday gt; #{dto.minBirthday} /if if testdto.maxBirthday ! null AND p.birthday lt; #{dto.maxBirthday} /if /where ORDER BY p.create_time DESC /select这里有个常用技能点模糊查询不要用LIKE %keyword%全文扫描。如果业务里确实需要按姓名模糊查找数据量在几万条以下时完全没问题但如果要做高性能优化应该用全文索引或者Elasticsearch。社区医疗的数据量远没到需要上ES的地步MySQL的普通索引配合合理的查询条件就足够快。另外注意一个细节日期查询注意边界。按建档日期范围查询很多人直接写BETWEEN startDate AND endDate当天最后23:59之前的记录都会漏掉因为endDate被传进来时往往只是2025-01-01这种日期字符串而数据库时间是2025-01-01 00:00:00。正确写法是endDate加一天或者用 #{endDatePlusOne}。这个bug我在实际项目中见过太多次一定要提醒你留意。5. 前端Vue实现与交互细节5.1 Vue项目结构与核心依赖前端部分的开发用Vue 2还是Vue 3取决于源码作者的版本选择。现在新项目基本上都是Vue 3 Vite Element Plus的组合。如果是Vue 2的项目装Element UI就够了Vue 3必须配Element Plus注意不要装错版本不然组件渲染不出来卡在环境问题上浪费时间。整个前端项目的核心目录结构src ├── api # 接口请求封装 │ ├── login.js │ ├── patient.js │ └── drug.js ├── router # 路由配置 ├── store # Pinia或Vuex状态管理 ├── views # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── patient/PatientList.vue │ ├── registration/RegistrationList.vue │ └── drug/DrugStock.vue ├── components # 公共组件 ├── utils # axios封装、工具函数 └── App.vueApi目录的封装很关键。系统里每个页面都需要请求接口如果每个页面直接写axios那么接口地址分散、错误处理逻辑重复、Token管理缺失。统一的封装方式是在utils/request.js里创建axios实例设置baseURL、请求拦截器自动带Token、响应拦截器统一处理后端Result对象。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] Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code 0) { return res.data } else if (res.code 401) { // 登录过期跳转登录页 localStorage.removeItem(token) window.location.href /login return Promise.reject(new Error(登录已过期)) } else { return Promise.reject(new Error(res.message)) } }, error { return Promise.reject(error) } ) export default request这套拦截器封装好了之后业务页面里调用接口只需要写import request from /utils/request export function getPatientPage(data) { return request.get(/patient/page, { params: data }) }页面里只需要关注业务逻辑不需要重复处理错误和Token逻辑代码清爽得多。5.2 居民档案列表页的“搜索分页弹窗”黄金模板管理系统页面有一个出现频率极高的组合顶部搜索栏、中间表格、右下角分页器、右上角新增按钮、表格行内操作按钮。这套系统里居民档案页面就是这个黄金模板的标准范例。拆开看这个页面的Vue组件逻辑。data里维护queryParams查询参数对象包含name、idCard、chronicDisease、currentPage、pageSize这些字段点搜索按钮时把currentPage重置为1然后调用getPatientPage接口刷新数据表格columns直接绑定居民对象的关键展示字段操作列放“编辑”“详情”“删除”三个按钮。新增和编辑共用一个弹窗表单组件通过dialogVisible控制显示编辑时把行数据缓存在editForm里回显。这里有一个很小但很影响体验的细节新增和编辑两个场景下表单提交后要区分是走POST还是PUT请求。不少新手在这里用一个isEdit标记去判断其实更简洁的方法是看editForm.id是否存在有id就是编辑没id就是新增。分页组件这里推荐使用el-pagination把total绑定后端的total字段。很多接口返回的total是查询结果总数而不是当前页数量前端分页条要显示的是总条数而不是本页条数这个千万别搞混。5.3 医生工作台页面与就诊流程的前端交互医生工作台是系统里交互最复杂的页面它是角色化页面的典型代表。页面布局一般分三栏左侧是待就诊队列中间是患者档案和本次就诊表单右侧是常用诊断、常用处方快捷按钮。待就诊队列使用el-tabs选项卡区分“待就诊”“已就诊”“全部”三种状态点击队列中的某个患者中间区域的档案摘要和就诊表单随之联动更新。就诊表单最关键的是处方明细部分。医生选药品前端把选中的药品push进一个medicineList数组表格展示药品名称、规格、单价、数量、小计底部实时汇总总金额。这个交互逻辑用Vue的双向绑定非常好实现只要维护好medicineList数组当数量字段更改时通过computed属性自动重算合计金额。提交处方时前端需要把medicineList组装成后端DTO需要的结构即将数组传递给后端接口。前端有一个容易忽略的细节请求Loading状态。提交按钮点击后要立刻置为disabled并显示Loading防止医生连点提交产生重复订单。真实案例有开发者在社区医疗系统上线时没加这个防重复提交一个医生手速太快连点三次提交患者莫名其妙名下多出三份处方后来药房对账时发现了这个数据异常费了很大力气才修正。5.4 前端路由懒加载与菜单权限控制前端权限控制的核心思路是登录接口返回当前用户的角色和可访问的菜单列表前端根据这个列表动态生成路由和侧边栏菜单。实现方式有两种一种是在路由配置里给每个route添加meta.roles字段通过路由守卫判断当前用户角色是否在允许列表里另一种是根据菜单树动态addRoute添加路由。对社区医疗这种固定角色少、菜单层级浅的系统用第一种方式完全够用且更直观。在router.beforeEach守卫里写判断逻辑router.beforeEach((to, from, next) { const token localStorage.getItem(token) const userInfo JSON.parse(localStorage.getItem(userInfo) || {}) if (!token) { if (to.path /login) { next() } else { next(/login) } return } if (to.path /login) { next(/) return } // 判断角色访问权限 const allowedRoles to.meta.roles if (allowedRoles !allowedRoles.includes(userInfo.roleType)) { next(/403) return } next() })前端做一层控制只是提升用户体验真正的安全防线在后端拦截器。有些网络安全意识薄弱的人以为“前端不显示这个菜单用户就没法访问这个页面”而不在前后端同时设置权限关卡这是很危险的想法——用户完全可以打开浏览器开发者工具手动修改localStorage里的角色信息或者直接拼一个接口URL去调后端接口。所以后端对每个敏感接口都要做权限校验双关口保险这一点务必死在代码里。6. 本地跑通全流程实操教程6.1 环境准备清单在动手之前先检查自己机器上的环境省得跑到一半发现某个插件缺了。整理一份清单JDK推荐1.8或11SpringBoot 2.x配JDK8最稳SpringBoot 3.x必须要JDK17以上先看清源码里的版本Maven3.6以上用来编译打包后端项目IDEA里内置的Maven也行MySQL5.7或8.0均可8.0是当前主流注意连接驱动版本要和数据库匹配Node.js建议14以上版本Vue项目需要npm或yarn安装依赖IDE后端推荐IDEA前端用VS Code或者直接全用IDEA新版也支持Vue插件6.2 后端启动的具体步骤先把后端跑起来。步骤拆细一点跟着操作就行第一步用IDEA打开后端源码目录等待Maven自动导入依赖。注意这里经常发生的问题仓库里有一些依赖没有下载成功IDEA左下角报红。解决办法是打开Maven面板点击刷新按钮强制重新拉取或者到本地Maven仓库的目录下删除未下载完的半截文件以.lastUpdated结尾再重新导入。第二步创建数据库。在MySQL客户端执行项目里的init.sql脚本或者直接在Navicat里新建数据库字符集选utf8mb4排序规则utf8mb4_general_ci然后运行SQL文件导入表结构和初始数据。初始数据里一般自带管理员账号通常是admin/admin123之类先确认一下脚本里有没有免得建完了没账号登录。第三步修改配置文件。SpringBoot的配置文件通常在src/main/resources/application.yml里核心要改的是MySQL连接信息spring: datasource: url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver这里有一个高频报错修改密码后启动报Access denied九成是application.yml里密码写错或没改。还有一个报错是Java连接MySQL 8.x时驱动类名写成了com.mysql.jdbc.Driver——这是MySQL 5.x的驱动类8.x必须用com.mysql.cj.jdbc.Driver别把新旧驱动类搞错。第四步启动项目。运行启动类Application里的main方法等几秒钟看到SpringBoot启动成功的日志再测试访问 http://localhost:8080 如果配置了context-path还要加上对应前缀。一个稳妥的自检方法是直接访问登录接口比如用curl发一个POST请求或者浏览器访问,查看是否返回JSON数据。6.3 前端启动与联调后端跑起来之后再启动Vue前端。第一步在VSCode或IDEA里打开前端目录安装依赖npm install这里有一个容易卡住的点npm install执行到一半卡死。大多是网络问题可以切换国内镜像源解决npm config set registry https://registry.npmmirror.com npm install如果还是有问题可以删除node_modules目录和package-lock.json然后重新install。前端环境问题百分之八十可以用这一招解决。第二步检查接口代理配置。Vue项目里vite.config.jsVue 3或vue.config.jsVue 2配置了开发服务器代理server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }前端代码里的请求地址都写成相对路径请求/api开头的地址由开发服务器代理转发到后端8080端口。如果没配这个代理前端请求直接请求8080端口就会出现跨域问题浏览器控制台报CORS错误。第三步运行npm run dev启动开发服务器浏览器访问 http://localhost:3000 就能看到登录页。用配置里的管理员账号登录进入系统验收以下功能链路新建居民档案、挂号、录就诊记录、开药品处方、收费、药房看到库存扣减、生成收费流水。这条链路完整跑通整个项目就算真正在本地跑起来了。6.4 部署到服务器的最小方案如果是要给真实的社区卫生服务中心用或者毕业答辩时现场演示建议直接把前后端打两个包部署省心又稳。后端的部署方式是Maven打包成Jar文件mvn clean package -DskipTests打包产物在target目录下文件名大概是xxx-0.0.1-SNAPSHOT.jar。传输到服务器上后台运行nohup java -jar community-health-0.0.1-SNAPSHOT.jar app.log 21 然后到前端目录执行npm run build生成dist静态目录用Nginx托管配置一个反向代理把/api请求转发到SpringBoot服务server { listen 80; server_name your_domain_or_ip; location / { root /opt/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有一个前端路由的坑必须提醒Vue Router启用history模式后刷新页面会出现404所以配置了try_files把请求都重定向到index.html。如果用hash模式路由地址带#号则没这个问题但为了美观通常在部署时用history模式加Nginx配置解决。7. 常见问题与排查技巧实录7.1 跨域报错与Token失效问题前端接口报CORS错误是最常见的联调问题。现象是浏览器控制台提示Access to XMLHttpRequest... has been blocked by CORS policy。排查思路分为两路开发环境优先检查代理是否配置生产环境优先检查Nginx代理配置。这两层代理只要有一层没配好就会出现这个错误。还有一个关联问题登录成功后页面刷新又跳回登录页。排查步骤按顺序来先看localStorage里token是否被存下来了再看axios拦截器是否在每次请求带上了Token最后看后端拦截器校验逻辑是不是放行了登录接口和静态资源。经常有新手把登录接口也拦截了导致用户永远无法登录。7.2 数据库乱码与时间差8小时问题中文乱码问题在MySQL项目中很经典分两步排查。第一步建表时字符集是否用了utf8mb4——如果用了utf8一些特殊字符比如emoji会报错或变成问号。第二步连接串里的characterEncodingutf8 ——如果缺了这参数Java写入的字符串会按系统默认编码存储读取时又按另一个编码解析乱码在所难免而且这种乱码很诡异地只出现在某些字段里。时间差8小时的问题也很容易遇到。数据库里存的时间正常但Java查询出来少了8小时或者反过来。原因是Java和MySQL之间默认时区不一致。连接串里加serverTimezoneAsia/Shanghai就能解决。另外SpringBoot的jackson序列化时间时也要注意设置时区否则传给前端的时间可能字符串或格式不对spring.jackson.time-zoneGMT8 spring.jackson.date-formatyyyy-MM-dd HH:mm:ss7.3 MyBatis常见坑字段映射失败与动态SQL语法错误MyBatis写多了总结出几个高频报错。第一个是查询结果返回null但数据库里明明有数据。这一步先看实体类字段名和表字段能不能对应上。如果配置了mapUnderscoreToCamelCaseuser_name字段能自动映射到userName没配置的话必须手动写resultMap映射关系。排查时在日志里看MyBatis打印的SQL和查询结果通常能立刻定位。第二个是动态SQL标签写错导致的语法异常。比如if标签里test属性写的是java判断的等号一个等号但MyBatis的test里应该用双等号。还有人在where标签里多余地写了WHERE关键字导致生成的SQL变成WHERE WHERE。这类问题靠看控制台打印的错误SQL日志就能看出来问题所在重要的还是SQL日志功能必须打开。7.4 前端接口请求返回404/405的排查路径前端调接口报404先分清楚是路由404还是接口404。如果是net::ERR_ABORTED 404且为静态资源路径多半是路由配置问题如果明确了接口请求路径报404问题大概率出在SpringBoot项目中的Controller上RequestMapping路径写错或者上下文路径配置不一致。405 Method Not Allowed则更有针对性基本就是请求方法不匹配前端用POST调了后端的GET接口或者反过来。遇到这类问题打开浏览器开发者工具的Network面板复制请求的Method和URL对一下后端的Mapping注解很快就能定位。我曾经在排查一个405问题时花了半小时最后发现是配置文件中的大小写问题前端请求URL是/PatientLogin后端接口是/patientLogin虽然Linux服务器上路径是区分大小写的但开发环境Windows不区分导致联调没暴露这个坑。这种大小写问题在真实项目里偶有发生值得留意。8. 从项目源码中还能学到什么8.1 毕业设计如何基于此项目挖掘亮点如果你拿这套系统做毕业设计不要满足于“能跑通”要在论文和答辩中讲出两个维度的亮点——业务理解深度和工程实践规范。业务深度方向可以围绕“慢病管理”做延伸。社区医疗的定位已经从治疗转向健康管理高血压、糖尿病这类慢病的长期随访是社区医院的核心工作。这套系统的档案表里有慢病标签字段但功能上没有专门的慢病随访模块。你可以加一张慢病随访记录表记录随访日期、血压血糖数值、用药依从性、生活方式干预建议再做一个月度随访计划提醒功能。论文里的业务章节能写出真正的业务理解。工程实践方向可以引入单元测试和接口文档。在Service层给核心业务写JUnit测试用例比如处方提交后的库存扣减测试、重复提交测试、金额计算测试。再用Swagger生成接口文档展示系统的接口规范。这两点在毕业设计评委眼中比单纯“功能都实现了”的含金量高得多。8.2 如何扩展成可商用的社区医疗服务平台这套系统离真正的商用还有一些距离但它的骨架是可用的。商用化要做的事情集中在几个方面。第一是数据安全加固。社区的居民健康数据属于个人敏感信息商用部署至少要支持HTTPS协议、数据库字段加密存储、用户操作日志审计。有些信息需要符合数据安全相关规范要求这些在论文的“不足与展望”章节也可以体现。第二是引入在线支付。社区医疗很多场景支持移动支付现阶段收费模块可能还停留在人工登记商用对接微信支付或支付宝的付款码支付并不复杂。考虑医疗支付场景的特殊性还需要对接支付对账单保证交易流水可核对。第三是消息通知联动。居民体检报告完成、慢病随访到期、疫苗接诊提醒这些场景用短信或小程序订阅消息推送能把系统从被动录入工具变成主动服务工具这是社区医疗信息化最有价值的方向。如果你要从一套源码开始做商用系统个人建议先在一个小的社区试点跑三个月收集真实用户的反馈后逐步迭代。技术栈完全不用换SpringBootVue足够扛住这个体量的业务换框架反而是给自己找麻烦。想清楚需求边界控制好复杂度这套系统能做到的事情远比初始版本多得多。与其纠结于要不要用更新的技术栈不如先把业务的核心痛点抠透。很多社区医疗机构需要的不是功能堆砌而是一个稳定、好用、能被阿姨们接受的工具。做这种系统把“用户真的会用”这件事解决好了比什么都有说服力。