新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java + Vue医疗问诊拿药系统设计与实战:处方、库存与事务处理全解析

发布时间:2026/9/30 7:55:17来源:尧图网络
Java + Vue医疗问诊拿药系统设计与实战:处方、库存与事务处理全解析
这个项目是我去年给一家社区门诊做的。回想当时线下流程最要命的不是人多而是所有人都在靠纸质单据和口头沟通每天闭店光对账就要折腾一个多小时。所以当对方提出想要一套Java Vue的医疗问诊拿药系统并且明确要求必须带完整的源码、数据库和部署文档时我几乎没犹豫就接了。做完之后回头看这类系统的难点从来不在某个单独功能而在问诊、开方、发药、库存这几条业务线怎么被数据表牢牢串起来。这篇文章就把整个项目的设计思路、核心表结构、接口实现和踩坑过程完整拆一遍如果你正在做类似的诊所管理、药店信息管理项目或者想找一个能写进简历的Java全栈实战项目可以参考我的做法。整个系统不复杂但胜在业务链路完整患者登记建档、预约问诊、医生书写病历、开电子处方、药房核对发药、库存自动扣减、各类统计报表覆盖了一家小型门诊日常运营的全部环节。前端用Vue Element UI构建医生工作台和药师发药台后端基于Spring Boot提供REST接口数据库采用MySQL存储全部业务数据项目还额外配了初始化脚本、接口文档和部署说明。下面逐个环节拆开讲。1. 项目到底在解决诊所里的哪一类麻烦1.1 线下流程的真实痛点没做过诊所后台系统的人可能觉得看病拿药这种场景数据量又不算大Excel就能管。但实际上手过就知道线下模式的漏洞多得惊人医生开的纸质处方字迹潦草药师辨认错误拿错药的情况不是段子是真会发生。药房不知道医生开了什么药医生也不知道药房有没有库存。经常出现医生刚开完处方患者去药房却被告知缺货还得折返找医生换药。患者复诊时医生调不出历史病历全靠患者自己口头描述诊疗质量和效率都很受影响。月底对账要把一个月的处方单一张张录入Excel耗时耗力还容易漏账。这套系统的核心目标就是从源头上把这些麻烦摁住处方数字化库存实时可见病历自动归档对账由系统统计完成。技术上的挑战不大真正的功夫全在流程设计和数据一致性上。1.2 系统的功能边界与角色划分系统按业务角色划分功能模块没有做很复杂的权限框架而是用一个用户类型字段区分身份。大致分三种角色角色主要操作患者登记信息、查看问诊记录、查看历史处方医生接诊、写病历、开处方、查看历次问诊记录药师查看待发药列表、核对处方、确认发药、管理库存管理员维护药品信息、管理用户账号、查看统计报表开发这类系统时第一个容易犯的错误就是一上来把功能铺得很宽连排班、收费、医保对接都想做进去。但我建议第一版一定要砍掉非核心需求专注问诊和拿药这一条主线。把这条主链路跑顺了后续想扩展什么模块都能自然加上去。2. 技术选型Java Vue这套组合是怎么定下来的2.1 后端框架Spring Boot MyBatis-Plus后端没怎么纠结直接选了Spring Boot 2.7 MyBatis-Plus组合。理由很实在招人容易资料最多遇到问题搜索引擎一搜就有答案。Spring Boot自带的自动配置和Starter机制省掉了大量XML配置哪怕是第一次接触这种项目的开发者对着官方文档也能快速把环境跑起来。MyBatis-Plus给我最大的帮助是单表CRUD基本不用写SQL。像患者档案的新增修改、药品列表的分页查询这种操作直接用内置方法配合Wrapper条件构造器就能完成。不过复杂一点的多表关联查询比如“查某个患者的所有处方及其明细”我选择手写SQL做连表查询避免框架自动生成的SQL在性能上打折扣。2.2 前端为何押注Vue而不是其他框架前端采用Vue Element UI这个组合做后台管理类界面几乎是最稳妥的选项。Vue的双向数据绑定让表单类交互写起来非常省力Element UI把表格、表单、弹窗、消息提示这些后台高频组件都封装好了开发效率比手搓HTML高出一大截。项目用的是Vue 2因为Element UI对Vue 2的支持最成熟第三方兼容性问题最少。2.3 源码、数据库与文档的三件套价值这里特别说一下源码、数据库、文档这三个交付物。很多个人开发者交付项目时只扔一个源码压缩包这是很不专业的。真正接手一个项目的人最需要的是一份能跑起来的完整环境建库脚本、初始化数据、前端构建步骤、后端启动参数、接口说明。我这个项目把这三件事分得比较清楚数据库脚本放在单独的sql目录下包含建表语句和演示数据接口文档按模块列举了关键接口的请求方式、参数和返回结构README文档写了从零到启动的完整过程。这对接手者——哪怕是未来的自己——都很重要因为花几个小时重新熟悉自己上个月写的代码是每个程序员都经历过的事。3. 数据库设计一张处方单怎么把全库的表串起来3.1 核心表结构与关系梳理数据库是这个系统最值得仔细讲的部分因为医疗业务的表关系比一般的管理系统更强调“链条”完整。我从一张患者问诊在系统中的流转过程来描述这些表是怎么被串起来的。系统的核心表大致有以下这些patient患者表存储患者基本信息包括姓名、性别、年龄、联系电话。sys_user系统用户表存储医生、药师、管理员的登录账号通过type区分角色。appointment问诊记录表每一条记录代表一次问诊关联患者ID和接诊医生ID用status表示待诊/问诊中/已完成。medical_record病历表关联问诊记录ID存放主诉、现病史、既往史、诊断结论。prescription处方表关联问诊记录ID包含处方编号、医生ID、状态待发药/已发药/已取消、备注。prescription_item处方明细表一条处方对应多条明细每条明细关联药品ID、数量、单价、小计金额。drug_info药品表存放药品名称、规格、单位、库存、售价等基础信息。drug_stock_log库存变动日志表发药时记录扣减日志用于追溯。dispensing_record发药记录表药师确认发药后写入包含发药人、发药时间、关联处方ID。这些表通过外键逻辑形成一条清晰的主链appointment一次问诊→ medical_record病历→ prescription处方→ prescription_item明细→ drug_info药品→ dispensing_record发药记录。从这条链可以看到处方明细是整个系统里信息密度最高的表它既要引用药品信息又要承载数量价格还要通过处方表间接关联问诊记录和患者。3.2 一张关键的处方明细表设计处方明细表我单独拿出来给个建表示例因为它是这个项目里最能体现设计思路的一张表。表结构大致如下CREATE TABLE prescription_item ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, prescription_id bigint(20) NOT NULL COMMENT 处方ID, drug_id bigint(20) NOT NULL COMMENT 药品ID, drug_name varchar(128) NOT NULL COMMENT 药品名称快照, specification varchar(128) DEFAULT NULL COMMENT 规格快照, quantity int(11) NOT NULL COMMENT 数量, unit_price decimal(10,2) NOT NULL COMMENT 单价, total_price decimal(10,2) NOT NULL COMMENT 小计金额, usage_method varchar(255) DEFAULT NULL COMMENT 用法用量, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_prescription_id (prescription_id), KEY idx_drug_id (drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;有一个细节值得注意我在明细表里冗余了drug_name和specification字段。因为在真实业务流程中药品价格和名称可能调整如果明细表只存药品ID将来药品信息变更后历史处方的显示就会跟着变这在医疗场景下是不可接受的——开方那一刻的药品名称、价格必须被固定下来。这就是所谓的“快照”设计在订单、处方、票据这类不可变业务单据里非常常见。3.3 为什么不在处方表里直接存总金额设计处方表时我最初的想法是直接加一个total_amount字段保存处方总价查询时不用实时计算。后来想通了总金额可以从明细表sum出来但要保证一致性就要付出额外的计算成本如果存了冗余总金额一旦明细调整总金额也要同步更新事务没控制好就容易对不上账。最终方案是不存冗余总金额查询接口里用SQL的SUM聚合得出。这样数据源只有一个永远不会出现明细加起来和总额不一致的情况。4. 问诊到发药的完整链路核心接口与业务流程实现4.1 后端接口的整体规划这个系统的后端接口按业务流程组织没有刻意追求RESTful的纯度而是以可读性和易用性优先。核心接口大致这些接口作用说明POST /api/appointment/create创建问诊记录患者登记或前台挂号写入appointment表状态置为待诊PUT /api/appointment/start开始问诊医生点击接诊状态改为问诊中POST /api/medical-record/save保存病历医生填写主诉、诊断同时关联当前问诊IDPOST /api/prescription/save保存处方接收处方明细JSON数组事务性写入处方主表和明细表POST /api/dispensing/confirm确认发药药师核对处方扣减库存写入发药记录更新处方状态GET /api/prescription/detail查看处方详情返回处方基本信息、明细列表、病历摘要、患者信息GET /api/drug/page药品分页查询药品列表支持名称模糊搜索用于医生开药选择器从列表可以看出整个业务流程的骨架就是“预约问诊 → 写病历 → 开处方 → 发药”。这几个操作完成后数据记录的最终落点各有不同但它们都是在同一条业务链的前后环节。4.2 处方保存的事务控制开处方这个接口是整个后端最需要小心的地方。一次开方操作同时涉及三件事写入处方主表、批量写入处方明细表、更新问诊记录状态。任何一个环节失败都不能留下残缺数据。我用Spring的Transactional注解把整个方法包起来默认情况下RuntimeException导致回滚。一个非常容易被忽视的点是前端传给后端的处方明细是以JSON数组形式提交的后端接收时需要先校验明细不能为空、药品ID是否存在、数量必须大于零然后才是写入数据库。校验逻辑要放在事务内、写入之前。如果校验放在事务外并发场景下可能出现“校验通过了但写入时药品已经被删掉”的极端情况。4.3 发药扣库存防止超卖的核心写法发药时扣减药品库存是最典型的并发写入场景。两个窗口同时给两位患者发同一个药品如果代码是这样写的先查库存判断库存够不够再执行UPDATE减少库存——在并发下会出现两人都查到库存充足然后各自执行扣减最终库存变成负数的情况。我的解决办法是直接用条件更新语句把判断逻辑下推到数据库Update(UPDATE drug_info SET stock stock - #{quantity} WHERE id #{drugId} AND stock #{quantity}) int deductStock(Param(drugId) Long drugId, Param(quantity) Integer quantity);注意这里的WHERE条件里带着stock #{quantity}。如果更新返回0说明库存不足这时候直接抛出业务异常提示药师库存不够整个发药事务就回滚了。这样就不需要额外加锁也不需要先select再update性能更好逻辑也更安全。4.4 幂等性处理防止重复提交实际运营中经常出现这种情况药师点了确认发药但网络卡了一下页面没跳转药师又点了一次。如果后端没做幂等处理同一次发药就会被执行两遍库存也被扣两次。我的处理方式是双保险。前端在点击发药按钮后立即置灰禁用并在请求未返回前不允许再次点击后端在发药接口里检查处方状态只有状态为“待发药”时才允许发药一旦发药成功马上把状态更新为“已发药”。这样就算前端防不住后端这个状态判断也能拦住大部分的重复提交。更严格的方案可以引入一个业务唯一标识每次发药请求带上同样的标识后端用Redis setnx实现分布式锁但对于这个体量的门诊系统状态判断已经够用。4.5 金额计算一律使用BigDecimal药品金额的计算直接关系对账准确所以要格外谨慎。我见过不少项目在金额上使用double这在大多数场景下看不出问题直到出现了像0.1 0.2 0.30000000000000004这样的小数精度丢失对账时差几分钱甚至几毛钱排查起来非常痛苦。所以这个项目的所有金额字段都是decimal类型Java后端统一使用BigDecimal进行运算前端展示时再转为字符串传给页面。数据库层面unit_price和total_price都定义成decimal(10,2)从底层杜绝了浮点数精度问题。到这里问诊到发药的主链路已经打通。从患者建档、医生接诊到处方生成、药师发药一条业务线全部落地。这套流程虽然不算复杂但做了完整的状态控制、事务管理、并发处理和金额精度防护作为中小型诊所的信息化方案已经很能打了。5. Vue端做了些什么医生和药师各自看到的界面逻辑5.1 前端目录与路由权限前端项目基于Vue CLI构建目录结构如下src/ ├── api/ # 所有接口请求封装 ├── router/ # 路由配置 ├── views/ │ ├── login/ # 登录页 │ ├── doctor/ # 医生工作台 │ ├── pharmacist/ # 药师发药台 │ ├── admin/ # 管理端 │ └── patient/ # 患者查询页 ├── utils/ # 工具函数包括axios封装 └── App.vue路由权限用的是最直接的方式路由元信息中标注哪些角色允许访问配合Vue Router的前置守卫每次跳转时从本地缓存里取用户角色判断能不能进。虽然不如动态路由那么高级但这个系统角色只有三种静态约定完全够用代码也更好理解。登录后根据用户角色跳到不同的默认页面医生进入问诊队列药师进入待发药列表管理员进入药品管理。这样一个简单的分流就能避免用户看到一堆自己不需要的菜单。5.2 医生端的核心交互医生端最常用的界面有问诊队列、病历编辑、开方窗口三个。问诊队列是一张Table展示待诊和问诊中的患者点击“开始问诊”按钮后右侧弹出一个病历表单。病历表单包含主诉、现病史、诊断结论等字段保存后写入medical_record表。开方窗口是另一个Dialog里面嵌入一个可动态添加行的小表格。医生点“添加药品”时弹出药品选择器支持按药品名称模糊搜索选中后自动带出规格和单价医生只填数量和用法用量。前端每添加一行就计算该行小计金额。这个交互看似简单但做的时候有个细节药品下拉选择用的数据不能每次都请求全表我用的是分页加远程搜索的Select组件输入关键字才触发查询而不是一次性加载几千条药品记录页面流畅度完全不一样。5.3 药师端与库存预警药师端的核心界面是待发药列表和发药确认弹窗。点击某条待发处方后弹窗展示处方的完整信息患者姓名、处方编号、药品明细表、合计金额。药师核对无误后点“确认发药”后端执行库存扣减和状态更新。库存预警是管理端的一个小功能在药品管理列表里会根据药品库存量判断状态低于阈值的药品在前面显示一个红色标签提示“库存不足”。这个逻辑很简单用前端template里的判断就能实现。但真正的价值在于帮助药房在缺货前提前采购减少“医生开方后方发现没药”的情况。5.4 axios封装统一处理Token和错误整个前端的接口请求都通过utils里封装的axios实例发出。请求拦截器从本地存储取token放到请求头的Authorization字段响应拦截器统一处理状态码如果后端返回401就认为登录过期自动跳转到登录页。业务异常码和HTTP状态码分开处理根据接口返回的code字段判断业务成功与否如果失败则用Element UI的Message组件弹出后端返回的错误信息。这个封装在前端开发中属于基本功但很多人会忽略一个细节——拦截器里要单独处理文件下载类的响应不能统一走JSON解析逻辑否则下载附件时拿到的是乱码。6. 实测中踩过的坑并发、重复和时区问题6.1 数据库连接池满导致接口假死项目刚上线没几天出现过一次界面卡顿很严重的现象。排查下来发现是数据库连接池的配置问题。当时用的Druid连接池默认的maxActive数量没有根据实际并发调整。问诊高峰时段所有接口都在等数据库连接新请求排队等待时间超过Phoenix框架的默认超时后就报获取连接超时。解决方案并不复杂把Druid的最大活跃连接数从默认的8调到了50同时设置合理的minIdle和validationQuery在获取连接时校验连接是否有效。调优之后高峰时段接口响应时间一下子恢复正常。这个坑在本地开发时根本暴露不出来因为本地就一个用户一到真实环境并发上来就现原形。做这类后端系统时部署前一定要按业务的峰值并发量估算连接池参数而不是用默认值一直跑。6.2 MySQL时区导致时间显示差了8个小时这个问题很典型但也很隐蔽。开发机连的MySQL是本地安装的时区默认就是东八区。测试环境的MySQL是Docker启动的Docker容器默认用的是UTC时区。结果测试人员反馈说发药记录时间比实际时间慢了8个小时。排查过程不复杂执行show variables like %time_zone%一查就发现system_time_zone是UTC。在数据库连接串上加上serverTimezoneAsia/Shanghai参数同时启动Docker容器时挂载-e TZAsia/Shanghai问题彻底解决。这里要提醒大家如果你用Docker部署数据库时区问题几乎一定会遇到别等到上线了才被用户提醒时间不对。6.3 前端打包后接口地址却还是localhost刚开始构建生产环境的包发现前端打包后还是疯狂请求localhost当然404。原因是我把接口地址写死在了一个js文件里而这个文件被打包进了生产资源。正确做法是在不同环境下使用不同的环境变量文件来控制API地址——项目根目录下的.env.development和.env.production分别配置测试地址和正式地址打包时按环境自动替换。这个机制Vue CLI本身就有我只是偷懒没第一时间用。6.4 医生重复开方状态机挡住了大部分错误测试阶段我一度以为重复开方的问题已经解决了结果有一天药师反馈有个患者的处方出现两条一模一样的记录。查完日志发现医生在问诊界面开了方后浏览器返回键回退到上一页页面缓存里表单数据还在医生以为没提交成功又点了一次保存于是一张问诊单下生成了两条处方。后端只校验了问诊状态但没限制“同一问诊单不能重复开方”所以第二条处方也写进库了。修复办法是在prescription表加一个数据库唯一索引字段是appointment_id保证一次问诊只能产生一条有效处方。这个方案比单纯加代码判断更可靠因为数据库唯一约束是并发场景下最后一道锁。这个坑也让我形成了一个习惯凡是业务上不允许重复的记录一律在表上加唯一索引兜底而不是只依赖应用层逻辑。6.5 模糊查询的LIKR注入风险药品搜索和患者姓名的模糊查询是高频操作但直接用前端传的关键字拼SQL存在SQL注入风险。MyBatis-Plus的like方法本身是安全的会自动转义特殊字符但手写SQL时就要特别注意使用CONCAT(%, #{keyword}, %)而不是% #{keyword} %在注解里拼接字符串后者会让MyBatis把整个表达式当成一个字符串Literal处理引发注入漏洞。7. 源码结构和文档接手这个项目的人该从哪看起7.1 后端包结构与启动说明后端项目的包结构按职责分层不花哨但很好找东西src/main/java/com/clinic/ ├── controller/ # 接口层接收参数、返回结果 ├── service/ # 业务层事务和核心逻辑都在这 ├── mapper/ # MyBatis-Plus的Mapper接口 ├── entity/ # 数据库实体类 ├── config/ # 配置类包括跨域、拦截器 └── common/ # 通用返回结果、异常处理、工具类后端启动基本就是maven clean package然后java -jar跑起来。需要改的配置都在application.yml里端口、数据库连接串、MyBatis-Plus日志等。我建议首次运行先用项目自带的demo数据账号密码都写在README里这样能快速验证环境是否正常。7.2 数据库脚本与演示数据的重要性sql目录下有两个文件schema.sql和data.sql。schema.sql是完整的建表语句data.sql是初始化数据包括几个测试账号、一批演示用的药品数据、少量示例患者记录。最初交付的时候我只给了schema.sql结果接手的人跑起来之后发现登录不上——因为用户表是空的。后来补上data.sql整个体验就顺畅多了。这里也想提醒做开源项目或者交git仓库的同学初始化数据至少要有几个明显标记的测试账号和基础字典数据让别人跑起来第一分钟就能看到界面而不是先花半天去研究怎么造数据。7.3 接口文档应该覆盖哪些内容接口文档不需要把每个方法都写一遍但关键业务接口一定要写清楚参数和返回示例。我在README里按模块列出了7篇较多的核心接口每篇包含请求地址、请求方式、请求体JSON示例、返回体JSON示例以及常见的业务异常码。这样不管是自己后来维护还是别人二次开发都能很快对上号。不少开发者在交付项目时压根不写接口文档这是个大坑——依赖源码里的注释去理解接口效率非常低。7.4 可以继续扩展的方向这个项目做完后我对它的定位一直很清醒它做的是诊所内部的信息化管理离完整的在线问诊平台还有距离。如果继续演进比较有价值的扩展方向有这么几个引入Redis缓存药品目录和字典数据减少数据库的查询压力。接入消息队列实现发药通知药师端实时感知新处方。搭载一个患者微信小程序端让患者自己去查报告和处方记录。增加出库统计和月度财务报表直接在系统里完成对账。这些方向在源码的架构下都有比较好的延展空间因为核心业务表的边界划得比较清楚扩展时不会牵一发动全身。最后分享一个我在做这类系统时比较深的体会技术选型也好、功能设计也好都要先问自己“业务上哪里会出问题”而不是“什么技术热用什么都行”。这套Java Vue的医疗问诊拿药系统技术上没有任何炫技的地方但它把诊所最在意的几件事——处方不能错、库存不能超卖、账目不能乱、记录不能丢——全部用数据结构和状态机设计兜住了。如果你打算拿这个项目练手我建议你不光看源码怎么跑起来更要重点关注第3章的表结构设计和第4章的事务、并发处理这两块才是真正值的沉淀的东西。实际运营了几个月整个系统没有出过一次对账差错我想这就是它最大的意义了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

一亩田客服咨询AI流量赋能,一亩田科技重塑智能体验新标杆 2026/9/30 11:05:45

一亩田客服咨询AI流量赋能,一亩田科技重塑智能体验新标杆

近期,由湖南改变生物科技有限公司主办、本因内酵未徕品牌协办的“生物科技健康论坛暨AI赋能大健康产业启动会”在长沙市步步高福鹏喜来登酒店隆重举行。活动以“AI流量赋能实体破局——中小企业增长峰会”为主题,汇聚全国大健康行业专家、中小企业负责人、机构代表及…

阅读更多 →
K-seal焊接单耳卡箍和传统喉箍、抱箍有什么区别?工业流体管路选型指南 2026/9/30 11:05:36

K-seal焊接单耳卡箍和传统喉箍、抱箍有什么区别?工业流体管路选型指南

一张维修单写“给管子配不锈钢抱箍”,却没说是要防漏、固定还是修补。仓库送来三种外观相近的金属环,现场人员都能把它们套在管上,效果却可能完全不同。选型第一步不是比哪只钢带更厚,而是弄清这只环究竟要承担什么力。 先分清三件…

阅读更多 →
AI Job Search 工作区 Agent 指南:基于 thin-pointer 设计的跨框架单一事实来源实践 2026/9/30 11:05:36

AI Job Search 工作区 Agent 指南:基于 thin-pointer 设计的跨框架单一事实来源实践

AI 应用AI 技能 【免费下载链接】ai-job-search The job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it. 项目地址: https://…

阅读更多 →
力扣第2题两数相加C++解法:链表遍历与进位处理详解 2026/9/30 11:05:35

力扣第2题两数相加C++解法:链表遍历与进位处理详解

讲真,力扣第2题“两数相加”几乎是所有C刷题人绕不开的一道题。它表面上是一道链表题,实际上却把“链表遍历、进位处理、边界控制、内存管理”这四件事全塞在了一个看似简单的模型里。很多人觉得这题简单,结果一提交就栽在“最后一个进位没加…

阅读更多 →
社区康养业务架构:健康记录、服务工单功能梳理 2026/9/30 11:05:35

社区康养业务架构:健康记录、服务工单功能梳理

社区康养业务架构:健康记录、服务工单功能梳理社区智慧康养系统的核心业务底座,由健康记录体系与服务工单体系两大核心模块构成。健康记录负责沉淀老人全生命周期健康数据,是精准康养服务的数据依据;服务工单负责落地所有居家上门…

阅读更多 →
Why Cannot Large Language Models Ever Make True Correct Reasoning? 2026/9/30 11:05:23

Why Cannot Large Language Models Ever Make True Correct Reasoning?

文章总结与翻译 一、文章主要内容 本文围绕“大型语言模型(LLMs)为何无法实现真正正确推理”展开研究,核心内容可分为以下五部分: 引言:指出当前以ChatGPT为代表的基于LLMs的AIGC工具应用中,许多专家与非专业人士夸大LLMs的“推理能力”,作者认为这是概念模糊导致的错…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉