清除过滤器
文章
Nicky Zhu · 二月 3, 2021
下一篇:
[案例: 建立只能使用SQL的用户](https://cn.community.intersystems.com/post/%E6%A1%88%E4%BE%8B-%E5%BB%BA%E7%AB%8B%E5%8F%AA%E8%83%BD%E4%BD%BF%E7%94%A8sql%E7%9A%84%E7%94%A8%E6%88%B7)
IRIS通过认证(Authentication)与授权(Authorization)两项机制控制外部用户对系统及应用、数据资源的可访问性。因此。如需要进行权限控制,则需要通过配置认证和授权进行。
## IRIS中的认证 {#2}
认证可以验证任何试图连接到InterSystems IRIS®的用户的身份。一旦通过认证,用户就与IRIS建立了通信,从而可以使用其数据和工具。有许多不同的方法可以验证用户的身份;每种方法都称为验证机制。IRIS 通常被配置为只使用其中一种方式。
支持的认证方式
* 实例认证:通过用户名/密码对登录平台,即密码认证
* LDAP:通过第三方LDAP服务器(如Windows Active Directory )完成认证
* 操作系统认证:建立操作系统用户-平台用户映射,使用操作系统用户登录平台
* Kerberos:使用Kerberos协议进行认证
* 代理认证:使用自定义的代码实现认证过程
### 系统服务与认证 {#2.1}
在安装时,IRIS会启动一系列系统级的服务用与控制与外部用户或系统的交互,这些服务都绑定了默认的认证机制

图中红框标出的即为系统安装后会自动启用并需经认证才可使用的系统服务,认证手段可配置。
例如,如果变更%Service_Console的身份验证方法,取消密码方法,用户就不能通过输入用户名密码登入Terminal。
通过Portal的菜单 系统管理 > 安全 > 服务 可访问该设置。
### 账户控制参数 {#2.2}
通过系统管理 > 安全 > 系统安全 > 系统范围的安全参数中的选项可对于用户名/密码认证手段的行为进行更多的约束。

* 非活动限制 - 指定用户账户不活跃的最大天数,它被定义为成功登录之间的时间。当达到此限制时,该帐户将被禁用。值为0(0)表示对登录之间的天数没有限制。[对于最低安全级别的安装,默认为0,对于正常和锁定的安装,默认为90]。
* 无效登录限制 (0-64) - 指定连续不成功的登录尝试的最大次数。在达到此限制后,要么禁用账户,要么对每次尝试进行递增的时间延迟;行动取决于如果达到登录限制字段则禁用账户的值。值为0(零)表示对无效登录的次数没有限制。[默认为5]
* 如果达到登录限制,则禁用账户 - 如果选中,则指定达到无效登录次数(在前一字段中指定)将导致用户账户被禁用。
* 密码有效期天数(0-99999) - 指定密码过期的频率以及用户更改密码的频率(天数)。当初始设置时,指定密码过期的天数。0(0)表示密码永远不会过期。不会影响已设置了下次登录时更改密码字段的用户。[默认为0]
需要特别注意的是,密码有效性、过期和禁用账户等设置会影响IRIS实例的所有账户,包括IRIS超级管理员账户。如触发了控制策略,则在更新这些帐户的信息之前,可能无法进行各种操作,这可能导致意外的结果。如超级管理员账户被锁定,则需要通过紧急模式启动实例再进行修改。
对于系统可用的认证手段的配置和其他可用的安全配置,请参见[Security Administration Guide](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS "安全管理向导")
## IRIS中的授权 {#3}
### 授权模型 {#3.1}
InterSystems公司的授权模式采用基于角色的访问控制。
* Users – 用户
* Roles – 角色
* Privileges – 权限
* Resources – 资源 | Permissions – 许可
在这种模式下,用户拥有与分配给各自用户身份的角色相关的权限。

* 一个角色是一个命名的特权集合
* 一个用户可以拥有一个以上的角色
* 权限分配给角色,角色分配给用户
其中,Roles就是权限的集合,而权限提供对资源的特定类型的访问的许可。
* 可控资源: 数据库,服务,应用(包括Web应用 )和其他
* 可选用的许可: Read, Write or Use,其中执行代码需要数据库的读权限
### 资源的定义 {#3.2}
资源是一项相对抽象的概念,用来指代IRIS中的数据库,服务,应用等可被访问的对象。例如,对于数据库,在建立时默认采用%DB_%DEFAULT指代,也可自定义资源(数据库资源必须以%DB_开头):

对于Web应用,默认不需要通过资源控制,即所有可登录用户都可访问(但该用户进程不一定能访问到数据,还需参照是否具有对数据库的访问权限)。如通过分配资源进行控制,则登录用户还需具有资源才能访问这个Web应用:

因此,一项权限实际上是指对某个资源的一些特定操作的集合。
例如,对于数据库UserDB具有读写操作许可的权限A,对于Web应用/csp/sys具有使用操作许可的权限B。如果我们将这两项权限都赋给角色RoleA,那么这个角色就同时拥有A权限和B权限,从而能够访问数据库UserDB和访问Web应用/csp/sys。
### SQL授权 {#3.3}
除了对数据库进行授权外,IRIS作为一个数据平台,需要对外提供数据访问。因此,IRIS也提供了SQL授权对用户可执行的SQL进行细粒度的权限控制。
SQL的授权可以分配给角色或用户。但通常在企业环境中,用户数量会很多,仍然需要对SQL用户进行分组,根据分组规划角色,通过角色进行授权的控制,才能有效降低维护授权所需的工作量。
SQL的授权针对SQL类型,可分为库、表级授权。
对于Create table、drop view、truncate table这一类的DDL,使用库级授权,即用户可在特定的库中执行建表、删除视图等经过授权的操作。如下:

对于Select、update等DML,则使用表级授权,使用户能够通过DML访问特定的表中的数据。如下:

除通过Portal操作之外,对于SQL的授权,还可使用IRIS SQL中的额GRANT语句,例如:
`GRANT * ON Schema Test TO TestRole`
这个SQL即可以将当前操作数据库下Schema Test中的所有表的所有权限都赋给TestRole这个角色。
关于GRANT语句的用法,可参见[GRANT指令](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_grant)
以上即为IRIS中进行权限控制所需掌握的概念和内容,在后续文章中,我们会结合实例向大家介绍其使用。
下一篇:
[案例: 建立只能使用SQL的用户](https://cn.community.intersystems.com/post/%E6%A1%88%E4%BE%8B-%E5%BB%BA%E7%AB%8B%E5%8F%AA%E8%83%BD%E4%BD%BF%E7%94%A8sql%E7%9A%84%E7%94%A8%E6%88%B7)
推荐阅读
[Security Administration Guide](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS "安全管理向导") - https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS
文章
姚 鑫 · 六月 13, 2021
# 第六章 控制名称空间的使用
# 控制名称空间的使用
如将对象投射到`XML`中所述,可以将类分配给名称空间,以便相应的XML元素属于该名称空间,还可以控制类的属性是否也属于该名称空间。
将类中的对象导出为XML时,`%XML.Write`提供其他选项,例如指定元素是否为其父级的本地元素。本节包括以下主题:
- 默认情况下,`%XML.Writer`如何处理命名空间
- 如何指定本地元素是否合格
- 如何指定元素是否为其父元素的本地元素
- 如何指定属性是否合格
- 命名空间分配方式的摘要
注意:在InterSystems IRIS XML支持中,可以按类指定名称空间。通常,每个类都有自己的命名空间声明;但是,通常只需要一个或少量的命名空间。还可以在逐个类的基础上指定相关信息(而不是以某种全局方式)。这包括控制元素是否为其父元素的本地元素以及子元素是否合格的设置。为简单起见,建议使用一致的方法。
## 名称空间的默认处理
若要将启用XML的类分配给命名空间,请设置该类的`Namespace`参数,如将对象投影到XML中所述。在`%XML.Writer`会自动插入命名空间声明,生成命名空间前缀,并在适当的地方应用前缀。例如,以下类定义:
```java
Class GXML.Objects.WithNamespaces.Person Extends (%Persistent, %Populate, %XML.Adaptor)
{
Parameter NAMESPACE = "http://www.person.com";
Property Name As %Name [ Required ];
Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h") [ Required ];
Property GroupID As %Integer(MAXVAL=10,MINVAL=1,XMLPROJECTION="ATTRIBUTE");
}
```
```java
Uberoth,Amanda Q.
1952-01-13
```
请注意以下事项:
- 名称空间声明被添加到每``元素。
- 默认情况下,``元素的局部元素(``和``)是限定的。
该名称空间被添加为默认名称空间,因此应用于这些元素。
- ``元素的属性(`GroupID`)默认是不限定的。
这个属性没有前缀,因此被认为是未限定的。
- 这里显示的前缀是自动生成的。
(请记住,当对象分配给名称空间时,只指定名称空间,而不是前缀。)
- 此输出不会在写入器中设置任何与名称空间相关的属性,也不会在写入器中使用任何与名称空间相关的方法。
### 命名空间分配的上下文效应
为支持xml的对象分配的名称空间取决于该对象是在顶层导出还是作为另一个对象的属性导出。
一个名为`Address`的类。
假设使用`NAMESPACE`参数将`Address`类分配给名称空间`“http://www.address.org”`。
如果你直接导出`Address`类的一个对象,你可能会收到如下输出:
```java
8280 Main Avenue
Washington
VT
15355
```
注意, `` 元素及其所有元素都在同一个名称空间(`“http://www.address.org”`)中。
相反,假设`Person`类的属性是`Address`对象。
使用`NAMESPACE`参数将`Person`类分配给名称空间`“http://www.person.org”`。
如果导出`Person`类的一个对象,将收到如下输出:
```java
Zevon,Samantha H.
1964-05-24
8280 Main Avenue
Washington
VT
15355
```
注意,``元素位于其父对象(`“http://www.person.org”`)的名称空间中。
但是,``的元素位于名称空间`“http://www.address.org”`中。
## 控制局部元素是否限定
在顶层导出对象时,通常将其视为全局元素。然后根据启用XML的对象的`ELEMENTQUALIFIED`参数的设置处理其本地元素。如果未设置此类参数,则改用编写器属性`ElementQualified`的值;默认情况下,文本格式为1,编码格式为0。
下面的示例显示一个默认设置为`ElementQualified`的对象,即1:
```java
Pybus,Gertrude X.
1986-10-19
```
该名称空间被添加到``元素中作为默认名称空间,因此应用于元素``和``子元素。
我们修改了写入器定义并将`ElementQualified`属性设置为0。
在本例中,相同的对象如下所示:
```java
Pybus,Gertrude X.
1986-10-19
```
在本例中,名称空间被添加到带有前缀的``元素中,该前缀用于``元素,但不用于其子元素。
## 控制一个元素是否局部于它的父元素
默认情况下,当使用`object()`方法生成一个元素并且该元素具有命名空间时,该元素不是其父元素的本地元素。相反,可以强制元素属于其父元素的命名空间。为此,可以使用`object()`方法的可选本地参数;这是第四个参数。
### 本地参数为0(默认值)
在这里的示例中虑将`NAMESPACE`类参数指定为`“http://www.person.com”`的`Person`类。
如果打开根元素,然后使用`Object()`生成`Person`,则``元素位于`“http://www.person.com”`名称空间中。
以下例子:
```java
Adam,George L.
1947-06-29
```
如果我们将``元素更深地嵌套到其他元素中,也会出现类似的结果。
```java
Adam,George L.
1947-06-29
```
### 局部参数设置为1
为了强制``元素成为其父元素的本地元素,我们将`local`参数设置为1。
如果我们这样做并再次生成前面的输出,我们将收到以下少嵌套版本的输出:
```java
Adam,George L.
1947-06-29
```
注意,现在``元素在`“http://www.rootns.org”`名称空间中,这是它的父元素的名称空间。
类似地,嵌套程度更高的版本应该是这样的:
```java
Adam,George L.
1947-06-29
```
## 控制属性是否限定
导出对象时,默认情况下其属性不合格。要使它们合格,请将编写器属性`AttributeQualified`设置为1。下面的示例显示`AttributeQualified`等于0(或尚未设置)的编写器生成的输出:
```java
Leiberman,Amanda E.
1988-10-28
```
相反,下面的示例显示使用`AttributeQualified`等于1的编写器生成的同一对象:
```java
Leiberman,Amanda E.
1988-10-28
```
在这两种情况下,元素都是不合格的。
## 命名空间分配摘要
本节介绍如何为`XML`输出中的任何给定元素确定命名空间。
### 顶级元素
对于与在顶级导出的InterSystems IRIS类相对应的元素,适用以下规则:
1. 如果为类指定了`Namespace`参数,则元素位于该命名空间中。
2. 如果未指定该参数,则元素位于在生成元素的输出方法(`RootObject()`、`RootElement()`、`Object()`或`Element()`)中指定的命名空间中。
3. 如果未在输出方法中指定命名空间,则元素位于编写器的`DefaultNamespace`属性指定的命名空间中。
4. 如果`DefaultNamespace`属性为空,则元素不在任何命名空间中。
### 低层元素
要导出的类的子元素受该类的`ELEMENTQUALIFIED`参数影响。如果未设置`ELEMENTQUALIFIED`,则改用编写器属性`ElementQualified`的值;默认情况下,文本格式为1,编码格式为0。
如果元素符合给定类的条件,则该类的子元素将按如下方式分配给命名空间:
1. 如果为父对象指定了`Namespace`参数,则子元素将显式分配给该命名空间。
2. 如果未指定该参数,子元素将显式分配给在生成元素的输出方法(`RootObject()`、`RootElement()`、`Object()`或`Element()`)中指定的命名空间。
3. 如果未在输出方法中指定命名空间,则子元素将显式分配给由编写器的`DefaultNamespace`属性指定的命名空间。
4. 如果`DefaultNamespace`属性为空,则子元素不会显式分配给任何命名空间。
文章
姚 鑫 · 二月 26, 2021
# 第四十八章 Caché 变量大全 ^$LOCK 变量
提供锁名信息。
# 大纲
```
^$|nspace|LOCK(lock_name,info_type,pid)
^$|nspace|L(lock_name,info_type,pid)
```
# 参数
- `|nspace|`或`[nspace]` [nspace]可选-扩展SSVN引用,显式名称空间名称或隐含名称空间。必须计算为带引号的字符串,该字符串括在方括号(`[“nspace”]`)或竖线(`|“nspace”|`)中。命名空间名称不区分大小写;它们以大写字母存储和显示。
- lock_name 计算结果为包含锁定变量名称(带下标或无下标)的字符串的表达式。如果是文字,则必须指定为带引号的字符串。
- info_type 可选-解析为所有大写字母指定为带引号字符串的关键字的表达式。info_type指定返回关于`lock_name`的哪种类型的信息。可用选项有“所有者”、“标志”、“模式”和“计数”。作为独立函数调用`^$LOCK`时需要。
- pid 可选-用于“计数”关键字。一个整数,指定锁所有者的进程标识。如果指定,最多为“计数”返回一个列表元素。如果省略(或指定为0),将为持有指定锁的每个所有者返回一个列表元素。`pid`对其他info_type关键字没有影响。
# 描述
`^$LOCK`结构化系统变量返回有关当前命名空间或本地系统上指定命名空间中的锁的信息。可以通过两种方式使用`^$LOCK`:
- info_type作为独立函数返回指定锁的信息。
- `$DATA`、`$ORDER`或`$QUERY`函数没有info_type作为参数。
注意:`^$LOCK`从本地系统的锁表中检索锁表信息。它不会从远程服务器上的锁表中返回信息。
## ECP环境中的`^$LOCK。`
- 本地系统:为本地系统持有的锁调用`^$LOCK`时,`^$LOCK`的行为与没有ECP时相同,只有一个例外:info_type的`“FLAGS”`返回一个星号(*),表示锁处于ECP环境中。这意味着一旦锁被释放,远程系统间IRIS实例就能够持有锁。
- 应用服务器:当通过ECP(数据服务器或另一个应用服务器)在一个应用服务器上为另一个服务器上持有的锁调用`^$LOCK`时,`^$LOCK`不返回任何信息。请注意,这是与锁不存在时相同的行为。
- 数据服务器:当在数据服务器上为应用服务器持有的锁调用`^$LOCK`时,`^$LOCK`的行为与本地系统略有不同,如下所示:
- “Owner”:如果锁由连接到调用`^$Lock`的数据服务器的应用程序服务器持有,则`^$Lock(LOCKNAME,“OWNER”)`为持有该锁的实例返回`“ECPConfigurationName:MachineName:InstanceName”`,但不标识持有该锁的特定进程。
- `“FLAGS”`:如果锁由连接到调用`^$LOCK`的数据服务器的应用程序服务器持有,则独占锁的`^$LOCK(lockname,“FLAGS”)`将返回`“Z”`标志。这个`“Z”`表示除了ECP环境之外,在InterSystems IRIS中不再使用的一种遗留锁。
- `“mode”`:如果锁由连接到调用`^$lock`的数据服务器的应用程序服务器持有,则独占锁的`^$lock(lockname,“mode”)`返回`“ZAX”`而不是`“X”`。`ZA`是一种遗留锁,除ECP环境外,在系统间IRIS中不再使用。对于共享锁,`^$lock(lockname,“mode”)`返回`“S”`,与本地锁相同。
# 参数
## nspace
此可选参数允许您使用扩展的SSVN引用在另一个名称空间中指定全局变量。可以显式指定名称空间名称,将其命名为带引号的字符串文字或变量,或者通过指定隐式名称空间。命名空间名称不区分大小写。可以使用方括号语法`[“ USER”]`或环境语法`|“ USER” |`。 nspace分隔符前后不允许有空格。
可以使用以下方法测试是否定义了名称空间:
```java
WRITE ##class(%SYS.Namespace).Exists("USER"),!
WRITE ##class(%SYS.Namespace).Exists("LOSER")
```
可以使用`$NAMESPACE`特殊变量来确定当前的名称空间。更改当前名称空间的首选方法是`NEW $NAMESPACE`,然后`SET $NAMESPACE =“nspacename”`。
## lock_name
该表达式的计算结果为包含锁定变量名称(带下标或未下标)的字符串。使用`LOCK`命令定义一个锁变量(通常是全局变量)。
## info_type
当将`^$LOCK`用作独立函数时,需要一个`info_type`关键字;当将`^$LOCK`用作另一个函数的参数时,则是一个可选参数。 info_type必须以大写字母指定为带引号的字符串。
- `“OWNER”`返回锁所有者的进程ID(pid)。如果该锁是共享锁,则以逗号分隔列表的形式返回该锁的所有所有者的进程ID。如果指定的锁不存在,则`^$LOCK`返回空字符串。
- `“FLAGS”`返回锁的状态。它可以返回以下值:`“D”`-处于挂起挂起状态; `“P”`-处于锁定挂起状态; `“N”`-这是一个节点锁定,后代未锁定; `“Z”`-此锁处于ZAX模式; `“L”`-锁丢失,服务器不再具有此锁; `“*”`-这是一个远程锁定。如果指定的锁处于正常锁状态或不存在,则`^$LOCK`返回空字符串。
- `“MODE”`返回当前节点的锁定模式。对于排他锁定模式,它返回`“X”`,对于共享锁定模式,它返回`“S”`,对于`ZALLOCATE`锁定模式,它返回`“ZAX”`。如果指定的锁不存在,则`^$LOCK`返回空字符串。
- `“COUNTS”`返回锁的锁计数,指定为二进制列表结构。对于排他锁,列表包含一个元素;对于共享锁,列表包含每个锁所有者的元素。可以使用pid参数仅返回指定锁定所有者的list元素。每个元素都包含所有者的pid,独占模式增量计数和共享模式增量计数。如果独占模式和共享模式的增量计数均为0(或`“”`),则锁定处于`“ZAX”`模式。增量计数后可以跟一个`“D”`,以指示该锁已在当前事务中解锁,但是其释放被延迟(`“D”`),直到事务被提交或回滚为止。如果指定的锁不存在,则`^$LOCK`返回空字符串。
必须使用所有大写字母指定`info_type`关键字。指定无效的`info_type`关键字会生成``错误。
## pid
锁所有者的进程ID。仅在使用`“COUNTS”`关键字时有意义。用于将“ ”返回值限制为(最多)一个列表元素。 pid在所有平台上均指定为整数。如果pid与`lock_name ^$LOCK`的所有者的进程ID匹配,则返回该所有者的`“COUNTS”`列表元素;如果pid与`lock_name ^$LOCK`的所有者的进程ID不匹配,则返回空字符串。将pid指定为0表示与省略pid相同; `^$LOCK`返回所有`“COUNTS”`列表元素。pid参数与`“OWNER”`,`“FLAGS”`或`“MODE”`关键字一起使用,但被忽略。
# 示例
下面的示例显示由info_type关键字返回的排他锁的值:
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK()
ClassMethod LOCK()
{
LOCK ^B(1,1) ; define lock
WRITE !,"lock owner: ",^$LOCK("^B(1,1)","OWNER")
WRITE !,"lock flags: ",^$LOCK("^B(1,1)","FLAGS")
WRITE !,"lock mode: ",^$LOCK("^B(1,1)","MODE")
WRITE !,"lock counts: "
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK -^B(1,1) ; delete lock
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK()
lock owner: 17824
lock flags:
lock mode: X
lock counts:
0000: 0B 01 04 04 A0 45 03 04 01 02 01 ....??E.....
```
下面的示例显示在递增和递减独占锁时,info_type `“COUNTS”`返回的值如何变化:
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK1()
ClassMethod LOCK1()
{
LOCK ^B(1,1) ; define exclusive lock
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK +^B(1,1) ; increment lock
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK +^B(1,1) ; increment lock again
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK -^B(1,1) ; decrement lock
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK -^B(1,1) ; decrement lock again
ZZDUMP ^$LOCK("^B(1,1)","COUNTS")
LOCK -^B(1,1) ; delete exclusive lock
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK1()
0000: 0B 01 04 04 A0 45 03 04 01 02 01 ....??E.....
0000: 0B 01 04 04 A0 45 03 04 02 02 01 ....??E.....
0000: 0B 01 04 04 A0 45 03 04 03 02 01 ....??E.....
0000: 0B 01 04 04 A0 45 03 04 02 02 01 ....??E.....
0000: 0B 01 04 04 A0 45 03 04 01 02 01 ....??E.....
```
下面的示例显示在递增和递减共享锁时,info_type`“COUNTS”`返回的值如何变化
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK2()
ClassMethod LOCK2()
{
LOCK ^S(1,1)#"S" ; define shared lock
ZZDUMP ^$LOCK("^S(1,1)","COUNTS")
LOCK +^S(1,1)#"S" ; increment lock
ZZDUMP ^$LOCK("^S(1,1)","COUNTS")
LOCK +^S(1,1)#"S" ; increment lock again
ZZDUMP ^$LOCK("^S(1,1)","COUNTS")
LOCK -^S(1,1)#"S" ; decrement lock
ZZDUMP ^$LOCK("^S(1,1)","COUNTS")
LOCK -^S(1,1)#"S" ; decrement lock again
ZZDUMP ^$LOCK("^S(1,1)","COUNTS")
LOCK -^S(1,1)#"S" ; delete shared lock
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK2()
0000: 0B 01 04 04 A0 45 02 01 03 04 01 ....??E.....
0000: 0B 01 04 04 A0 45 02 01 03 04 02 ....??E.....
0000: 0B 01 04 04 A0 45 02 01 03 04 03 ....??E.....
0000: 0B 01 04 04 A0 45 02 01 03 04 02 ....??E.....
0000: 0B 01 04 04 A0 45 02 01 03 04 01 ....??E.....
```
以下示例显示如何将`^$lock`用作`$DATA`、`$ORDER`和`$QUERY`函数的参数。
## 作为$DATA的参数
`$DATA(^$|nspace|LOCK(lock_name))`
`^$lock`作为`$DATA`的参数返回一个整数值,该值指定锁定名称是否作为节点存在于`^$lock`中。下表显示了`$DATA`可以返回的整数值。
Value| Meaning
---|---
0 |锁信息不存在
10 |锁信息存在
请注意,在此上下文中使用的`$DATA`只能返回0或10,其中10表示指定的锁存在。它不能确定锁是否有后代,也不能返回1或11。
下面的示例测试当前命名空间中是否存在锁名。第一次写入返回10(锁名存在),第二次写入返回0(锁名不存在):
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK3()
ClassMethod LOCK3()
{
LOCK ^B(1,2) ; define lock
WRITE !,$DATA(^$LOCK("^B(1,2)"))
LOCK -^B(1,2) ; delete lock
WRITE !,$DATA(^$LOCK("^B(1,2)"))
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK3()
10
0
```
## 作为`$ORDER`的参数
`$ORDER(^$|nspace|LOCK(lock_name),direction)`
`^$lock`作为`$ORDER`的参数,按排序顺序将下一个或上一个`^$lock`锁名节点返回到指定的锁名。如果不存在这样的锁名作为`^$lock`节点,`$ORDER`将返回空字符串。
锁以区分大小写的字符串排序顺序返回。使用数字排序规则以下标树顺序返回命名锁的下标。
Direction参数指定是返回下一个锁名称还是返回上一个锁名称。如果不提供方向参数,InterSystems IRIS会将排序序列中的下一个锁名返回到您指定的锁名。
以下子例程在Samples名称空间中搜索锁,并将锁名称存储在名为locket的本地数组中。
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK4()
ClassMethod LOCK4()
{
LOCKARRAY
SET lname=""
FOR I=1:1 {
SET lname=$ORDER(^$|"SAMPLES"|LOCK(lname))
QUIT:lname=""
SET LOCKET(I)=lname
WRITE !,"the lock name is: ",lname
}
WRITE !,"All lock names listed"
QUIT
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK4()
the lock name is: ^%SYS("CSP","Daemon")
All lock names listed
```
## 作为`$QUERY`的参数
`$QUERY(^$|nspace|LOCK(lock_name))`
`^$lock`作为`$query`的参数,将排序序列中的下一个锁名返回到您指定的锁名。如果没有将下一个锁名定义为`^$lock`中的节点,则`$query`将返回空字符串。
锁以区分大小写的字符串排序顺序返回。使用数字排序规则以下标树顺序返回命名锁的下标。
在下面的示例中,在当前命名空间中(按随机顺序)创建了五个全局锁名称。
```java
/// d ##class(PHA.TEST.SpecialVariables).LOCK5()
ClassMethod LOCK5()
{
LOCK (^B(1),^A,^D,^A(1,2,3),^A(1,2))
WRITE !,"lock name: ",$QUERY(^$LOCK(""))
WRITE !,"lock name: ",$QUERY(^$LOCK("^C"))
WRITE !,"lock name: ",$QUERY(^$LOCK("^A(1,2)"))
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).LOCK5()
lock name: ^$LOCK("^%SYS(""CSP"",""Daemon"")")
lock name: ^$LOCK("^D")
lock name: ^$LOCK("^A(1,2,3)")
```
`$QUERY`将所有全局锁变量名(带下标或无下标)视为字符串,并按字符串排序顺序检索它们。因此,`$QUERY(^$LOCK(“”))`按排序顺序检索第一个锁名:`^$LOCK(“^A”)`或排序序列中位置较高的InterSystems IRIS定义的锁。`$QUERY(^$LOCK(“^C”))`检索排序序列中不存在的`^C`:`^$LOCK(“^D”)`之后的下一个锁名。`$QUERY(^$LOCK(“^A(1,2)”))`检索排序规则序列中它后面的`^$LOCK(“^A(1,2,3)”)`。 想知道锁产生的原因大概有哪些?应该怎么避免呢? 我有个想法,希望结合应用场景来介绍,这样能够明白使用场景。
文章
Michael Lei · 一月 10, 2022
正如德勤最近的一份报告所强调的,数字孪生的设计和部署是为了实现虚拟协作,吸收和处理大数据,以及协助我们以更有效和安全的方式管理物理世界。
有几个行业在使用数字孪生能力方面取得了重大进展,如汽车、飞机、能源、城市规划和医疗保健等,仅举几例。疫情的影响和对全球经济造成的破坏只会加速全球数字孪生的发展速度和采用。因此,预计到2026年,全球数字孪生市场将达到482亿美元,年复合增长率为58%。虽然目前北美市场拥有最大的份额,但预计亚太市场将在未来几年经历最快的增长。
按照Gartner的说法,数字孪生是现实世界实体或系统的数字代表。数字孪生体的实现是一个封装的软件对象或模型,它反映了一个独特的物理对象、过程、组织、人或其他抽象概念。
数字孪生的设计和部署是复杂的,与其他数字技术密切相关,如云计算、人工智能、物联网、5G网络、区块链和虚拟、增强或混合现实。预计所有这些技术的采用率将对数字孪生产业产生深远影响。迄今为止,系统数字孪生体已经在所有行业中占主导地位,但专家们一致认为,产品和流程数字孪生体类型将在未来几年内上升。
作为一个社会,我们可以通过部署这些高度通用的数字化身阿凡达获得巨大的利益,因为它们可以优化效率,降低长期成本,提高质量,并进一步提高我们的研究能力。
医疗保健是一个很好的例子,数字孪生医疗有可能解决我们在全球医疗保健领域面临的许多主要挑战。从医疗服务的角度来看,它们可以提高医疗质量和安全,改善临床决策支持系统,加速各种药物从研究到临床部署的过渡,并加快各种新的精准医疗解决方案的批准。对于医疗设备行业来说,这也可能意味着更短的审批周期和更高的安全性。在医学教育中,它们可以促进更多的互动教学方法和更有效的安全培训。对于外科、肿瘤学或精准医学等不同学科来说,拥有这些数字孪生可能是革命性的、颠覆性的。最后且很重要的是,数字孪生还可以让我们发展一个超级连接的全球医疗保健网络,并获得重要的洞察,从而推动研究和创新诊断或治疗方法的发展。
然而,为了取得成功,我们还需要克服任何新兴技术所面临的一些挑战,并减轻恶意使用的风险。具体而言,道德、网络安全问题和跨行业标准将被列入大多数希望部署数字孪生的企业的议程。
数字化旅程是漫长的,然而所有的最新研究都指向数字技术的加速采用。通过将数字孪生与所有其他新兴技术相结合的部署,我们有可能建立一个高性能的全球医学互联网,它可以成为全球医疗保健生态系统的管道。通过利用脑机接口、植入物、可穿戴设备、增强智能、区块链、5G网络速度和云计算的全部能力,我们将实现指数级的效果。医疗保健未来学家甚至会大胆设想存在着最先进的智能城市,在那里,数字医疗孪生、数字金融孪生和数字法律孪生将成为日常生活的一部分。
原文链接:https://www.bbntimes.com/science/digital-health-twins-the-great-enablers-of-new-healthcare-ecosystems?suid=SU00014&medium=li&cmp=566
作者介绍:
Ingrid Vasiliu-Feltes博士, 是一位医疗高管、未来学家和全球主义者。她是福布斯商业委员会成员、数字战略家、教育家和创业生态系统建设者。在她的职业生涯中,她因在研究、教学或领导方面的卓越表现而获得多个奖项。2021年WBAF世界卓越奖社会创业奖,数字孪生技术全球20强领导人,健康技术全球50强领导人,全球生态系统50强领导人,2021年教育领域100强远见奖,2021年全球100强女性领导力奖。2021年世界女性远见奖100强--创新与技术,2021年社会企业女性100强(被提名者),全球思想家50强(被提名者),女性变革者国家奖(入围),2020年医疗保健领袖100强,2020年金融领袖100强,以及2020年加密货币领域女性100强。此外,她还担任欧盟区块链观察站论坛的专家顾问,并被任命为联合国法律和经济赋权网络的董事会成员。Vasiliu-Feltes博士是Softhread公司的首席执行官,科学、创业和投资研究所的创始人和首席执行官,目前担任WBAF美国的国家主任,WBAF的参议员,WBAF商学院-创业部的教员,并在UM商学院教授行政MBA商业技术课程。她还担任政府区块链协会的首席创新官。最近,她担任Detect Genomix的总裁,Mednax的首席质量和安全官首席和创新官,迈阿密大学UHealth系统的首席质量和安全官和合规主管 在她的学术任期内,她在医学院内教授多门课程,以及MD/PhD和MD/MPH联合课程。在她的职业生涯中,Vasiliu-Feltes博士担任过几个领导职位,是许多著名专业组织的成员。她拥有多项认证,如哈佛大学的生物伦理学、麻省理工学院斯隆商学院的人工智能和商业战略、麻省理工学院斯隆商学院的区块链技术和商业创新、哈佛商学院的金融学、哈佛法学院的谈判学、斯坦福大学商学院的创新和创业学、医疗风险管理认证专家、美国医疗主管学院院士。国际安全经理人委员会联合会的病人安全官员,精益和六西格玛管理黑带大师,全国医疗质量协会的医疗质量专业人员,美国质量协会的质量和组织卓越经理,以及美国医疗风险管理协会的认证风险管理专业人员。此外,Vasiliu-Feltes博士还是多家公司的荣誉顾问委员会成员,以及多家国际出版物的编辑委员会成员、作家和电视/媒体合作伙伴。
文章
Weiwei Gu · 九月 13, 2023
我们最近发布了 IRIS 2023.2版本。
此版本从此类`“HS.HC.Util.Installer”`中删除了“`InstallFoundation”`。
这是一个没有记录在官方文档中的私有的方法,但它被社区广泛用来安装 FHIR 服务器。
所以如果你遇到这个错误:` *InstallFoundation,HS.HC.Util.Installer
`
`在构建我们自己的 FHIR 服务器的演示时,您可以通过替换此行来修复它:
把`do ##class(HS.HC.Util.Installer).InstallFoundation (namespace) `替换成
` Do ##class(HS.Util.Installer.Foundation).Install(namespace) `
文章
Michael Lei · 六月 23, 2021
> 注(2019 年 6 月):许多内容发生了变化,[最新的详细信息请参见此处](https://community.intersystems.com/post/unpacking-pbuttons-yape-update-notes-and-quick-guides)
> 注(2018 年 9 月):自本帖首次发布以来,内容已经有了很大改动,我建议使用 Docker 容器版本,以容器形式运行的项目以及详细信息仍然在 [GitHub 的同一个地址发布](https://github.com/murrayo/yape),您可以下载、运行并根据需要进行修改。
与客户合作进行性能评估、容量规划和故障排除时,我经常解包和查看来自 pButtons 的 Caché 和操作系统指标。 [我不久前发布了一个帖子,介绍了一个用来解包 pButtons 指标的实用工具](https://community.intersystems.com/post/extracting-pbuttons-data-csv-file-easy-charting)(该实用工具使用 unix shell、perl 和 awk 脚本编写),而不是费力地浏览 html 文件,再将需要绘制的部分剪切并粘贴到 excel 中。 虽然这是一个*有用的省时工具*,但还不够完善... 我还使用脚本自动绘制指标图表,以便快速查看并包含在报告中。 但是,这些绘图脚本不容易维护,并且当需要站点特定的配置(例如 iostat 或 Windows perfmon 的磁盘列表)时会变得特别混乱,所以我从未公开发布过绘图实用工具。 不过我现在可以很高兴地说,已经有了简单得多的解决方案。
当我与 Fabian 一起在客户站点查看系统性能时,有了意外发现,[他向我展示了使用实用的 Python 绘图模块所做的工作](https://community.intersystems.com/post/visualizing-data-jungle-part-i-lets-make-graph)。 这是一个比我使用的脚本更灵活、更容易维护的解决方案。 集成 Python 模块进行文件管理和绘制图表的简便性,包括可以分享的交互式 html,意味着输出可以有更大用处。 以 Fabian 的帖子为基础,我编写了 __Yape__,旨在快速简单地提取客户的多种格式的 pButtons 文件,然后绘制图表。 该项目已[在 GitHub 上发布](https://github.com/murrayo/yape),您可以下载、运行并根据需要进行修改。
## 概述
目前,此过程有_两个_步骤。
### 步骤 1. `extract_pButtons.py`
从 pButtons 提取感兴趣的部分并写入到 .csv 文件,以便使用 Excel 打开或使用 `graph_pButtons.py` 进行绘图处理。
### 步骤 2. `graph_pButtons.py`
绘制步骤 1 中创建的文件的图表。 目前,输出可以是 `.png` 形式的线形图或点阵图,也可以是带有平移、缩放、打印等选项的`交互式 .html`。
GitHub 上的 _Readme.md_ 详细介绍了如何设置和运行这两个 python 脚本,并且将是最新的参考。
## 其他说明
例如:使用向输出和输入目录添加前缀的选项,可以轻松遍历包含一组(例如一个星期)pButtons html 文件的目录,并针对每个 pButtons 文件都输出到一个单独目录。
for i in `ls *.html`; do ./extract_pButtons.py $i -p ${i}_; done
for i in `ls *.html`; do ./graph_pButtons.py ./${i}_metrics -p ${i}_; done
在短期内,当我继续撰写有关 [Caché 容量规划和性能](https://community.intersystems.com/post/intersystems-data-platforms-capacity-planning-and-performance-series-index)的系列文章时,我将使用由这些实用工具创建的图表。
我已经在 OSX 上进行了测试,但没有在 Windows 上测试。 您应该能够在 Windows 上安装和运行 Python,请留下您在 Windows 下的经验反馈。 例如,我猜想必须对文件路径斜杠进行更改。
> 注:直到几周前,我都没有用 Python 编写过任何东西,所以如果您是 Python 专家,那么代码中可能会有一些内容并不是最佳做法。 但是,我几乎每天都使用这些脚本,因此我将继续进行改进。 我希望我的 Python 技能会有所提高 — 但是如果您看到一些应该纠正的地方,请随意“教导”我!
如果您发现这些脚本有用,请告诉我,并不时回来看看以获取新功能和更新。
文章
姚 鑫 · 七月 11, 2022
# 第二章 嵌入式Python概述(二)
# 从 Python 调用 IRIS API
如果使用的是嵌入式 `Python` 并且需要与 `IRIS` 交互,可以使用 `Python shell` 中的 `iris` 模块,或者使用 `Python` 编写的 `IRIS` 类中的方法。要遵循本节中的示例,可以使用 `ObjectScript` 命令 `do ##class(%SYS.Python).Shell()` 从终端会话启动 `Python shell`。
当启动终端会话时,将被放置在 `IRIS` 的 `USER` 命名空间中,将看到提示 `USER>`。但是,如果从 `GitHub` 加载了示例类,则需要在 `SAMPLES` 命名空间中才能访问它们。
在终端中,更改为 `SAMPLES` 命名空间,然后启动 `Python shell`,如下所示:
```java
USER>set $namespace = "SAMPLES"
SAMPLES>do ##class(%SYS.Python).Shell()
Python 3.9.5 (default, Jul 19 2021, 17:50:44) [MSC v.1927 64 bit (AMD64)] on win32
Type quit() or Ctrl-D to exit this shell.
>>>
```
当从终端会话启动 `Python shell` 时,`Python shell` 继承与终端相同的上下文,例如,当前命名空间和用户。局部变量不被继承。
## 使用类
要从 `Python` 访问 `IRIS` 类,请使用 `iris` 模块来实例化要使用的类。然后,可以像访问 `Python` 类一样使用访问它的属性和方法。
注意:可能习惯于在 `Python` 中导入模块,然后再使用它,例如:
```java
>>> import iris
```
但是,在使用 `%SYS.Python` 类的 `Shell()` 方法运行 `Python shell `时,不需要显式导入 `iris` 模块。继续使用该模块。
以下示例使用系统类 `%Library.File` 的 `ManagerDirectory()` 方法打印 `IRIS` 管理器目录的路径:
```java
>>> lf = iris.cls('%Library.File')
>>> print(lf.ManagerDirectory())
C:\InterSystems\IRIS\mgr\
```
此示例使用系统类 `%SYSTEM.CPU` 的 `Dump()` 方法来显示有关正在运行 `IRIS` 实例的服务器的信息:
```java
>>> cpu = iris.cls('%SYSTEM.CPU')
>>> cpu.Dump()
-- CPU Info for node MYSERVER ----------------------------------------------
Architecture: x86_64
Model: Intel(R) Core(TM) i7-7600U CPU @ 2.80GHz
Vendor: Intel
# of threads: 4
# of cores: 2
# of chips: 1
# of threads per core: 2
# of cores per chip: 2
MT supported: 1
MT enabled: 1
MHz: 2904
------------------------------------------------------------------------------
```
此示例使用 `GitHub` 上 `Samples-Data` 存储库中的 `Sample.Company` 类。虽然可以使用任何命名空间中以百分号 (`%`) 开头的类(如 `%SYS.Python` 或 `%Library.File`)来访问 `Sample.Company` 类,但如前所述,必须位于 `SAMPLES` 命名空间中。
`Sample.Company` 的类定义如下:
```java
Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor)
{
/// The company's name.
Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ];
/// The company's mission statement.
Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()");
/// The unique Tax ID number for the company.
Property TaxID As %String [ Required ];
/// The last reported revenue for the company.
Property Revenue As %Integer;
/// The Employee objects associated with this Company.
Relationship Employees As Employee [ Cardinality = many, Inverse = Company ];
}
```
此类扩展 `%Library.Persistent`(通常缩写为 `%Persistent`),这意味着此类的对象可以持久保存在 `IRIS` 数据库中。该类还具有多个属性,包括 `Name` 和 `TaxID`,这两个属性都是保存对象所必需的。
尽管不会在类定义中看到它们,但持久类带有许多用于操作此类对象的方法,例如 `%New()`、`%Save()`、`%Id()` 和 `%OpenId()`。但是,`Python` 方法名称中不允许使用百分号 (`%`),因此请改用下划线 (`_`)。
下面的代码创建一个新的 `Company` 对象,设置所需的 `Name` 和 `TaxID` 属性,然后将公司保存在数据库中:
```java
>>> myCompany = iris.cls('Sample.Company')._New()
>>> myCompany.Name = 'Acme Widgets, Inc.'
>>> myCompany.TaxID = '123456789'
>>> status = myCompany._Save()
>>> print(status)
1
>>> print(myCompany._Id())
22
```
上面的代码使用 `_New()` 方法创建类的实例,并使用 `_Save()` 将实例保存在数据库中。 `_Save()` 方法返回一个状态码。在这种情况下,`1` 表示保存成功。当保存一个对象时, `IRIS` 会为其分配一个唯一 `ID`,可以在以后使用该 `ID` 从存储中检索该对象。 `_Id()` 方法返回对象的 `ID`。
使用类的 `_OpenId()` 方法将对象从持久存储中检索到内存中进行处理:
```java
>>> yourCompany = iris.cls("Sample.Company")._OpenId(22)
>>> print(yourCompany.Name)
Acme Widgets, Inc.
```
将以下代码添加到类定义中会创建一个 `Print()` 方法,该方法打印当前公司的 `Name` 和 `TaxID`。将 `Language` 关键字设置为 `python` 会告诉类编译器该方法是用 `Python` 编写的。
```java
Method Print() [ Language = python ]
{
print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID)
}
```
给定一个 `Company` 对象,可以调用它的 `Print()` 方法,如下所示:
```java
>>> yourCompany.Print()
Name: Acme Widgets, Inc. TaxID: 123456789
```
文章
Nicky Zhu · 五月 7, 2022
我们的一位客户五一期间向使用IRIS搭建的数据流推送一家三甲医院数年的历史数据,导致实施的同事们经历了一系列噩梦,包括但不限与:
1. 由于未通知实施团队有这样规模的数据推送,数据推送过程与全库备份任务重叠。尽管实例和数据流正常运行,但备份任务与数据流争抢IO,导致备份任务不能在预期时间内完成,实施童鞋五一加班处理问题。
2. 为了节省磁盘空间,服务器上部署了定期删除IRIS备份文件的任务,原本能够保持一周的全备+增量备份,但在本次数据暴增的情况下,新的备份尚未完成而旧的全备已被删除,导致问题发生时没有可用于恢复的备份。
3. 由于这次数据推送前未进行数据质量校验,推送的数据全部不合规,但已经历了较长的数据流进行处理全部入库;同时由于备份文件已被删除,无法通过恢复数据库的方法回滚,导致实施童鞋不得不逐条从生产环境三个库的数百张表中挑出问题数据逐一删除,从五一放假结束至今还未完成善后工作。大家可以设想一下,如果备份还在,那么恢复备份就可以了。
因此,我们希望再次提醒各位在前线奋斗的亲们:
1. 善待你的备份。尽管对于大型医院或医疗集团来说,两周的全备+增量备份策略下,备份文件会占据数个TB的存储空间。但在需要回滚时,这几个T的空间能救命。
2. 保持可用的测试环境。尤其是对于可能出现随机数据需求的客户,随机产生数据需求意味着随机出现测试需求。
3. 验证新数据的合规性,永远不要假设新数据一定合规。未经测试的新数据必然毫无悬念地导致新问题。
4. 对于任何批量数据处理任务,请务必提前规划,错开资源(CPU、内存、IO)的抢占,避免抢不到资源的任务饿饭。
5. 保持与最终客户的频繁沟通,所有对于生产环境进行的改动都应该经过项目组评估。虽然客户是上帝,但命运有时很顽皮,生产环境的安全保障也需要客户的合作。
最后,大家都知道InterSystems的IRIS在多数客户的场景下都不需要搭建负载均衡集群,这家客户也不例外,数据流中的数层结点上部署的都是单实例IRIS,通过Mirror实现高可用。在这次新数据的上传过程中,IRIS的数据流自然经历了突如其来的爆发式数据压力,以其中一个实例的消息量为例:
该用户在实例上保存30天的数据,可见在经历了五一的消息暴增之后,该客户的每日平均消息量已超过3300万条每天(实际上我们已经查到其中数天单日消息增量已超过5000万条),而该客户平时的消息量不过数十万条每天。
这次IRIS经洪峰而不倒固然可喜可贺,但相信在需要在客户面前经历各种千夫所指的PM、实施、开发与测试同事一定不希望经历这种惊喜。
Good luck.
值得一篇专门的文章介绍客户应该怎么使用备份以及数据推流
文章
姚 鑫 · 七月 27, 2022
# 第九章 REST 服务安全
如果 `REST` 服务正在访问机密数据,应该对服务使用身份验证。如果需要为不同的用户提供不同级别的访问权限,还要指定端点所需的权限。
# 为 `REST` 服务设置身份验证
可以对 `IRIS REST` 服务使用以下任何形式的身份验证:
- `HTTP` 身份验证标头 — 这是 `REST` 服务的推荐身份验证形式。
- `Web` 会话身份验证 — 其中用户名和密码在 URL 中的问号后面指定。
- `OAuth 2.0` 身份验证 - 请参阅以下小节。
## `REST` 应用程序和 `OAuth 2.0`
要通过 `OAuth 2.0` 对 `REST` 应用程序进行身份验证,请执行以下所有操作:
- 将包含 `REST` 应用程序的资源服务器配置为 `OAuth 2.0` 资源服务器。
- 允许对 `%Service.CSP` 进行委派身份验证。
- 确保将 `Web` 应用程序(用于 `REST` 应用程序)配置为使用委托身份验证。
- 在 `%SYS` 命名空间中创建一个名为 `ZAUTHENTICATE` 的例程。 提供了一个示例例程 `REST.ZAUTHENTICATE.mac`,可以复制和修改它。此例程是 GitHub (https://github.com/intersystems/Samples-Security) 上 Samples-Security 示例的一部分。可以按照“下载用于 IRIS 的示例”中的说明下载整个示例,但在 `GitHub` 上打开例程并复制其内容可能更方便。
在例程中,修改 `applicationName` 的值并根据需要进行其他更改。
## 指定使用 `REST` 服务所需的权限
为了指定执行代码或访问数据所需的权限, 技术使用基于角色的访问控制 (`RBAC`)。
如果需要为不同的用户提供不同级别的访问权限,请执行以下操作来指定权限:
- 修改规范类以指定使用 `REST` 服务或 `REST` 服务中的特定端点所需的权限;然后重新编译。权限是与资源名称组合的权限(例如读取或写入)。
- 使用管理门户:
- 定义在规范类中引用的资源。
- 定义提供权限集的角色。例如,角色可以提供对端点的读取访问权限或对不同端点的写入访问权限。一个角色可以包含多组权限。
- 将用户置于其任务所需的所有角色中。
此外,可以使用 `%CSP.REST` 类的 `SECURITYRESOURCE` 参数来执行授权。
# 指定权限
可以为整个 `REST` 服务指定权限列表,也可以为每个端点指定权限列表。为此:
1. 要指定访问服务所需的权限,请编辑规范类中的 `OpenAPI XData` 块。对于 `info` 对象,添加一个名为 `x-ISC_RequiredResource` 的新属性,其值是以逗号分隔的已定义资源列表及其访问模式 (`resource:mode`),这是访问 `REST` 服务的任何端点所必需的。
下面显示了一个示例:
```java
"swagger":"2.0",
"info":{
"version":"1.0.0",
"title":"Swagger Petstore",
"description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
"termsOfService":"http://swagger.io/terms/",
"x-ISC_RequiredResource":["resource1:read","resource2:read","resource3:read"],
"contact":{
"name":"Swagger API Team"
},
...
```
2. 要指定访问特定端点所需的权限,请将 `x-ISC_RequiredResource` 属性添加到定义该端点的操作对象,如下例所示:
```java
"post":{
"description":"Creates a new pet in the store. Duplicates are allowed",
"operationId":"addPet",
"x-ISC_RequiredResource":["resource1:read","resource2:read","resource3:read"],
"produces":[
"application/json"
],
...
```
3. 编译规范类。此操作重新生成调度类。
## 使用 `SECURITYRESOURC` 参数
作为附加的授权工具,分派 `%CSP.REST` 子类的类具有 `SECURITYRESOURCE` 参数。 `SECURITYRESOURCE` 的值要么是资源及其权限,要么只是资源(在这种情况下,相关权限是使用)。系统检查用户是否对与 `SECURITYRESOURCE` 关联的资源具有所需的权限。
注意:如果调度类为 `SECURITYRESOURCE` 指定了一个值,并且 `CSPSystem` 用户没有足够的权限,那么这可能会导致登录尝试失败时出现意外的 `HTTP` 错误代码。为防止这种情况发生, 建议您将指定资源的权限授予 `CSPSystem` 用户。
文章
Michael Lei · 九月 20, 2023
根据剑桥词典的解释,令牌化数据是“用令牌(=代表第一个数据的不同数据)替换隐私数据,以防止隐私信息被不被允许做的人看到”(https://dictionary.cambridge.org/pt/dicionario/ingles/tokenize)。如今,一些公司,尤其是金融和医疗保健领域的公司,正在将其数据令牌/代币化作为满足网络安全和数据隐私(GDPR、CCPA、HIPAA 和 LGPD)要求的重要策略。但是,为什么不使用加密呢?保护敏感数据的令牌化过程比数据加密更常用,原因如下:
更好的性能:在密集的操作处理中动态加密和解密数据会提高性能并需要更多的处理器能力。
测试:可以标记生产数据库并复制到测试数据库,并维护适合更真实的单元和功能测试的测试数据。
更好的安全性:如果黑客破解或获得密钥,所有加密数据都将可用,因为加密是一个可逆过程。令牌化过程是不可逆的。如果您需要从令牌化数据中获取原始数据,则需要维护一个安全且独立的数据库来链接到原始数据和令牌化数据。
令牌化架构
令牌化架构需要两个数据库:应用程序数据库(App DB)用于存储令牌化数据和来自业务的其他数据;令牌数据库(Token Database)用于存储原始值和令牌化值,因此当您需要时,您的应用程序可以获得原始值以向用户显示。还有一个 令牌生成器 REST API 用于标记敏感数据、存储到令牌数据库中并返回票证。应用程序将票据、令牌化数据和其他数据存储在应用程序数据库中。参见架构图:
分词器应用程序
了解它在令牌化应用程序中的工作原理: https://openexchange.intersystems.com/package/Tokenizator。
该应用程序是一个用于标记化的 REST API:
对任何数值显示为“*”号。示例:信用卡 4450 3456 1212 0050 至 4450 **** **** 0050。
任何真实的IP地址都是假值。示例:192.168.0.1 到 168.1.1.1。
任何人的数据都是假的。示例:地址为北京海淀的张三到地址为西藏拉萨的仓央嘉措。
任何数值为假数值。示例:300.00 到 320.00。
任何信用卡数据都是虚假的信用卡号码。示例:4450 3456 1212 0050 至 4250 2256 4512 5050。
任何值到哈希值。示例:dfgdgasdrrrdd123 的系统架构师。
正则表达式的任何值。示例:使用正则表达式规则 [AZ]{2}-\\d{5}-[az]{5} 将 EI-54105-tjfdk 转换为 AI-44102-ghdfg。
如果您需要任何其他选项,请在 github 项目上提交问题。
要对值进行标记并在之后获取原始值,请按照以下步骤操作:
打开您的 Postman 或从您的应用程序使用此 API。
使用 STARS、PERSON、NUMBER、CREDITCARD、HASH、IPADDRESS 和 REGEX 方法对此敏感数据样本创建 令牌化请求:
方法:POST
网址: http://localhost:8080/token/tokenize
正文(JSON):
[
{
"tokenType":"STARS",
"originalValueString":"545049405679",
"settings": {
"starsPosition":"1",
"starsQuantity":"4"
}
},
{
"tokenType":"IPADDRESS",
"originalValueString":"192.168.0.1",
"settings": {
"classType":"CLASS_B",
"ipSize":"4"
}
},
{
"tokenType":"PERSON",
"originalValueString":"Yuri Marx Pereira Gomes",
"settings": {
"localeLanguage":"en",
"localeCountry":"US",
"withAddress":"true",
"withEmail":"true"
}
},
{
"tokenType":"NUMBER",
"originalValueNumber":300.0,
"settings": {
"minRange":"100.0",
"maxRange":"400.0"
}
},
{
"tokenType":"CREDITCARD",
"originalValueString":"4892879268763190",
"settings": {
"type":"VISA"
}
},
{
"tokenType":"HASH",
"originalValueString":"System Architect"
},
{
"tokenType":"REGEX",
"originalValueString":"EI-54105-tjfdk",
"settings": {
"regex":"[A-Z]{2}-\\d{5}-[a-z]{5}"
}
}
]
查看结果。您将获得一个令牌化的数值 (tokenizedValueString) 并将其存储到本地数据库中。
从响应中复制票证(将其与令牌化化值一起存储到本地数据库中)
现在凭票证即可获得原数值。创建一个请求以使用票证获取原始值:
方法:获取
网址: http://localhost:8080/token/query-ticket/TICKET-VALUE-HERE
查看您的原始值
生成的所有令牌都存储到 InterSystems IRIS Cloud SQL 中,以便您能够可靠地获得原始值。
欢迎体验!
公告
Claire Zheng · 十月 8, 2024
各位社区成员,大家好!
我们想念 Global Masters,相信你们也是!
🚀 Global Masters 将于 2024 年 10 月 3 日再次启动!
10 月 3 日,我们将向所有开发者社区成员分享访问新平台的链接。敬请关注开发者社区的专属帖子!
我们一直在紧锣密鼓地筹备这一计划的回归,但如果您发现有任何细节之处做得不到位,请多加包涵,这项工作仍在进行中! 我们将重点放在先提供最有价值的功能:挑战(我们现在称之为“邀请”… 但有时仍然是“挑战”,因为我们在不断调整!)、奖励和徽章。
一些功能仍在开发中:
1. API 集成:Global Masters、开发者社区、Open Exchange 和 Ideas Portal 之间的集成仍在进行中。 预计启动时间是 10 月底。 与此同时,我们正在跟踪您的贡献,并将在功能上线后更新您的积分余额。
2. 级别:新平台不支持级别,但我们正紧锣密鼓地筹备恢复级别功能。
等待已结束!🎉 冲吧!
文章
Michael Lei · 十二月 7, 2022
Intersystems IRIS for Health 对 FHIR 行业标准提供了出色的支持。主要特点是:1.FHIR 服务器2. FHIR数据库3. REST 和 ObjectScript API 用于 FHIR 资源(患者、问卷、疫苗等)的 CRUD 操作
本文演示了如何使用这些功能,并展示了用于创建和查看表单类型的 FHIR 资源的Angula前端。
第 1 步 - 使用 InterSystems IRIS for Health 部署您的 FHIR 服务器
要创建 FHIR 服务器,您必须将以下说明添加到 iris.script 文件中(来自:https://openexchange.intersystems.com/package/iris-fhir-template)
zn "HSLIB" set namespace= "FHIRSERVER" Set appKey = "/fhir/r4" Set strategyClass = "HS.FHIRServer.Storage.Json.InteractionsStrategy" set metadataPackages = $lb ( "hl7.fhir.r4.core@4.0.1" ) set importdir= "/opt/irisapp/src" //Install a Foundation namespace and change to it Do ##class (HS.HC.Util.Installer).InstallFoundation(namespace) zn namespace // Install elements that are required for a FHIR-enabled namespace Do ##class (HS.FHIRServer.Installer).InstallNamespace() // Install an instance of a FHIR Service into the current namespace Do ##class (HS.FHIRServer.Installer).InstallInstance(appKey, strategyClass, metadataPackages) // Configure FHIR Service instance to accept unauthenticated requests set strategy = ##class (HS.FHIRServer.API.InteractionsStrategy).GetStrategyForEndpoint(appKey) set config = strategy.GetServiceConfigData() set config.DebugMode = 4 do strategy.SaveServiceConfigData(config) zw ##class (HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles( "/opt/irisapp/fhirdata/" , "FHIRServer" , appKey) do $System .OBJ.LoadDir( "/opt/irisapp/src" , "ck" ,, 1 ) zn "%SYS" Do ##class (Security.Users).UnExpireUserPasswords( "*" ) zn "FHIRSERVER" zpm "load /opt/irisapp/ -v" : 1 : 1 //zpm "install fhir-portal" halt
使用实用程序类 HS.FHIRServer.Installer,您可以创建 FHIR 服务器。
第 2 步 - 使用 FHIR REST 或 ObjectScript API 读取、更新、删除和查找 FHIR 数据
我喜欢使用 ObjectScript 类 HS.FHIRServer.Service 来执行所有 CRUD 操作。
要从资源类型(表单)中获取所有 FHIR 数据:
/// Retreive all the records of questionnaire ClassMethod GetAllQuestionnaire() As %Status { set tSC = $$$OK Set %response.ContentType = ..#CONTENTTYPEJSON Set %response.Headers ( "Access-Control-Allow-Origin" )= "*" Try { set fhirService = ##class (HS.FHIRServer.Service).EnsureInstance(..#URL) set request = ##class (HS.FHIRServer.API.Data.Request). %New () set request.RequestPath = "/Questionnaire/" set request.RequestMethod = "GET" do fhirService.DispatchRequest(request, .pResponse) set json = pResponse.Json set resp = [] set iter = json.entry. %GetIterator () while iter. %GetNext (.key, .value) { do resp. %Push (value.resource) } write resp. %ToJSON () } Catch Err { set tSC = 1 set message = {} set message.type= "ERROR" set message.details = "Error on get all questionnairies" } Quit tSC }
要从 FHIR 数据存储库中获取特定数据项(调查表):
/// Retreive a questionnaire by id ClassMethod GetQuestionnaire(id As %String ) As %Status { set tSC = $$$OK Set %response.ContentType = ..#CONTENTTYPEJSON Set %response.Headers ( "Access-Control-Allow-Origin" )= "*" Try { set fhirService = ##class (HS.FHIRServer.Service).EnsureInstance(..#URL) set request = ##class (HS.FHIRServer.API.Data.Request). %New () set request.RequestPath = "/Questionnaire/" _id set request.RequestMethod = "GET" do fhirService.DispatchRequest(request, .pResponse) write pResponse.Json. %ToJSON () } Catch Err { set tSC = 1 set message = {} set message.type= "ERROR" set message.details = "Error on get the questionnaire" } Quit tSC }
要创建新的 FHIR 资源事件(新的调查表):
/// Create questionnaire ClassMethod CreateQuestionnaire() As %Status { set tSC = $$$OK Set %response.ContentType = ..#CONTENTTYPEJSON Set %response.Headers ( "Access-Control-Allow-Origin" )= "*" Try { set fhirService = ##class (HS.FHIRServer.Service).EnsureInstance(..#URL) set request = ##class (HS.FHIRServer.API.Data.Request). %New () set request.RequestPath = "/Questionnaire/" set request.RequestMethod = "POST" set data = {}. %FromJSON ( %request.Content ) set data.resourceType = "Questionnaire" set request.Json = data do fhirService.DispatchRequest(request, .response) write response.Json. %ToJSON () } Catch Err { set tSC = 1 set message = {} set message.type= "ERROR" set message.details = "Error on create questionnaire" } Return tSC }
要更新 FHIR 资源(表单):
/// Update a questionnaire ClassMethod UpdateQuestionnaire(id As %String ) As %Status { set tSC = $$$OK Set %response.ContentType = ..#CONTENTTYPEJSON Set %response.Headers ( "Access-Control-Allow-Origin" )= "*" Try { set fhirService = ##class (HS.FHIRServer.Service).EnsureInstance(..#URL) set request = ##class (HS.FHIRServer.API.Data.Request). %New () set request.RequestPath = "/Questionnaire/" _id set request.RequestMethod = "PUT" set data = {}. %FromJSON ( %request.Content ) set data.resourceType = "Questionnaire" set request.Json = data do fhirService.DispatchRequest(request, .response) write response.Json. %ToJSON () } Catch Err { set tSC = 1 set message = {} set message.type= "ERROR" set message.details = "Error on update questionnaire" } Return tSC }
要删除 FHIR 资源事件(表单):
/// Delete a questionnaire by id ClassMethod DeleteQuestionnaire(id As %String ) As %Status { set tSC = $$$OK Set %response.ContentType = ..#CONTENTTYPEJSON Set %response.Headers ( "Access-Control-Allow-Origin" )= "*" Try { set fhirService = ##class (HS.FHIRServer.Service).EnsureInstance(..#URL) set request = ##class (HS.FHIRServer.API.Data.Request). %New () set request.RequestPath = "/Questionnaire/" _id set request.RequestMethod = "DELETE" do fhirService.DispatchRequest(request, .pResponse) } Catch Err { set tSC = 1 set message = {} set message.type= "ERROR" set message.details = "Error on delete the questionnaire" } Quit tSC }
如您所见,您想要创建使用 POST,更新使用 PUT,删除使用 DELETE,查询使用 GET 动词。
第 3 步 - 创建 Angular 客户端以使用您的 FHIR 服务器应用程序
我使用 PrimeNG 创建了一个角度应用程序并安装了包 npm install --save @types/fhir。此包具有映射到 TypeScript 的所有 FHIR 类型。
Angular控制器类:
import { Component, OnInit, ViewEncapsulation } from '@angular/core' ; import { ActivatedRoute, Router } from '@angular/router' ; import { Period, Questionnaire } from 'fhir/r4' ; import { ConfirmationService, MessageService, SelectItem } from 'primeng/api' ; import { QuestionnaireService } from './questionnaireservice' ; const QUESTIONNAIREID = 'questionnaireId' ; @Component({ selector: 'app-questionnaire', templateUrl: './questionnaire.component.html', providers: [MessageService, ConfirmationService], styleUrls: ['./questionnaire.component.css'], encapsulation: ViewEncapsulation.None }) export class QuestionnaireComponent implements OnInit { public questionnaire: Questionnaire ; public questionnairies: Questionnaire[] ; public selectedQuestionnaire: Questionnaire ; public questionnaireId: string ; public sub: any ; public publicationStatusList: SelectItem[] ; constructor( private questionnaireService: QuestionnaireService, private router: Router, private route: ActivatedRoute, private confirmationService: ConfirmationService, private messageService: MessageService){ this.publicationStatusList = [ {label: 'Draft', value: 'draft'}, {label: 'Active', value: 'active'}, {label: 'Retired', value: 'retired'}, {label: 'Unknown', value: 'unknown'} ] } ngOnInit() { this.reset() ; this.listQuestionnaires() ; this.sub = this.route.params.subscribe(params => { this.questionnaireId = String(+params[QUESTIONNAIREID]) ; if (!Number.isNaN(this.questionnaireId)) { this.loadQuestionnaire(this.questionnaireId) ; } }) ; } private loadQuestionnaire(questionnaireId) { this.questionnaireService.load(questionnaireId).subscribe(response => { this.questionnaire = response ; this.selectedQuestionnaire = this.questionnaire ; if (!response.effectivePeriod) { this.questionnaire.effectivePeriod = <Period>{} ; } }, error => { console.log(error) ; this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load questionnaire.' }) ; }) ; } public loadQuestions() { if (this.questionnaire && this.questionnaire.id) { this.router.navigate(['/question', this.questionnaire.id]) ; } else { this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Choose a questionnaire.' }) ; } } private listQuestionnaires() { this.questionnaireService.list().subscribe(response => { this.questionnairies = response ; this.reset() ; }, error => { console.log(error) ; this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load the questionnaries.' }) ; }) ; } public onChangeQuestionnaire() { if (this.selectedQuestionnaire && !this.selectedQuestionnaire.id) { this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' }) ; } else { if (this.selectedQuestionnaire && this.selectedQuestionnaire.id) { this.loadQuestionnaire(this.selectedQuestionnaire.id) ; } } } public reset() { this.questionnaire = <Questionnaire>{} ; this.questionnaire.effectivePeriod = <Period>{} ; } public save() { if (this.questionnaire.id && this.questionnaire.id != "" ) { this.questionnaireService.update(this.questionnaire).subscribe( (resp) => { this.messageService.add({ severity: 'success', summary: 'Success', detail: 'Questionnaire saved.' }) ; this.listQuestionnaires() this.loadQuestionnaire(this.questionnaire.id) ; }, error => { console.log(error) ; this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on save the questionnaire.' }) ; } ) ; } else { this.questionnaireService.save(this.questionnaire).subscribe( (resp) => { this.messageService.add({ severity: 'success', summary: 'Success', detail: 'Questionnaire saved.' }) ; this.listQuestionnaires() this.loadQuestionnaire(resp.id) ; }, error => { console.log(error) ; this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on save the questionnaire.' }) ; } ) ; } } public delete(id: string) { if (!this.questionnaire || !this.questionnaire.id) { this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' }) ; } else { this.confirmationService.confirm({ message: ' Do you confirm?', accept: () => { this.questionnaireService.delete(id).subscribe( () => { this.messageService.add({ severity: 'success', summary: 'Success', detail: 'Questionnaire deleted.' }) ; this.listQuestionnaires() ; this.reset() ; }, error => { console.log(error) ; this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on delete questionnaire.' }) ; } ) ; } }) ; } } }
Angular HTML 文件
<p-toast [style]= "{marginTop: '80px', width: '320px'}" ></p-toast> <p-card> <div class = "p-fluid p-formgrid grid" > <div class = "field col-12 lg:col-12 md:col-12" > <p-dropdown id= "dropquestions1" [options]= "questionnairies" [(ngModel)]= "selectedQuestionnaire" (onChange)= "onChangeQuestionnaire()" placeholder= "Select a Questionnaire" optionLabel= "title" [filter]= "true" [showClear]= "true" ></p-dropdown> </div> </div> <p-tabView> <p-tabPanel leftIcon= "fa fa-question" header= "Basic Data" > <div class = "p-fluid p-formgrid grid" > <div class = "field col-3 lg:col-3 md:col-12" > <label for = "txtname" >Name</label> <input class = "inputfield w-full" id= "txtname" required type= "text" [(ngModel)]= "questionnaire.name" pInputText placeholder= "Name" > </div> <div class = "field col-7 lg:col-7 md:col-12" > <label for = "txttitle" >Title</label> <input class = "inputfield w-full" id= "txttitle" required type= "text" [(ngModel)]= "questionnaire.title" pInputText placeholder= "Title" > </div> <div class = "field col-2 lg:col-2 md:col-12" > <label for = "txtdate" >Date</label> <p-inputMask id= "txtdate" mask= "9999-99-99" [(ngModel)]= "questionnaire.date" placeholder= "9999-99-99" slotChar= "yyyy-mm-dd" ></p-inputMask> </div> <div class = "field col-2 lg:col-2 md:col-12" > <label for = "txtstatus" >Status</label> <p-dropdown [options]= "publicationStatusList" [(ngModel)]= "questionnaire.status" ></p-dropdown> </div> <div class = "field col-3 lg:col-3 md:col-12" > <label for = "txtpublisher" >Publisher</label> <input class = "inputfield w-full" id= "txtpublisher" required type= "text" [(ngModel)]= "questionnaire.publisher" pInputText placeholder= "Publisher" > </div> <div class = "field col-2 lg:col-2 md:col-12" > <label for = "txtstartperiod" >Start Period</label> <p-inputMask id= "txtstartperiod" mask= "9999-99-99" [(ngModel)]= "questionnaire.effectivePeriod.start" placeholder= "9999-99-99" slotChar= "yyyy-mm-dd" ></p-inputMask> </div> <div class = "field col-2 lg:col-2 md:col-12" > <label for = "txtendperiod" >End Period</label> <p-inputMask id= "txtendperiod" mask= "9999-99-99" [(ngModel)]= "questionnaire.effectivePeriod.end" placeholder= "9999-99-99" slotChar= "yyyy-mm-dd" ></p-inputMask> </div> <div class = "field col-12 lg:col-12 md:col-12" > <label for = "txtcontent" >Description</label> <p-editor [(ngModel)]= "questionnaire.description" [style]= "{'height':'100px'}" ></p-editor> </div> </div> <div class = "grid justify-content-end" > <button pButton pRipple type= "button" label= "New Record" (click)= "reset()" class = "p-button-rounded p-button-success mr-2 mb-2" ></button> <button pButton pRipple type= "button" label= "Save" (click)= "save()" class = "p-button-rounded p-button-info mr-2 mb-2" ></button> <button pButton pRipple type= "button" label= "Delete" (click)= "delete(questionnaire.id)" class = "p-button-rounded p-button-danger mr-2 mb-2" ></button> <button pButton pRipple type= "button" label= "Questions" (click)= "loadQuestions()" class = "p-button-rounded p-button-info mr-2 mb-2" ></button> </div> </p-tabPanel> </p-tabView> </p-card> <p-confirmDialog #cd header= "Atenção" icon= "pi pi-exclamation-triangle" > <p-footer> <button type= "button" pButton icon= "pi pi-times" label= "Não" (click)= "cd.reject()" ></button> <button type= "button" pButton icon= "pi pi-check" label= "Sim" (click)= "cd.accept()" ></button> </p-footer> </p-confirmDialog>
角度服务类
import { Injectable } from '@angular/core' ; import { HttpClient, HttpHeaders } from '@angular/common/http' ; import { Observable } from 'rxjs' ; import { environment } from 'src/environments/environment' ; import { take } from 'rxjs/operators' ; import { Questionnaire } from 'fhir/r4' ; @Injectable({ providedIn: 'root' }) export class QuestionnaireService { private url = environment.host2 + 'questionnaire' ; constructor(private http: HttpClient) { } public save(Questionnaire: Questionnaire): Observable<Questionnaire> { return this.http.post<Questionnaire>(this.url, Questionnaire).pipe(take( 1 )) ; } public update(Questionnaire: Questionnaire): Observable<Questionnaire> { return this.http.put<Questionnaire>(`${this.url}/${Questionnaire.id}`, Questionnaire).pipe(take( 1 )) ; } public load(id: string): Observable<Questionnaire> { return this.http.get<Questionnaire>(`${this.url}/${id}`).pipe(take( 1 )) ; } public delete(id: string): Observable<any> { return this.http.delete(`${this.url}/${id}`).pipe(take( 1 )) ; } public list(): Observable<Questionnaire[]> { return this.http.get<Questionnaire[]>(this.url).pipe(take( 1 )) ; } }
第 4 步 - 实际应用
1. 转到 https://openexchange.intersystems.com/package/FHIR-Questionnaires 应用程序。
2. clone/git pull repo 到任何本地目录
$ git clone https://github.com/yurimarx/fhir-questions.git
3、在该目录下打开终端,运行:
$ docker-compose up -d
4.打开网络应用程序: http://localhost:52773/fhirquestions/index.html
截图:
文章
Lilian Huang · 九月 7, 2023
您好!社区的各位老师,
在我的上一篇文章中,我们学习了以下主题:
什么是 Docker?
Docker 的一些好处
Docker 是如何工作的?
Docker 镜像
Docker容器
Docker 镜像存储库
InterSystems 的 Docker 镜像存储库
Docker安装
Docker 基本命令
使用 Docker 运行 IRIS 社区版
Docker 桌面图形用户界面
在本文中,我们将讨论以下主题:
使用 Docker Compose 文件( YAML 文件)
Docker 文件的使用(用于构建 Docker 镜像)
Docker 卷的使用
那么让我们开始吧。
1.使用Docker Compose文件( YAML文件)
Docker Compose 是一款旨在帮助定义和共享多容器应用程序的工具。使用 Compose,我们可以创建一个 YAML 文件来定义服务,并且通过单个命令,我们可以启动或拆除所有内容。
使用 Compose 的一大优势是能够在文件中定义应用程序堆栈并将其保存在项目存储库的根目录中。您还可以让其他人轻松地为您的项目做出贡献。 他们只需要克隆您的存储库并启动撰写应用程序。
在我的上一篇文章中,我们使用下面提到的命令来创建并启动带有 InterSystems 社区镜像的容器:
docker run -d -p 52773:52773 intersystemsdc/iris-community
此时,让我们修改此命令并添加容器名称、映射更多端口并设置重新启动选项:
docker run -d -p 52773:52773 -p 53773:53773 -p 1972:1972 --name iris --restart=always intersystemsdc/iris-community
让我为您分解一下上述命令:
# docker run command to create and start the container docker run # -d -an option used to start container in deattach mode -d # -p -an option is used to map the ports -p 52773:52773 -p 53773:53773 -p 1972:1972 # name of the container --name iris # set the restart option to always --restart=always # base image intersystemsdc/iris-community
创建撰写文件
在根文件夹中,创建一个名为-----100-----的文件,并写入上述命令,如下所示:
#specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036 :1972 - 55037 :52773 - 53773 :53773
docker run 命令和 docker-compose 文件的映射如下所示:Docker-compose 文件快照如下所示:
为了运行 docker-compose 文件代码,我们将使用 docker-compose up 命令:
docker-compose up -d
-----101----- 或 -----102-----:在后台运行命令并将控制返回到终端的选项。
容器已启动。让我们运行“docker ps”命令来列出正在运行的容器
正如您所看到的,我们使用 docker-compose 文件得到了相同的结果。
创建并启动多个容器
在 docker-compose 的帮助下,我们不仅可以运行多个容器,还可以组织并向其添加更多命令。
例如,在下面的 docker-compose 文件中,我们运行 MongoDB 容器和 iris 容器:
#specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036:1972 - 55037:52773 - 53773:53773 #start MongoDB container mongodb: image: mongo ports: - 27017:27017
让我们运行 docker-compose up 命令
MongoDB 和 iris 容器现已创建并启动。
2.Docker 文件
Docker可以通过读取-----103-----的指令来自动构建镜像。 -----103----- 是一个文本文档,其中包含用户可以在命令行上调用来组装图像的所有命令。
所以,我们的第一个问题很简单。什么是 Dockerfile? Docker 使用它来构建镜像本身。 Dockerfile 本质上是如何创建镜像的构建指令。
与简单存储二进制映像相比,Dockerfile 的优点是自动构建将确保您拥有可用的最新版本。从安全角度来看这是一件好事,因为您希望确保没有安装任何易受攻击的软件。
常用 Docker 文件命令
您可以在下面找到一些最常用的 docker 命令。请注意,所有 docker 命令都必须大写字母。
从
第一个是 FROM 命令,它告诉您映像基于什么。正是这种多层方法使得 Docker 如此高效和强大。在本例中,使用了 iris-community Docker 映像,它再次引用 Dockerfile 来自动化构建过程。
FROM intersystemsdc/iris-community
工作目录
该命令用于设置一个工作目录,我们将在其中复制文件。例如,下面提到的命令将设置 /opt/irisbuild 作为工作目录:
WORKDIR /opt/irisbuild
复制
COPY 命令就像听起来一样简单。它可以将文件复制到容器中。通常,我们复制自定义配置文件、应用程序源文件、数据文件等。
#coping Installer.cls to a root of workdir. Don't miss the dot, here. COPY Installer.cls . #copy source files from src folder to src folder in workdir in the docker container. COPY src src #copy source files from data/fhir folder to fhirdata folder in the docker container. COPY data/fhir fhirdata
环境电压
这会设置环境变量,这些变量可以在 Dockerfile 及其调用的任何脚本中使用。-----105-----指令将环境变量-----106-----定义为值-----107-----
ENV USER_ID "SYSTEM" ENV USER_PASSWORD "MANAGER"
跑步
-----108-----命令用于在镜像构建过程中执行命令。
#here we give the rights to irisowner user and group which are run IRIS. RUN chown ${ISC_PACKAGE_MGRUSER} : ${ISC_PACKAGE_IRISGROUP} /opt/irisapp #start IRIS and run script in iris.script file RUN iris start IRIS \ && iris session IRIS < /tmp/iris.script
用户
默认情况下,容器以 root 身份运行,这使它们能够完全控制主机系统。随着容器技术的成熟,可能会出现更安全的默认选项。目前,要求 root 对其他人来说是危险的,并且可能不适用于所有环境。您的映像应使用 -----109----- 指令来指定容器运行的非 root用户。如果您的软件没有创建自己的用户,您可以在 Dockerfile 中创建用户和组。
#here we switch user to a root to create a folder and copy files in docker. USER root WORKDIR /opt/irisapp #switching user from root to irisowner, to copy files USER irisowner COPY src src
更多细节请阅读Docker官方文档
3.Docker卷
Docker卷是一个完全由Docker管理的独立文件系统。它作为标准文件或目录存在于数据保存的主机上。
使用 Docker 卷的目的是将数据保留在容器外部,以便可用于备份或共享。
Docker 卷依赖于 Docker 的文件系统,是 Docker 容器和服务保存数据的首选方法。当容器启动时,Docker 会加载只读镜像层,在镜像堆栈顶部添加读写层,并将卷挂载到容器文件系统上。
我们使用 -----110----- 或 -----111----- 标志来允许我们将本地文件挂载到容器中。
卷是保存 Docker 容器生成和使用的数据的首选机制。虽然绑定挂载取决于主机的目录结构和操作系统,但卷完全由 Docker 管理。与绑定挂载相比,卷有几个优点:
卷比绑定安装更容易备份或迁移。
您可以使用 Docker CLI 命令或 Docker API 管理卷。
卷适用于 Linux 和 Windows 容器。
卷可以在多个容器之间更安全地共享。
卷驱动程序允许您将卷存储在远程主机或云提供商上,以加密卷的内容或添加其他功能。
新卷的内容可以由容器预先填充。
Docker Desktop 上的卷比 Mac 和 Windows 主机上的绑定挂载具有更高的性能。
此外,卷通常是比将数据保留在容器的可写层中更好的选择,因为卷在使用时不会增加容器的大小。此外,卷的内容存在于给定容器的生命周期之外。
我们可以在 docker-compose 文件的 services 部分下提及卷。
#specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036 :1972 - 55037 :52773 - 53773 :53773 #create a volume volumes: - ./:/irisdev/app
我们已经创建了 irisdev/app 卷。
概括
Docker 是一个功能强大的工具,允许开发人员和 IT 团队在容器化环境中创建、部署和运行应用程序。通过提供可移植性、一致性、可扩展性、资源效率和安全性,Docker 可以轻松地跨不同环境和基础设施部署应用程序。随着容器化的日益普及,Docker正在成为现代软件开发和部署的重要工具。在本文中,我们学习了如何使用 Docker compose(一个 YAML 文件,指定应用程序中每个容器的配置选项)、Docker 文件(用于构建 Docker 镜像)和 Docker 卷(一种用于共享数据的持久数据存储机制) Docker 容器和主机之间的数据。)
感谢您的阅读!
文章
Jeff Liu · 一月 27, 2021
1.关于本文
就像Caché模式匹配一样,正则表达式也可以在Caché中用来识别文本数据中的模式--只是表达能力更强。本文简要介绍了正则表达式,以及在Caché中如何使用它。这里提供的信息基于各种来源,最值得拜读的是Jeffrey Friedl的《掌握正则表达式》一书,当然还有Caché在线文档。本文无意讨论正则表达式的所有可能性和细节。如果你想了解更多,请参考第5章中列出的信息来源。
使用模式进行文本处理有时会变得很复杂。在处理正则表达式时,我们通常有几种实体:我们正在搜索模式的文本、模式本身(正则表达式)和匹配(文本中与模式匹配的部分)。为了便于区分这些实体,本文档中使用了以下约定。
文本样本以单色字体单独列出,不加引号。
This is a "text string" in which we want to find "something".
除非不明确,否则正文中的正则表达式会以灰色背景显示,如本例。\".*?\".
需要时用不同的颜色突出显示匹配。
这是一个"text string",我们要在其中找到"something"。
代码样本会显示在如下的文本框里:
set t=" This is a ""text string"" in which we want to find ""something "
set r="\"".*?\"""
w $locate(t,r,,tMatch)
2.一些历史(和一些小事)。
在20世纪40年代初,神经生理学家开发了人类神经系统的模型。几年后,一位数学家用一种代数来描述这些模型,他称之为"正则集"。这种代数的符号被命名为"正则表达式"。
1965年,正则表达式第一次在计算机的范畴内被提及。随着qed,一个作为UNIX操作系统一部分的编辑器,正则表达式开始传播。该编辑器后来的版本提供了一个命令序列g/正则表达式/p(全局、正则表达式、打印),在所有文本行中搜索匹配的正则表达式并输出结果。这个命令序列最终成为独立的UNIX命令行程序"grep"。
今天,许多编程语言都存在正则表达式(RegEx)的各种实现(见3.3节)。
3.Regex 101
就像Caché模式匹配一样,正则表达式也可以用来识别文本数据中的模式--只是表达能力更强。下面的章节概述了正则表达式的组成部分,它们的评估和一些可用的引擎,然后在第4章中详细介绍如何使用。
3.1.正则表达式的组成部分
3.1.1.Regex元字符
以下字符在正则表达式中具有特殊意义。
. ( ) [ ] \ ^ $ |
如果你需要将它们作为字面数使用,你需要使用反斜杠来转义。你也可以使用 \Q <literal sequence> \E显式指定字面序列。
3.1.2.文字
普通文本和转义字符被视为字面,例如:。
abc
abc
\f
换页
\n
换行
\r
回车
\v
标签
\0+三位数(如:0101)
八进制数Caché (ICU)中使用的regex引擎支持八进制数,最高可达\0377(十进制系统为255)。当你从另一个引擎迁移正则表达式时,请确保你了解它如何处理八进制数。
\x+两位数(如:x41)
十六进制数Caché库确实提供了更多处理十六进制数的选项,请参考文档(链接可以在5.8节找到)。
3.1.3.锚
使用锚点,你可以匹配文本/字符串中的位置,例如:。
\A 字符串的开始
\Z 字符串的末端
^ 文本或行的开始
$ 文末或行末
\b 字词边界
\B 不字界
< 词的开头
#> 词尾
和一些RegEx引擎的行为有所不同,例如,对构成单词的确切定义以及哪些字符被视为单词定界符。
3.1.4.量词
使用正则表达式量词,你可以指定前面的元素可能出现的频率来进行匹配。
{x}正好出现x次
{x,y}最小x,最大y的出现次数。
* 0或更多;相当于{0,}。
+1或更多;相当于{1,}。
? 0或1
量词很"贪婪",它们会尽可能多地抓取字符。假设我们有下面的文本字符串,想找到带引号的文本。
This is "a text" with "four quotes".
由于选择器的贪婪性质,正则表达式"/".*/"会找到太多的文本。
This is "a text" with "four quotes".
在这个例子中,正则表达式.*试图捕捉尽可能多的位于一对引号之间的字符。然而,由于点选择器 ( .) 也匹配引号,我们没有得到我们想要的结果。
通过一些regex引擎(包括Caché使用的引擎),你可以通过添加一个问号来控制量化符的贪婪程度。因此,正则表达式"\".*?\"现在可以匹配引号中的两部分文本--这正是我们要找的。
This is "a text" with "four quotes".
3.1.5.字符类(范围)
方括号用于指定字符的范围或字符集,例如 [a-zA-Z0-9] 或 [abcd] - 在 regex 中,这被称为字符类。一个范围可以匹配单个字符,所以范围定义中的字符顺序无关紧要--[dbac]返回的匹配结果与[abcd]相同。
要排除一个字符范围,只需在字符范围定义前面加上^(在方括号内!)。[^abc] 匹配除了a, b或c以外的任何字符.
一些regex引擎确实提供了预先定义的字符类(POSIX),例如。
[:alnum:] [a-zA-z0-9]
[:alpha:] [a-zA-Z]
[:blank:] [\t]
…
3.1.6.Groups (组)
正则表达式的部分内容可以使用对括号进行分组。这对于将量化符应用于一组选择符,以及从同一regex内(反向引用)和从调用正则表达式的Caché对象脚本代码(捕获缓冲区)中引用分组都很有用。组可以被嵌套。
下面的regex匹配由一个三位数组成的字符串,后面是一个破折号,然后是3对大写字母和一个数字,后面是一个破折号,然后是与第一部分相同的三位数。
([0-9]{3})-([A-Z][[0-9]){3}-\1
这个例子展示了如何使用反向引用(见下文)不仅匹配结构,而且匹配内容:反向引用(紫色)告诉引擎在结尾处寻找与开头处相同的三位数数字(黄色)。它还演示了如何将量词应用于更复杂的结构(绿色)。
上面的regex将匹配以下字符串。
123-D1E2F3-123
在这些上面是不匹配的。
123-D1E2F3-456(最后三位数与前三位数不同)
123-1DE2F3-123(中间部分不包含三个字母/数字对)
123-D1E2-123(中间部分只包含两个字母/数字对)
组也会填充所谓的捕获缓冲区(见4.5.1节)。这是一个非常强大的功能,它允许同时匹配和提取信息。
3.1.7. Alternations(交替)
使用管道字符来指定alternations,例如skyfall|done。这允许匹配更复杂的表达式,如3.1.5节中描述的字符类。
3.1.8.回溯引用
后面的引用允许您引用以前定义的组(括号中的选择器)。下面的例子显示了一个正则表达式,它匹配三个必须相等的连续字符。
([a-zA-Z])/1/1
后面的引用由\x指定,而x代表第x个括号中的表达式。
3.1.9.优先规则
[]在()之前
+和? 在序列前:ab等于a(b*),而不是(ab)*。
序列在alternation前:ab|c等于(ab)|c,而不是a(b|c)
3.2.一些理论
正则表达式的评估通常采用以下两种方法之一来实现(这里描述是简化的,请参考第5章中提到的文献进行深入讨论)。
文本驱动(DFA - Deterministic Finite Automaton) 引擎逐字逐句地检查输入文本,并尝试匹配它目前所拥有的内容。当它真正到达输入文本的结尾时,它宣布成功。
Regex-driven (NFA - Non-deterministic Finite Automaton) 引擎会逐一检查正则表达式,并尝试将其应用到文本中。当它真正到达(并匹配)最后一个标记时,它宣布成功。
方法1是确定性的,执行时间只取决于输入文本的长度。正则表达式中选择符的顺序不影响执行时间。
方法2是非决定性的,引擎会遍历正则表达式中选择符的所有组合,直到找到匹配或遇到错误。因此,当它没有找到匹配项时,这种方法特别慢(因为它必须遍历所有可能的组合)。选择符的顺序确实对执行时间有影响。但是,这种方法允许回溯和捕获缓冲区。
3.3.Regex引擎
目前有很多不同的regex引擎,有些是编程语言或操作系统的内置部分,有些是几乎可以在任何地方使用的库。以下是一些regex引擎,按评估方法分组。
DFA: grep, awk, lex.
NFA:Perl、Tcl、Python、Emacs、sed、vi、ICU。
下表是各种编程语言和库中可用的regex功能的比较。
详情请点击这里:https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines
4.RegEx和Caché
InterSystems Caché使用ICU库来处理正则表达式,Caché在线文档描述了它的许多功能。请参考ICU库的在线文档以了解完整的细节(包括诸如回溯引用等)--ICU的链接可以在5.8节中找到。以下章节旨在为您快速介绍如何使用它。
4.4.$match()和$locate()
在Caché ObjectScript (COS)中,两个函数$match()和$locate()提供了对ICU库提供的大部分regex功能的直接访问。$match(String, Regex) 在输入的字符串中搜索指定的Regex模式。当它找到一个匹配的模式时,它返回1,否则它返回0。
例如:
w $match("baaacd",".*(a)\1/1.*") 返回1。
w $match("baacd",".*(a)\1/1.*") 返回0。
$locate(String,Regex,Start,End,Value) 就像$match()一样,以指定的regex模式搜索输入字符串。然而,$locate()给你更多的控制权,它返回更多的信息。在Start中,你可以告诉$locate应该在哪个位置开始搜索输入字符串中的模式。当$locate()找到一个匹配时,它会返回匹配的第一个字符的位置,并将End设置为匹配后的下一个字符位置。匹配的内容会在Value中返回。
如果$locate()没有找到匹配的对象,它将返回0,并且不触及End和Value(如果指定)的内容。End和Value是以引用的形式传递的,所以如果你重复使用它(例如在循环中)要小心。
例如:
w $locate("abcdexyz",".d.",1,e,x) 返回3,e设为6,x设为"cde"
$locate()执行模式匹配,并且可以同时返回第一个匹配的内容,如果需要提取所有匹配的内容,可以在循环中反复调用$locate(),也可以使用%Regex.Matcher提供的方法。 如果需要提取所有匹配的内容,你可以在一个循环中重复调用$locate(),或者你可以使用%Regex.Matcher提供的方法(见下一节)。
4.5.%Regex.Matcher.
%Regex.Matcher提供了ICU库的regex功能,就像$match()和$locate()一样。然而,%Regex.Matcher还提供了一些高级功能,使更复杂的任务变得非常容易使用。下面的章节将重新审视捕获缓冲区,看看用正则表达式替换字符串的可能性以及控制运行时行为的方法。
4.5.1.捕获缓冲区(Buffers)
正如我们在关于组、回溯引用和$locate()的章节中已经看到的,正则表达式允许你同时搜索文本中的模式并返回匹配的内容。它的工作原理是将您想要提取的模式的部分放在一对括号中(组)。匹配成功后,捕获缓冲区包含所有匹配的组的内容。请注意,这与$locate()通过其值参数提供的内容略有不同:$locate()返回整个匹配本身的内容,而捕获缓冲区则让您访问匹配的部分内容(组)。
要使用它,你需要创建一个 %Regex.Matcher 类的对象,并将正则表达式和输入字符串传递给它。然后你可以调用%Regex.Matcher提供的方法来执行实际工作。
例1(简单组):
set m=##class(%Regex.Matcher).%New("(a|b).*(de)", "abcdeabcde")
w m.Locate()返回1
w m.Group(1) 返回 a
w m.Group(2) 返回 de
例2(嵌套组和回溯引用)。
set m=##class(%Regex.Matcher).%New("((a|b).*?(de))(\1)", "abcdeabcde")
w m.Match()返回1
w m.GroupCount返回4
w m.Group(1) 返回 abcde。
w m.Group(2) 返回 a
w m.Group(3) 返回 de
w m.Group(4) 返回 abcde。
(注意嵌套组的顺序--因为开头的括号标志着一个组的开始,所以内部组的索引号比外部组的索引号高)
如前所述,捕获缓冲区是一个非常强大的功能,因为它们允许您同时匹配模式和提取匹配的内容。如果没有正则表达式,您必须在第一步中找到匹配的内容(例如使用模式匹配操作符),并在第二步中根据一些标准提取匹配的内容(或部分内容)。
如果您需要对模式中的部分进行分组(例如对该部分应用量化符),但又不想用匹配部分的内容来填充捕获缓冲区,您可以通过在组前加上问号和冒号的方式将组定义为"非捕获"或"害羞",如下面的例子3。
例3。
set m=##class(%Regex.Matcher).%New("((a|b).*?(?:de))(\1)","abcdeabcde")
w m.Match()返回1
w m.Group(1) 返回 abcde。
w m.Group(2) 返回 a
w m.Group(3) 返回 abcde。
w m.Group(4) 返回 <REGULAR EXPRESSION>zGroupGet+3^%Regex.Matcher.1。
4.5.2.替换
%Regex.Matcher也提供了立即替换匹配内容的方法。ReplaceAll()和ReplaceFirst()。
set m=##class(%Regex.Matcher).%New(".c.","abcdeabcde")
w m.ReplaceAll("xxxx") 返回 axxxxeaxxxxe。
w m.ReplaceFirst("xxxx") 返回 axxxxeabcde。
你也可以在替换字符串中引用组。如果我们在上一个例子的模式中添加一个组,我们可以通过在替换字符串中包含$1来引用它的内容。
set m=##class(%Regex.Matcher).%New(".(c).","abcdeabcde").","abcdeabcde")
w m.ReplaceFirst("xx$1xx") 返回 axxcxxeabcde。
使用$0在替换字符串中包含匹配的全部内容。
w m.ReplaceFirst("xx$0xx") 返回 axxbcdxxeabcde。
4.5.3.操作限制(OperationLimit)
在3.2节中,我们了解了评估正则表达式的两种方法(DFA和NFA)。Caché中使用的正则表达式引擎是一个非确定性的有限自动机(NFA)。因此,对给定输入字符串评估各种正则表达式的持续时间可能会有所不同。[1]
您可以使用%Regex.Matcher对象的OperationLimit属性来限制执行单元的数量(所谓的簇)。执行一个簇的确切持续时间取决于你的环境。通常情况下,一个簇的执行持续时间是非常少的几毫秒。默认情况下,OperationLimit被设置为0(无限制)。
4.6.真实世界的例子:从Perl到Caché的迁移。
本节介绍了从Perl迁移到Caché的过程中与正则表达式有关的部分。Perl 脚本实际上由几十个或多或少复杂的正则表达式组成,这些正则表达式被用来匹配和提取内容。
如果Caché中没有regex功能,迁移项目就会变成一项重大工作。然而,Caché中的regex功能是可用的,Perl脚本中的正则表达式几乎可以在Caché中使用,而不需要任何改变。
下面是Perl脚本的一部分。
将正则表达式从 Perl 移到 Caché 的唯一改动是 /i 修饰符(使 regex 不区分大小写)--这必须从 regex 的结尾移到开头。
在Perl中,捕获缓冲区的内容被复制到特殊的变量中(在上面的Perl代码中是$1和$2)。在Perl项目中,几乎所有的正则表达式都使用了这种机制。为了类似于这种机制,我们在Caché对象脚本中写了一个简单的包装方法。它使用 %Regex.Matcher 对文本字符串评估正则表达式,并将捕获缓冲区的内容以列表的形式返回($lb())。
由此产生的Caché对象脚本代码如下。
如果...RegexMatch(
tVCSFullName。
"(?i)[\\\/]([^\\^\/]+)[\\\/]ProjectDB[\\\/](.+)[\\\/]archives[\\\/]",
.tCaptureBufferList)
{
set tDomainPrefix=$zcvt($lg(tCaptureBufferList,1), "U")
set tDomain=$zcvt($lg(tCaptureBufferList,2), "U")
}
…
Classmethod RegexMatch(pString as %String, pRegex as %String, Output pCaptureBuffer="") {
#Dim tRetVal as %Boolean=0。
set m=##class(%Regex.Matcher).%New(pRegex,pString)
set m.Locate() {
set tRetVal=1
for i=1:1:m.GroupCount {
set pCaptureBuffer=pCaptureBuffer_$lb(m.Group(i))
}
}
quit tRetVal
}
5.参考资料
5.7.一般资料
概括信息和教程。
http://www.regular-expressions.info/engine.html
教程和实例。
http://www.sitepoint.com/demystifying-regex-with-practical-examples/
几种regex引擎的比较。
https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines
常用表达式cheat sheet。
https://www.cheatography.com/davechild/cheat-sheets/regular-expressions/pdf/
书。
Jeffrey E. F. Friedl:"掌握正则表达式"(见http://regex.info/book.html)
5.8.Caché在线文件
关于Caché中正则表达式的用法概述。 http://docs.intersystems.com/latest/csp/docbook/ DocBook.UI.Page.cls?KEY=GCOS_regexp。
$match()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fmatch
$locate()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_flocate
%Regex.Matcher的类引用。 http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Regex.Matcher
5.9.ICU
如上所述,InterSystems Caché使用ICU引擎。全面的文档可在网上查阅。
http://userguide.icu-project.org/strings/regexp
http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Metacharacters
http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Operators
http://userguide.icu-project.org/strings/regexp#TOC-Replacement-Text
http://userguide.icu-project.org/strings/regexp#TOC-Flag-Options
5.10.工具
有许多工具支持开发人员创建正则表达式--其中一些是免费的,另一些则有商业许可。我个人的选择是RegexBuddy(http://www.regexbuddy.com/)--它提供了一套全面的交互式和可视化功能,可以创建和测试不同风味的正则表达式。
thanks for sharing!