资讯动态

用Qt与C++构建基层医疗电子健康档案系统的实践

发布时间:2026/10/21 18:34:42 来源:尧图企业网站定制
前阵子接了一个活儿给一家社区卫生服务中心做电子健康档案系统。说实话刚听到这个需求的时候我一度怀疑是不是听错了——现在谁还从头用 Qt C 从零搭医疗业务系统Web 技术都快把桌面端淹没了吧。但等我把现场跑了一圈把医生怎么开处方、护士怎么核药、档案管理员怎么应对上级检查这些流程捋完之后我反而觉得这个选择没那么离谱甚至可以说在很多基层医疗场景里桌面原生应用比浏览器方案更务实。这篇文章就把我完整做完这个 Qt C 电子健康档案系统的过程拆给你看包括选型逻辑、数据库设计、门诊核心流程、安全与审计、权限模型以及在真实部署中踩过的一些很具体的坑。不管是打算自研医疗内部系统还是做其他行业桌面业务软件里面这些思路大概率都能借鉴。1. 选型阶段为什么在 Web 包围圈里选了 Qt 和 C1.1 客户现场的四个约束条件先说说委托方的情况。这是一家规模不大的社区医疗服务中心大概几个诊室、一个药房、一个护士站加上行政和档案室一共十来号人。他们手里的老系统是一个单机版的 Excel 表格加 Word 文档管理方案病人信息靠手工编号处方靠手写检查报告靠纸质送检。每天的门诊量不算大但高峰期排队一多窗口登记的速度就成了瓶颈。我一开始也按惯性思维给他们推了一个 Web 方案前后端分离后端用 Java 或者 Go前端用 Vue 或者 React部署在内网服务器上浏览器访问。但到现场之后我发现几个非常实际的约束他们的“机房”其实就是档案室角落的一台旧电脑没有 UPS还经常跳闸Web 服务一断电全部终端跟着瘫。诊室里的电脑是好几年前的低配迷你主机浏览器同时开着病历列表、处方录入、检验单几个标签页风扇就狂转页面操作明显掉帧。有些医生年纪偏大对浏览器里“我不知道刚才点到哪里了”的体验非常抵触他们更习惯传统桌面软件的确定性一个窗口一个功能不会突然跳出一个新标签页。网络环境也很朴素整个中心就一个千兆交换机局部终端偶尔还会出现网口松动、IP 冲突的情况。Web 应用对网络状态的敏感度在这种现场就是灾难级别的。这些条件叠加起来我意识到这个场景里桌面端反而是最稳的解法。因为桌面应用天然具备离线可用、单机容错、启动速度快、界面状态可控这些特性。而我手头最熟的桌面技术栈就是 Qt 和 C。1.2 Qt 开发模式带来的几个实际收益Qt 在桌面 GUI 开发里属于老牌选手用 QWidgets 做医疗这种表单密集型界面我认为比 QML 还顺手。原因是医疗业务界面本质上是大量输入控件的组合病人基本信息、过敏史、诊断列表、用药明细、检查项勾选。QWidgets 的表单布局、Tab 焦点切换、信号槽联动写起来非常直接调试也方便。另一个实际收益是 model/view 架构。医疗数据里有很多大数据量表格场景——比如全院患者列表、某天的挂号流水、药品库存台账。如果用 QTableWidget 硬塞几万行界面会明显卡顿而用 QTableView 配合自定义 QAbstractTableModel配合排序过滤代理十几万行数据也可以做到流畅滚动。这一点到后面讲性能优化的时候我会再展开但选型阶段就要提前考虑因为你一旦选了错误的控件后面的返工成本很高。还有一点是部署成本。Qt 的应用程序是编译后的原生可执行文件依赖可以打包进安装目录不需要目标机器装运行库或中间件。客户端机器配置再低也能跑得起来。而且我可以直接在服务端装一套客户端通过网络共享访问同一个数据库文件或者干脆全内网局域网部署后期维护时我远程过去升级一个 exe 就完事。1.3 我给自己划定的项目边界做医疗系统最怕的就是需求无限蔓延。电子健康档案这个概念听起来不大但真要往深了做可以牵扯到电子病历、LIS检验系统、RIS影像系统、PACS、药品进销存、医保接口、区域卫生平台互联互通……任何一个模块都是无底洞。所以在这个项目启动之前我反复和委托方确认了边界最终锁定在下面几条主线上患者基础档案管理建档、编辑、查重、去重。门诊就诊流程挂号/分诊、医生看诊、处方开立、检查检验申请、护士执行、发药记录。电子病历文书门诊病历的结构化录入与打印。基础数据维护科室、医生、药品字典、检查检验项目字典。系统支撑用户权限、审计日志、数据备份、离线缓存。这个边界一画后面的数据表设计和模块拆分就清晰了。凡是“区域卫生平台”“医保实时结算”“影像归档”这些事我直接写进合同备注里作为后续扩展项不在本轮实现。给自己减负也给客户一个明确的预期。2. 数据库选型与基础表结构设计2.1 为什么选了 SQLite 而不是 PostgreSQL 或 MySQL说到数据库这个环节也纠结了一阵。最初考虑到未来可能往区域平台扩展PostgreSQL 是最“正确”的选择功能强、并发控制好、JSON 支持完善。但我最终还是把核心存储定在了 SQLite 上主要出于三个层面的判断第一并发规模。这个中心只有十来台客户端高峰期间同时写库的连接数不会超过十个典型的事务量对于 SQLite 来说根本不算压力。SQLite 的锁粒度虽然不如服务端数据库但只要用好 WAL 模式和忙等待超时单机多客户端完全扛得住。第二运维复杂度。让他们维护一个 PostgreSQL 服务端需要解决安装、服务自启、防火墙、账号密码、周期性 vacuum 一堆问题。而 SQLite 就是一个文件我写好备份脚本每天定时把这个文件复制走恢复的时候也是把文件拷回来整个院方信息人员如果那台旧电脑算运维的话很容易理解。第三断电容错。SQLite 的 WAL 模式对断电有很好的恢复能力核心数据文件不容易损坏。相比之下在低配 Windows 机器上跑一个数据库服务断电后数据文件损坏的概率反而更高而且恢复门槛高。当然我在设计的时候刻意把数据访问层做了接口化所有数据库操作都通过一个 Repository 层封装以后真要迁移到 PostgreSQL改写 SQL 方言就够了不会动到界面代码。-- 这里给出几个关键表的建表语句方便参考 CREATE TABLE t_patient ( id INTEGER PRIMARY KEY AUTOINCREMENT, patient_no TEXT NOT NULL UNIQUE, -- 内部档案号 id_card TEXT NOT NULL, -- 身份证号加密存储 name TEXT NOT NULL, gender INTEGER NOT NULL DEFAULT 0, -- 0男 1女 2未知 birthday TEXT, phone TEXT, allergy_history TEXT, -- 过敏史 address TEXT, create_time TEXT NOT NULL, update_time TEXT NOT NULL, status INTEGER NOT NULL DEFAULT 1 -- 1有效 0作废 ); CREATE TABLE t_visit ( id INTEGER PRIMARY KEY AUTOINCREMENT, visit_no TEXT NOT NULL UNIQUE, patient_id INTEGER NOT NULL REFERENCES t_patient(id), department_id INTEGER NOT NULL, doctor_id INTEGER NOT NULL, visit_time TEXT NOT NULL, status INTEGER NOT NULL DEFAULT 0, -- 0待诊 1就诊中 2已开单 3已完成 4已取消 create_time TEXT NOT NULL ); CREATE TABLE t_prescription ( id INTEGER PRIMARY KEY AUTOINCREMENT, visit_id INTEGER NOT NULL REFERENCES t_visit(id), drug_id INTEGER NOT NULL REFERENCES t_drug(id), dose TEXT NOT NULL, -- 单次剂量如5mg frequency TEXT NOT NULL, -- 频次如每日两次 days INTEGER NOT NULL, -- 用药天数 quantity REAL NOT NULL, -- 总数量 remark TEXT, create_time TEXT NOT NULL ); CREATE TABLE t_lab_order ( id INTEGER PRIMARY KEY AUTOINCREMENT, visit_id INTEGER NOT NULL REFERENCES t_visit(id), item_id INTEGER NOT NULL REFERENCES t_lab_item(id), status INTEGER NOT NULL DEFAULT 0, -- 0待取样 1已取样 2结果已出 sample_id TEXT, report_time TEXT, result_text TEXT, create_time TEXT NOT NULL );2.2 患者主数据模型档案号和身份证号的关系电子健康档案最核心的主数据就是患者。这里有个基础的业务细节容易踩坑档案号和身份证号绝对不能混为一谈。档案号是我们系统内部的自增编号或者规则编号它唯一的责任是保证每条记录在系统内部可引用而身份证号是患者身份的外键是跨系统、跨机构识别患者的依据。实际建模时我把身份证号设为逻辑唯一键但因为涉及加密存储这个细节下一节说没法直接在数据库上建立普通唯一索引来做查重。所以我在应用层维护了一个“身份证号指纹哈希表”用不可逆的摘要值做唯一约束这样既不暴露明文又能快速查重。查重的场景也很具体窗口护士建档时输入身份证号系统先在本地指纹表里一查立即提示“已有档案档案号xxx是否直接挂本次就诊”。这套逻辑对基层卫生服务中心特别重要因为患者不少是老年人名字在笔录里被写错是很常见的事但身份证号一般错不了。2.3 敏感字段加密身份证号不能躺在明文数据库里医疗档案涉及敏感个人信息就算系统只在单位内网跑我也坚持要把身份证号和手机号这类字段做字段级加密。理由很简单一个数据库文件如果被复制走明文数据等于拱手交给别人。我采用的方案是AES-256-GCM 对称加密每个患者的明文身份证号使用独立的随机密钥加密而这个患者的密钥再用系统主密钥加密保存。好处是即使某一个患者的密钥泄露也不会波及其他患者系统主密钥被单独保存在服务端的一个加密配置文件中权限只归系统管理员所有。在 Qt 环境下我使用的是 OpenSSL 的 EVP 接口避免自己包装密码学逻辑。实际封装出来就是一个encryptField/decryptField两个函数调用点在 Repository 层// 伪代码示例密码学细节由 OpenSSL EVP 封装业务层不接触 QString CryptoService::encryptField(const QString plainText, const QByteArray dataKey) { // 1. 生成 12 字节随机 nonce // 2. EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm()) // 3. 写入密文 认证标签 // 4. 返回 Base64(nonce ciphertext tag) } QString CryptoService::decryptField(const QString sealedText, const QByteArray dataKey) { // 解码后验 tag校验失败则返回空串并记审计日志 }解密只发生在医生用工作台打开患者详情的那一瞬间而且界面控件只保留内存明文关闭窗口立即清除。这虽然会带来一点性能开销但对单机查询场景毫秒级延迟完全无感。3. 门诊业务核心流程从建档到发药的状态机设计3.1 窗口建档与挂号一条龙和查重系统第一个高频入口是挂号窗口。实际操作中患者到窗口后护士做的动作可能是“建档挂号”一气呵成也可能只是“复诊挂号”。所以我把建档和挂号拆成两个独立服务但界面上做成一个向导建档界面录入完会立刻进入挂号的科室选择、医生选择和号别选择。这段业务里比较关键的设计点是重复挂号和爽约处理。门诊不是预约制当天多次来诊属于正常现象所以同一天内同一患者可以挂多次号但每次 visit 有唯一的 visit_no。另外医生休诊的时候必须在挂号界面直接置灰不能等挂完号到了诊室才发现没人这个数据从科室排班表实时拉过来。挂号完成后visit 状态为“待诊”。此时患者的信息会通过 Qt 信号槽机制推送到对应诊室的医生工作台待诊列表里。3.2 医生工作台结构化病历、处方开立和检查申请医生工作台是整个系统里我投入精力最多的部分。布局上左侧是当前科室的待诊患者列表右侧是选中患者的完整就诊页签。就诊页签里又分成四块病历编辑区、诊断列表区、处方明细区、检查检验申请区。病历编辑区我用的是结构化录入而不是自由文本框。预置了主诉、现病史、既往史、体格检查、初步诊断这几个段落每段一个多行编辑框打印时拼成标准门诊病历版式。这个取舍非常实用——医生录入速度快打印格式又统一还方便以后按字段检索历史病历。如果你一上来就是一大块富文本后续做数据统计和分析的时候会很被动。处方开立这块药品字典是提前用 Excel 导入的一张表包含药品名称、规格、厂家、单位、默认剂量、默认频次、库存数量。医生输入药品名称首字母拼音缩写自动提示下拉选中后带出默认剂量和频次只需调整天数、总量和备注。我再三提醒自己医生没时间敲全名检索体验直接决定这个系统是“助力”还是“负担”。检查检验申请则是勾选式操作从检查项目字典里挑项目系统自动生成申请单号。检验项目会让护士站打印条码贴在采样管上检查项目则打印导引单给患者去对应的功能检查室。当医生保存完病历并开完处方/检查单后点击“完成就诊”visit 状态变为“已开单”。这些单据通过信号槽广播到收费/护士站模块实现前端各岗位的数据联动。3.3 护士站执行闭环从处方到发药的审计链护士站负责的部分是执行闭环。处方从医生端推送到药房后药师做一次“四查十对”式的核对然后点击“发药确认”系统记录发药时间和操作人。检查检验单推送过来后护士打印条码、取样、送检检验结果回来后在系统中录入或导入结果状态流转为“结果已出”。这个流程我用一个很简单的状态机来覆盖所有单据都有一张状态字段状态迁移是靠服务层方法而不是界面随意改处方待审核 → 已审核 → 已发药 → 已退药检查申请待预约 → 已预约 → 已执行 → 已出报告检验申请待取样 → 已取样 → 检验中 → 结果已出状态机的价值在后期维护时才体现出来。现场人员操作水平参差不齐如果每个窗口都直接改状态字段最后查账时数据全乱了。统一走服务方法后每个状态迁移可以附加权限校验、前置条件校验和审计日志管理成本低很多。代码层面我用了一个简单的枚举加服务类来做这个事class ClinicalFlowService { public: enum class RxStatus { PendingReview 0, Reviewed, Dispensed, Returned }; enum class LabStatus { PendingSample 0, Sampled, Testing, ResultReady }; bool reviewPrescription(int prescriptionId); bool dispensePrescription(int prescriptionId, int operatorId); bool updateLabResult(int labOrderId, const QString resultText); };发药时还会联动扣减药品库存。这个动作要放在事务里避免出现“处方已发但库存没扣”的脏数据。SQLite 的事务我是用显式BEGIN IMMEDIATE启动的保证多客户端同时操作时不会互相死锁。4. 权限、审计与备份医疗系统绕不开的三件事4.1 角色权限模型账号、角色、权限三张表医疗系统的权限设计不能随便来每个岗位能看到的患者信息范围是严格受限的。我用的是经典 RBAC用户表、角色表、权限表外加用户—角色关联和角色—权限关联。具体到角色我划分了系统管理员、挂号/收费员、医生、护士/药师、档案管理员、只读用户给来检查的领导或者驻场工程师用这六类。权限粒度控制在模块级别加操作级别患者档案查询受范围限制、新增、编辑、作废门诊业务挂号、接诊、开处方、发药、退药、执行检查基础数据维护药品字典、维护检查项目、维护科室医生系统管理用户管理、操作日志、数据备份权限校验放在服务层而不是界面层这样能防止绕过界面直接调服务的风险。界面上只是通过 Qt 的信号槽机制动态禁用菜单和按钮而已。bool PermissionService::check(UserRole role, Permission perm) { // 查内存缓存里的角色-权限映射表 // 返回 true/false并记录一次未授权访问尝试审计事件 }4.2 审计日志为什么每个写操作都必须留痕医疗数据是强审计对象这是医疗行业的基本常识。我在系统里设计了统一的审计日志表所有关键业务操作——建档、修改档案、登录、发药、退药、导出数据、删除记录——都会写一条审计记录内容包括操作人 ID、操作时间、操作类型、目标对象类型、目标对象 ID、操作前后的关键摘要以及客户端 IP。为了不污染业务主流程、不让审计拖慢事务日志写入我放到事务之外用异步队列提交。如果审计表写入失败界面照常执行但在内存日志里记为警报并由后台线程重试。一个很有用的细节是审计日志表本身要做成只追加、不可修改。我的做法是审计表的 UPDATE/DELETE 权限在数据库连接层面就默认拒掉应用层根本不提供编辑审计日志的方法。这样即使有人拿到数据库想抹掉痕迹也必须直接操作文件难度和风险都高了很多。4.3 备份恢复一个文件搞定省去机房工程SQLite 的备份策略异常简单但也最容易被人做错。很多人直接复制数据库文件忘了 WAL 模式下还有一个-wal文件没复制导致恢复出来的库丢掉最近一笔事务。正确的做法是用 SQLite 的VACUUM INTO命令生成一个一致性的全量备份文件或者用在线备份 API。我设计的是每天营业结束后自动执行一次全量备份保留最近 30 天的备份文件另外在每次发药、建档这类关键写操作后把 WAL 文件同步 checkpoint。备份文件放到另一台存储机器上避免主机硬盘坏了全完蛋。恢复演练这个事我在上线前做了两遍。第一次直接停服恢复发现有个备份文件损坏了原因是备份时机正好赶上某客户端正在写库。改成VACUUM INTO之后再也没出现过这种问题。你好我好大家好但“备份后要恢复演练一次”这句话真是拿数据买来的教训。5. 实际部署中反复折腾我的几个坑前面说的都是架构和业务上的设计转眼调到现场真正让程序员掉头发的往往是几个非常琐碎的基础问题。5.1 高 DPI 屏幕带来的界面错位现场有一台新配的电脑是 2K 分辨率系统缩放比例设置的 125%。Qt 5.15 在默认情况下对高 DPI 支持要额外设置否则会出现界面布局错乱、字体发虚、点击位置偏移这些现象。我最终是启动时显式设置了几个属性int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); // ... 其余初始化 }同时所有界面不再写死固定数值的setFixedSize改用布局器加最小尺寸约束。字体方面也改用 pt 而不是 px让字体随 DPI 自适应。这一套改完之后2K 屏和老旧的 1366×768 屏都能正常显示不用再针对每台机器单独调。5.2 中文输入法导致的编辑框状态错乱这个问题如果不在医疗系统现场很难撞见。有些科室电脑装的是第三方中文输入法在医生切换病历多行编辑框时偶尔出现输入法状态卡住、要么打不出中文、要么候选框跑到屏幕另一个角落的诡异情况。排查过程中我锁定到一个根因医生在编辑框之间快速切换焦点时Qt 的 focusOutEvent 和输入法上下文切换有竞争条件。后来我做了两个处理一是所有编辑框在focusInEvent里重置输入法上下文避免上次输入状态的残留二是在窗口失活比如医生切出去查资料又切回来时强制刷新焦点。void DoctorNoteEdit::focusInEvent(QFocusEvent *event) { QWidget::focusInEvent(event); QInputMethod *im QGuiApplication::inputMethod(); im-reset(); im-update(Qt::ImQueryAll); }这个改动很小但在现场直接解决了“时不时打不出汉字”的问题。要知道医生如果被输入法卡住两分钟他对整个系统的信任就崩塌一半。5.3 大数据量加载导致的界面假死和内存暴涨老患者数据量上来以后挂号窗口的“历史患者查询”开始变得迟钝。最多的时候是查询整个院区的几万条档案记录如果直接把结果塞进QTableWidget几万行的QTableWidgetItem创建和布局计算直接把 UI 线程卡死三五秒这在白大褂排队的现场是不可接受的。我最后重构了这部分的列表组件用的是QTableView QAbstractTableModel QSortFilterProxyModel。数据查询先在后台线程拿一个只读的快照然后分页加载到 model 中滚动时再按需填充。排序和过滤交给 ProxyModel不重启查询。这样即使过滤出上万行滚动依然丝滑。这里有个非常关键的原则UI 线程里永远不做耗时数据库查询和复杂模型构建。我把耗时操作全部放进QtConcurrent::run或者一个工作线程完成后通过信号槽把结果回传到 UI 线程再刷新视图。这样不光是档案列表包括药品库存查询、流水报表这类所有大数据量场景都统一遵守这个约定。5.4 打印门诊病历和处方笺的版式差异医疗系统的另一座大山是打印。不是打印功能复杂而是打印机型号和纸张规格五花八门。普通病历纸是 A4但处方笺可能是宽度只有 210mm、长度不固定的连续纸有的还是带孔的针式打印机专用纸。我用QPrinter做了两套打印模版一类走标准 A4一类走自定义纸张尺寸。处方打印的关键细节是必须按纸张宽度换行否则药名一长处方笺上就出现截断。这个其实用QTextDocument配合 CSS 式的宽度设置就能搞定关键是不要在print()之后才发现内容溢出一定要先用QPageSize指定好页面大小。另外针式打印机的进纸方向对打印距离有硬性要求我的经验是处方打印不要走 Qt 默认的居中模式而是基于打印机自定义进纸偏移量做参数配置把偏移量配置到系统设置里现场换纸型时不用改代码。6. 上线之后我的几点复盘和真正值得注意的事6.1 培训永远比开发重要系统开发完只是开始。上线前我用两周时间做了三轮培训第一轮给窗口护士和药师第二轮给医生第三轮给管理员。每一轮都拿真实脱敏数据演练让每个人亲手走一遍“建档→挂号→看诊→开药→发药”全流程。最大的体会是基层系统的失败往往不是功能不够而是用户不会用或者不敢用。所以我特意把所有确认按钮的文案改成口语化表达比如“保存并结束就诊”而不是“提交”所有会删除/作废数据的操作都点了之后先弹确认框里面直接告知“此操作不可恢复”。6.2 医疗软件的一票否决项数据不能丢责任能追溯复盘整个项目我始终把两件事放在最优先级不丢数据、能追责任。功能上有些东西可以上线后迭代但数据完整性和审计可追溯性在第一天就得做对。这也是我在架构里反复强调事务、备份和审计日志的真正原因。给同样想做这类桌面医疗系统的朋友一个建议如果你面对的客户机性能捉急、网络不稳定、操作人员年纪偏大那就别执着于 Web 那套华丽交互了。一个编译好的原生 exe配合本地文件型数据库做一个离线优先、在线同步的系统往往是现场成功率最高的方案。Qt C 可能不如时髦框架那样自带流量但它在这个场景里就是那个“皮实耐操”的家伙。上线三个月系统到现在没有出过一次数据损坏或丢单事故光这一点我觉得就值回所有开发成本了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑