简介:这是 OpenMRS 开源企业电子病历系统的独立部署版本 2.3.1,面向医院、诊所及医疗信息化开发与运维人员,用于在资源受限或常规办公环境中快速搭建可扩展的病历管理平台。整个 zip 包共 15 个文件,整体约 394.68MB,涵盖核心 jar/war 程序、properties 与 xml 配置、Unix 启动脚本、tomcat/webapps 目录结构、空库与示例数据库以及说明文档,可帮助使用者快速理解系统组成并完成本地化部署。截至目前已有 591 人学习下载。借助该资源可实际体验 OpenMRS 的模块化扩展能力、多语言支持、患者登记、病史记录、处方与实验室结果管理等核心功能,同时掌握 standalone 模式的轻量部署方式;对需要评估开源电子病历方案或从事基层医疗信息化建设的技术人员来说,是一份可直接对照操作并二次开发的完整素材。通过示例数据库与空库,还能快速验证业务流程、准备初始数据,为后续功能扩展提供直接起点。1. 先拆开这个压缩包:为什么医院信息科会把它当原型底座某区级医院信息科联系我问,OpenMRS 这个开源企业电子病历系统,能不能在他们一个外科病区先做小范围试点。我的习惯是先拆包再回答:这个压缩包的核心其实就一个可执行 jar,内部自带嵌入式 Tomcat 和 H2 数据库,常用模块也预装好了。也就是说,你不用先搭 MySQL、不用配 Tomcat,一条命令起来就是一套完整病历系统。它特别适合四类人:想快速验证病历流程的信息科、带学生看数据模型的老师、做单机离线病历的基层诊所、研究开源病历代码的开发者。它不是演示壳,患者档案、就诊记录、临床表单、概念字典这些重功能都在,只是默认跑在嵌入式数据库上,部署成本压到了最低。这个定位决定了后面所有操作方式:把它当“数据库随身的迷你 HIS”,而不是当集群方案。下文从选型说起,再走一遍解压初始化、二次开发入口和排错细节,最后给一个备份迁移的技巧。整体就一句话:先让系统在你机器上转起来,再想生产化改造。动手前先建立预期。OpenMRS 2.3.1 是平台加模块的结构,平台管用户、权限、患者、访问、概念字典这类基础能力,模块往上叠临床功能。Standalone 打包的是平台加一批常用模块,所以你能直接看到一条就医链路:建档、预约、就诊、录入观察项、开处方、看报告。2. OpenMRS 的架构选型:Standalone 与 WAR 部署差在哪信息科同事问我的第一个问题是“干嘛不直接装最新版”。这跟资源本身没关系,而是 OpenMRS 的发布形态决定了安装复杂度。同一套源码有两种包:一种是打成 war 包丢进外部 Tomcat 的 Enterprise 形态,另一种就是 Standalone 形态。弄明白两者的边界,才知道手上这个压缩包能撑到哪一步、什么情况下要换形态。2.1 部署形态对比:自带 H2 和嵌入式 Tomcat 的取舍这两者的差异列一张表,适合给不太看源码的同事解释:对比项Standalone 2.3.1Enterprise(WAR 部署)数据库内置 H2,单文件存储需要外部 MySQL/MariaDBWeb 容器内置 Tomcat外部 Tomcat初始模块预装常用模块按需安装,数量可自由定制并发能力适合单机或小规模并行可支撑较大并发读写运维成本一条命令起服务要管数据库备份、Tomcat 调优适用阶段试点、教学、单机、原型多科室长期生产选型理由说白了就是三句话。第一,H2 揉完整支持 SQL 的磁盘数据库,不是内存玩具,病历规模到几万患者也能扛,只是并发写不如 MySQL 稳。第二,嵌入式 Tomcat 把首页、登录、REST API 端口都藏好了,出现端口冲突时只要改一个数字,不需要碰 server.xml。第三,预装模块决定系统第一次启动就带参考应用界面,登录后已经有菜单可点,不是光秃秃的后台。如果是一家几十个医生同时在线录入的中型医院,我会直接劝你别用 Standalone 顶生产,去走 Enterprise 加 MySQL。反过来,如果只是做单病区试点,或者给学生演示,或者做离线诊所病历,走 Standalone 是最划算的路径。这个判断很重要,因为后续所有备份、迁移方案都围绕这个前提展开。2.2 运行时目录与模块机制:先看懂再动手Standalone 首次启动会在当前用户主目录下生成运行时目录,典型路径可能是 ~/.openmrs 或 ~/.openmrs-standalone,具体名字随版本有差异,但内部结构基本一致。我按最常见的形态画一个文件树:~/.openmrs-standalone/ ├── modules/ # 存放 .omod 模块文件 ├── data/ # H2 数据库文件与事务日志 │ ├── openmrs.mv.db # 主数据库文件 │ └── openmrs.trace.db # 调试追溯文件 ├── openmrs-runtime.properties └── openmrs.log这棵树决定了备份对象和排错对象。modules 目录里每一个 .omod 文件对应一个功能模块;data 目录下那两个 H2 文件是全部病历数据;openmrs-runtime.properties 是运行时配置。很多人把注意力放在界面和菜单上,真正出问题时要看的是这三个位置。模块机制值得多说一句。OpenMRS 平台启动时扫描 modules 目录,把每个 .omod 里的 class 和资源加载进来,然后按依赖顺序初始化。如果你新塞进一个 omod,它会在下次启动时执行数据库变更脚本,比如建表加列。这个机制带来的风险是,模块间版本不匹配时可能出现“启动到一半就挂”,这也是避坑章节要专门讲回滚的原因。所以我的建议是:拿到压缩包后先不要急着增删模块,按默认启动一遍,确认日志出现“Started”或“Startup completed”字样,再去折腾开发改动。每改动一步都要记笔记,这包没有自带后悔药,回滚全靠手动。3. 从零跑通初始化:安装启动、JVM 参数与首个管理员账号前面把结构讲清楚,这一章直接上手。我按一次完整初始化流程来写:先交代环境,再跑首次启动,最后演示建档和接诊的最小操作。照着走一遍,大约二十分钟就能看到一个可用的病历系统。3.1 环境准备与 JVM 参数OpenMRS 2.3.1 时代默认用 Java 8,但我实测 Java 11 也能跑,只是启动日志里可能出现“非法反射访问”的警告,不影响使用;Java 17 以上很容易在平台初始化阶段踩到模块内部反射调用的坑,建议不要一上来就用最新版。装好 JDK 后先确认版本:java -version # 输出里 major 是 8 或 11 都可以;如果是 17+,慎重启动命令最简单就一行:java -jar openmrs-standalone-2.3.1.jar不做任何参数也能起,但会有隐患:运行时数据落在当前用户主目录。如果这台机器同时被几个人用,或者想把数据目录挪到独立磁盘,就得显式指定。常见做法是加系统属性:java -DOPENMRS_APPLICATION_DATA_DIRECTORY=/var/lib/openmrs-data -jar openmrs-standalone-2.3.1.jarOPENMRS_APPLICATION_DATA_DIRECTORY 用来覆盖默认数据目录,我一般都会指定到独立目录。好处有两个:一是备份时只要拷这一个目录;二是避免主目录清理把自己数据清掉。如果只改 Web 服务端口,可以追加类似-Dserver.port=8081的参数,具体键名以这个版本解压后自带的说明或 help 输出为准,每个小版本的命名习惯不太一样。内存方面,内存小的机器建议主动限堆:java -Xms512m -Xmx1g -jar openmrs-standalone-2.3.1.jar-Xms 是最小堆,-Xmx 是最大堆。OpenMRS 初始化模块时比较吃内存,512M 以下容易出现中途内存不足,1G 作为上限足够单机原型场景。3.2 第一次启动的引导流程执行上面命令后,控制台先会解压内置资源,然后启动嵌入式 Tomcat。当天志出现类似“Starting ProtocolHandler”或“Server startup”字样,就说明服务已经监听端口了。这时打开浏览器访问:http://localhost:8080/openmrs你看到的是一个初始化向导,不是登录页,这是 OpenMRS 的首次安装流程。常规顺序是这样:选择界面语言,选简体中文后页面会切中文,后续提示都用中文。设置系统时区和地区样式,这一步影响日期格式。创建管理员账号,用户名建议用 admin,密码按系统策略保持一定强度。确认数据库类型,默认点 H2,不要在这里改 MySQL,改库是迁移章节的事。点击继续后,系统执行初始化脚本,创建核心表并预置基础概念。整个向导跑完后会自动跳到登录页,用刚建的管理员账号登录即可。有两点要提醒:第一,向导一旦跑完,重复访问初始化页不会再出现,想重来只能清掉数据目录;第二,向导里填的语言是界面语言,不是强制数据语言,病历内容是自由文本,汉字录入不受限,但某些字段排序可能按拼音或编码序,这个后面讲。登录进去会看到参考应用主界面,左侧菜单一般是“寻找患者”“注册患者”“数据管理”之类。新装系统默认没有任何患者,所有检查项、科室、床位这些字典数据也是初始状态,需要先补配置再进临床流程。3.3 建档与接诊最小流程最小流程是“注册患者→找到患者→新增就诊→录入一个观察项”,这是使用 OpenMRS 最频繁的链路。先从主界面进入“注册患者”,姓名、性别、出生日期必填,出生日期可以选“大致日期”,这对基层病历比较实用。接着填患者标识符。Standalone 预置的标识符类型包含“OpenMRS 识别号”和“患者编号”,你可以给患者分配一个系统自动生成的编号,也可以手动录入身份证号或医保号。这个字段在后续 REST API 里非常重要,因为标识符类型要用 uuid 引用。保存后系统会生成患者详情页,里面已有“近期就诊”入口。点“添加就诊”,选择就诊类型和专科,保存后就会生成一条 Encounter 记录。再往这条 Encounter 里增加观察项时,系统会拉出概念字典,比如“体温”“血压”“主诉”等,选中即可记录具体值,这里录入的每一条都对应数据模型里 obs 表的一行。第一次操作你会发现一个入口叫“批量数据导入”,其实不用管,手工点几条,理解数据流向比贪多更重要。从这一刻起,你已经有了第一份真实病案数据,后面所有 API 调试和备份操作都有了实验对象。4. 二次开发入口:数据模型、REST API 与概念字典如果不想只点点界面,而是希望把电子病历接进自己的挂号系统或报表平台,那这一章才是重点。OpenMRS 的数据模型围绕“人—就诊—观察”三层转,先把模型讲清楚,再给两个可以直接抄的接口示例,最后讲概念字典怎么维护。4.1 核心数据模型:从 Person 到 EncounterOpenMRS 的核心表不复杂,但命名很容易劝退新人。摘几张大表说明:表名业务含义关键字段person自然人基本信息gender, birthdate, deadpatient患者扩展patient_id, creatorpatient_identifier患者标识符identifier, identifier_typeencounter一次就诊活动encounter_datetime, encounter_typeobs观察项数据concept_id, value_numeric, value_textvisit病程中的就诊记录start_datetime, stop_datetimeconcept概念字典项short_name, datatype, concept_class可以这样理解:person 是“人”,patient 加了一层“患者身份”,encounter 是“一次医疗活动”,obs 是这次活动里记录下来的每一条观察数据。visit 则纵向贯穿多次 encounter。这个模型足够抽象,既能做门诊,也能做住院,只要换 encounter_type 就能区分。实际操作中新人最容易把 obs 理解成“检查结果表”。它确实存了体温、血压这些结果,但也存主诉、诊断文本、问卷答案,因为它有 value_text、value_numeric、value_coded 三套列族,用哪个由概念的数据类型决定。所以当你要统计某个指标时,别直接 select obs 表,一定要先 join concept 拿到标准概念,再按 value_ 前缀判断实际值在哪一列。4.2 用 REST API 做患者建档OpenMRS 自带 REST API,根路径在 /openmrs/ws/rest/v1。先演示登录和取会话:curl -s -u admin:YourPassword \ http://localhost:8080/openmrs/ws/rest/v1/session | python3 -m json.tool参数说明:-u 传入管理员账号密码,如果服务启在别的端口,把地址和端口对应改掉;返回 JSON 里 authenticated 字段为 true 表示会话有效,没传对会返回 401。接下来注册一个新患者:curl -s -u admin:YourPassword -X POST \ -H "Content-Type: application/json" \ http://localhost:8080/openmrs/ws/rest/v1/patient \ -d '{ "person": { "gender": "F", "names": [{"givenName": "李", "familyName": "明月"}], "birthdate": "1990-03-12", "birthdateEstimated": false, "addresses": [{"cityVillage": "某城区"}] }, "identifiers": [ { "identifier": "MZ20240001", "identifierType": "把你在系统里查到的类型uuid填这里,形如 2c8aa520-..." } ] }'逻辑拆解:person 对象是必填,names 是姓名数组,单个就诊人一般只有一个名字,但系统设计支持多语种姓名;birthdateEstimated 控制时间精度,填 true 时系统只保证年月。identifiers 里 identifierType 一定要传 uuid,直接用类型名称字符串经常报 400,所以要先查类型再写进请求体。查询一下刚才建的患者,顺便拿 uuid 作为后续写入 encounter 的主键:curl -s -u admin:YourPassword \ "http://localhost:8080/openmrs/ws/rest/v1/patient?q=明月&v=full" \ | python3 -m json.tool参数解释:q 是模糊查询关键字,v=full 表示返回关联子对象,默认的 default 视图不含若干嵌套实体。完整病历开发时一般都用 v=full,响应会大很多,生产环境建议按需裁剪字段。4.3 概念字典维护病历系统能不能统计,取决于概念字典是否统一。OpenMRS 概念字典里一个概念有名称、数据类型(数值、文本、编码)、单位和映射。第一次安装后,系统预置了一批常用概念,比如体温、体重、血压。但不同机构对血压的录入方式不同,有人存“收缩压/舒张压”两个概念,有人存一个复合概念,这个要在上线前拉临床科室确认。在管理界面新增概念的流程一般是:进入数据管理模块→概念→新增概念;填写全名、短名,选择数据类型。这里有个很关键的坑:如果选“文本”类型,后续 obs 表用 value_text 存;选“数字”就用 value_numeric。一旦有数据写入再改数据类型,历史数据会出现取值列不一致,所以上线前模板一定要冻结。有映射需求的概念,比如要对应医保编码或 ICD-10,就在概念映射区添加映射来源和目标代码。OpenMRS 的概念服务会对同义词、短名做搜索匹配,录入时尽量保持命名风格统一。维护字典的原则一句话总结:宁可多建临时概念做调研,也不要让临床部门直接往系统里造新词,否则统计口径迟早会乱。5. 避坑指南:Standalone 使用中 5 个高频翻车点这章写一点血泪经验。我在帮同事部署和教学演示过程中,碰到的问题基本集中在 5 个方向:端口冲突、数据文件锁、模块回滚、中文乱码、内存占用。每条都按“现象→原因→解决”整理。5.1 现象:服务起不来,日志提示 Address already in use: JVM_Bind端口被占是最常见的翻车点。默认 8080,如果这台机器已经跑了 Nginx 或另一个 Tomcat,启动就失败。原因很好查,就是端口被占用,没有玄学。解决步骤:先看谁占了端口。Linux 上执行netstat -tlnp | grep 8080,Windows 上执行netstat -ano | findstr 8080,看到 PID 后再去任务管理器关掉对应进程。如果这个端口必须留作他用,就给 OpenMRS 换端口,在启动命令里追加端口参数,或者编辑 openmrs-runtime.properties 里的 server.port 项。改完端口后浏览器访问地址要同步改。另外要注意,OpenMRS 的根路径不是 /,直接访问会 404,一定要访问 /openmrs。5.2 现象:第二次启动报数据库文件被占用或被锁定表现是启动日志出现 “Database may be already in use” 或 “Locked by another process”。原因一般有两个:一是上一次没正常停掉服务,H2 的 .lock.db 文件残留;二是同一台机器起了两个 Standalone 实例指向同一个数据目录。常规操作是先把进程彻底杀掉,Windows 下注意 java.exe 可能不止一个,然后进入 data 目录删除.lock.db文件,再启动。如果删除后发现 mv.db 文件异常变小,说明数据可能没写完,先去 openmrs.log 看看是否有异常。数据目录不能同时被两个 java 进程使用,这一点我在多开演示时翻过车,后来强制规定一台机器一次只起一个实例。提示:删除 .lock.db 前先确认服务已经停止,否则会加剧数据文件损坏。5.3 现象:装新模块后系统启动停在一个报错页模块机制是把双刃剑。常见场景是你从模块中心下载一个新功能包放进 modules 目录,启动时平台因为模块版本不匹配或数据库脚本执行失败直接中断,页面打不开,日志里一堆 NullPointerException 或 SQLException。原因就是模块间依赖没满足,新模块要求的平台版本或前置模块跟你当前版本不一致。解决要快,因为系统可能处于半启动状态。我的做法是先把 modules 目录里所有后来手动加进去的 omod 移出到备份目录,只保留初始自带的模块,然后重启观察是否恢复正常。如果恢复,再把移出的模块一个一个加回去,每加一个就重启验证一次。这就是最小回滚操作,不需要重装系统。记住,modules 目录改名前先备份,后缀建议保留 .bak。5.4 现象:界面显示英文字符正常,中文写入变成乱码中文乱码分两种。一种是启动向导里选了简体中文,但菜单偶尔缺字或乱码,这多半是浏览器编码或 JVM 默认编码问题;另一种是 API 请求写入的中文,在页面上变成问号或菱形符号,这多半是数据库连接串没指定编码。解决方法是双管齐下:启动命令加-Dfile.encoding=UTF-8,确保 JVM 以 UTF-8 读写文件;如果是自己扩展的模块,数据库连接 URL 里也显式带 useUnicode=true 和 characterEncoding=UTF-8。另外 Windows 控制台本身是 GBK,你在控制台敲中文 JSON 丢给 curl,控制台编码也会影响发送内容,建议把请求体写成文件再用-d @request.json提交。5.5 现象:一两天不重启内存涨到 80% 以上Standalone 进程本身承载了 Tomcat 和 H2,空闲时间长了内存居高不下也常见。原因分两类:一是 H2 的缓存和临时表占用;二是应用内的 session 和病人列表查询没释放。解决不是盲目加内存,而是先看日志里有没有频繁 Full GC,再压堆上限。前面提到的-Xmx1g就是干这个的。如果确认是报表或列表页面大量 query 导致的内存堆积,尽量在 REST 查询时限制结果条数,不要一次性拉全库。H2 数据量比较大时,还可以定期执行SHUTDOWN COMPACT这个 SQL 来整理文件并回收空间,执行时机选在夜里停服窗口,效果很像做碎片整理。6. 让数据活起来的最后一个技巧:备份 H2 数据文件和迁移前校验前面所有工作做完,系统能用了,真正的考验才来:数据怎么保住。最后只讲一件事,如何用最小成本在 Standalone 上做备份,以及什么情况下才考虑迁到 MySQL。6.1 备份与恢复的最小操作Standalone 的备份策略非常笨但非常有效:停止服务,把数据目录整个拷走。命令层面的操作:# 停服后执行 mkdir -p /backup/openmrs cp -a /var/lib/openmrs-data /backup/openmrs/data-$(date +%Y%m%d)参数说明:-a 保留文件属性和时间戳;目录名字带上日期是给自己留后悔药。恢复也很简单,把备份目录覆盖回原数据目录,确保文件属主一致后启动即可。如果服务不停,直接拷 mv.db 文件,大概率会拷到不一致的中间态,H2 可能恢复不了,所以我的习惯是宁可停机十分钟。想要在线备份,可以用 H2 自带的 SQL 命令。登录到 H2 控制台或通过 Java 客户端执行:BACKUP TO '/backup/openmrs-20240601.zip'注意这个命令只备份数据文件,模块目录和 runtime properties 还是要单独备份。6.2 迁移到 MySQL 的判断标准与校验清单在迁到 MySQL 之前先做一次判断。患者总量几万以下、并发医生数几十以内、没有复杂报表,H2 完全够用;如果出现“锁表等待”“列表查询超秒”这类现场,才考虑迁移。迁移不是把文件拷贝过去,而是要导出再导入,OpenMRS 提供数据导出工具,或者直接用 REST API 拉取患者及 obs,再写脚本灌入新库,这个过程比较耗时间,建议挑测试环境演练两遍再动生产。迁移前必须校验三件事:概念字典是否完整,特别是 value_coded 引用的 concept_id 不能有悬空;患者标识符是否唯一,重复标识会在新库触发主键冲突;用户权限有没有遗漏,因为角色授权是配置数据,导出工具经常漏。这三项都过了,再停服切库,切换完用一条只读查询验证患者数和 obs 数:SELECT COUNT(*) AS pcount FROM patient; SELECT COUNT(*) AS obcount FROM obs;这个对比法和导出前的数字一致,才算迁移成功。我从那以后每接手一套 Standalone 实例,都强制先做一次“停服备份-数据目录落盘-启动恢复”的演练,顺手把数据目录路径和模块清单写进交接文档。这半个小时的准备工作能杜绝九成紧急问题,希望帮到你。本文还有配套的精品资源,点击获取
简介:这是 OpenMRS 开源企业电子病历系统的独立部署版本 2.3.1,面向医院、诊所及医疗信息化开发与运维人员,用于在资源受限或常规办公环境中快速搭建可扩展的病历管理平台。整个 zip 包共 15 个文件,整体约 394.68MB,涵盖核心 jar/war 程序、properties 与 xml 配置、Unix 启动脚本、tomcat/webapps 目录结构、空库与示例数据库以及说明文档,可帮助使用者快速理解系统组成并完成本地化部署。截至目前已有 591 人学习下载。借助该资源可实际体验 OpenMRS 的模块化扩展能力、多语言支持、患者登记、病史记录、处方与实验室结果管理等核心功能,同时掌握 standalone 模式的轻量部署方式;对需要评估开源电子病历方案或从事基层医疗信息化建设的技术人员来说,是一份可直接对照操作并二次开发的完整素材。通过示例数据库与空库,还能快速验证业务流程、准备初始数据,为后续功能扩展提供直接起点。1. 先拆开这个压缩包:为什么医院信息科会把它当原型底座某区级医院信息科联系我问,OpenMRS 这个开源企业电子病历系统,能不能在他们一个外科病区先做小范围试点。我的习惯是先拆包再回答:这个压缩包的核心其实就一个可执行 jar,内部自带嵌入式 Tomcat 和 H2 数据库,常用模块也预装好了。也就是说,你不用先搭 MySQL、不用配 Tomcat,一条命令起来就是一套完整病历系统。它特别适合四类人:想快速验证病历流程的信息科、带学生看数据模型的老师、做单机离线病历的基层诊所、研究开源病历代码的开发者。它不是演示壳,患者档案、就诊记录、临床表单、概念字典这些重功能都在,只是默认跑在嵌入式数据库上,部署成本压到了最低。这个定位决定了后面所有操作方式:把它当“数据库随身的迷你 HIS”,而不是当集群方案。下文从选型说起,再走一遍解压初始化、二次开发入口和排错细节,最后给一个备份迁移的技巧。整体就一句话:先让系统在你机器上转起来,再想生产化改造。动手前先建立预期。OpenMRS 2.3.1 是平台加模块的结构,平台管用户、权限、患者、访问、概念字典这类基础能力,模块往上叠临床功能。Standalone 打包的是平台加一批常用模块,所以你能直接看到一条就医链路:建档、预约、就诊、录入观察项、开处方、看报告。2. OpenMRS 的架构选型:Standalone 与 WAR 部署差在哪信息科同事问我的第一个问题是“干嘛不直接装最新版”。这跟资源本身没关系,而是 OpenMRS 的发布形态决定了安装复杂度。同一套源码有两种包:一种是打成 war 包丢进外部 Tomcat 的 Enterprise 形态,另一种就是 Standalone 形态。弄明白两者的边界,才知道手上这个压缩包能撑到哪一步、什么情况下要换形态。2.1 部署形态对比:自带 H2 和嵌入式 Tomcat 的取舍这两者的差异列一张表,适合给不太看源码的同事解释:对比项Standalone 2.3.1Enterprise(WAR 部署)数据库内置 H2,单文件存储需要外部 MySQL/MariaDBWeb 容器内置 Tomcat外部 Tomcat初始模块预装常用模块按需安装,数量可自由定制并发能力适合单机或小规模并行可支撑较大并发读写运维成本一条命令起服务要管数据库备份、Tomcat 调优适用阶段试点、教学、单机、原型多科室长期生产选型理由说白了就是三句话。第一,H2 揉完整支持 SQL 的磁盘数据库,不是内存玩具,病历规模到几万患者也能扛,只是并发写不如 MySQL 稳。第二,嵌入式 Tomcat 把首页、登录、REST API 端口都藏好了,出现端口冲突时只要改一个数字,不需要碰 server.xml。第三,预装模块决定系统第一次启动就带参考应用界面,登录后已经有菜单可点,不是光秃秃的后台。如果是一家几十个医生同时在线录入的中型医院,我会直接劝你别用 Standalone 顶生产,去走 Enterprise 加 MySQL。反过来,如果只是做单病区试点,或者给学生演示,或者做离线诊所病历,走 Standalone 是最划算的路径。这个判断很重要,因为后续所有备份、迁移方案都围绕这个前提展开。2.2 运行时目录与模块机制:先看懂再动手Standalone 首次启动会在当前用户主目录下生成运行时目录,典型路径可能是 ~/.openmrs 或 ~/.openmrs-standalone,具体名字随版本有差异,但内部结构基本一致。我按最常见的形态画一个文件树:~/.openmrs-standalone/ ├── modules/ # 存放 .omod 模块文件 ├── data/ # H2 数据库文件与事务日志 │ ├── openmrs.mv.db # 主数据库文件 │ └── openmrs.trace.db # 调试追溯文件 ├── openmrs-runtime.properties └── openmrs.log这棵树决定了备份对象和排错对象。modules 目录里每一个 .omod 文件对应一个功能模块;data 目录下那两个 H2 文件是全部病历数据;openmrs-runtime.properties 是运行时配置。很多人把注意力放在界面和菜单上,真正出问题时要看的是这三个位置。模块机制值得多说一句。OpenMRS 平台启动时扫描 modules 目录,把每个 .omod 里的 class 和资源加载进来,然后按依赖顺序初始化。如果你新塞进一个 omod,它会在下次启动时执行数据库变更脚本,比如建表加列。这个机制带来的风险是,模块间版本不匹配时可能出现“启动到一半就挂”,这也是避坑章节要专门讲回滚的原因。所以我的建议是:拿到压缩包后先不要急着增删模块,按默认启动一遍,确认日志出现“Started”或“Startup completed”字样,再去折腾开发改动。每改动一步都要记笔记,这包没有自带后悔药,回滚全靠手动。3. 从零跑通初始化:安装启动、JVM 参数与首个管理员账号前面把结构讲清楚,这一章直接上手。我按一次完整初始化流程来写:先交代环境,再跑首次启动,最后演示建档和接诊的最小操作。照着走一遍,大约二十分钟就能看到一个可用的病历系统。3.1 环境准备与 JVM 参数OpenMRS 2.3.1 时代默认用 Java 8,但我实测 Java 11 也能跑,只是启动日志里可能出现“非法反射访问”的警告,不影响使用;Java 17 以上很容易在平台初始化阶段踩到模块内部反射调用的坑,建议不要一上来就用最新版。装好 JDK 后先确认版本:java -version # 输出里 major 是 8 或 11 都可以;如果是 17+,慎重启动命令最简单就一行:java -jar openmrs-standalone-2.3.1.jar不做任何参数也能起,但会有隐患:运行时数据落在当前用户主目录。如果这台机器同时被几个人用,或者想把数据目录挪到独立磁盘,就得显式指定。常见做法是加系统属性:java -DOPENMRS_APPLICATION_DATA_DIRECTORY=/var/lib/openmrs-data -jar openmrs-standalone-2.3.1.jarOPENMRS_APPLICATION_DATA_DIRECTORY 用来覆盖默认数据目录,我一般都会指定到独立目录。好处有两个:一是备份时只要拷这一个目录;二是避免主目录清理把自己数据清掉。如果只改 Web 服务端口,可以追加类似-Dserver.port=8081的参数,具体键名以这个版本解压后自带的说明或 help 输出为准,每个小版本的命名习惯不太一样。内存方面,内存小的机器建议主动限堆:java -Xms512m -Xmx1g -jar openmrs-standalone-2.3.1.jar-Xms 是最小堆,-Xmx 是最大堆。OpenMRS 初始化模块时比较吃内存,512M 以下容易出现中途内存不足,1G 作为上限足够单机原型场景。3.2 第一次启动的引导流程执行上面命令后,控制台先会解压内置资源,然后启动嵌入式 Tomcat。当天志出现类似“Starting ProtocolHandler”或“Server startup”字样,就说明服务已经监听端口了。这时打开浏览器访问:http://localhost:8080/openmrs你看到的是一个初始化向导,不是登录页,这是 OpenMRS 的首次安装流程。常规顺序是这样:选择界面语言,选简体中文后页面会切中文,后续提示都用中文。设置系统时区和地区样式,这一步影响日期格式。创建管理员账号,用户名建议用 admin,密码按系统策略保持一定强度。确认数据库类型,默认点 H2,不要在这里改 MySQL,改库是迁移章节的事。点击继续后,系统执行初始化脚本,创建核心表并预置基础概念。整个向导跑完后会自动跳到登录页,用刚建的管理员账号登录即可。有两点要提醒:第一,向导一旦跑完,重复访问初始化页不会再出现,想重来只能清掉数据目录;第二,向导里填的语言是界面语言,不是强制数据语言,病历内容是自由文本,汉字录入不受限,但某些字段排序可能按拼音或编码序,这个后面讲。登录进去会看到参考应用主界面,左侧菜单一般是“寻找患者”“注册患者”“数据管理”之类。新装系统默认没有任何患者,所有检查项、科室、床位这些字典数据也是初始状态,需要先补配置再进临床流程。3.3 建档与接诊最小流程最小流程是“注册患者→找到患者→新增就诊→录入一个观察项”,这是使用 OpenMRS 最频繁的链路。先从主界面进入“注册患者”,姓名、性别、出生日期必填,出生日期可以选“大致日期”,这对基层病历比较实用。接着填患者标识符。Standalone 预置的标识符类型包含“OpenMRS 识别号”和“患者编号”,你可以给患者分配一个系统自动生成的编号,也可以手动录入身份证号或医保号。这个字段在后续 REST API 里非常重要,因为标识符类型要用 uuid 引用。保存后系统会生成患者详情页,里面已有“近期就诊”入口。点“添加就诊”,选择就诊类型和专科,保存后就会生成一条 Encounter 记录。再往这条 Encounter 里增加观察项时,系统会拉出概念字典,比如“体温”“血压”“主诉”等,选中即可记录具体值,这里录入的每一条都对应数据模型里 obs 表的一行。第一次操作你会发现一个入口叫“批量数据导入”,其实不用管,手工点几条,理解数据流向比贪多更重要。从这一刻起,你已经有了第一份真实病案数据,后面所有 API 调试和备份操作都有了实验对象。4. 二次开发入口:数据模型、REST API 与概念字典如果不想只点点界面,而是希望把电子病历接进自己的挂号系统或报表平台,那这一章才是重点。OpenMRS 的数据模型围绕“人—就诊—观察”三层转,先把模型讲清楚,再给两个可以直接抄的接口示例,最后讲概念字典怎么维护。4.1 核心数据模型:从 Person 到 EncounterOpenMRS 的核心表不复杂,但命名很容易劝退新人。摘几张大表说明:表名业务含义关键字段person自然人基本信息gender, birthdate, deadpatient患者扩展patient_id, creatorpatient_identifier患者标识符identifier, identifier_typeencounter一次就诊活动encounter_datetime, encounter_typeobs观察项数据concept_id, value_numeric, value_textvisit病程中的就诊记录start_datetime, stop_datetimeconcept概念字典项short_name, datatype, concept_class可以这样理解:person 是“人”,patient 加了一层“患者身份”,encounter 是“一次医疗活动”,obs 是这次活动里记录下来的每一条观察数据。visit 则纵向贯穿多次 encounter。这个模型足够抽象,既能做门诊,也能做住院,只要换 encounter_type 就能区分。实际操作中新人最容易把 obs 理解成“检查结果表”。它确实存了体温、血压这些结果,但也存主诉、诊断文本、问卷答案,因为它有 value_text、value_numeric、value_coded 三套列族,用哪个由概念的数据类型决定。所以当你要统计某个指标时,别直接 select obs 表,一定要先 join concept 拿到标准概念,再按 value_ 前缀判断实际值在哪一列。4.2 用 REST API 做患者建档OpenMRS 自带 REST API,根路径在 /openmrs/ws/rest/v1。先演示登录和取会话:curl -s -u admin:YourPassword \ http://localhost:8080/openmrs/ws/rest/v1/session | python3 -m json.tool参数说明:-u 传入管理员账号密码,如果服务启在别的端口,把地址和端口对应改掉;返回 JSON 里 authenticated 字段为 true 表示会话有效,没传对会返回 401。接下来注册一个新患者:curl -s -u admin:YourPassword -X POST \ -H "Content-Type: application/json" \ http://localhost:8080/openmrs/ws/rest/v1/patient \ -d '{ "person": { "gender": "F", "names": [{"givenName": "李", "familyName": "明月"}], "birthdate": "1990-03-12", "birthdateEstimated": false, "addresses": [{"cityVillage": "某城区"}] }, "identifiers": [ { "identifier": "MZ20240001", "identifierType": "把你在系统里查到的类型uuid填这里,形如 2c8aa520-..." } ] }'逻辑拆解:person 对象是必填,names 是姓名数组,单个就诊人一般只有一个名字,但系统设计支持多语种姓名;birthdateEstimated 控制时间精度,填 true 时系统只保证年月。identifiers 里 identifierType 一定要传 uuid,直接用类型名称字符串经常报 400,所以要先查类型再写进请求体。查询一下刚才建的患者,顺便拿 uuid 作为后续写入 encounter 的主键:curl -s -u admin:YourPassword \ "http://localhost:8080/openmrs/ws/rest/v1/patient?q=明月&v=full" \ | python3 -m json.tool参数解释:q 是模糊查询关键字,v=full 表示返回关联子对象,默认的 default 视图不含若干嵌套实体。完整病历开发时一般都用 v=full,响应会大很多,生产环境建议按需裁剪字段。4.3 概念字典维护病历系统能不能统计,取决于概念字典是否统一。OpenMRS 概念字典里一个概念有名称、数据类型(数值、文本、编码)、单位和映射。第一次安装后,系统预置了一批常用概念,比如体温、体重、血压。但不同机构对血压的录入方式不同,有人存“收缩压/舒张压”两个概念,有人存一个复合概念,这个要在上线前拉临床科室确认。在管理界面新增概念的流程一般是:进入数据管理模块→概念→新增概念;填写全名、短名,选择数据类型。这里有个很关键的坑:如果选“文本”类型,后续 obs 表用 value_text 存;选“数字”就用 value_numeric。一旦有数据写入再改数据类型,历史数据会出现取值列不一致,所以上线前模板一定要冻结。有映射需求的概念,比如要对应医保编码或 ICD-10,就在概念映射区添加映射来源和目标代码。OpenMRS 的概念服务会对同义词、短名做搜索匹配,录入时尽量保持命名风格统一。维护字典的原则一句话总结:宁可多建临时概念做调研,也不要让临床部门直接往系统里造新词,否则统计口径迟早会乱。5. 避坑指南:Standalone 使用中 5 个高频翻车点这章写一点血泪经验。我在帮同事部署和教学演示过程中,碰到的问题基本集中在 5 个方向:端口冲突、数据文件锁、模块回滚、中文乱码、内存占用。每条都按“现象→原因→解决”整理。5.1 现象:服务起不来,日志提示 Address already in use: JVM_Bind端口被占是最常见的翻车点。默认 8080,如果这台机器已经跑了 Nginx 或另一个 Tomcat,启动就失败。原因很好查,就是端口被占用,没有玄学。解决步骤:先看谁占了端口。Linux 上执行netstat -tlnp | grep 8080,Windows 上执行netstat -ano | findstr 8080,看到 PID 后再去任务管理器关掉对应进程。如果这个端口必须留作他用,就给 OpenMRS 换端口,在启动命令里追加端口参数,或者编辑 openmrs-runtime.properties 里的 server.port 项。改完端口后浏览器访问地址要同步改。另外要注意,OpenMRS 的根路径不是 /,直接访问会 404,一定要访问 /openmrs。5.2 现象:第二次启动报数据库文件被占用或被锁定表现是启动日志出现 “Database may be already in use” 或 “Locked by another process”。原因一般有两个:一是上一次没正常停掉服务,H2 的 .lock.db 文件残留;二是同一台机器起了两个 Standalone 实例指向同一个数据目录。常规操作是先把进程彻底杀掉,Windows 下注意 java.exe 可能不止一个,然后进入 data 目录删除.lock.db文件,再启动。如果删除后发现 mv.db 文件异常变小,说明数据可能没写完,先去 openmrs.log 看看是否有异常。数据目录不能同时被两个 java 进程使用,这一点我在多开演示时翻过车,后来强制规定一台机器一次只起一个实例。提示:删除 .lock.db 前先确认服务已经停止,否则会加剧数据文件损坏。5.3 现象:装新模块后系统启动停在一个报错页模块机制是把双刃剑。常见场景是你从模块中心下载一个新功能包放进 modules 目录,启动时平台因为模块版本不匹配或数据库脚本执行失败直接中断,页面打不开,日志里一堆 NullPointerException 或 SQLException。原因就是模块间依赖没满足,新模块要求的平台版本或前置模块跟你当前版本不一致。解决要快,因为系统可能处于半启动状态。我的做法是先把 modules 目录里所有后来手动加进去的 omod 移出到备份目录,只保留初始自带的模块,然后重启观察是否恢复正常。如果恢复,再把移出的模块一个一个加回去,每加一个就重启验证一次。这就是最小回滚操作,不需要重装系统。记住,modules 目录改名前先备份,后缀建议保留 .bak。5.4 现象:界面显示英文字符正常,中文写入变成乱码中文乱码分两种。一种是启动向导里选了简体中文,但菜单偶尔缺字或乱码,这多半是浏览器编码或 JVM 默认编码问题;另一种是 API 请求写入的中文,在页面上变成问号或菱形符号,这多半是数据库连接串没指定编码。解决方法是双管齐下:启动命令加-Dfile.encoding=UTF-8,确保 JVM 以 UTF-8 读写文件;如果是自己扩展的模块,数据库连接 URL 里也显式带 useUnicode=true 和 characterEncoding=UTF-8。另外 Windows 控制台本身是 GBK,你在控制台敲中文 JSON 丢给 curl,控制台编码也会影响发送内容,建议把请求体写成文件再用-d @request.json提交。5.5 现象:一两天不重启内存涨到 80% 以上Standalone 进程本身承载了 Tomcat 和 H2,空闲时间长了内存居高不下也常见。原因分两类:一是 H2 的缓存和临时表占用;二是应用内的 session 和病人列表查询没释放。解决不是盲目加内存,而是先看日志里有没有频繁 Full GC,再压堆上限。前面提到的-Xmx1g就是干这个的。如果确认是报表或列表页面大量 query 导致的内存堆积,尽量在 REST 查询时限制结果条数,不要一次性拉全库。H2 数据量比较大时,还可以定期执行SHUTDOWN COMPACT这个 SQL 来整理文件并回收空间,执行时机选在夜里停服窗口,效果很像做碎片整理。6. 让数据活起来的最后一个技巧:备份 H2 数据文件和迁移前校验前面所有工作做完,系统能用了,真正的考验才来:数据怎么保住。最后只讲一件事,如何用最小成本在 Standalone 上做备份,以及什么情况下才考虑迁到 MySQL。6.1 备份与恢复的最小操作Standalone 的备份策略非常笨但非常有效:停止服务,把数据目录整个拷走。命令层面的操作:# 停服后执行 mkdir -p /backup/openmrs cp -a /var/lib/openmrs-data /backup/openmrs/data-$(date +%Y%m%d)参数说明:-a 保留文件属性和时间戳;目录名字带上日期是给自己留后悔药。恢复也很简单,把备份目录覆盖回原数据目录,确保文件属主一致后启动即可。如果服务不停,直接拷 mv.db 文件,大概率会拷到不一致的中间态,H2 可能恢复不了,所以我的习惯是宁可停机十分钟。想要在线备份,可以用 H2 自带的 SQL 命令。登录到 H2 控制台或通过 Java 客户端执行:BACKUP TO '/backup/openmrs-20240601.zip'注意这个命令只备份数据文件,模块目录和 runtime properties 还是要单独备份。6.2 迁移到 MySQL 的判断标准与校验清单在迁到 MySQL 之前先做一次判断。患者总量几万以下、并发医生数几十以内、没有复杂报表,H2 完全够用;如果出现“锁表等待”“列表查询超秒”这类现场,才考虑迁移。迁移不是把文件拷贝过去,而是要导出再导入,OpenMRS 提供数据导出工具,或者直接用 REST API 拉取患者及 obs,再写脚本灌入新库,这个过程比较耗时间,建议挑测试环境演练两遍再动生产。迁移前必须校验三件事:概念字典是否完整,特别是 value_coded 引用的 concept_id 不能有悬空;患者标识符是否唯一,重复标识会在新库触发主键冲突;用户权限有没有遗漏,因为角色授权是配置数据,导出工具经常漏。这三项都过了,再停服切库,切换完用一条只读查询验证患者数和 obs 数:SELECT COUNT(*) AS pcount FROM patient; SELECT COUNT(*) AS obcount FROM obs;这个对比法和导出前的数字一致,才算迁移成功。我从那以后每接手一套 Standalone 实例,都强制先做一次“停服备份-数据目录落盘-启动恢复”的演练,顺手把数据目录路径和模块清单写进交接文档。这半个小时的准备工作能杜绝九成紧急问题,希望帮到你。本文还有配套的精品资源,点击获取