☰
SpringBoot+Vue社区医院管理系统实战:从数据库设计到前后端联调
2026/10/22 8:10:13 网站建设 项目流程

做社区医院管理系统这类选题,说实话是一个相当经典的“练手+落地”组合。这几年我在朋友之间也接手过类似的模拟项目,比如某个社区卫生服务中心要上的信息管理系统,技术栈用的就是SpringBoot+Vue,后端配MySQL和MyBatis。这个任务听起来像是常见的学生课题,但实际上如果认真拆开做,它涉及的东西比大部分人想的要多得多:患者档案、挂号、医生排班、处方、收费、药品库存、用户权限——每一条线都是真实业务,不是随手增删改查就完事的。

下面我会围绕“基于SpringBoot+Vue的社区医院信息平台管理系统”这个项目,把我从需求梳理、数据库设计、后端接口开发到前端Vue页面落地的完整过程捋一遍。重点不是堆概念,而是给你看我在真实开发中是先做什么、后做什么、哪些地方踩了坑、哪些地方做了取舍。代码部分会给出核心片段的思路,不追求贴整份源码,但关键的映射配置、接口设计、路由处理一定讲明白。

1. 社区医院信息管理系统:从业务场景倒推功能边界和表结构

做这种系统,第一个冲动往往是先建表、先写Controller,这其实顺序错了。我见过太多人一上来就把用户表、角色表、权限表建得特别复杂,结果业务上根本用不到那么多。真正应该做的,是先搞清楚这个社区医院每天到底在发生什么。

以我参考的某社区卫生服务中心的业务为例,一天的工作大致是:患者进门建档或者刷医保卡调档;前台根据当天坐诊医生的排班表安排挂号;医生接诊后写病历、开处方(处方包含药品和用法用量);患者到收费窗口缴费;缴费后去药房取药,药房扣减库存;如果患者需要复诊,还要能在系统里查到上一次的处方和病历记录。再加上后台需要管理医生信息、药品信息、科室信息,整个系统就出来了。

把这些流程拆成功能模块,系统至少要有以下几个核心部分:

  • 系统登录与用户认证(管理员、医生、药师、收费员、护士等角色)
  • 患者管理:建档、编辑、查询,包括既往病历摘要
  • 挂号管理:按科室/医生排班挂号、退号
  • 医生工作台:查看今日挂号患者、书写病历、开处方
  • 收费管理:对处方进行费用明细核算、收款、打印小票
  • 药品管理:药品字典、库存预警、入库和出库
  • 统计报表:门诊量、收费汇总、科室工作量统计

技术选型反而不是难题。社区医院这种规模,并发量不会太大,SpringBoot+Vue这种前后端分离架构完全够用,开发效率还高。Java生态的稳定性、MySQL的开源免费、MyBatis对动态SQL和复杂查询的把控能力,都是选它最直接的理由。有人会问为什么不用更重的技术,比如微服务或者统一权限框架,我认为杀鸡不用牛刀,系统上线后的维护人可能只有一两个,技术越简单越安全。

2. 后端模块划分与核心接口设计:用实体表画出业务流

模块划分决定了代码后期好不好改。我推荐把后端按业务域拆包,而不是按技术职责硬拆。所谓按业务域拆包,就是每个包内包含自己的Controller、Service、Mapper,例如:

com.hospital.admin ├── common // 通用类:统一Result、异常、常量 ├── config // 配置:跨域、拦截器、MyBatis ├── security // 登录认证、JWT工具、自定义注解 ├── patient // 患者管理模块 ├── registration // 挂号模块 ├── outpatient // 医生接诊/病历/处方模块 ├── pharmacy // 药品管理模块 ├── finance // 收费模块 └── system // 用户、角色、科室管理

每个模块内部可以有自己的Entity、Mapper接口、XML映射文件。这样做的好处是,接口之间的依赖关系通过Service层交互,表格之间的关系也被限制在合理的范围内。如果按Controller/Service/Mapper这样的技术方式建包,一开始会很爽,后面多几个模块就会乱成一锅粥。

2.1 数据库设计中的几个关键依赖

数据库设计是这种项目最容易翻车的地方。我建议把表设计成两组:一组是基础数据表(用户、角色、科室、药品),另一组是业务流水表(挂号、病历、处方、收费)。两组的核心区别在于,基础数据表重在唯一性和规范化,业务流水表重在记录“某时某刻发生了什么”。

举例说明,患者表最核心的字段包括:姓名、性别、出生日期、身份证号、联系电话、既往病史描述、建档时间。身份证号需要一个唯一索引,但要注意,有些老年患者可能没有身份证,或者证件号异常,所以这个字段不能设为非空,只能做成允许NULL的唯一索引,否则建档会变得很困难。

挂号表就是一个典型的流水表,包含:挂号单号、患者ID、科室ID、医生ID、挂号时段、就诊状态(待诊/已诊/已退号)、创建时间。这里最重要的设计决定是“是否保留医生排班表”。如果挂号规则比较简单(比如每天固定那些医生坐诊),我选择不建独立的排班总表,而是在挂号时通过一个查询接口返回当天各科室各医生剩余号源。这样少一张表,业务并不复杂多少。但如果医院有明确的排班管理制度,增加一张doctor_schedule表把一周的出诊计划完整拨出来,会更规范。

处方表和药品表是另一对容易设计错的关系。一张处方对应多种药品,每种药品有数量和用法,所以至少需要处方主表prescription和处方明细表prescription_item。明细表里不应该存药品名称这种冗余文本,而应该存药品ID、单价快照、数量、用法用量说明。这里存单价快照是一个关键细节——药品日后调价,历史处方仍然要能算出真实的收费金额,不能因为药品表改了价就影响旧数据。

收费表需要与处方明细进行强关联,每一笔费用要么属于挂号费,要么属于处方药品费。我的做法是统一加一个业务类型字段,bill_type区分“挂号”和“处方”,再用对应ID关联具体业务记录。使用这种设计,收费汇总时一张表就够,不需要分别统计两个模块的流水。

2.2 关键接口的边界与参数设计

后端接口设计容易陷入两个极端:要么一个接口流程全包,要么每个动作都开一个新接口。一个项目下来,两个极端都会让前端很难写。我总结的习惯是:查询类接口尽量通用,操作类接口尽量精确。

拿“医生工作台”举例,医生打开工作台要看到两类内容:今天是哪几时刻段的患者,以及自己负责哪些科室、哪些时段的号源。我会拆成两个接口:

GET /api/doctor/todayPatients GET /api/doctor/schedule

第一个接口返回患者列表,包含患者ID、姓名、性别、年龄、挂号时段、挂号单号、就诊状态,这些数据就已经足够医生进入接诊界面。第二个接口返回今天该医生的排班时段,供医生确认自己负责的范围。

接诊动作则拆成三个独立接口:

POST /api/outpatient/createRecord // 接诊,更新挂号状态 POST /api/outpatient/savePrescription // 保存处方(带明细) POST /api/finance/charge // 收费结算

为什么不合并成一个“一站式接诊”接口?因为真实业务场景里,医生开处方和患者付款往往有时间差。如果强制两个动作在同一事务里完成,患者口袋里钱没带够或者收费员正在交接班,这个接口就变得非常难处理。拆开以后,每个动作都可以独立完成、独立失败、独立重试,业务上更合理。

事务边界也需要想清楚。创建病历、保存处方、扣减库存这三件事,理论上应该是一个事务里完成的,但实际上这三件事在业务上分属不同的时刻。扣减库存更适合发生在药房发药确认时,而不是在医生保存处方的瞬间。如果将库存扣减事务前移,相当于提前“锁货”,如果患者没有付款买药,库存就要回滚,这种业务模式很笨重。我的做法是:保存处方时只写明细和金额,药房界面提供“发药确认”按钮,点击后同时更新明细状态并扣减库存,这两个动作放在同一个事务里,事务范围控制在极小的粒度。

3. MyBatis映射的真实落地:动态SQL处理多表联查与条件筛选

MyBatis在这套系统当中负责的是数据库访问层,但实际上很多项目中它承载了大量SQL编写工作。社区医院管理系统里面的列表查询普遍带筛选条件,比如挂号记录按时间段、科室、医生、状态筛选,这种需求用动态SQL实现很顺手。

3.1 MySQL数据库脚本与MyBatis映射文件的配合写法

动态SQL最大的价值是让同一条SQL根据前端传入的筛选条件自动拼Where子句。比如挂号列表是这样的:

<select id="selectRegistrationPage" resultType="com.hospital.registration.domain.RegistrationVO"> SELECT r.reg_no, p.name AS patientName, p.sex, p.age, d.name AS doctorName, dept.name AS deptName, r.reg_time, r.status 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 department dept ON r.dept_id = dept.id <where> <if test="deptId != null"> AND r.dept_id = #{deptId} </if> <if test="doctorId != null"> AND r.doctor_id = #{doctorId} </if> <if test="startDate != null and startDate != ''"> AND r.reg_time &gt;= #{startDate} </if> <if test="endDate != null and endDate != ''"> AND r.reg_time &lt;= #{endDate} </if> <if test="status != null and status != ''"> AND r.status = #{status} </if> </where> ORDER BY r.reg_time DESC </select>

这里有几个我重点想说的细节。第一是表别名,多表联查中每个字段都要带别名前缀,防止字段重名。第二是<where>标签本身会智能去第一个多余AND,比起手工写WHERE 1=1要干净太多。第三是日期区间比较用&gt;=和&lt;=,XML中不能直接写大于小于号,这是一个新手一定会碰到的坑。

动态SQL并不只有<where><if>这种组合。查询医生排班情况时,可能需要根据星期几来筛选;查询收费汇总时,可能需要按日/月分组统计。这些都可以在XML里用<choose>、<foreach>、<bind>处理。我的经验是,不要把大量业务逻辑写入SQL,但也不要矫枉过正地把本来SQL能很好完成的统计逻辑强行拿到Java里循环计算。数据库擅长的事交给数据库,代码才干净。

3.2 分页方案选型:PageHelper还是手动分页

分页是列表类接口绕不开的问题。一般有两种方案:用PageHelper插件或者手写LIMIT。PageHelper的最大优势是使用简单——不用改SQL,只需在调用前启动分页即可。但网上对PageHelper的诟病也多,主要集中在一对多查询时如果集合嵌套映射,会出现总数不对或者SQL被错误包裹的问题。

对于这个项目,我的体会是:PageHelper在单表单表简单联查场景下非常好用,但一旦查询结果里嵌套了集合(比如查询所有科室及其医生列表),就千万不要用PageHelper,因为它会把外层主查询的LIMIT强行加上,而计数SQL却可能无法正确识别,导致分页计数错位。处理这种嵌套集合,要么用两条SQL分别查询再在Service层组装,要么干脆手写LIMIT。社区医院系统的列表页面基本都是简单单表联查,用PageHelper足够,嵌套集合的情况极少。

还要注意一个细节:PageHelper在查询结束后一定要在finally块里调用PageHelper.clearPage(),或者在Service层保持方法链的清洁。如果忘了清理,同一个线程的下一次查询很可能会被上一次遗留的分页参数带偏,而这类问题排查起来非常隐蔽。

3.3 事务控制:什么时候用注解,什么时候用编程式事务

SpringBoot体系中事务最常见的方式是@Transactional。这个注解用起来很舒服,但前提是明确什么场景需要事务。我之前提到发药确认涉及“更新明细状态+扣库存”两个操作,这种情况必须在一个事务里:

@Transactional(rollbackFor = Exception.class) public void confirmDispense(Long prescriptionId) { // 1. 校验处方状态,必须处于“已缴费待发药” Prescription prescription = prescriptionMapper.selectById(prescriptionId); if (prescription == null || !"PAID".equals(prescription.getStatus())) { throw new BusinessException("处方状态不允许发药"); } // 2. 逐条更新明细为“已发药”,并扣减药品库存 List<PrescriptionItem> items = prescriptionItemMapper.selectByPrescriptionId(prescriptionId); for (PrescriptionItem item : items) { prescriptionItemMapper.updateDispenseStatus(item.getId(), 1); stockMapper.deductStock(item.getDrugId(), item.getQuantity()); } // 3. 更新处方主表状态 prescriptionMapper.updateStatus(prescriptionId, "DISPOSED"); }

rollbackFor = Exception.class是必须的。如果不指定,Spring默认只在抛出RuntimeException时回滚事务。而一旦代码里有一个受检异常路径没有被拦截,整个事务都不会回滚,导致数据不一致。数据库层面的库存扣减不能写成先查出来再在Java里减一,再更新回去,这是典型的丢失更新问题。更好的做法是直接在SQL里扣减,并且加上库存足够的条件:

@Update("UPDATE drug_stock SET stock = stock - #{quantity} WHERE drug_id = #{drugId} AND stock >= #{quantity}") int deductStock(@Param("drugId") Long drugId, @Param("quantity") Integer quantity);

当SQL执行后返回0行,说明库存不足,Service层就可以响应报错。这种方式比先select后update安全得多,并发情况下也不怕超卖。

4. Vue前端落地:路由权限、Axios封装与核心页面实现思路

前端部分我用的技术组合是Vue2.x + Element UI + Vuex + Vue Router。为什么不选Vue3?因为当前社区很多现成的模板、组件、示例代码都是基于Vue2,选Vue2可以降低搭建成本,团队也好上手。当然,如果是从零开始的新项目,直接Vue3+Vite也是合理的,只是配套组件和工程实践稍微新一些。这个项目我按Vue2讲,因为跟随项目源码的版本更贴近实际。

4.1 登录页、Token存储与路由守卫

前后端分离系统的核心是登录态。后端登录成功后返回JWT,前端需要把Token存起来并在每次请求时带上。存储位置我推荐放在localStorage,毕竟刷新页面后需要恢复登录态。路由守卫是安全的关键一环:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() return } if (!token) { next('/login') return } if (to.meta && to.meta.roles) { const userRole = store.state.user.role if (!to.meta.roles.includes(userRole)) { next('/401') return } } next() })

这里的做法是前端通过meta.roles做粗粒度权限过滤。但要注意,前端路由守卫只是用户体验层面的拦截,真正的权限把关必须在后端接口上,否则别人直接调用接口就绕过限制。后端要做的权限控制在用户登录时返回角色标识,在接口层面用拦截器对特定URL和角色进行校验。

4.2 Axios封装里的几个关键决策

Axios封装不是一个可选动作,而是统一处理请求头、错误提示和返回解包的必要模块。我的写法大概是:

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 }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error('网络异常,请稍后重试') return Promise.reject(error) } )

后端返回结构的统一很重要,我习惯使用{ code: 200, msg: "success", data: ... }这套结构。成功时code是200,业务失败时code是其他值但HTTP状态码仍是200,便于前端统一处理业务错误。对于认证过期的情况,HTTP状态码用401,响应拦截器里直接清理Token并跳转登录页。

还有几个细节值得注意。文件下载类型的接口不能直接用这个封装,因为responseType要设置为blob,response拦截器也会因为拿不到code导致误判。做法是在axios中为这类特殊请求单独设置实例,或者将响应拦截器加一个判断,当response.config.responseType === 'blob'时直接返回。上传文件也是类似处理。多实例模式在这种项目中非常实用。

4.3 功能页面的落地:从表格到表单再到弹窗

前端的核心页面大致可以分为三类:查询类页面(患者列表、挂号列表、药品列表)、表单类页面(患者建档、医生信息维护)、业务操作类页面(医生工作台、收费结算)。这三类页面在后端设计中对应的接口形态完全不同,我在前端实现时也刻意区别对待。

查询类页面用表格配合查询表单,每一行提供“详情”入口,在详情弹窗中展示信息。这里要注意后端接口返回的字段名和前端表格列保持一致。我踩过一个坑:MySQL的date类型字段通过MyBatis映射到Java对象是java.sql.Date,序列化给前端时默认是带时区的字符串,例如“2025-03-01T00:00:00.000+00:00”。这种格式直接展示给用户奇怪影响体验。解决方式是在字段上添加@JsonFormat(pattern = "yyyy-MM-dd")注解,这是后端配合前端最省事的方式。

表单类页面做好校验是基本功,但真正麻烦的是编辑回显。患者修改时要把患者ID带到后端,而后端更新接口需要能区分“新增”和“修改”。我的习惯是表单对象中包含id字段,为空视为新增,非空则更新。Service层判断id是否为空并调用不同Mapper方法,逻辑不复杂,前端也不用维护两个接口。

医生工作台是我认为最能体现系统业务特色的页面。左侧显示今日患者列表,点选患者后右侧区展示患者基本信息并允许录入病历和处方。前端需要维护一个当前接诊对象,通过Vuex或局部状态管理器保存。这个页面没有复杂的组件,但是业务耦合度较高,前端和后端接口之间的一致性测试会花很多时间。没有经验的人容易在小细节上反复改,比如点击“保存处方”后要不要同时清空处方列表,我的建议是:保存成功后保留已保存的处方明细,清空“新增药品”区域,这样医生可以进行修改而不必担心误清空。

5. 收费与药品库存联动:别让前端状态骗了你的数据库

收费模块和药品库存管理在业务上紧密关联,但两者又有独立性。社区医院的流程通常是:医生开处方,处方状态为“开立”;患者去收费窗口缴费,收费员点击收费,系统调用收费接口,收费成功后处方状态变为“已缴费”;患者拿着缴费单去药房取药,药师点击发药,此时库存扣减工序才正式进行。

这个流程明确了,就会知道收费和库存是两个完全独立的动作。有些项目把这两件事捏在一个按钮里,虽然演示时看起来很快,但真实场景中一旦药房缺货或患者取药延迟,后面的退款和库存回滚会非常痛苦。

收费接口我这里有一个必须强调的细节:收费金额不能直接信任前端传来的totalAmount。正确做法是后端根据处方明细重新计算金额,再用这个金额生成收费单。原因很简单,前端页面可以被篡改,而且如果前端传递的不是实际数据库里的处方明细,会产生送的金额错乱问题。我的写法是收费接口只接收处方编号或收费类型,然后后端把明细查出来重新计算:

public ChargeResult chargePrescription(Long prescriptionId) { List<PrescriptionItem> items = prescriptionItemMapper.selectByPrescriptionId(prescriptionId); BigDecimal total = items.stream() .map(item -> item.getUnitPrice().multiply(new BigDecimal(item.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add); // 生成收费记录并更新处方状态 }

库存预警也是一个很容易被做得太过或太简单的地方。社区医院药品不多,不需要实时大数据流式统计,只需在药品列表中展示当前库存量,并在库存低于预警阈值时高亮显示即可。MySQL数据库每天扫一次表来汇总过期的批次已经足够。关键指标是:预警字段要设置合理的阈值,比如常用感冒药可以预警在50盒,而急抢救类药品要预警在10支。系统上线前,由药房管理员校准每个药品的预警阈值,比程序做得花里胡哨有意义得多。

6. 前后端联调、部署与常见报错的解决清单

这个项目跑通功能不难,难在前端联调和部署上出问题后不知道如何排查。我把常见问题整理成一个清单,方便直接对照。这里的坑每一个都是我测试时亲历过的,不是从文档里复制来的。

一个是跨域问题。开发环境下前端地址通常是http://localhost:3000,后端是http://localhost:8080,如果不处理跨域,浏览器会直接拦截请求。解决办法是在后端加入CORS配置,允许指定来源和带凭证请求。需要注意,不能用allowedOrigins("*")加上allowCredentials(true),这样组合在浏览器规范中是不合法的,很多版本下会直接报错。要么明确写死允许的来源,要么在凭证模式下不使用通配符。

第二个是数据库连接时区问题。MySQL 8.x的JDBC连接时区不指定会报“The server time zone value”的错误。解决办法是在连接串中明确serverTimezone=Asia/Shanghai,这个配置在部署时容易漏。遗漏的直接表现不是系统完全不可用,而是某些接口可以访问,某些带时间操作的SQL报错,非常迷惑人。

第三个是Long类型精度丢失。Java后端的自增主键是Long类型,序列化到前端后,JavaScript数字类型可能无法准确表示超过2的53次方的整数,常见表现为主键最后几位变成了0。这个坑在列表操作和详情操作时几乎必然触发。解决办法是在个别字段添加@JsonSerialize(using = ToStringSerializer.class),或者全局配置Long转String序列化。不要为了省事把所有Long都转String,查数据和ID比较时会很别扭。

第四个是Vue Router的history模式在部署到Nginx后刷新404。Vue Router使用history模式时,需要Nginx配置try_files $uri $uri/ /index.html;才能让前端路由在刷新时重定向到index.html。如果是hash模式就没有这个问题,但URL会带着'#'。我建议部署时直接用这两个方案之一,不要用开发服务器直接上线。

第五个是跨域配置后接口正常了但登录接口还是不通。问题往往出在自定义拦截器或过滤器中。有些开发者会在Filter里对OPTIONS预检请求做拦截并抛出401,导致浏览器预检失败。正确做法是无论是否登录,都允许OPTIONS请求直接放行,在拦截器里加上if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; }。

类冲突、依赖版本不一致这种问题虽然也常见,但相对容易通过控制台报错定位。真正花时间的是上述这些“看起来没问题但是就是不通”的问题。联调过程中如果发现前端实际拿到的数据与预期不一致,我可以给一个排除策略:先打开浏览器开发者工具看Network里的响应体,再打开后端控制台看SQL日志,两者对比就能定位到问题是出在SQL、数据映射还是前后端参数命名不一致上。

7. 上线前必须做的校验、性能排查和代码整理

功能写完之后,项目还远没有结束。很多初学者把“能跑通”等同于“开发完了”,但真实交付中,我至少要花两到三天做上线前的检查和瘦身。

第一件事是删掉测试代码和调试输出。很多Service层写着临时System.out,或者留着永远不会被调用的mapping方法,一部分是有用的日志,一部分是垃圾。给项目中加入统一的日志框架,并且只输出需要跟踪的关键节点——挂号创建、收费金额、药品扣减。上线后如果某个操作需要排查,统一的日志起着很大作用。

第二件事是做索引校验。社区医院的日门诊量可能只有几十到几百条,报表查询一个月的数据也就是几千到几万,大多数人觉得这种量级不需要索引。但有一个例外:按日期范围查收费流水、按身份证查患者档案这类高频条件,没有索引会让前端感觉到明显卡顿。至少给以下字段建立索引:registration的reg_time、bill的pay_time、patient的id_card_no、prescription的status。

第三件事是处理N+1查询问题。如果循环查患者列表时,每条记录都要再查一次挂号记录,就会产生N+1次查询,列表稍微长一点就会拖垮数据库。解决方式是尽量在一次SQL里把主表和关联表数据取出来。开发过程中禁止使用循环查询这种写法,成员代码审查时就要把这类问题掐掉。

第四件事是统一字典值管理。这个项目中存在大量状态字段,包括挂号状态、处方状态、收费状态。后端代码中最好集中定义一个常量类或者枚举类,不要直接在代码里散落字符串“PAID”、“待缴费”之类的判断。否则状态一多,连作者自己也会记混。定义枚举之后,前端显示时通过字典接口获取对应标签,以后再新增状态只改一个地方,逻辑一致性会好很多。

8. 这个系统的扩展方向:如果还要继续演进,我会先加什么

做一个毕设或者练手项目,把目前的系统跑通已经是达标了。但如果这是一个要正式商用或者继续迭代的系统,我会对它做几项扩展,优先级分级我心里也有数。

第一优先级是“药品批次和有效期管理”。现在只做了库存数量管理,但真实药房必须有批次(货品批号)和有效期跟踪,否则过期的药还在货架上,这是质量事故。我要在drug_stock表上增加batch_no和expire_date字段,发药扣库存时按最早过期批次出库(FEFO),并且每天定时检查临期药品并产生提醒。

第二优先级是“排队叫号”。社区医院虽然规模不大,但就诊顺序管理仍然很重要。可以在挂号时生成一个排队序号,医生结束上一名就诊后从队列中取下一名。这个模块不复杂,在后端用一个队列状态机就能实现,却能让医院前台工作从纸质名单中解脱出来。

第三优先级是“短信或公众号通知”。检查报告出来、药品到货、复诊提醒这类场景,能通过短信或微信通知患者,减少患者多次跑医院的次数。这个扩展不会改变现有业务模式,只需要在挂号、收费、出报告等节点增加一个事件通知机制即可。

第四优先级是“数据备份与归档”。系统上线后数据库会不断增长,超过某个年限后查询会变慢。必须明确历史数据的归档策略,比如一年前的处方、收费流水转入history表,只保留一年的在线数据用于高频业务查询。这个动作虽然不动代码,但比很多功能开发更重要。

而这些扩展的前提,都是目前的数据库设计不要把自己逼进死胡同,这也是我始终强调表结构遵循基础数据与业务流水分离的原因。

作为开发完这个系统的人,我最想提醒的是:技术本身并不复杂,SpringBoot+Vue+MySQL+MyBatis是特别成熟的一套组合,资料一搜一大把。真正让项目有价值的地方在于你对自己所面对的业务有没有建立完整、连贯的理解。患者来了怎么办,医生开了处方怎么流转,财务怎么对账,库房怎么管理批次——你现在如何设计表和状态,决定了这套系统未来是顺手还是难用。所以,如果你正在做同类型的项目,我建议你先把业务流程图在纸上画清楚,再打开IDE写代码。画图的时间不会白费,它会帮你避开前期大部分改来改去的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询