2. HL7 v2消息结构解剖

HL7 v2 的互操作性,本质上建立在一套“可切分、可定位、可回填”的文本结构之上。一条消息看起来像一串被竖线、尖号、波浪号切开的字符,但工程上必须把它理解为五层嵌套模型:消息(Message)→ 段(Segment)→ 字段(Field)→ 组件(Component)→ 子组件(Sub-Component)。只有把每一层的边界和分隔符规则吃透,后续做解析、映射、校验、回写时才能定位到唯一坐标,而不是靠字符串截取碰运气。

五层结构:从整条消息到最小可寻址单元

最外层是消息,一条消息由若干段按顺序组成,段与段之间用段分隔符(通常为回车 \r,即十六进制 0x0D)分隔。每个段以三个大写字母的段名开头,例如 MSH、PID、PV1、OBX。段名之后紧跟字段分隔符,再往后是各字段。字段是段内由字段分隔符切分出来的位置单元,字段内部若需要表达复合信息,就用组件分隔符切成组件;组件内部若还要再细分,就用子组件分隔符切成子组件。重复字段则用重复分隔符表达同一字段的多个取值。

这五层不是抽象概念,而是解析器里真实存在的递归下降路径。以最常见的 PID-5(患者姓名)为例:PID 是段,5 是字段序号,字段值 ZHANG^SAN^A^JR^DR 中的 ZHANG、SAN、A、JR、DR 分别是第 1 到第 5 个组件,依次对应姓、名、中间名、后缀、前缀。若某个组件本身还是复合值,例如 PID-3 患者标识中的 123456^^^MRN^MR,则第 4 组件 MRN 是分配机构,第 5 组件 MR 是标识类型;再往下若出现 &,才进入子组件层。

分隔符规则:MSH-1 与 MSH-2 是整条消息的“元数据”

HL7 v2 最容易被忽视、也最容易出错的一点是:分隔符本身不是硬编码常量,而是由消息自己声明的。声明位置就在 MSH 段的前两个字段:MSH-1 是字段分隔符,MSH-2 是编码字符(Encoding Characters),按固定顺序给出组件分隔符、重复分隔符、转义字符、子组件分隔符。标准默认值为:

MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20240515103000||ADT^A01|MSG00001|P|2.5\r

这里 MSH-1 是 |,MSH-2 是 ^~\&,依次表示组件分隔符 ^、重复分隔符 ~、转义字符 \、子组件分隔符 &。解析器必须先读 MSH-1 和 MSH-2,才能正确切分后续所有段。如果发送方把字段分隔符改成 #,那么整条消息的切分规则都要跟着变,接收方不能假设它一定是竖线。工程上常见的做法是:解析入口先取前 4 个字符,第 4 个字符即 MSH-1,第 5 到第 8 个字符即 MSH-2 的四个编码字符,再据此初始化分词器。

注意:MSH-1 和 MSH-2 是“自描述”的,但 MSH-2 内部四个字符的顺序是固定的,不能因为某个字符没出现就跳过位置。例如组件分隔符缺失时,仍要保留占位,否则后续重复分隔符、转义字符、子组件分隔符会整体错位。

消息实例逐层拆解

下面给出一条教学示例消息(示例数据,非实测结果),用于逐层定位:

MSH|^~\&|HIS|HOSP_A|LIS|HOSP_B|20240515103000||ADT^A01^ADT_A01|MSG00001|P|2.5
PID|1||123456^^^MRN^MR~789012^^^SSN^SS||ZHANG^SAN^A^JR^DR||19800101|M|||NO.1 ROAD^SUITE 5^BEIJING^BJ^100000^CHN^H||13800000000
PV1|1|I|WARD01^ROOM02^BED03||||1234^LI^MING|||SUR|||||||1234^LI^MING

逐层看:第一段 MSH 声明了分隔符和消息类型 ADT^A01^ADT_A01,其中 ADT 是消息类型,A01 是触发事件,ADT_A01 是消息结构 ID。第二段 PID 中,PID-3 是患者标识列表,用 ~ 重复出两个标识:123456^^^MRN^MR 和 789012^^^SSN^SS,每个标识内部又用 ^ 切成 5 个组件。第三段 PV1 中,PV1-3 是病房位置,WARD01^ROOM02^BED03 三个组件分别表示病区、房间、床位;PV1-7 和 PV1-17 是医生字段,值 1234^LI^MING 中 1234 是 ID,LI 是姓,MING 是名。

如果要把 PID-5 的姓回填到数据库,解析路径就是:先按 \r 切段,找到段名 PID;再按 | 切字段,取第 5 个字段;再按 ^ 切组件,取第 1 个组件。若字段内出现 ~,说明该字段有重复值,需要先按 ~ 展开再逐项处理。若组件内出现 &,则继续按子组件切分。这个“先段、后字段、再组件、最后子组件”的顺序,是 HL7 v2 解析的稳定路径。

HL7 v2 消息五层结构与分隔符定位 ① 消息 Message 由多个段组成,段间以 \r(0x0D)分隔;整条消息以 MSH 开头 分隔符:\r ② 段 Segment 3 字母段名 + 字段序列,如 MSH、PID、PV1、OBX;段名后紧跟字段分隔符 分隔符:\r ③ 字段 Field 段内按字段分隔符切分的位置单元,如 PID-5、PV1-3;字段可重复 分隔符:|(MSH-1) ④ 组件 Component 字段内按组件分隔符切分,如 PID-5 的姓^名^中间名^后缀^前缀 分隔符:^(MSH-2 第1位) ⑤ 子组件 Sub-Component 组件内按子组件分隔符切分,用于更细粒度复合值,如机构&系统 分隔符:&(MSH-2 第4位) MSH-2 编码字符(固定顺序) ^ 组件分隔符 ~ 重复分隔符 \ 转义字符 & 子组件分隔符

工程实现中的关键判断

实际开发时,解析器不能只按默认分隔符写死。正确做法是:先读取 MSH 段的前 8 个字符,第 4 个字符作为字段分隔符,第 5 到第 8 个字符作为四个编码字符,再据此构建分词规则。对于缺失字段,HL7 v2 用连续两个字段分隔符表示空字段,例如 PID|1||123456 中 PID-2 为空;解析时必须保留位置,不能因为空值就跳过,否则后续字段序号会整体前移。对于重复字段,先按 ~ 展开,再对每个重复项按组件、子组件逐层处理。对于转义字符 \,常见转义序列如 \F\ 表示字段分隔符本身、\S\ 表示组件分隔符、\R\ 表示重复分隔符、\E\ 表示转义字符,解析时要在切分之前先做转义还原,否则会把数据内容误判为结构分隔符。

建议在解析入口处先做一次“分隔符探测 + 转义还原”,再进入段、字段、组件、子组件的递归切分。这样即使发送方使用非默认分隔符,或字段值里包含被转义的分隔符,也能稳定定位到目标节点。

把五层结构和分隔符规则固化下来之后,后续做消息映射时就可以用“段名 + 字段序号 + 组件序号 + 子组件序号”的唯一坐标描述任意数据点。例如 PID-5-1 表示患者姓,PID-3-4 表示患者标识的分配机构,PV1-3-3 表示床位。这个坐标体系是 HL7 v2 接口开发中最基础的“寻址语言”,也是后续做校验、转换、回写和排障时最可靠的定位依据。