医院住院信息管理系统三层C/S架构设计与实现
发布时间:2026/9/19 0:45:38 锦皓数字建站

简介一份面向医疗信息化开发者和系统设计学习者的医院住院信息管理系统设计文档。系统围绕出入院管理、医嘱录入、发药计费等核心业务展开详细阐述了从需求分析、方案论证到系统实现的完整构建过程。文档重点介绍了表示层、业务逻辑层、数据访问层的三层架构设计并结合UML建模、分布式计算等关键技术分析了出入院收费、病区管理、中心药房、西药库等子系统的功能结构与数据流。针对各业务环节存在的问题文档还给出了具体的系统目标与建议方案有助于理解医院住院流程的信息化改造思路。资源为单个doc文档压缩包大小约4.96MB内容结构清晰适合作为医院信息系统课程设计或相关项目开发的参考资料。目前已有571人学习下载其系统性论述对理解医疗信息化系统的规划设计具有实用价值。1. 为什么一家二甲医院最终选择了三层C/S架构在医院信息化里住院病区管理一直是比门诊更“重”的部分病人从入院登记到医嘱执行、中心药房发药、费用日结中间隔了四五个部门信息靠服药本和治疗单手手传递。这套医院住院信息管理系统给出了一个不太“时髦”但很务实的答案——用 Delphi 5 与 SQL Server 2000 构建三层 C/S 架构把出入院收费、病区医嘱、中心药房发药和西药库四个业务域串成一条信息链。对还在用 C/S 模式做医院信息系统的团队来说这份文档的价值不只是业务代码而是把需求分析、UML 建模、分布式运算落到真实场景的完整过程。下面按需求、架构、实现、部署的路径拆解这份设计中间会给出表结构、存储过程和排错经验。2. 需求分析与UML建模医嘱、发药、计费的边界怎么划一份医院信息系统的设计文档最容易翻车的地方不是代码而是业务边界。这个项目把需求分析占了全文接近三分之一的篇幅而且用了大量用例描述来固定各子系统的行为原因很简单住院业务链跨出入院收费处、病区、中心药房、西药库四个部门任何一个功能点归属不清后期就会出现“医嘱录了没人发药、费用算了没人结算”的断链事故。2.1 手工流程里的四个信息孤岛原始的流程是纯手工的。出入院收费处根据病区送来的医嘱手算费用但它并不掌握医嘱的实际执行情况病区护士把长期医嘱抄到服药本、把临时医嘱写到治疗单再送到中心药房领药中心药房被动地按单发药发出去的药是否用完、有没有退回完全不知道西药库对药房库存和临床用药消耗也无从掌握。这四个环节彼此靠纸质单据传递速度快不起来差错也追不到源头。文档把它们总结为“信息孤岛”系统建设的首要目标就是在四个部门之间建立一条自动化的信息通道。2.2 用UML用例图锁定系统边界用 UML 做需求分析在这个项目里的价值是把抽象的“提高管理水平”翻译成可验收的功能条目。每个用例都有参与者、目的、典型事件流程并且直接关联需求编号R1.x 对应出入院收费处、R2.x 对应病区、R3.x 对应中心药房。这样设计评审时医生、收费员、药师坐在一张桌上对着用例文本逐条确认不会反复吵功能归属。2.2.1 “办理入院”用例中的关键约束以“办理入院”这个主用例为例参与者是住院病人和收费员典型流程是收费员按姓名或住院号检索病人资料若系统里已有记录则复用没有则新录随后安排住院病区和入院时间系统保存入院记录并通知病区管理系统接收。这里面有两个容易被忽略的点一是住院号是病人的唯一标识重复住院的病人要复用上一次的住院号二是病人状态是流转的从“未入院”到“住院”再到“阶段结算”“办理出院”“出院”每一步都有状态机约束。状态字段如果设计成自由文本后面结算逻辑一定会出错。2.2.2 “医嘱核对与录入”用例中的隐藏需求病区录入医嘱后出入院收费处还有一个“核对医嘱”的动作收费员调出病人已有的医嘱与纸面医嘱逐条比对发现漏录的补录发现多录的走费用冲正。这里埋了一条重要规则——已确认的医嘱记录不允许删除和修改只能通过冲正来反向调整。这是从财务审计角度定的规矩防止有人直接改数据掩盖差错。系统设计时把这类标记为“隐藏的”需求恰恰是最需要写进代码约束的部分。2.3 活动图与信息交换的主路径把各用例串起来系统的活动主路径很清晰办理入院 → 病区接收 → 录入医嘱 → 确认医嘱 → 生成发药记录 → 中心药房发药 → 记录治疗执行 → 计算费用 → 费用结算 → 办理出院。这条路径决定了数据表的读写顺序也决定了哪些操作必须同步、哪些可以异步。核心用例主导角色关键需求约束办理入院收费员住院号唯一复用旧号病人状态流转录入医嘱病区护士长期医嘱有停止时间临时医嘱只执行一次核对费用收费员不可删除医嘱只可冲正费用结算收费员可中途结算按时间范围筛选中心药房发药药师缺药标记发药后扣减库存对应到这个项目的实现病区管理应用服务器暴露给客户端的服务接口大致可以抽象如下type // 病区管理应用服务器暴露给客户端的核心接口 IWardService interface function ReceivePatient(APatientID: string): Boolean; // 从出入院系统接收病人 function AddOrder(AOrder: TOrderRec): Boolean; // 新增医嘱未确认状态 function ConfirmOrder(AOrderID: Integer): Boolean; // 确认医嘱生成执行依据 function GenerateDeliveries(AWardCode: string; ADate: TDateTime): Integer; // 生成当日发药记录 function GetPatientCharges(APatientID: string): OleVariant; // 查询病人费用 function ReverseCharge(AChargeID: Integer; AQty: Double): Boolean; // 费用冲正 end;这段接口定义说明了三层架构中应用服务器的职能边界客户端不直接写库所有业务动作接收病人、确认医嘱、生成发药记录都封装在服务接口后面。其中GenerateDeliveries返回的是本次生成的发药记录条数客户端拿这个数去提示护士“今日需领药 X 条”这是一个很实用的交互设计。3. 三层C/S架构与数据库设计MIDAS、BDE与表结构这套系统在选型上没有追新用的是当时 Windows 平台上相当务实的三层 C/S 方案客户端用 Delphi 5应用服务器用 MIDAS 做中间层数据库用 SQL Server 2000。它选三层结构的原因文档里写得直白——两层 C/S 在用户数增长后效率下降中心服务器和网络压力大而且客户端业务升级要逐台安装新程序。中小医院资金紧张希望通过较低配置的硬件获得稳定的业务支撑。3.1 两层C/S的问题连接数、发布成本与业务逻辑散落两层 C/S 架构里每个客户端直接通过 BDE 连接数据库。假设住院部有 20 台客户端数据库就要同时维持 20 条以上连接再加上后台统计、报表任务SQL Server 的连接池很容易被打满。更要命的是费用计算、库存扣减这类核心业务逻辑如果写在客户端窗体里一旦规则调整比如计价方式改变就得给每一台终端重新安装程序。文档里提到“程序的发布也较麻烦”指的就是这个场景。三层结构的做法是把这些规则全部收拢到应用服务器客户端只负责展示和输入。数据访问通过 MIDAS 的远程数据模块完成客户端使用 SocketConnection 或 DCOMConnection 连接应用服务器应用服务器再通过 BDE 访问数据库。中间层成为唯一与数据库对话的进程连接数被压缩到服务器进程级客户端本身不持有数据库账号和密码。3.2 应用服务器与客户端的职责划分层次技术组件职责变更影响客户端Delphi 窗体 SocketConnection界面展示、输入校验、调用远端服务仅界面改动时应用服务器MIDAS 远程数据模块 业务服务接口医嘱规则、发药生成、费用计算、库存扣减业务规则变更时集中更新数据库SQL Server 2000数据持久化、存储过程、事务与约束结构变更需评估全链路这种分层在实际开发里带来的直接好处是收费处的“核对医嘱”和病区的“录入医嘱”可以并行开发两边的开发进度只依赖服务接口定义不依赖对方窗体代码。业务规则变更时只需更新应用服务器上的中间层组件客户端程序即便不动也能拿到新逻辑前提是接口签名没有破坏性变化。3.3 数据库设计以病人、医嘱、费用三张表为骨架数据库设计是这份文档里信息量最大的部分。整体可以看成三个业务域人员病人、操作员、临床医嘱、治疗记录、财务费用、押金、收据、结算外加药品药品资料、入出仓、库存。核心的三张表是病人表、医嘱表和费用表几乎所有业务流程都围绕它们展开。3.3.1 病人表与医嘱表的结构CREATE TABLE PATIENT ( PATIENT_ID CHAR(10) NOT NULL, -- 住院号全院唯一 PATIENT_NAME VARCHAR(20) NOT NULL, -- 姓名 SEX CHAR(2) NULL, -- 性别 BIRTH_DATE DATETIME NULL, ID_CARD_NO VARCHAR(18) NULL, -- 身份证号 MEDICAL_HISTORY VARCHAR(200) NULL, -- 既往病史摘要 CONSTRAINT PK_PATIENT PRIMARY KEY (PATIENT_ID) ); CREATE TABLE ORDERS ( ORDER_ID INT IDENTITY(1,1) PRIMARY KEY, PATIENT_ID CHAR(10) NOT NULL, -- 关联住院号 ORDER_TYPE CHAR(1) NOT NULL, -- L长期医嘱 T临时医嘱 ITEM_CODE VARCHAR(10) NOT NULL, -- 治疗项目代码对应价表 DOSAGE NUMERIC(10,2) NULL, -- 单次剂量 FREQUENCY VARCHAR(10) NULL, -- 频次如 QD/BID/TID START_DATE DATETIME NOT NULL, STOP_DATE DATETIME NULL, -- 长期医嘱的停止时间NULL表示执行中 DOCTOR VARCHAR(20) NULL, -- 开嘱医师 NURSE VARCHAR(20) NULL, -- 核对执行护士 STATUS CHAR(1) NOT NULL DEFAULT 0, -- 0未确认 1已确认 2已停止 CONSTRAINT FK_ORDERS_PATIENT FOREIGN KEY (PATIENT_ID) REFERENCES PATIENT(PATIENT_ID) );两张表的关键字段需要解释一下。PATIENT_ID用定长CHAR(10)而不是自增整数是刻意为之——住院号既是业务编号又是关联键重复住院的病人要复用旧号自增列无法表达这种业务语义。ORDERS表的ORDER_TYPE把长期医嘱和临时医嘱从数据层分开长期医嘱有STOP_DATE、临时医嘱没有这个差异直接决定后面发药记录怎么生成。STATUS字段的三态设计也很重要未确认的医嘱不能产生费用确认后才进入执行流程停止的长期医嘱不再参与每日发药。3.3.2 费用表与“结算标记”的作用CREATE TABLE CHARGES ( CHARGE_ID INT IDENTITY(1,1) PRIMARY KEY, PATIENT_ID CHAR(10) NOT NULL, ORDER_ID INT NULL, -- 关联产生该费用的医嘱 ITEM_CODE VARCHAR(10) NOT NULL, -- 计价项目代码 ITEM_NAME VARCHAR(30) NOT NULL, -- 项目名称含规格 UNIT_PRICE NUMERIC(10,2) NOT NULL, -- 单价取费用发生当日价格 QUANTITY NUMERIC(10,2) NOT NULL, -- 数量 FEE_DATE DATETIME NOT NULL, -- 费用发生日期 SETTLE_FLAG CHAR(1) NOT NULL DEFAULT 0, -- 0未结算 1已结算 2已冲正 SETTLE_NO VARCHAR(20) NULL, -- 结算单号一次结算一个号 CONSTRAINT FK_CHARGES_PATIENT FOREIGN KEY (PATIENT_ID) REFERENCES PATIENT(PATIENT_ID) );费用表里值得注意的细节是UNIT_PRICE存的是费用发生当天的价格而不是当前价。医院调价是常态药品调价后库存差价需要单独统计如果费用表只存项目代码不存价格快照后续追加的费用会和实际记账对不上。SETTLE_FLAG配合结算单号把“已结算”“未结算”“已冲正”分开支持病人中途结算——只结算某个时间段内的费用其余费用留待下次。这个设计比简单的“收支两条线”更能适应现实中病人“今天只结一部分”的诉求。4. 核心业务实现医嘱录入、发药驱动与费用结算逻辑如果说需求分析定的是“做什么”这一章解决的是“怎么做才不出错”。住院信息管理系统最核心的三个动作是医嘱录入、发药驱动、费用结算三者串起来就是病人在院期间的主线业务。下面的实现细节以病区管理子系统和出入院收费处子系统的交互为焦点。4.1 医嘱录入长期与临时的分流以及附加项目自动带出病区护士录入医嘱时首先要区分长期医嘱和临时医嘱。长期医嘱每天执行要有停止时间临时医嘱只执行一次没有停止时间。这个区分不是界面上的一个单选按钮那么简单它决定了后续发药周期、费用计算范围。长期医嘱的治疗一天执行一次早上新开的口服药医嘱在中午送服药本到中心药房下午领药执行下午新开的长期医嘱则通过治疗单立即领药保证当天能开始治疗。4.1.1 附加项目自动生成的实现医嘱录入还有一个让新手容易漏掉的环节附加治疗项目。比如“静脉注射”这个项目本身有材料费和注射费开注射医嘱时系统要自动把注射费加进去不需要护士单独再录一条。常见做法是在治疗项目字典表里配置EXTRA_ITEM字段录入时根据主项目的附加项代码联动生成子费用。// 医嘱录入时自动生成附加收费项目 procedure TfrmOrderInput.DoAfterItemSelected; var MainItem, ExtraItem: TItemRec; begin // 根据项目代码加载治疗项目主记录 MainItem : LoadItem(edtItemCode.Text); if MainItem nil then begin ShowMessage(项目代码不存在或已停用); Abort; end; // 主项目有附加项目例如注射费、材料费时自动带出 if MainItem.ExtraItemCode then begin ExtraItem : LoadItem(MainItem.ExtraItemCode); gridAttachment.Append; gridAttachment.FieldByName(ITEM_CODE).AsString : ExtraItem.ItemCode; gridAttachment.FieldByName(ITEM_NAME).AsString : ExtraItem.ItemName; gridAttachment.FieldByName(PRICE).AsFloat : ExtraItem.Price; gridAttachment.FieldByName(QTY).AsFloat : 1; // 附加项目数量默认1 gridAttachment.Post; end; end;这段代码的逻辑是录入主项目后立即查询项目字典若发现配置了附加项目就在明细网格里自动追加一条并带入价格。附加项目的数量默认 1实际可按执行次数修正。这样能把护士从繁琐的重复录入中解放出来同时保证费用不漏收对应文档里的需求 R2.3。4.1.2 医嘱确认与执行记录的关系医嘱录入后不能直接生效需要经过确认。未确认的医嘱不产生发药记录、不参与费用计算确认后长期医嘱进入每日发药队列临时医嘱立即生成治疗单。确认操作同时记录核对护士签名作为执行责任凭证。这与原始流程中“护士核对后执行”的习惯是一致的只是把纸质签名换成了系统记录。4.2 长期医嘱的日发药驱动频次换算与去重长期医嘱的发药是按“每日一发”的节奏走的。病区护士每天驱动一次发药生成系统把当日所有有效的长期医嘱汇总成发药记录送到中心药房。这里的难点在于频次换算医嘱里记录的是一次剂量和频次发药量需要换算成日总剂量。频次含义日剂量计算实际场景QD每日一次剂量 × 1晨起口服药BID每日两次剂量 × 2早晚各一次TID每日三次剂量 × 3三餐后服用PRN按需不参与每日批量发药临时按需给药批量生成发药记录的存储过程采用“按需插入、记录级去重”的策略CREATE PROCEDURE sp_GenerateDailyDelivery WardCode CHAR(4), DeliveryDate DATETIME AS BEGIN SET NOCOUNT ON; -- 插入前先排除当天已生成过发药记录的医嘱避免重复发药 INSERT INTO DELIVERY (ORDER_ID, PATIENT_ID, DELIVERY_DATE, DELIVERY_QTY, STATUS) SELECT O.ORDER_ID, O.PATIENT_ID, DeliveryDate, CASE O.FREQUENCY WHEN BID THEN O.DOSAGE * 2 WHEN TID THEN O.DOSAGE * 3 ELSE O.DOSAGE * 1 -- QD 及未定义频次按每日一次处理 END AS DAILY_QTY, PENDING -- 待中心药房发药 FROM ORDERS O WHERE O.ORDER_TYPE L -- 仅长期医嘱 AND O.STATUS 1 -- 已确认 AND O.START_DATE DeliveryDate AND (O.STOP_DATE IS NULL OR O.STOP_DATE DeliveryDate) AND NOT EXISTS ( SELECT 1 FROM DELIVERY D WHERE D.ORDER_ID O.ORDER_ID AND D.DELIVERY_DATE DeliveryDate ); RETURN ROWCOUNT; END;存储过程的核心是 WHERE 子句里的三重过滤医嘱类型、确认状态、有效期。NOT EXISTS子查询是防重复的关键无论护士当天点了多少次“生成发药”同一张长期医嘱只会生成一条发药记录。实际使用中可以把ROWCOUNT的值传到客户端显示“今日生成 X 条发药记录”护士据此核对是否有遗漏。发药记录生成后中心药房按单发药发药完成回写DELIVERY.STATUS遇到缺药时置为缺药标记并通知病区改嘱。4.3 费用计算与冲正不能删只能反向调整住院费用产生于治疗执行。口服药在发药后计费注射类在确认执行后计费床位费按日自动生成。出入院收费处计算费用时不是把病区传来的数据直接求和而是要经过一个“核对”流程收费员调出病人的全部医嘱和已产生费用与纸面医嘱比对漏了的补开医嘱多了的走费用冲正。CREATE PROCEDURE sp_SettlePatientCharge PatientID CHAR(10), StartDate DATETIME, EndDate DATETIME, SettleNo VARCHAR(20) AS BEGIN BEGIN TRANSACTION; -- 锁定该病人未结算的费用记录 UPDATE CHARGES SET SETTLE_FLAG 1, SETTLE_NO SettleNo WHERE PATIENT_ID PatientID AND SETTLE_FLAG 0 AND FEE_DATE BETWEEN StartDate AND EndDate; -- 汇总应收金额 SELECT SUM(CASE WHEN SETTLE_FLAG 1 THEN UNIT_PRICE * QUANTITY ELSE 0 END) AS TOTAL_FEE, COUNT(*) AS SETTLE_COUNT FROM CHARGES WHERE PATIENT_ID PatientID AND SETTLE_NO SettleNo; COMMIT TRANSACTION; END;这里的事务边界是有讲究的UPDATE 和 SELECT 放在同一个事务里确保结算金额对应的恰好是被标记为已结算的那批记录不会出现“数算完了标记没写上”或反过来。费用冲正则单独设计——针对已被计费的错误医嘱新增一条数量为负、标志为已冲正的记录原始记录保留不动。这样任何时候追溯费用明细都能看到“为什么会产生这笔费用、后来又冲掉了什么”的完整链条。4.4 中途结算与出院结算的差异处理现实中总有病人住院时间很长押金用完了先结一部分、后续再结。系统支持按时间范围做中途结算收费员选定起止日期只对该区间内的费用做收款剩余费用保持未结算状态。出院结算的逻辑是中途结算的特例——病人状态必须是“办理出院”且所有费用必须结清后才能置为出院。这个状态约束能挡住一个经典错误病人费用还没结完系统就放行出院出院后账目变成死账。所以结算模块里出院动作前必须重新校验一次SUM(未结算费用) 0。5. 部署与调优MIDAS连接配置与并发性能验证5.1 客户端连接配置与MIDAS运行库客户端要能连上应用服务器才能访问业务数据。使用 SocketConnection 时需要确保应用服务器上的 SocketServer 监听端口默认 211可用客户端的Host指向应用服务器 IP。配置示例with SocketConnection1 do begin Host : AppServerIP; // 应用服务器地址 Port : 211; // MIDAS Socket 默认端口 ServerName : HospitalSvr.rdmWardApp; Connected : True; end;客户端连接报“远程服务器无响应”时先查三件事应用服务器上的 SocketServer 是否启动、中间层服务是否注册运行一次TRegSvr.exe、客户端运行目录是否放了 MIDAS.dll。5.2 性能指标怎么验证文档给出的性能指标只有落到测试场景上才可验收。我一般会按下面的口径组织测试指标场景要求验证方式并发客户端数至少 5 台病区不少于 20 台压测脚本模拟 20 个 Socket 连接同时登录打开病人医嘱≤ 2 秒记录从点击到医嘱网格刷新的时间病区全量发药生成≤ 1 分钟选取医嘱最多的病区记录存储过程执行时间单个病人费用计算≤ 10 秒峰值 ≤ 1 分钟对住院 30 天以上的病人执行结算存储过程20 台客户端同时用 211 端口连应用服务器是这套架构最常见的瓶颈。如果出现连接延迟先把 SocketServer 的最大连接数调大再在应用服务器上开启 BDE 的连接池减少反复建连的开销。还有一个容易踩的坑Windows 防火墙要放行 211 端口否则客户端换个网络环境就“突然连不上”。提示上线前做一次“退药链路”验证——录入一张长期医嘱确认后生成发药记录模拟中心药房缺药退回再看库存和费用两条链路的数据是否同步。这个验证通过说明发药、计费、库存三个模块的事务边界是对的。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。