清除过滤器
文章
Jingwei Wang · 七月 4, 2022
高可用性(HA)指的是使系统或应用程序在很高比例的时间内保持运行,最大限度地减少计划内和计划外的停机时间。
维持系统高可用性的主要机制被称为故障转移。在这种方法下,一个故障的主系统被一个备份系统所取代;也就是说,生产系统故障转移到备份系统上。许多HA配置还提供了灾难恢复(DR)的机制,即在HA机制无法保持系统的可用性时,也能及时恢复系统的可用性。
本文简要讨论了可用于基于InterSystems IRIS的应用程序的HA策略机制,提供了HA解决方案的功能比较,并讨论了使用分布式缓存的故障转移策略。
操作系统级别的集群HA
在操作系统层面上提供的一个常见的HA解决方案是故障转移集群,其中主要的生产系统由一个(通常是相同的)备用系统补充,共享存储和一个跟随活动成员的集群IP地址。在生产系统发生故障的情况下,备用系统承担生产工作量,接管以前在故障主系统上运行的程序和服务。备用机必须能够处理正常的生产工作负载,只要恢复故障主机所需的时间就可以了。也可以选择让备用机成为主机,一旦主机恢复,故障主机将成为备用机。
InterSystems IRIS的设计可以轻松地与所支持的平台的故障转移集群技术相结合(如InterSystems支持的平台中所述)。InterSystems IRIS实例安装在集群的共享存储设备上,以便两个集群成员都能识别它,然后添加到集群配置中,这样它将作为故障转移的一部分在备用机上自动重新启动。在故障转移后重新启动时,系统自动执行正常的启动恢复,保持结构和逻辑的完整性,就像InterSystems IRIS在故障系统上重新启动一样。如果需要,可以在一个集群上安装多个InterSystems IRIS实例。
InterSystems IRIS 镜像
具有自动故障转移功能的 InterSystems IRIS 数据库镜像为计划内和意外停机提供了经济有效的高可用性解决方案。镜像依赖于数据复制而不是共享存储,避免了存储故障导致的重大服务中断。
InterSystems IRIS 镜像由两个物理上独立的 InterSystems IRIS 系统组成,称为故障转移成员。每个故障转移成员在镜像中维护每个镜像数据库的副本;应用程序更新是在主故障转移成员上进行的,而备机故障转移用户的数据库则通过主成员的日志文件保持数据同步。
镜像会自动将主节点的角色分配给两个故障转移成员中的一个,而另一个故障转移成员则自动成为备机系统。当主 InterSystems IRIS 实例失败或变得不可用时,备机将自动快速接管并成为主机。
第三个系统称为仲裁机,它与故障转移成员保持持续的联系,在无法直接通信时安全地做出故障转移决策,为他们提供所需信息。在每个故障转移系统主机上运行的代理(称为 ISCAgents )进程也有助于自动故障转移逻辑。除非备机能够确认主节点确实处于停机状态或不可用状态,并且无法再作为主节点运行,否则备机将无法接管。在仲裁机和 ISCAgents 之间,这可以在几乎每个中断场景下完成。
当镜像配置使用 virtual IP address(虚拟 IP 地址)时,将应用程序连接重定向到新主节点是透明的。如果连接是通过 ECP,它们会自动重置到新的主节点。用于重定向应用程序连接的其他机制也是可用的。
当主机实例恢复正常时,它将自动成为备机实例。操作员启动的故障转移也可在计划的维护或升级停机期间维护可用性。
自动故障转移机制
镜像主旨在当主节点失败或变得不可用时,为备机提供安全的自动故障转移。本部分描述了允许这种情况发生的机制,包括:
• 自动故障转移的安全性要求
• 自动故障转移规则
• 对于各种中断情况的镜像响应
• 自动故障转移机制详节
自动故障转移的安全性要求
备机的 InterSystems IRIS 实例只有在能够确保满足以下两个条件时才能自动接管主节点:
• 备机实例已从主节点接收到最新的日志数据。
这一要求保证了主节点上镜像数据库的所有持久更新都已经或将要对备机的相同数据库进行,从而确保不会丢失任何数据。
• 主机实例不再作为主机实例运行,并且在没有手动干预的情况下无法这样做。
这个要求消除了两个故障转移成员同时作为主节点的可能性,这可能导致逻辑数据库性能下降和完整性损失。
自动故障转移规则
备机状态和自动故障转移
在正常镜像操作期间,备机故障转移成员的日志传输状态为活动,这意味着它已从主节点接收到所有日志数据,并与其同步。活跃的备机接收写在主机上的当前日志数据,主机等待备机确认收到日志之后才考虑该数据的持久化。因此,备机为活跃状态满足故障转移的第一个条件。
如果活跃备机在Quality of Service(QoS)(服务质量超时)内不能确定已从主节点接收新数据,主节点会撤销备机的活动状态,断开备机并暂时进入故障状态。当处于故障状态时,主节点不提交任何新的日志数据(可能导致应用程序暂停),确保充足的时间,使故障转移成员在不发生异步的情况下恢复联系或进行适当的安全故障转移决策
当备机重新连接到主节点时,它首先通过从主节点获取所有最近的日志数据来跟上,然后变为活动状态。当备机通过从主节点同步了最新的日志数据并确认其接收而跟上时,将恢复其活动状态。
备机处于活动状态时的自动故障转移
当备机处于活动状态时,如果它能够确认故障转移的第二个条件,主节点不可以再作为主节点,并且在没有人工干预的情况下不能继续成为主节点,那么它就有资格作为主机进行接管。备机可以通过以下三种方式中的任意一种来接管主节点:
• 通过接收来自主机的请求接管的通信
这发生在主机实例正常关闭期间或主机实例检测到主机实例挂起时。一旦主节点发送了这条消息,它就不能再作为主机了,活动备机可以安全地接管它。如果前一个主机被挂起,新的主机就会迫使它关机。
• 通过从仲裁机处收到信息,得知其已与主机失去联系。
当一个网络事件同时将主节点与备机和仲裁机隔离时,它将无限期地进入故障状态。因此,如果一个活跃备机与主机失去联系,并且从仲裁机那里得知它也与主机用失去了联系,那么备机可以安全地接管,因为主机用必须要么已经出现故障,要么被隔离并处于故障状态,因此不能再作为主节点运行了。当连接恢复时,如果前一个主节点挂起,新的主节点就会将之前的主节点强制关闭。
• 通过从主机的 ISCAgent 接收主机实例已关闭或挂起的信息。
当仲裁机不可用或未配置仲裁机时,与主机实例失去联系的活动备机可以尝试联系主机实例的 ISCAgent(只有在主机实例主机系统仍在运行时才可能)以确认主机实例已关闭或处于挂起状态。一旦代理确认主节点不能再作为主机,并且故障转移因此是安全的,备机就会接管。
备机不处于活动状态时的自动故障转移
不活动的备机可以尝试与主机实例的 ISCAgent 联系,以确认主机实例处于关闭状态,或在挂起时强制关闭主机实例,并从代理获取主机实例最近的日志数据。如果在这两方面都成功,那么备机可以安全地作为主机接管。
不活动且无法与主机的 ISCAgent 联系的备机无法确保主机不再作为主机,并且无法保证此时的备机拥有主机最新的日志更新,因此无法接管主机。
当备机没有活动时,仲裁机在故障转移机制中不起任何作用。
对于各种中断情况的镜像响应
自动故障转移中主机中断场景的响应
在以下几种主要的主节点中断情况下,活动的备机故障转移成员会自动接管:
主节点的计划中断(例如出于维护目的)通过关闭其 InterSystems IRIS 实例来启动。
自动故障转移发生是因为主机指示活跃备机接管。
主 InterSystems IRIS 实例由于意外情况而挂起。
自动故障转移发生是因为主节点检测到它被挂起并指示活跃备机接管。
主 InterSystems IRIS 实例由于意外情况而被迫关闭或完全失去响应。
在此情况下,主节点不能指示备机接管。然而,活动备机可以在从仲裁机那里得知它也与主节点失去联系后接管,也可以通过与主机的 ISCAgent 联系并获得主机停机的确认后接管。
主机存储的子系统失败。
存储失败的典型后果是主机实例挂起,原因是 I/O 错误,在这种情况下,主节点检测到它被挂起,并指示活动的备机接管(如场景 2)。然而,在某些情况下,场景 3 或场景 5 描述的行为可能适用。
主机的主机系统发生故障或失去响应。
如果活动的备机从仲裁机获悉它也与主机失去联系,则发生自动故障转移。
如果没有配置仲裁机,或者仲裁机在主机故障前已经不可用,则不可能进行自动故障转移;在这些情况下,可以选择手动强制备机成为主机。
一个网络问题隔离了主机。
如果配置了仲裁机,并且在网络故障时两个故障转移成员都不能连接到它,则主节点将无限地进入故障状态。
• 如果活动的备机从仲裁机获悉它也与主机失去联系,则发生自动故障转移。
• 如果备机在与主节点失去联系的同时失去与仲裁机的联系,则不可能进行自动故障转移。如果两个故障转移成员都已启动,则在还原网络时,备机将与主节点联系,主节点恢复操作。或者,可以手动指定主节点。
如果没有配置仲裁机,或者在网络故障发生前,故障转移成员之一已经与仲裁机断开,则无法进行自动故障转移,主节点将继续作为主机运行。
一个未活动的备机(例如备机正在启动或未追上主机最新日志)可以通过与主节点的 ISCAgent 联系并获得最新的日志数据,在上述场景 1 到 4 下接管。未活动的备机不能在场景 5 和 6 中接管,因为它不能与 ISCAgent 联系;在这些情况下,手动强制使备机成为主机可能是一种选择。
自动故障转移机制详解
代理控制模式
当镜像启动时,故障转移成员在代理控制模式下开始操作。如果仲裁机不可用或未配置仲裁机,则保持此模式。当处于代理控制模式时,故障转移成员响应彼此之间的联系丢失,如下所述。
主机对失联的反应
如果主节点失去了与活动备机的连接,或者超过了等待它确认数据接收的 QoS timeout (QoS 超时),则主程序撤消备机的活动状态并进入故障状态,等待备机确认其不再活动。当主节点收到来自备机的确认或故障超时(是 QoS 超时的两倍)过期时,主程序退出故障状态,恢复为主程序运行。
如果主节点失去了与非活动备机的连接,则它将继续作为主程序运行,不会进入故障状态。
备机对失联的反应
如果备机失去了与主节点的连接,或者超过了等待来自主机的消息的 QoS timeout (QoS 超时),那么它将尝试与主客户的 ISCAgent 联系。如果代理报告主机实例仍作为主机实例运行,则备机重新连接。如果代理确认主节点处于关闭状态,或者它已将其强制关闭,则备机行为如下:
• 如果备机处于活动状态,且代理确认主节点在故障超时内处于停机状态,则备机将作为主服务端接管。
• 如果备机未处于活动状态,或者超过了故障超时时间,那么如果代理确认主节点已停机,并且能够从代理获得最新的日志数据,则备机将接管。
无论是否处于活动状态,备机永远无法在代理控制模式下自动接管,除非主节点自身确认它已挂起,或者主服务的代理确认主服务已停机(可能是在强制停机之后),如果主节点已停机或网络隔离,这两种情况备机无法自动接管。
注意: 当其中一个故障转移成员重启时,它会尝试联系另一个成员的 ISCAgent,其行为与不活动备机的描述一样。
仲裁机控制模式
当故障转移成员相互连接时,两者都连接到仲裁机,并且备机是活动的,它们进入仲裁机控制模式,在该模式中,故障转移会员根据仲裁机提供的关于另一个故障转移成员的信息对它们之间的联系丢失做出响应。因为每个故障转移成员通过测试其与另一个故障转移成员的连接来响应其仲裁连接的丢失,反之亦然,由单个网络事件引起的多个连接丢失被作为单个事件处理。
在仲裁机控制模式中,如果故障转移成员仅丢失其仲裁机连接,或者备机 丢失其活动状态,则故障转移成员协调切换到代理控制模式。
如果主节点和备机节点之间的连接在仲裁机控制模式下断开,则每个故障转移成员根据仲裁机连接的状态进行响应,如下图所述。
所有三个系统连接:
镜像进入仲裁机控制模式(如果尚未进入仲裁机控制模式)
备机失去与仲裁机的连接,但仍连接到主节点:
镜像切换到代理控制模式
主节点继续作为主节点运行
备机尝试重新连接仲裁机
主节点失去与仲裁机的连接,但仍连接到备机:
镜像切换到代理控制模式
主节点继续作为主节点运行
主节点尝试重新连接仲裁程序
故障转移成员彼此失去连接,仍然连接到仲裁机:
镜像切换到代理控制模式
主节点继续作为主节点运行
备机尝试重新连接主节点
仲裁机失败或隔离-故障转移成员失去与仲裁机的连接,但仍彼此连接:
镜像切换到代理控制模式
主节点继续作为主节点运行
两个故障转移成员都尝试重新连接仲裁机
备机中断或被隔离-主节点和仲裁机失去与备机的连接,但仍相互连接:
主节点切换到代理控制模式并继续作为主节点操作
备机(如果在操作中)切换到代理控制模式并尝试重新连接到主节点
主节点中断或被隔离-备机和仲裁机失去与主节点的连接,但仍相互连接:
主节点(如果在运行中)将无限期地保持在仲裁控制模式和故障状态
备机作为主机接管,切换到代理控制模式,并在恢复连接时强制主机关闭
三个连接全部丢失:
主节点(如果在运行中)将无限期地保持在仲裁控制模式和故障状态;如果与备机设备联系,则切换到代理控制模式并恢复主设备的运行
备机(如果在操作中)切换到代理控制模式并尝试重新连接到主节点
注意: 由于单个事件(或多个同时发生的事件)而导致所有连接丢失的情况很少见。在大多数情况下,镜像在所有连接丢失之前切换到代理控制模式,在这种情况下:
主节点(如果在运行)继续作为主节点运行
备机(如果正在运行)尝试重新连接到主节点
文章
姚 鑫 · 五月 7, 2021
# 第三章 使用多维存储(全局变量)(三)
# 在全局变量中复制数据
若要将全局变量(全部或部分)的内容复制到另一个全局变量(或局部数组)中,请使用ObjectScript `Merge`命令。
下面的示例演示如何使用`Merge`命令将`OldData`全局变量的全部内容复制到`NewData`全局变量中:
```
Merge ^NewData = ^OldData
```
如果合并命令的`source`参数有下标,则复制该节点及其后代中的所有数据。如果`Destination`参数有下标,则使用目标地址作为顶级节点复制数据。例如,以下代码:
```java
Merge ^NewData(1,2) = ^OldData(5,6,7)
```
将`^OldData(5,6,7)`及其下的所有数据复制到`^NewData(1,2)`。
# 维护全局变量内的共享计数器
大规模事务处理应用程序的一个主要并发瓶颈可能是创建唯一标识符值。例如,考虑一个订单处理应用程序,在该应用程序中,必须为每一张新发票指定一个唯一的标识号。传统的方法是维护某种计数器表。每个创建新发票的进程都会等待获取此计数器上的锁,递增其值,然后将其解锁。这可能会导致对此单个记录的激烈资源争用。
为了解决此问题,InterSystems IRIS提供了ObjectScript `$INCREMENT`函数。`$INCREMENT`自动递增全局节点的值(如果该节点没有值,则设置为1)。`$INCREMENT`的原子性意味着不需要锁;该函数保证返回一个新的增量值,不会受到任何其他进程的干扰。
可以使用`$INCREMENT`,如下所示。首先,必须决定在其中存放计数器的全局节点。接下来,无论何时需要新的计数器值,只需调用`$INCREMENT`:
```java
SET counter = $INCREMENT(^MyCounter)
```
InterSystems IRIS对象和SQL使用的默认存储结构使用`$INCREMENT`来分配唯一的对象(行)标识符值。
# 对全局变量中的数据进行排序
存储在全局变量中的数据会根据下标的值自动排序。例如,下面的ObjectScript代码定义了一组全局变量(按随机顺序),然后遍历它们以演示全局节点按下标自动排序:
```java
/// w ##class(PHA.TEST.Global).GlobalSort()
ClassMethod GlobalSort()
{
Kill ^Data
Set ^Data("Cambridge") = ""
Set ^Data("New York") = ""
Set ^Data("Boston") = ""
Set ^Data("London") = ""
Set ^Data("Athens") = ""
Set key = $Order(^Data(""))
While (key '= "") {
Write key,!
Set key = $Order(^Data(key))
}
q ""
}
```
```java
DHC-APP> w ##class(PHA.TEST.Global).GlobalSort()
Athens
Boston
Cambridge
London
New York
```
应用程序可以利用全局函数提供的自动排序来执行排序操作或维护对某些值的有序、交叉引用的索引。
InterSystems SQL和ObjectScript使用全局变量自动执行这些任务。
# 全局变量节点排序规则
全局变量节点的排序顺序(称为排序)在两个级别上进行控制:全局变量本身内部和使用全局变量的应用程序。
在应用程序级别,可以通过对用作下标的值执行数据转换来控制全局节点的排序方式(InterSystems SQL和对象通过用户指定的排序函数来执行此操作)。
例如,如果创建一个按字母顺序排序但忽略大小写的名称列表,那么通常你会使用名称的大写版本作为下标:
```java
/// w ##class(PHA.TEST.Global).GlobalSortAlpha()
ClassMethod GlobalSortAlpha()
{
Kill ^Data
For name = "Cobra","jackal","zebra","AARDVark" {
Set ^Data($ZCONVERT(name,"U")) = name
}
Set key = $Order(^Data(""))
While (key '= "") {
Write ^Data(key),!
Set key = $Order(^Data(key))
}
q ""
}
```
```
DHC-APP>w ##class(PHA.TEST.Global).GlobalSortAlpha()
AARDVark
Cobra
jackal
zebra
```
此示例将每个名称转换为大写(使用`$ZCONVERT`函数),以便对下标进行排序,而不考虑大小写。每个节点都包含未转换的值,以便可以显示原始值。
## 数值和字符串值下标
**数字值在字符串值之前进行排序;也就是说,值`1`在值`“a”`之前。如果对给定的下标同时使用数值和字符串值,则需要注意这一点。如果将全局变量用于索引(即根据值对数据进行排序),则最常见的是将值排序为数字(如薪水`salaries`)或字符串(如邮政编码`postal codes`)。**
**对于按数字排序的节点,典型的解决方案是使用一元`+`运算符将下标值强制为数字值。例如,如果要构建按年龄对`id`值进行排序的索引,则可以强制年龄始终为数字:**
```java
Set ^Data(+age,id) = ""
```
**如果希望将值排序为字符串(如`“0022”`、`“0342”`、`“1584”`),则可以通过添加空格(`“”`)字符来强制下标值始终为字符串。例如,如果正在构建一个按邮政编码对`id`值进行排序的索引,则可以强制`zipcode`始终为字符串:**
```java
Set ^Data(" "_zipcode,id) = ""
```
这确保带有前导零的值(如`“0022”`)始终被视为字符串。
## `$SORTBEGIN`和`$SORTEND`函数
通常,不必担心在InterSystems IRIS中对数据进行排序。无论使用SQL还是直接全局访问,排序都是自动处理的。
然而,在某些情况下,可以更有效地进行排序。
具体来说,在以下情况下(`1`)需要设置大量随机(即未排序)的全局节点,(`2`)生成的全局节点的总大小接近InterSystems IRIS缓冲池的很大一部分,那么性能可能会受到不利影响-
因为很多SET操作涉及到磁盘操作(因为数据不适合缓存)。
这种情况通常出现在涉及创建索引全局函数的情况下,例如批量数据加载、索引填充或对临时全局函数中的未索引值进行排序
为了有效地处理这些情况,ObjectScript提供了`$SORTBEGIN`和`$SORTEND`函数。
`$SORTBEGIN`函数为全局变量(或其中的一部分)启动了一种特殊模式,在这种模式中,进入全局变量的数据集被写入一个特殊的临时缓冲区,并在内存(或临时磁盘存储)中进行排序。
当在操作结束时调用`$SORTEND`函数时,数据将按顺序写入实际的全局存储中。
总体操作效率更高,因为实际的写操作是按照要求更少磁盘操作的顺序完成的。
`$SORTBEGIN`函数很容易使用;
在开始排序操作之前,用你想要排序的全局变量的名称调用它,并在操作完成时调用`$SORTEND`:
```java
/// w ##class(PHA.TEST.Global).GlobalSortBeginEnd()
ClassMethod GlobalSortBeginEnd()
{
Kill ^Data
// 为^Data全局初始化排序模式
Set ret = $SortBegin(^Data)
For i = 1:1:10000 {
Set ^Data($Random(1000000)) = ""
}
Set ret = $SortEnd(^Data)
// ^Data现在已经设置和排序
Set start = $ZH
// 现在迭代并显示(按顺序)
Set key = $Order(^Data(""))
While (key '= "") {
Write key,!
Set key = $Order(^Data(key))
}
Set elap = $ZH - start
Write "Time (seconds): ",elap
q ""
}
```
`$SORTBEGIN`函数是为全局变量创建的特殊情况而设计的,在使用时必须小心。
特别地,在`$SORTBEGIN`模式下,不能从正在写入的全局变量中读取数据;
由于数据没有写入,读取将是不正确的。
InterSystems SQL自动使用这些函数创建临时全局索引(例如对未索引的字段进行排序)。
## 在全局变量中使用间接
通过间接方式,ObjectScript提供了一种在运行时创建全局变量引用的方法。
这对于在程序编译时不知道全局变量结构或名称的应用程序非常有用。
间接操作符@支持间接操作,它解除了对包含表达式的字符串的引用。
根据@操作符的使用方式,有几种间接类型。
下面的代码提供了一个名称间接引用的示例,在这个示例中,使用`@`操作符对包含全局引用的字符串进行解引用:
```java
/// w ##class(PHA.TEST.Global).GlobalIndirect()
ClassMethod GlobalIndirect()
{
Kill ^Data
Set var = "^Data(100)"
// 现在使用间接设置^Data(100)
Set @var = "This data was set indirectly."
// 现在直接显示值:
Write "Value: ",^Data(100)
q ""
}
```
```java
DHC-APP> w ##class(PHA.TEST.Global).GlobalIndirect()
Value: This data was set indirectly.
```
也可以使用下标间接在间接语句中混合表达式(变量或文字值):
```java
/// w ##class(PHA.TEST.Global).GlobalIndirect1()
ClassMethod GlobalIndirect1()
{
Kill ^Data
Set glvn = "^Data"
For i = 1:1:10 {
Set @glvn@(i) = "This data was set indirectly."
}
Set key = $Order(^Data(""))
While (key '= "") {
Write "Value ",key, ": ", ^Data(key),!
Set key = $Order(^Data(key))
}
q ""
}
```
```javan
DHC-APP>w ##class(PHA.TEST.Global).GlobalIndirect1()
Value 1: This data was set indirectly.
Value 2: This data was set indirectly.
Value 3: This data was set indirectly.
Value 4: This data was set indirectly.
Value 5: This data was set indirectly.
Value 6: This data was set indirectly.
Value 7: This data was set indirectly.
Value 8: This data was set indirectly.
Value 9: This data was set indirectly.
Value 10: This data was set indirectly.
```
间接是ObjectScript的一个基本特性;
它并不局限于全局引用。
问题
water huang · 四月 22, 2021
m 里面如何获取cpu的序列号? 可以调用操作系统的命令来获取CPU序列号。例如在Cache' for Windows上,可以执行:SAMPLES>s args=3SAMPLES>s args(1)="CPU"SAMPLES>s args(2)="get"SAMPLES>s args(3)="ProcessorID"SAMPLES>d $ZF(-100,"","wmic",.args)ProcessorId0FABFBFF000506EX0FABFBFF000006EX0FABFBFF000006EX0FABFBFF000006EX 乔工,请问 $zf函数的使用,在哪里可以查询到它的所有使用说明 InterSystems Cache'InterSystems IRIS 刚才试了一下,这个不行呢 感谢你的回答 但是我用的是ensemble2016 是Windows吗?在Windows命令行,执行wmic CPU get ProcessorID,能得到CPU序列号吗? Ensemble 2016有点久,没有$ZF(-100)。用$ZF(-1):
https://cedocs.intersystems.com/ens20161/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fzf-1#RCOS_B80417 是Windows,在Windows命令行,执行wmic CPU get ProcessorID,能得到CPU序列号 刚才试了 还是不行 用$ZF(-1), 可以将OS命令输出保存到文件里。例如:w $ZF(-1,"wmic CPU get ProcessorID > c:\temp\cpuinfo.txt") 系统是windows 10 试了,不行,返回的值是1 返回值是1,说明报错了。确认一下是否OS命令写正确了。另外,输出不是看返回值,是看输出的文件 我直接复制的你写的这个命令。
文章
Michael Lei · 五月 24, 2021
作为一个软件架构师,如果要设计一个企业级的架构来满足当前的业务需求时,你需要达到5级的水平,这是一个巨大的挑战。有了InterSystems IRIS。这是有可能的。通过1个产品,你可以得到SQL + NoSQL + ESB + BI + Open Analytics + Real Time Virtual cubes + NLP + AutoML + ML(使用Python)和高级云支持 + Sharding支持。
以Oracle为例,你需要Oracle数据库+ Oracle NoSQL+ Oracle BI+ Oracle SOA套件+一些附加组件,如R、Partitioning和RAC以及一些Oracle云产品的NLP和AI功能。对于开源架构、IBM或者其他友商来说,也是类似的,你需要的组件只可能更多。
医疗机构在数字化转型在技术上最大的挑战之一,就是技术栈过于丰富、快速变化以及对人的技术要求很高(这也是为什么全栈工程师最值钱的原因);一个统一、高效、稳定、现代化、专为医疗定制的一体化数据平台能帮助客户很好地解决这个问题。IRIS for Health 是您的不二选择。
文章
Michael Lei · 八月 18, 2022
Hi 社区的朋友们,大家好!
有时我们需要以编程方式自动将CSV数据从文件或者UR网址L导入到InterSystems IRIS。我们希望创建具有适当数据类型的类并导入数据。
我在Open Exchange上发布了一个模块csvgen,它正是这样做的。
如果你只需要将CSV文件导入IRIS,你可以这么做:
USER>do ##class(community.csvgen).Generate("/usr/data/titanic.csv",,"Data.Titanic")
Class name: Data.Titanic
Header: PassengerId INTEGER,Survived INTEGER,Pclass INTEGER,Name VARCHAR(250),Sex VARCHAR(250),Age INTEGER,SibSp INTEGER,Parch INTEGER,Ticket VARCHAR(250),Fare MONEY,Cabin VARCHAR(250),Embarked VARCHAR(250)
Records imported: 891
USER>
或者你的CSV文件在互联网上, 例如GitHub上面的新冠疫情数据 你可以这样获得数据:
USER>d ##class(community.csvgen).GenerateFromURL("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/05-29-2020.csv",",","Data.Covid19")
Class name: Data.Covid19
Header: FIPS INTEGER,Admin2 VARCHAR(250),Province_State VARCHAR(250),Country_Region VARCHAR(250),Last_Update DATE,Lat MONEY,Long_ DOUBLE,Confirmed INTEGER,Deaths INTEGER,Recovered INTEGER,Active INTEGER,Combined_Key VARCHAR(250),Incidence_Rate DOUBLE,Case-Fatality_Ratio DOUBLE
Records imported: 3522
USER>
Installation安装
从ZPM安装:
USER>zpm
zpm:USER>install csvgen
csvgen 模块是打包了 CSV2CLASS method .
ObjectiveScript 的profile还不是很理想,欢迎各种建议意见。谢谢!
公告
Claire Zheng · 十一月 22, 2021
大家好!
我们很高兴地向大家介绍我们开发者社区中文版的新版主——段海华( @Duan.Haihua)!
让我们以热烈的掌声欢迎段海华( @Duan.Haihua),以下是他的个人介绍。
段海华,东华医为利润中心副主管
以下是他的个人介绍 @Duan.Haihua:
– 我主持参加了多家医院的互联互通评审工作,并主持了互联互通成熟度测评(Health Information Connectivity Maturity Evaluation, HICME)工具的开发与维护。我有丰富的基于InterSystems产品和项目工作经验,对互联互通测评相关标准有较深刻的理解。
– 通过开发者社区,我希望能够帮助刚开始接触互联互通标准的开发者快速理解相应内容,分享互联互通测评过程中常见问题的解决方案,为开发者对系统标准化改造过程提供参考意见。
热烈欢迎!
感谢段海华的付出( @Duan.Haihua)!我们相信你会成为一名优秀的版主! 👏🏼 热烈欢迎👏!!!
文章
Michael Lei · 六月 1, 2022
InterSystems Production 监控是管理门户中的一个页面,用于显示当前运行的Production监控信息。我喜欢这个页面的样子,但这并不适合所有人。
2022年5月13日,我在开发者社区上看到这个帖子。
https://community.intersystems.com/post/creating-custom-monitoring-page
我同意马克的观点,Production监控很复杂。我想创建一个更漂亮干净的监控页面。
我开始着手制作一个利用类方法提供Production数据的 CSP(Cache Server Page)页面。
我与马克分享了我的第一次尝试。他根据自己的想法定制了这个页面。我喜欢他对页面的布局,使其在视觉上更有吸引力。我把他的设计整合到我的应用程序Production监控中。
我看到马克对业务服务的显示进行了过滤,只显示需要注意的服务。他在页面的底部添加了服务器的名称和它的镜像状态。
你可以在Open Exchange和当前的竞赛中找到我的Production监控器的应用。
https://openexchange.intersystems.com/package/production-monitor
文章
Frank Ma · 一月 5, 2022
本文译自 https://community.intersystems.com/post/using-sql-apache-hive-hadoop-big-data-repositories
大家好,
在使用Spark做Hadoop时,InterSystems IRIS有一个很好的连接器。但市场上也提供了大数据Hadoop访问的其他优秀替代方案-Aparche Hive。请看区别:
HIVE
SPARK
Hive是一个数据库,用类似于RDBMS数据库的表格形式存储数据。
Spark不是一个数据库,它是一个数据分析框架,可以在内存中对大至PB字节的大容量数据进行复杂的数据分析。
使用称作HiveQL的自己的SQL引擎,数据可以从Hive中抽取出来。只能使用SQLs来抽取数据。
Spark既能使用复杂SQLs(Complex SQLs)也能使用MapReduce机制进行数据分析。它支持Java, Scala 和Python写的分析框架。
Hive在Hadoop之上运行。
Spark没有自己专用的存储。实际上,它是从外部的分布式数据存储如运行在Hadoop和MongoDB上的Hive、HBase中抽取数据。
Hive是一个基于数据仓库技术的数据库
Spark更适合在内存中进行复杂和快速的数据分析以及对数据进行流式处理。
对于那些需要在可横向扩展的RDBMS数据库上运行数据仓库操作的应用来说,Hive是最适合的。
Spark最适合于那些要求比MapReduce机制更快地进行大数据分析的应用。
来源: https://dzone.com/articles/comparing-apache-hive-vs-spark
我做了一个PEX互操作性服务,可以让你在你的InterSystems IRIS应用内部使用Apache Hive。请试用如下步骤:
1. 在iris-hive-adapter 项目上做一个Git Clone:
$ git clone https://github.com/yurimarx/iris-hive-adapter.git
2. 在这个目录内打开terminal 并运行:
$ docker-compose build
3. 运行IRIS容器:
$ docker-compose up
4. 打开项目中的Hive Prouction,运行一个Hello样例): http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=dc.irishiveadapter.HiveProduction
5. 点击“开始”运行Production.
6. 现在我们来测试应用!
7. 运行你的REST客户端应用程序(比如Postman),在body部分使用项目的URLS和命令(使用POST请求):
7.1 在大数据中生成一个新的表:POST http://localhost:9980/?Type=DDL. 在BODY中: CREATE TABLE helloworld (消息字符串)
7.2 在表中插入: POST http://localhost:9980/?Type=DDL. 在BODY中: INSERT INTO helloworld VALUES ("hello")
7.3 T从表中得到结果清单: POST http://localhost:9980/?Type=DML. 在BODY中: SELECT * FROM helloworld (注意:这里的类型是)
现在,你有了2个在IRIS中使用大数据的选项:Hive 或者Spark。希望你喜欢。
文章
Michael Lei · 十二月 27, 2022
一个简单的生产配置,使 FHIR 交易捆绑包能够通过 Box 和 Dropbox 加载到 InterSystems® FHIR® 服务器中。使用包含的 MFT 连接组件和 14 行自定义业务流程,此生产配置会将您的交易捆绑包处理到 FHIR 资源,以便立即使用,就像哈利·波特的魔法一样。
我首先会展示该生产配置的短视频导览、MFT 连接以及 IRIS 上 Box 和 Dropbox 的 Oauth2 应用配置,接下来循序渐进地展示一些步骤,让您使用您喜欢的任何 MFT 供应商以及您选择的任何工作流程、桌面、API 或 Web 控制台拖放操作。
一些陷阱:
OAUTH2 回调需要使用 IRIS 的 SSL 端点来提供重定向… 最好在 Health Connect Cloud 上尝试一下!
Dropbox for Business 在基于团队的令牌方面面临挑战,个人 Dropbox 则运行良好。 这不是无法忍受的情形,但需要一些耐心。
配置 MFT 连接时,注意 Dropbox 的 baseurl 上的“/”(确保它存在)。
对于 Box 和 DropBox 的路径,MFT 出站适配器都需要具有结尾“/”。
现在,鉴于以上获奖的 OBS 支持的内容可能有不足之处,如果 InterSytems 文档还不够,下面是需要遵循的步骤。
步骤概览:
将 FHIRDrop 或 FHIRBox 应用创建到一个点,然后停止! (协作和倾听)
在您的 InterSystems FHIR 服务器、HealthConnect 或 I4H 上配置 MFT 连接。
完成您的 FHIRDrop 或 FHIRBox 应用,提供来自 MFT 连接的重定向 URL。
授权您的 MFT 连接。
构建您的生产配置。
正常拖放
创建 FHIRDrop 或 FHIRBox 应用
这里的想法是在每个 Box 和 Dropbox 开发者控制台中“开始”您的应用配置,这会让您看到客户端 ID 和客户端密码,然后让选项卡挂起并移动到 IRIS MFT 连接。
(协作和倾听)只需收集您的客户端 ID 和客户端密码,挂起浏览器选项卡,然后继续:
配置 MFT 连接
基 URL:https://api.box.com/2.0
基 URL:https://api.dropboxapi.com/2/(注意结尾斜杠)
完成应用注册
现在,回到应用注册并完成应用。确保插入上述步骤中的重定向 URL,并添加与 file.read、file.write 有关的范围。
授权您的托管文件传输连接
回到您的托管文件传输连接并通过调用“Get Access Token”(获取访问令牌)来“授权”连接。
构建您的生产配置
生产配置
以下是自定义业务流程,生产配置的源代码:https://gitlab.com/isc_cloud/fhir-drop-fhir-box-mft-2-fhir
正常拖放!
现在,获取 FHIR!
![图片](/sites/default/files/inline/images/gar.png)
文章
Louis Lu · 六月 12, 2023
文章相关视频参见Synthea生成FHIR测试数据,以及FHIR服务器加载FHIR资源文件
1. 什么是Synthea
Synthea是一个开源软件包,可以模拟生成患者就诊数据。他的github地址在这里。
生成的就诊模版从最初的模拟美国前十种常见病、前十种慢性病到现今超过90种不同的模型。详细模型参见这里。
基于当前版本,Synthea的特性包括:
从出生到死亡的全生命周期
可配置的人口统计学信息(默认为美国马萨诸塞州人口普查数据)
模块化规则系统
插入通用模块
用于附加功能的自定义 Java 规则模块
主要医疗事件就诊、急诊室就诊和症状驱动的就诊
症状、 过敏、药品、 疫苗接种、观察/生命体征、实验室、处置、 护理计划
支持格式
HL7 FHIR(R4、STU3 v3.0.1 和 DSTU2 v1.0.2)
ndjson 格式的批量 FHIR(设置 exporter.fhir.bulk_data = true 以激活)
C-CDA (设置 exporter.ccda.export = true 以激活)
CSV (设置 exporter.csv.export = true 以激活)
CPCDS (设置 exporter.cpcds.export = true 以激活)
使用Graphviz可视化呈现规则和疾病模块
支持的参数可见下图
比如 -p 5 生成5条测试数据
-g M 生成男性测试数据
-a 60-65 生成年龄在60-65周岁患者测试数据
2. 使用Synthea 生成测试数据
为了方便使用,也将该软件做成了docker,所以你可以简单的执行下面命令行
docker run --rm -v $PWD/output:/output --name synthea-docker intersystemsdc/irisdemo-base-synthea:version-1.3.4 -p 5
该命令会在当前路径的output文件夹下生成5条患者符合FHIR标准的就诊数据,数据相关摘要信息如下面终端输出:
3. 加载生成的 FHIR 数据至 InterSystems IRIS for Health
生成完FHIR数据后,需要加载到FHIR服务器(FHIR资源仓库)中。
我们在输出目录下可以看到生成7条json数据,其中5条患者就诊相关,1条就诊医院信息,一条参与者(就诊医生)信息。
在InterSystems IRIS for health中可以方便的使用DataLoader类中的方法,批量加载FHIR资源数据,进入FHIR 资源仓库命名空间后执行:
zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/external/fhir/","FHIRServer","/fhir/r4")
该方法中的第一个参数是fhir资源文件路径; 第二个参数服务类型,这里一般是FHIRServer; 第三个参数FHIRServer的service名称。
执行后显示如下:
之后我们可以进入管理门户,或者使用SQL客户端查询相关存储表,表明数据被正确导入
文章
Jingwei Wang · 九月 20, 2022
在虚拟化环境中使用镜像,构成镜像的InterSystems IRIS实例被安装在虚拟主机上,创造了一个混合的高可用性解决方案,将镜像的优点与虚拟化的优点结合起来。镜像通过自动故障切换对计划内或计划外的故障提供即时响应,而虚拟化HA软件在计划外的机器或操作系统故障后自动重新启动承载镜像成员的虚拟机。这允许失败的成员迅速重新加入镜像,充当备份(或在必要时作为主机)。
当镜像被配置在虚拟化环境中时,请参考以下建议:
故障转移成员的虚拟主机和备机不可以配置在同一台物理机上。
为了避免单点存储故障,故障转移成员上的InterSystems IRIS实例所使用的存储应永久隔离在不同磁盘组或存储阵列的独立数据存储中。
在虚拟化平台层面上进行的一些操作,如备份或迁移,可能会导致故障转移成员长时间没有反应,从而导致不需要的故障转移或不理想的警报频率。为了解决这个问题,你可以增加QoS超时设置。
在进行导致故障转移成员连接中断的计划性维护操作时,你可以暂时停止备份上的镜像,以避免不必要的故障转移和警报。
在镜像成员上必须非常谨慎地使用快照管理,因为将一个成员恢复到早期的快照,既会删除该成员的最新状态(例如,自拍摄快照以来,该成员可能已经从主机变为备机),也会删除其他成员仍然拥有的日志数据。 使用虚拟机备份恢复镜像时请特别注意:
被恢复到早期快照的故障转移成员只能从power-off 状态下恢复;从power-on 状态下恢复会造成两个故障转移成员同时作为主机的可能性。
如果被恢复到早期快照的故障转移成员在没有获得自快照以来创建的所有日志数据的情况下成为主要成员--例如,因为它被迫成为主要成员--所有其他镜像成员必须被重建(如需重建镜像,请联系WRC)。
文章
姚 鑫 · 六月 9, 2021
# 第二章 从对象写入XML输出
本章介绍如何从InterSystems IRIS对象生成XML输出。
# 创建XML编写器概述
InterSystems IRIS提供了用于为InterSystems IRIS对象生成`XML`输出的工具。可以指定XML投影的详细信息,如将对象投影到`XML`中所述。然后创建一个`Writer`方法,该方法指定`XML`输出的整体结构:字符编码、对象的显示顺序、是否包括处理指令等。
基本要求如下:
- 如果需要特定对象的输出,则该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。
- 输出方法必须创建`%XML.Writer`的实例,然后使用该实例的方法。
下面的终端会话显示了一个简单的示例,在该示例中,我们访问启用了XML的对象并为其生成输出:
```java
/// d ##class(Sample.Person).Populate(100)
/// w ##class(PHA.TEST.Xml).Obj2Xml(1)
ClassMethod Obj2Xml(ID)
{
s obj = ##class(Sample.Person).%OpenId(ID)
s xml = ##class(%XML.Writer).%New()
s xml.Indent=1
s status = xml.RootObject(obj)
q ""
}
```
```java
DHC-APP>w ##class(PHA.TEST.Xml).Obj2Xml(1)
yaoxin
111-11-1117
1990-04-25
889 Clinton Drive
St Louis
WI
78672
9619 Ash Avenue
Ukiah
AL
56589
濮氶懌
111-11-1115
Red
Orange
Yellow
Green
Red
Orange
Yellow
31
```
# 创建输出方法
输出方法按照指定的顺序逐段构造一个XML文档。输出方法的整体结构取决于需要输出完整的XML文档,还是仅仅输出一个片段。
## 输出方法的整体结构
方法应按以下顺序执行以下部分或全部操作:
1. 如果使用的对象可能无效,请调用该对象的`%ValidateObject()`方法并检查返回的状态。如果对象无效,则XML也将无效。
**`%XML.Writer` 在导出对象之前不会对其进行验证。这意味着,如果刚刚创建了一个对象,但尚未对其进行验证,则该对象(以及XML)可能是无效的(例如,因为缺少必需的属性)。**
2. 创建`%XML.Writer`类的实例,并根据需要设置其属性。
特别是,需要设置以下属性:
- `Indent` 缩进-控制输出是在缩进和换行中生成(如果缩进等于1),还是作为单个长行生成(如果缩进等于0)。后者是默认设置。
- `IndentChars` 缩进字符-指定用于缩进的字符。默认值为两个空格的字符串。如果缩进为0,则此属性无效。
- `Charset` 字符集-指定要使用的字符集。
为了提高可读性,本文档中的示例使用缩进等于1。
3. 指定输出目标。
默认情况下,输出写入当前设备。要指定输出目标,请在开始编写文档之前调用以下方法之一:
- `OutputToDevice()`-将输出定向到当前设备。
- `OutputToFile()`-将输出定向到指定文件。可以指定绝对路径或相对路径。请注意,该目录路径必须已经存在。
- `OutputToString()`-将输出定向到字符串。稍后,可以使用另一种方法来检索此字符串。
- `OutputToStream()`-将输出定向到指定的流。
4. 启动文档。可以使用`StartDocument()`方法。请注意,如果尚未通过`StartDocument()`启动文档,则以下方法会隐式启动文档:`Write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。
5. 可以选择写入文档的序言行。可以使用以下方法:
- `WriteDocType()` - 编写DOCTYPE声明。
- `WriteProcessingInstructions()`-编写处理指令。
6. 可以选择指定默认命名空间。编写器将其用于没有定义的XML命名空间的类。
7. 可以选择将命名空间声明添加到根元素。为此,可以在启动根元素之前调用几个实用程序方法。
8. 启动文档的根元素。详细信息取决于该文档的根元素是否对应于InterSystems IRIS对象。有两种可能性:
- 根元素可能直接对应于InterSystems IRIS对象。如果要为单个对象生成输出,通常会出现这种情况。
在本例中,使用`RootObject()`方法,该方法将指定的启用XML的对象作为根元素写入。
- 根元素可能只是一组元素的包装器,而这些元素是InterSystems IRIS对象。
在本例中,使用`RootElement()`方法,该方法插入具有指定名称的根级元素。
9. 如果使用`RootElement()`方法,请调用方法来为根元素内的一个或多个元素生成输出。可以按照选择的任何顺序或逻辑在根元素中编写任何元素。有几种方法可以编写单个元素,并且可以结合使用这些技术:
- 可以使用`object()`方法,该方法写入启用XML的对象。可以指定此元素的名称,也可以使用由对象定义的默认值。
- 可以使用`element()`方法,该方法使用提供的名称写入元素的开始标记。然后,可以使用`WriteAttribute()`、`WriteChars()`、`WriteCData()`等方法编写内容、属性和子元素。子元素可以是另一个`Element()`,也可以是`Object()`。使用`EndElement()`方法指示元素的结束。
- 可以使用`%XML.Element`并手动构造元素。
10. 如果使用的是`RootElement()`方法,请调用`EndRootElement()`方法。此方法关闭文档的根元素,并根据需要减少缩进(如果有)。
11. 如果文档是从`StartDocument()`开始的,请调用`EndDocument()`方法关闭文档。
12. 如果将输出定向到字符串,请使用`GetXMLString()`方法检索该字符串。
还有许多其他可能的组织,但请注意,某些方法只能在某些上下文中调用。具体地说,一旦开始一个文档,在结束第一个文档之前,不能开始另一个文档。如果这样做,`Writer`方法将返回以下状态:
```java
#6275: Cannot output a new XML document or change %XML.Writer properties
until the current document is completed.
#6275:在当前文档完成之前,无法输出新的XML文档或更改%XML。Writer属性。
```
`StartDocument()`方法的作用是:显式启动文档。如前所述,其他方法隐式启动文档:`write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。
注意:这里描述的方法旨在使够向XML文档写入特定的单元,但在某些情况下,可能需要更多的控制。在`%XML.Writer`提供了一个额外的方法`Write()`,可以使用该方法将任意字符串写入输出中的任何位置。
此外,还可以使用`Reset()`方法重新初始化编写器属性和输出方法。如果已经生成了一个XML文档,并且希望在不创建新的编写器实例的情况下生成另一个文档,这将非常有用。
## 错误检查
**%XML.Writer的大多数方法都会返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。**
## 插入注释行
如前所述,使用`WriteComment()`方法插入注释行。可以在文档中的任何位置使用此方法。如果尚未启动XML文档,此方法将隐式启动文档。
## 示例
```java
/// w ##class(PHA.TEST.Xml).Write()
ClassMethod Write() As %Status
{
s obj = ##class(Sample.Person).%OpenId(1)
set writer=##class(%XML.Writer).%New()
set writer.Indent=1
set status=writer.OutputToDevice()
if $$$ISERR(status) {
do $System.Status.DisplayError(status)
quit $$$ERROR($$$GeneralError, "输出目标无效")
}
set status=writer.RootObject(obj)
if $$$ISERR(status) {
do $System.Status.DisplayError(status)
quit $$$ERROR($$$GeneralError, "写入根对象时出错")
}
quit status
}
```
请注意,此方法使用`OutputToDevice()`方法将输出定向到当前设备(默认目标)。这不是必需的,但仅用于演示目的。
当然,输出取决于所使用的类,但可能如下所示:
```java
DHC-APP>w ##class(PHA.TEST.Xml).Write()
xiaoli
111-11-1111
test
2662
1
```
## 有关缩进选项的详细信息
如前所述,可以使用编写器的缩进属性来获取包含附加换行符的输出,以获得更好的可读性。此格式没有正式规范。本节介绍`%XML.Writer`使用的规则。如果缩进等于`1`:
- 任何只包含空格字符的元素都会转换为空元素。
- 每个元素都放在自己的行上。
- 如果某个元素是前一个元素的子元素,则该元素相对于该父元素缩进。缩进由`IndentChars`属性确定,该属性默认为两个空格。
文章
Kelly Huang · 七月 19, 2023
在之前的文章中,我们已经了解了如何配置和自定义我们的 EMPI,我们已经了解了如何通过 HL7 消息传递将新患者纳入我们的系统中,但当然,并不是所有的东西都是 HL7 v.2!我们如何配置 EMPI 实例以使用 FHIR 消息传递?
什么是FHIR?
对于那些不太熟悉 FHIR 这个术语的人,只需指出它们是Fast Healthcare Interoperability Resource的首字母缩写即可。 FHIR是HL7制定的医疗保健互操作性标准,基于JSON格式和REST通信,建立了一系列不同类型信息(患者数据、医院中心、诊断、医疗预约......)的“资源”。您可以在他们的官方页面上查看所有这些资源
InterSystems 和 FHIR
从 InterSystems 中,我们了解到 FHIR 为医疗保健互操作性领域提供的实用程序,因此我们拥有广泛的产品和功能,使我们能够充分利用并发挥其全部潜力:
InterSystems FHIR 服务器允许存储和管理 FHIR 资源。
InterSystems FHIR SQL Builder使用户可以通过 SQL 查询使用 FHIR 存储库中存储的所有信息。
InterSystems FHIR 适配器允许在我们的产品中发送和接收 FHIR 消息。
好吧,在我们的文章中,我们将利用适配器提供的功能来接收 FHIR 消息。
EMPI配置
FHIR 适配器安装
我们首先回顾一下独立 EMPI 安装中有哪些服务:
正如您所看到的,在独立模式下安装 EMPI 后,创建了一个注册表服务,其中包含配置 EMPI 所需的所有选项,以及我们称为 HSPIDATA 的命名空间,其中我们有一个产品来管理互操作性功能,需要。
对于我们的案例,我们创建了一个名为 WEBINAR 的新命名空间,我们将在其中部署正常的互操作性生产。我们将在此命名空间中安装FHIR 互操作性适配器,此安装将发布一个 Web 应用程序,我们将向该应用程序发送带有 FHIR 消息的 REST 调用。为此,我们将从终端执行以下命令:
zn "WEBINAR" set status = ##class (HS.FHIRServer.Installer).InteropAdapterConfig( "/csp/healthshare/webinar/fhir/r4" )
安装后,我们将检查从管理门户创建的新应用程序:
让我们在 WEBINAR 命名空间中检查我们的生产:
默认创建了两个新的业务组件:
InteropService :在我们的生产中,我们直接使用其类 HS.FHIRServer.Interop.Service 的名称,它将是负责接收发送到我们端点的 FHIR 消息的业务服务。 /csp/healthshare/网络研讨会/fhir/r4
InteropOperation :我们已将此名称更改为 HS.FHIRServer.Interop.Operation。对于我们的示例,我们忽略了此业务操作,因为我们将通过 TCP 将 FHIR 消息的信息发送到我们的 EMPI 生产
创建业务组件来管理 FHIR 消息传递
很好,我们已经启用了业务服务来接收 FHIR 消息,现在我们感兴趣的是从消息中提取信息并将其发送到我们的 EMPI 生产。让我们看一下 HS.FHIRServer.Interop.Service 将接收的消息类型:
Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic" ; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ] ; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ] ; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status
该消息的类型为 HS.FHIRServer.Interop.Request,如果我们查阅文档,我们将看到它有一个名为QuickStreamId的关联属性,我们将使用该属性来提取关联的 FHIR 消息。为此,我们将业务服务中收到的消息重定向到业务流程Webinar.BP.FHIRToHubRequest
该业务流程仅具有将消息重定向到负责通过 TCP 将消息发送到 EMPI 生产的业务操作FromFHIRToMPI的功能。
Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync ( "FromFHIRToMPI" , pRequest, .pResponse) Quit $$$OK }
让我们看一下Webinar.BO.ToMPI类,并解释它如何从 FHIR 获取消息并将其转换为我们将发送到 EMPI 生产的字符串:
Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter" ; Property Adapter As EnsLib.TCP.CountedOutboundAdapter ; Parameter INVOCATION = "Queue" ; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}" ; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String (MAXLEN = 256 ) ; Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" ) // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) } // Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType= "HS.FHIRServer.Interop.Request" > <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } }
让我们一步步分析我们的类:
第一点是了解我们将在业务运营选项中配置的 FHIR 消息的版本。
Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" )
我们恢复正在处理的 FHIR 消息的类型,在我们的例子中它将是 R4。
接下来我们提取FHIR消息的内容:
// We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) }
我们从 FHIR 消息中获取 %DynamicObject:
ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj }
最后,我们将其转换为字符串,并通过 TCP 将其发送到我们的 EMPI 生产,向向我们发送原始消息的系统返回 200:
// Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC
好吧,我们已经将消息发送到我们的 EMPI 生产中。让我们看一下示例消息的跟踪:
在下一篇文章中,我们将检索提交的字符串,将其转换回 %DynamicObject 并向 EMPI 提供数据以使用新数据执行注册/更新操作。
希望这些内容对你有用!
致敬原创作者 @Luis Angel
文章
姚 鑫 · 七月 6, 2021
# 第二十九章 从XML架构生成类
Studio提供了一个向导,该向导读取XML模式(从文件或URL),并生成一组支持XML的类,这些类对应于模式中定义的类型。
所有的类都扩展`%XML.Adaptor`。
指定一个包来包含类,以及控制类定义细节的各种选项。
向导还可以作为类方法使用,也可以使用该类方法。
在内部,SOAP向导在读取WSDL文档并生成web客户端或web服务时使用此方法;
注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。
# 使用向导
要使用XML架构向导,请执行以下操作:
1. 选择 Tools > Add-Ins > XML Schema Wizard.
![image](/sites/default/files/inline/images/1_50.png)
2. 在第一个屏幕上,指定要使用的XML模式。
做以下其中一项:
- 对于模式文件Schema File,选择Browse 以选择XML模式文件。
- 对于URL,指定模式的URL。
![image](/sites/default/files/inline/images/2_30.png)
3. 选择Next。
下一个屏幕显示模式,以便可以验证选择了正确的模式。
![image](/sites/default/files/inline/images/3_24.png)
4. 可选择以下选项:
- 保留空类Keep Empty Classes,它指定是否保留没有属性的未使用的类。
如果选择此选项,则不会在向导结束时删除此类;
否则,将删除它们。
- “不创建数组属性”Create No Array Properties控制向导是否生成数组属性。
如果选择此选项,向导不会生成数组属性,而是生成另一个表单。
- 为可为空的元素生成XMLNIL属性参数,它控制向导是否为生成的类中适用的属性指定XMLNIL属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNIL=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
- 为可为空的元素生成`XMLNILNOOBJECT`属性参数,它控制向导是否为生成的类中适用的属性指定`XMLNILNOOBJECT`属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNILNOOBJECT=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
5. 选择Next。
下一个屏幕显示关于要生成的类的选项的一些基本信息。
6. 在这个屏幕上,指定以下选项:
- 如果希望向导编译生成的类,可以选择“编译生成的类”。
- 可选择“添加NAMESPACE类参数”来指定`NAMESPACE`参数。
在本例中,`NAMESPACE`被设置为模式中`targetNamespace`的值。
如果不设置此选项,则不指定`NAMESPACE`。
建议在所有情况下都选择这个选项,因为每个支持XML的类都应该分配给一个XML名称空间。
(但是,为了向后兼容,可以将此选项清除。)
- 如果希望生成的类是持久类,请选择Create persistent classes。然后类扩展`%Persistent`。
可以稍后在向导中针对各个类更改这一点。
- 如果生成持久类,可以选择如何处理由另一个` b`的``组成的`` a。当向导生成一个包含属性`a`的持久类时,该属性有三种可能的形式。
可以将其定义为对象列表、一对多关系(默认)或父子关系。
下表总结了这些选择:
在持久性类中为集合属性使用关系 |向多对关系添加索引 |使用父子关系|生成的属性A的形式
---|---|---|---
selected (default)| not selected| not selected| 无索引的一对多关系
selected (default) |selected| not selected| 在多侧与索引的一对多关系
selected (default) |如果选择Use parent-child relationship,则忽略此选项| selected| 父子关系
not selected| not selected| not selected |List of objects
此外,如果未选择使用父子关系,则可以选择将`%OnDelete`方法添加到类以级联删除。如果选择此选项,当向导生成类定义时,它会在这些类中包含`%OnDelete()`回调方法的实现。生成的`%OnDelete()`方法删除类引用的所有持久对象。如确实选择了使用父子关系,请不要选择此选项;父子关系已经提供了类似的逻辑。
注意:如果修改生成的类,请确保根据需要修改`%OnDelete()`回调方法。
如果生成持久类,向导可以向每个对象类型类添加临时属性,以便可以为对象投影InterSystems IRIS内部标识符。选项如下:
- None-如果选择此选项,向导不会添加此处描述的任何属性。
- Use Id -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Id (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use Oid -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Oid (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use GUID-如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.GUID (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
底部的表格列出了模式中的XML名称空间。在这里,指定包含该行中显示的XML名称空间的类的包。要执行此操作,请在程序包名字段中为该行指定程序包名。
7. 选择下一步。
8. 在下一个屏幕上,指定以下选项:
- Java Enabled - 如果选择此选项,则每个类都包括一个Java映射。
- Data Population数据填充-如果选择此选项,则除`%XML.Adaptor`外,每个类还继承会`%Populate`。
- SQL Column Order-如果选择此选项,每个属性将为`SqlColumnNumber`关键字指定一个值,以便属性在SQL中的顺序与它们在架构中的顺序相同。
- No Sequence Check-如果选中此选项,向导将生成的类中的`XMLSEQUENCE`参数设置为0。在某些情况下,如果XML文件的元素顺序与XML架构不同,则此选项非常有用。
默认情况下,`XMLSEQUENCE`参数在生成的类中设置为1。这可确保属性以与架构中相同的顺序包含在类定义中。
- XMLIGNORENULL-如果选择此选项,向导会将`XMLIGNORENULL=1`添加到类定义中。否则,它不会添加此参数。
- 将流用于二进制Use Streams for Binary - 如果选择此选项,向导将为`xsd:base64Binary`类型的任何元素生成`%Stream.GlobalBinary`类型的属性。如果清除此选项,则该属性的类型为`%xsd.base64Binary`。
请注意,向导将忽略`xsd:base64Binary`类型的任何属性。
- 在复选框下方,该表列出了向导将生成的类。对于每个类,确保适当地设置了`Extensions/Type`。在此,可以选择以下选项之一:
- 持久类`Persistent` -如果选择此选项,则类是持久性类。
- `Serial`-如果选择此选项,则类为序列类。
- `Registered Object`-如果选择此选项,则类为注册对象类。
所有生成的类还扩展`%XML.Adaptor`。
- 在表的右列中,为每个应编制索引的属性选择索引。
9. 选择Finish(完成)。
然后,向导将生成这些类,并在需要时编译它们。
对于这些类的属性,如果架构中相应元素的名称以下划线(_)开头,则属性名称以百分号(%)开头。
# 以编程方式生成类
XML架构向导也可用作`%XML.Utils.SchemaReader`类的`process()`方法。要使用此方法,请执行以下操作:
1. 创建`%XML.Utils.SchemaReader`的实例。
2. 可以选择设置实例的属性以控制其行为。
3. 可以选择创建InterSystems IRIS多维数组,以包含有关其他设置的信息。
4. 调用实例的`process()`方法:
```
method Process(LocationURL As %String,
Package As %String = "Test",
ByRef Features As %String) as %Status
```
- LocationURL必须是架构的URL或架构文件的名称(包括其完整路径)。
- Package是用于放置生成的类的包的名称。如果不指定程序包,InterSystems IRIS将使用服务名称作为程序包名称。
- Feature是在上一步中选择创建的多维数组。
# 每种XSD类型的默认IRIS数据类型
对于它生成的每个属性,XML架构向导会根据架构中指定的XSD类型自动使用适当的InterSystems IRIS数据类型类。下表列出了XSD类型和相应的InterSystems IRIS数据类型:
用于XML类型的InterSystems IRIS数据类型
源文档中的XSD类型 |生成的IRIS类中的数据类型
---|---
anyURI | %xsd.anyURI
base64Binary | `%xsd.base64Binary`或`%Stream.GlobalBinary`,具体取决于选择的选项。确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的属性从`%xsd.base64Binary`修改为适当的流类。)
boolean | `%Boolean`
byte | `%xsd.byte`
date | `%Date`
dateTime | `%TimeStamp`
decimal |`%Numeric`
double | `%xsd.double`
float | `%xsd.float`
hexBinary | `%xsd.hexBinary`
int |`%xsd.int`
integer |`%Integer`
long | `%Integer`
negativeInteger | `%xsd.negativeIntege`
nonNegativeInteger | `%xsd.nonNegativeInteger`
nonPositiveInteger |`%xsd.nonPositiveInteger`
positiveInteger | `%xsd.positiveInteger`
short | `%xsd.short`
string | `%String` (注意:责任确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的类型修改为适当的流类。)
time | `%Time`
unsignedByte | `%xsd.unsignedByte`
unsignedInt |`%xsd.unsignedInt`
unsignedLong | `%xsd.unsignedLong`
unsignedShort | `%xsd.unsignedShort`
no type given | `%String`
# 生成的属性的属性关键字
对于它生成的每个属性,XML架构向导还使用架构中的信息自动设置以下关键字:
- Description
- Required
- ReadOnly (如果相应的元素或属性是用固定属性定义的)
- InitialExpression (该值取自架构中的固定属性)
- Keywords related to relationships
# 生成的属性的参数
对于它生成的每个属性,XML架构向导会根据需要自动设置`XMLNAME`、`XMLPROJECTION`和所有其他与XML相关的参数。它还根据需要设置其他参数,如`MAXVAL`、`MINVAL`和`VALUELIST`。
# 调整为超长字符串生成的类
在极少数情况下,可能需要编辑生成的类来容纳超长的字符串或二进制值,超出字符串长度限制。
对于任何字符串类型,XML架构都不包含任何指示字符串长度的信息。XML架构向导将所有字符串值映射到InterSystems IRIS `%String`类,并将所有`base64Binary`值映射到`%xsd.base64Binary`类。这些选择可能不合适,具体取决于类要承载的数据。
在使用生成的类之前,应该执行以下操作:
- 检查生成的类,找到定义为`%string`或`%xsd.base64Binary`的属性。考虑将在其中使用这些类的上下文,特别是这些属性。
- 如果认为`%string`属性可能需要包含超出字符串长度限制的字符串,请将该属性重新定义为适当的字符流。同样,如果认为`%xsd.base64Binary`属性可能需要包含超过相同限制的字符串,请将该属性重新定义为适当的二进制流。
- 另请注意,对于类型为`%string`、`%xsd.string`和`%BINARY`的属性,默认情况下,`MAXLEN`属性参数为`50`个字符。可能需要指定更高的限制才能进行正确的验证。
(对于`%xsd.base64Binary`类型的属性,`MAXLEN`为`“”`,这意味着不会通过验证检查长度。但是,字符串长度限制确实适用。)
文章
姚 鑫 · 六月 25, 2021
# 第十八章 签署XML文档
本章介绍如何向XML文档添加数字签名。
# 关于数字签名文档
数字签名的XML文档包括一个或多个``元素,每个元素都是数字签名。
每个``元素对文档中的特定元素进行如下签名:
- 每个签名元素都有一个ID属性,该属性等于某个唯一值。例如:
```xml
```
- 一个``元素包含一个``元素,它指向该Id,如下所示:
```xml
```
``元素是由私钥签名的。此元素包括由签名机构签署的X.509证书。如果已签名文档的接收方信任此签名机构,则接收方可以验证证书,并使用包含的公钥验证签名。
注意: IRIS还支持一种变体,其中有签名的元素有一个名为ID的属性,而不是ID。
下面是一个示例,为了便于阅读,添加了空格:
```xml
Persephone MacMillan
1976-02-20
FHwW2U58bztLI4cIE/mp+nsBNZg=
MTha3zLoj8Tg content omitted
MIICnDCCAYQCAWUwDQYJ content omitted
```
要创建数字签名,可以使用类`%XML.Security.Signature`。
这是一个支持xml的类,它的投影是适当名称空间中的有效``元素。
# 创建数字签名XML文档
要创建数字签名的XML文档,请使用`%XML.Writer`为一个或多个适当定义的启用了XML的对象生成输出。
在为对象生成输出之前,必须创建所需的签名并将其写入对象,以便可以将信息写入目标。
## 签名的前提条件
在签署文档之前,必须至少创建一个IRIS凭据集。InterSystems IRIS凭据集是存储在系统管理器数据库中的以下信息集的别名:
- 包含公钥的证书。证书应由文档接收者信任的签名机构签名。
- 关联的私钥, IRIS在需要时使用,但从不发送。签名需要私钥。
- (可选)私钥的密码, IRIS在需要时使用私钥,但从不发送。可以加载私钥,也可以在运行时提供私钥。
## 启用XML的类的要求
启用XML的类必须包括以下内容:
- 投影为ID属性的特性。
- 至少一个类型为`%XML.Security`的属性。投影为``元素的签名。(一个XML文档可以包含多个``元素。)
考虑以下类:
```java
Class XMLEncryption.Simple Extends (%RegisteredObject, %XML.Adaptor)
{
Parameter NAMESPACE = "http://mynamespace";
Parameter XMLNAME = "Person";
Property Name As %String;
Property DOB As %String;
Property PersonId As %String(XMLNAME = "Id", XMLPROJECTION = "ATTRIBUTE");
Property MySig As %XML.Security.Signature(XMLNAME = "Signature");
}
```
## 生成和添加签名
要生成和添加数字签名,请执行以下步骤:
1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。
2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,调用`%SYS.X509Credentials`的`GetByAlias()`类方法。
```java
classmethod GetByAlias(alias As %String, pwd As %String) as %SYS.X509Credentials
```
- alias 别名是证书的别名。
- pwd 是私钥密码。仅当关联的私钥已加密并且在加载私钥文件时未加载密码时,才需要私钥密码。
若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。
3. 在使用给定凭据集创建 `%XML.Security.Signature`的实例。为此,请调用该类的`Createx509()`类方法:
```java
classmethod CreateX509(credentials As %SYS.X509Credentials, signatureOption As %Integer, referenceOption As %Integer) as %XML.Security.Signature
```
- `credentials` 凭据是刚刚创建`%SYS.X509Credentials`的实例。
- `signatureOption`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此方案)
- `referenceOption` 指定对符号元素的引用的性质。
这里使用的宏在`%soap.inc`中定义包括文件。
4. 获取ID属性的值,对于此签名将点的ID。此详细信息取决于启用XML对象的定义。
5. 创建`%XML.Security.Reference`的实例,指向该ID。为此,请调用该类的`Create()`类方法:
```java
ClassMethod Create(id As %String, algorithm As %String, prefixList As %String)
```
- `id`是该参考应该指向的ID。
- `algorithm` 算法应该是以下之一:
- `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14`n — 使用此版本获取独占规范化。
- `$$$SOAPWSEnvelopedSignature` — 这相当于前面的选项。
- `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14n` — 使用此版本进行包容性规范化。
6. 对于签名对象,调用`AddReference()`方法将此引用添加到签名:
```java
Method AddReference(reference As %XML.Security.Reference)
```
7. 更新启用XML的类的相应属性以包含签名。
```java
set object.MySig=signature
```
8. 创建`%XML.Document`的实例,该实例包含序列化为XML的启用了XML的对象。
这是必要的,因为签名必须包括有关签名文档的信息。
注意:本文档不包含空格。
9. 调用签名对象的`SignDocument()`方法:
```java
Method SignDocument(document As %XML.Document) As %Status
```
此方法的参数是刚刚创建的中`%XML.Document`的实例。`SignDocument()`方法使用该实例中的信息更新签名对象。
10. 使用`%XML.Writer`中为对象生成输出。
注意:生成的输出必须包含与签名中使用的文档相同的空格(或不包含空格)。签名包含文档的摘要,如果将编写器中的缩进属性设置为1,则摘要将与文档不匹配。
例如:
**放入到对应的实体类中,有一些属性需要替换**
```java
Method WriteSigned(filename As %String = "")
{
#Include %soap
//创建签名对象
set cred=##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts=$$$SOAPWSIncludeNone
set ref=$$$KeyInfoX509Certificate
set signature=##class(%XML.Security.Signature).CreateX509(cred,parts,ref,.status)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
// 获取我们要签名的元素的ID属性;
set refid=$this.PersonId ; 此详细信息取决于类的结构
// 然后在签名对象中创建对该ID的引用
set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n
set reference=##class(%XML.Security.Reference).Create(refid,algorithm)
do signature.AddReference(reference)
//设置MySig属性,以便$this具有我们为其生成输出时所需的所有信息
set $this.MySig=signature ; 此详细信息取决于类的结构
//除了$this之外,我们还需要%XML.Document的一个实例,该实例包含序列化为XML的对象
set document=..GetXMLDoc($this)
//使用序列化的XML对象对文档进行签名,这将更新部分签名
set status=signature.SignDocument(document)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
// 写入对象的输出
set writer=##class(%XML.Writer).%New()
if (filename'="") {
set status=writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
do writer.RootObject($this)
}
```
前面的实例方法使用以下泛型类方法,该方法可以与任何启用了XML的对象一起使用:
```java
ClassMethod GetXMLDoc2(object) As %XML.Document
{
//步骤1-将对象作为XML写入流
set writer=##class(%XML.Writer).%New()
set stream=##class(%GlobalCharacterStream).%New()
set status=writer.OutputToStream(stream)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
set status=writer.RootObject(object)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
//步骤2-从流中提取%XML.Document
set status=##class(%XML.Document).GetDocumentFromStream(stream,.document)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
quit document
}
```
变体:引用中带有`URI=""`的数字签名
作为一种变体,签名的``元素可以具有`URI=""`,这是对包含签名的XML文档根节点的引用。
要通过以下方式创建数字签名:
1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。
2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,请调用`%SYS.X509Credentials`的`GetByAlias()`类方法,如前面的步骤所述。
3. 创建使用给定凭据集的`%XML.Security.Signature`的实例。为此,请调用该类的`CreateX509()`类方法,如前面的步骤所述。
4. 按如下方式创建`%XML.Security.X509Data`的实例:
```java
set valuetype=$$$KeyInfoX509SubjectName_","_$$$KeyInfoX509Certificate
set x509data=##class(%XML.Security.X509Data).Create(valuetype,cred)
```
其中,`cred`是`%SYS`的实例。
`x509credentials`在之前创建的新窗口中打开。
这些步骤创建了一个``元素,其中包含一个``元素和一个``元素。
5. 将``元素添加到签名的``元素中,方法如下:
```java
do signature.KeyInfo.KeyInfoClauseList.Insert(x509data)
```
其中签名是`%XML.Security`的实例。
`x509data`是`%XML.Security.X509Data`的实例。
6. 创建`%XML.Security`的实例。
参考如下:
```java
set algorithm=$$$SOAPWSEnvelopedSignature
set reference=##class(%XML.Security.Reference).Create("",algorithm)
```
7. 在步骤6(调用`AddReference()`)中继续上述步骤。
# 验证数字签名
对于收到的任何数字签名文档,都可以验证签名。不需要具有与文档内容匹配的启用XML的类。
## 验证签名的前提条件
若要验证数字签名,必须首先为签名者向InterSystems IRIS提供受信任的证书。如果InterSystems IRIS可以验证签名者的证书链(从签名者自己的证书到来自InterSystems IRIS信任的证书颁发机构(CA)的自签名证书),包括中间证书(如果有),则InterSystems IRIS可以验证签名。
## 验证签名
要验证数字签名的XML文档中的签名,请执行以下操作:
1. 创建`%XML.Reader`的实例并使用它打开文档。
2. 获取阅读器的`Document`属性。这是 `%XML.Document`的一个实例。包含作为`DOM的XML文档的文档
3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.Signature`关联起来。
例如:
```java
do reader.Correlate("Signature","%XML.Security.Signature")
```
4. 遍历文档以读取``元素或多个元素。为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。
例如:
```java
if 'reader.Next(.isig,.status) {
write !,"Unable to import signature",!
do $system.OBJ.DisplayError(status)
quit
}
```
导入的对象是`%XML.Security.Signature`的实例
5. 调用导入签名的`ValidateDocument()`方法。该方法的参数必须是`%XML`的实例。先前检索到的文档。
```java
set status=isig.ValidateDocument(document)
```
例如:
```java
ClassMethod ValidateDoc(filename As %String)
{
set reader=##class(%XML.Reader).%New()
set status=reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set document=reader.Document
//获取 元素
//假设只有一个签名
do reader.Correlate("Signature","%XML.Security.Signature")
if 'reader.Next(.isig,.status) {
write !,"无法导入签名",!
do $system.OBJ.DisplayError(status)
quit
}
set status=isig.ValidateDocument(document)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
}
```
# 变体:引用ID的数字签名
在典型的情况下,``元素包含一个``元素,该元素指向文档中其他地方的唯一Id。
InterSystems IRIS还支持一种变体,其中``元素指向名为ID(而不是ID)的属性。
在这种变体中,需要额外的工作来签署文档和验证文档。
要对文档进行数字签名,请遵循“创建数字签名XML文档”中的步骤,并进行以下更改:
- 对于支持xml的类,包含一个作为ID属性而不是ID属性投影的属性。
- 在生成和添加签名时,调用`%XML`的`AddIDs()`方法。文档实例。
在获得序列化的XML文档之后,在调用签名对象的`SignDocument()`方法之前,执行此操作。
例如:
```java
//设置MySig属性,使$this在为其生成输出时拥有所需的所有信息
set $this.MySig=signature ; 这个细节取决于类的结构
//除了$this之外,我们还需要%XML的实例 包含序列化为XML的对象的文档
set document=..GetXMLDoc($this)
//***** 当签名引用ID属性时添加步骤 *****
do document.AddIDs()
//使用序列化的XML对象对文档进行签名,这更新了部分签名
set status=signature.SignDocument(document)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
```
- 当验证文档时,在调用`Correlate()`之前包含以下步骤:
1. 调用 `%XML.Document`的`AddIDs()`方法。
2. 调用XML阅读器的`Rewind()`方法。
例如:
```
set document=reader.Document
//添加签名引用ID属性时的步骤
do document.AddIDs()
do reader.Rewind()
//获取 元素
do reader.Correlate("Signature","%XML.Security.Signature")
```