#InterSystems IRIS for Health

0 关注者 · 862 帖子

InterSystems IRIS for Health™ 是全球第一个也是唯一一个专门为医疗应用程序的快速开发而设计的数据平台,用于管理全世界最重要的数据。它包括强大的开箱即用的功能:事务处理和分析、可扩展的医疗保健数据模型、基于 FHIR 的解决方案开发、对医疗保健互操作性标准的支持等等。所有这些将使开发者能够快速实现价值并构建具有突破性的应用程序。了解更多信息

文章 姚 鑫 · 十二月 31, 2023 3m read

第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串

使用 J 链接类型传递标准计数字符串

iris-callin.h 头文件定义了计数字符串结构 IRIS_EXSTR,表示标准 IRIS 字符串。此结构包含一个字符元素数组(8 位、16Unicode32wchar t)和一个指定数组中元素数量的 int 值(最多字符串长度限制):

typedef struct {
   unsigned int   len;         /* length of string */
   union {
      Callin_char_t  *ch;      /* text of the 8-bit string */
      unsigned short *wch;     /* text of the 16-bit string */
      wchar_t        *lch;     /* text of the 32-bit string */
/* OR unsigned short *lch   if 32-bit characters are not enabled */
   } str;
} IRIS_EXSTR, *IRIS_EXSTRP;

C DatatypeInputIn/OutNotes
IRIS_EXSTR1j or j1J or J8 位国家字符的标准字符串
IRIS_EXSTR2j or n2J or N16 位 Unicode 字符的标准字符串
IRIS_EXSTR4j4J32 位字符的标准字符串 wchar_t 字符

IRIS_EXSTR 数据结构由 Callin API(低级 InterSystems 函数调用库)中的函数进行操作。有关详细信息,请参阅使用 Callin API 中的“Callin 函数参考”。尽管名称相似,Callin API$ZF标注接口是完全独立的产品)。

以下函数用于创建和销毁 IRIS_EXSTR 实例:

  • IrisExStrNew[W][H] — 为字符串分配请求的存储量,并使用长度和指向该结构的值字段的指针填充 IRIS_EXSTR 结构。
  • IrisExStrKill — 释放与 IRIS_EXSTR 字符串关联的存储。

这是一个 Callout 库,它使用所有三种链接类型来返回数字字符串:

使用 J 连接传递字符串

以下三个函数均生成一个随机整数,将其转换为最多包含 6 位数字的数字字符串,并使用 J 链接返回字符串 。

#define ZF_DLL   // Required when creating a Callout library.
#include <iris-cdzf.h>
#include <stdio.h>
#include <wchar.h>
#include <iris-callin.h>

int get_sample_L(IRIS_EXSTRP retval) {  // 8-bit characters
   Callin_char_t numstr[6];
   size_t len = 0;
   sprintf(numstr,"%d",(rand()%1000000));
   len = strlen(numstr);
   IRISEXSTRKILL(retval);
   if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
   memcpy(retval->str.ch,numstr,len);   // copy to retval->str.ch
   return ZF_SUCCESS;
}

int get_sample_LW(IRIS_EXSTRP retval) {  // 16-bit characters
   unsigned short numstr[6];
   size_t len = 0;
   swprintf(numstr,6,L"%d",(rand()%1000000));
   len = wcslen(numstr);
   IRISEXSTRKILL(retval);
   if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
   memcpy(retval->str.wch,numstr,(len*sizeof(unsigned short)));   // copy to retval->str.wch
   return ZF_SUCCESS;
}

int get_sample_LH(IRIS_EXSTRP retval) {  // 32-bit characters
   wchar_t numstr[6];
   size_t len = 0;
   swprintf(numstr,6,L"%d",(rand()%1000000));
   len = wcslen(numstr);
   IRISEXSTRKILL(retval);
   if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
   memcpy(retval->str.lch,numstr,(len*sizeof(wchar_t)));   // copy to retval->str.lch
   return ZF_SUCCESS;
}

ZFBEGIN
ZFENTRY("GetSampleL","1J",get_sample_L)
ZFENTRY("GetSampleLW","2J",get_sample_LW)
ZFENTRY("GetSampleLH","4J",get_sample_LH)
ZFEND

注意:始终终止 IRIS_EXSTRP 输入参数 在前面的示例中,始终调用 IRISEXSTRKILL(retval) 以从内存中删除输入参数。即使参数不用于输出,也应该始终这样做。如果不这样做可能会导致内存泄漏。

0
0 63
文章 Lilian Huang · 十二月 29, 2023 9m read

我们继续推出有关可供 HealthShare HealthConnect 和 InterSystems IRIS 用户使用的 FHIR 适配器工具的系列文章。

在前几篇文章中,我们介绍了小型应用程序,并在此基础上建立了我们的工作,并展示了安装 FHIR 适配器后在 IRIS 实例中部署的架构。在今天的文章中,我们将看到一个示例,说明如何执行最常见的 CRUD(创建 - 读取 - 更新 - 删除)操作之一,即读取操作,我们将通过恢复资源来完成此操作。

什么是资源?

FHIR 中的一个资源对应一种相关的临床信息,这种信息可以是病人(Patient)、对实验室的请求(ServiceRequest)或诊断(Condition)等。每种资源都定义了组成它的数据类型,以及对数据的限制和与其他类型资源的关系。每个资源都允许对其包含的信息进行扩展,从而满足 FHIR 80% 以外的需求(满足 80% 以上用户的需求)。

在本文的示例中,我们将使用最常见的资源 "Patient"。让我们来看看它的定义:

0
0 178
文章 Nicky Zhu · 十二月 27, 2023 7m read

在医疗行业中,处方是个非常重要的临床工作数据概念。因此,在考察用FHIR能如何构造我国所需医疗行业数据模型时,就会需要考虑如何用FHIR表达处方。

 

在2019年,FHIR的工作组已否认需要使用特定的资源来表达处方(不是药嘱)这个概念,见:

https://jira.hl7.org/browse/FHIR-24905

奇妙的是,IHE规范中却明确有处方(Prescription)的定义并需要引用药嘱(Medication Treatment Plan Item)。

https://www.ihe.net/uploadedFiles/Documents/Pharmacy/IHE_Pharmacy_Suppl_PRE.pdf?#page=12

 

FHIR官网指出这种复合式的request有三种表达方式:

• Shared requisition id

• "Based on" chain

• RequestOrchestration

https://build.fhir.org/request.html#compound

 

在FHIR实际应用中,则可以见到多种形态使用容器类资源表达处方并包含药嘱的表达形式,例如:

  1. 中国2023 FHIR Connectathon中使用消息Bundle来进行处方流转
0
0 180
文章 姚 鑫 · 十二月 26, 2023 4m read

第六章 创建Callout Library

Callout library 库是一个共享库,其中包含自定义Callout函数和允许 IRIS使用它们的启用代码。本章描述如何创建Callout库并在运行时访问它。

  • Callout library 简介-描述如何创建和访问Callout library
  • ZFEntry链接选项 - 提供了决定如何传递函数参数的链接选项的详细描述。
  • 兼容语言和编译器-描述如何使用C以外的语言创建Callout库。
  • Callout Library运行和运行函数-描述两个可选函数,可将其设置为在加载或卸载标注库时自动运行。
  • 故障排除和错误处理-列出了一些应该避免的编码实践,并描述了处理UNIX信号处理错误的特殊函数。

注:共享库和标注库的术语,共享库shared library是指动态链接的文件(Windows上的DLL文件或UNIX及相关操作系统上的SO文件)。Callout library是一个共享库,它包含到$ZF Callout接口的钩子,允许各种$ZF函数在运行时加载和访问它。

Callout库简介

ObjectScript代码访问Callout库有几种不同的方法,但一般原则是指定库名、函数名和任何必需的参数(请参阅“调用Callout库函数”)。例如,下面的代码调用一个简单的Callout库函数:

Calloutsimplecallout.dll调用AddInt函数

下面的ObjectScript代码在终端上执行。它加载一个名为simplecallout.dllCallout库,并调用一个名为AddInt的库函数,该函数将两个整数参数相加并返回总和。

   USER> set sum = $ZF(-3,"simplecallout.dll","AddInt",2,2)
   USER> write "The sum is ",sum,!
   The sum is 4

simplecallout.dll Callout 库并不比调用它的代码复杂多少。它包含所有 Callout 库所需的三个元素:

  1. 包含 iris-cdzf.h Callout 头文件时提供的标准代码。
  2. 具有正确指定参数的一个或多个函数。
  3. ZFEntry 表的宏代码,它生成 IRIS 将在加载库时用来定位 Callout 函数的机制。

以下是编译生成 simplecallout.dll Callout 库的代码:

simplecallout.dll 的标注代码

#define ZF_DLL  /* Required for all Callout code. */
#include <iris-cdzf.h>  /* Required for all Callout code. */

int AddTwoIntegers(int a, int b, int *outsum) {
   *outsum = a+b;   /* set value to be returned by the $ZF function call */
   return IRIS_SUCCESS;   /* set the exit status code */
}
ZFBEGIN
   ZFENTRY("AddInt","iiP",AddTwoIntegers)
ZFEND

  • 前两行必须定义 ZF_DLL 并包含 iris-cdzf.h 文件。这两行始终是必需的。

  • 接下来定义 AddTwoIntegers() 函数。它具有以下特点:

    • 两个输入参数,整数 ab,以及一个输出参数,整数指针 *outsum
    • 为输出参数 *outsum 赋值的语句。这将是调用 $ZF(-3) 返回的值。
    • return 语句不返回函数输出值。相反,它指定如果 $ZF 调用成功, IRIS 将接收的退出状态代码。如果该功能失败, IRIS 将收到系统生成的退出状态代码。
  • 最后三行是宏调用,生成 IRIS 用来定位 Callout 库函数的 ZFEntry 表。此示例只有一个条目,其中:

    • “AddInt”是用于标识 $ZF 调用中的函数的字符串。
    • “iiP”是一个字符串,指定两个输入值和输出值的数据类型。
    • AddTwoIntegersC 函数的入口点名称。

ZFEntry 表是允许通过 $ZF Callout Interface 加载和访问共享库的机制。 ZFENTRY 声明指定 C 函数和 ObjectScript $ZF 调用之间的接口。以下是该示例中接口的工作原理:

  • C 函数声明指定了三个参数:
   int AddTwoIntegers(int a, int b, int *outsum)

参数ab是输入,outsum将接收输出值。 AddTwoIntegers 的返回值是退出状态代码,而不是输出值。

  • ZFENTRY 宏定义了如何在 IRIS 中识别该函数以及如何传递参数:
   ZFENTRY("AddInt","iiP",AddTwoIntegers)

“AddInt”是库函数标识符,用于在 $ZF 调用中指定 C 函数 AddTwoIntegers。链接声明(“iiP”)将参数 ab 声明为链接类型 i(仅输入整数),并将 outsum 声明为链接类型 P(可用于输入和输出的整数指针)。

  • $ZF(-3) 函数调用指定库名称、库函数标识符和输入参数,并返回输出参数的值:
   set sum = $ZF(-3,"simplecallout.dll","AddInt",2,2)

参数 ab 由最后两个参数指定。第三个参数 outsum 不需要参数,因为它仅用于输出。当 $ZF(-3) 调用返回时,outsum 的值被分配给 sum

0
0 97
文章 姚 鑫 · 十二月 25, 2023 2m read

第五章 使用$ZF(-100)运行程序或系统命令 - 增加权限

增加%System_Callout:USE权限

$ZF(-100)需要%System_Callout:USE权限。如果安全设置高于最小值,则可能会禁用此特权。下面的过程描述了如何在%Developer角色中启用它:

%Developer角色中启用%System_Callout:USE

  1. 在“管理门户”中,选择“系统管理员>安全>角色”。
  2. Roles页面上,单击Names列中的%Developer
  3. Edit %Developer页面的General选项卡上,找到%System_Callout权限并单击Edit
  4. 在“编辑资源权限”对话框中,如果尚未选中,请选中“权限使用”复选框,然后单击“确定”。

%Developer角色总是在安装 IRIS时创建,但是管理员可能不希望所有用户都可以使用它。在某些情况下,可能需要为用户提供一个角色,使$ZF(-100)可用,但不授予任何其他特权。下面的过程创建了一个只授予%System_CallOut:USE权限的新角色:

在新角色中启用%System_Callout:USE

  1. 打开Management Portal,进入System Administration > Security > Roles

  2. Roles页面上,单击Create New Role按钮以调出Edit Roles页面。

  3. 填写姓名和描述:

    • Name: UseCallout
    • description:授予使用%System_CallOut资源的权限

    单击Save时,表单上将出现一个Add按钮。

  4. 单击Add按钮弹出资源滚动列表,从列表中选择%System_CallOut,然后单击Save。单击Edit Role表单上的Close

  5. Roles页面上,新的UseCallout角色现在位于角色定义列表中。

0
0 81
文章 姚 鑫 · 十二月 23, 2023 3m read

第三章 使用$ZF(-100)运行程序或系统命令

$ZF(-100)函数允许 IRIS 进程调用可执行程序或主机操作系统的命令。这是唯一可以在没有特殊的Callout共享库的情况下使用的$ZF函数。

  • $ZF(-100)的语法和功能概述。
  • 程序执行-程序可以选择异步运行或在操作系统外壳中运行。
  • 记录命令和重定向输出——可选设置可以记录命令或重定向I/O
  • 添加%System_Callout:USE特权—使用$ZF(-100)需要此特权。

注意:$ZF(-100)取代了已弃用的函数$ZF(-1)$ZF(-2),在所有情况下都应优先使用。

介绍

$ZF(-100)提供类似于命令行接口的功能,允许调用可执行程序或主机操作系统的命令。这个函数的语法是:

status = $ZF(-100, keywords, command, arguments )

第一个参数必须是字面量-100。其他三个参数指定以下信息:

  • Keywords - 包含指定各种选项的关键字的字符串。例如,字符串"/ASYNC/LOGCMD"指定程序应该异步运行,并将命令行写入日志文件。
  • Command - 指定要调用的程序或系统命令的字符串。如果未指定可执行文件的完整路径,则操作系统将应用标准搜索路径规则。
  • 参数 - 命令参数被指定为一系列以逗号分隔的表达式(如下面的示例所示)。

$ZF(-100)函数返回由操作系统和被调用的程序确定的退出状态码。

下面的示例将三个字符串传递给echo命令,然后显示状态码。这个例子没有使用关键字,所以关键字参数是一个空字符串。最后一个命令参数指定一个带引号的字符串(遵循标准的ObjectScript字符串规则):

USER>set status = $ZF(-100,"","echo","hello","world","""goodbye now""")
hello world "goodbye now"

USER>w status                                                          
0

程序执行

$ZF(-100)允许以同步或异步方式运行程序或命令,调用或不调用操作系统shell。默认情况下,同步执行而不调shell。可以通过在函数调用中指定可选关键字来覆盖默认执行。

以下关键字可用于控制程序的执行:

  • /ASYNC -指示程序应该异步运行,允许$ZF(-100)调用返回,而无需等待程序完成。
  • /SHELL -指示程序应该在操作系统SHELL中运行。

如上一节所述,如果不想使用这两个选项中的任何一个,可以为关键字参数指定一个空字符串。这个例子故意尝试列出不存在的文件,这样就会生成错误码1:

注:$ZF(-100)Linux中可用。

USER>set status = $ZF(-100,"", "ls","*.scala")
ls: cannot access *.scala: No such file or directory

USER>write status
1

如果我们异步运行相同的命令,则不会显示输出并且statusundefined,因为没有返回错误代码:

USER>kill status
USER>set status = $ZF(-100,"/ASYNC", "ls","*.scala")
USER>write status
WRITE status
^
<UNDEFINED> *status
``
0
0 104
文章 姚 鑫 · 十二月 22, 2023 3m read

第二章 $ZF Callout函数概述

IRIS $ZF系统功能是一套相关功能的容器。$ZF套件中的大多数函数都由函数调用的第一个参数标识,该参数将是一个负数,-100-3-6。例如,调用操作系统命令的函数具有$ZF(-100, <oscommand>)的形式,其中<oscommand>是包含要执行的命令的字符串。当讨论这个函数时,它将被称为$ZF(-100)。以同样的方式,其他函数将被称为$ZF(-3)$ZF(-6),只使用实际函数调用的第一个参数。也可以在不带负数参数的情况下调用$ZF()函数,在这种情况下,它调用名为iriszf的特殊Callout库中的函数。

注:Callout Libraries是动态链接的文件(WindowsDLL文件,UNIX及相关操作系统为SO文件)。Callout库是一个共享库,它包含到$ZF Callout接口的钩子,允许各种$ZF函数在运行时加载它并调用库函数。

$ZF函数集包括以下接口:

$ZF()函数(不带负数参数)

主要的$ZF()函数提供了对来自名为iriszf的特殊Callout库的函数的直接访问。当定义并编译了这个自定义库后,只需指定函数名和参数(例如,$ZF("myFunction",arg1)),就可以调用它的函数。与$ZF(-3)$ZF(-5)$ZF(-6)不同,不需要加载库或指定库标识符。

$ZF(-100)函数

$ZF(-100)函数用于运行shell命令和操作系统服务调用。它不用于访问Callout库,可以在没有任何先前设置的情况下调用。

$ZF(-3)函数

$ZF(-3)函数是一种加载Callout库并使用一条语句调用库函数的简单方法。库及其函数都是通过名称指定的,并且库保留在内存中,直到被对另一个库的调用所替换。

$ZF(-4)函数

$ZF(-4)函数为$ZF(-5)$ZF(-6)提供了一组服务。它是由前两个参数($ZF(-4,1)$ZF(-4,8))标识的八个效用函数的容器。$ZF(-5)函数接口使用$ZF(-4,1)$ZF(-4,3)函数,$ZF(-6)函数接口使用$ZF(-4,5)$ZF(-4,8)函数。

$ZF(-5)函数接口

$ZF(-5)函数及其实用函数允许高效地处理多个库。库及其函数都由系统定义的ID值标识。多个库可以同时位于虚拟内存中。以下$ZF(-4)函数用于加载和卸载库,并获取库和函数ID值:

  • $ZF(-4,1)加载由名称指定的库,并返回一个库ID
  • $ZF(-4,2)卸载一个库。
  • $ZF(-4,3)返回指定库ID和函数名的函数ID

$ZF(-6)函数接口

$ZF(-6)函数及其实用函数提供了一种编写不需要硬编码库名称的Callout应用程序的方法。相反,实际的库文件名包含在一个单独的索引表中,其中每个库都与一个唯一的用户定义的索引号相关联。一旦定义了索引表, IRIS实例中的所有进程都可以使用它。Callout应用程序通过索引号识别一个库,并通过读取索引表来加载它。多个库可以同时在内存中。以下函数用于管理索引和加载或卸载库:

  • $ZF(-6)调用一个库函数,如果库不在内存中,则加载该库。
  • $ZF(-4,4)卸载一个库。
  • $ZF(-4,5)$ZF(-4,6)用于创建和维护系统索引表,该索引表可以被 IRIS实例中的所有进程访问。
  • $ZF(-4,7)$ZF(-4,8)用于创建和维护进程索引表,该索引表可用于覆盖单个进程中的系统索引。
0
0 66
文章 姚 鑫 · 十二月 20, 2023 2m read

第四十一章 XML 映射参数摘要

TopicParameters
启用 XML 映射。XMLENABLED 类参数
将属性映射到元素或属性。XMLPROJECTION property parameter ("NONE", "ATTRIBUTE", "XMLATTRIBUTE", "CONTENT", "ELEMENT", or "WRAPPED")
XMLSUMMARY class parameter
XMLDEFAULTREFERENCE class parameter ("SUMMARY", "COMPLETE", "ID", "OID", or "GUID")
XMLREFERENCE property parameter ("SUMMARY", "COMPLETE", "ID", "OID", or "GUID")
XML 元素名称和属性名称。XMLNAME class parameter
XMLNAME property parameter
XMLITEMNAME property parameter
XMLKEYNAME property parameter
默认值基于 XML 类型名称。
XML 类型XMLTYPE class parameter
XMLTYPE property parameter
XSDTYPE class parameter
命名空间NAMESPACE class parameter
ELEMENTQUALIFIED class parameter (0 or 1)
You can override this upon export.
ELEMENTQUALIFIED property parameter (0 or 1)
You can override this upon export.
ATTRIBUTEQUALIFIED class parameter (0 or 1)
You can override this upon export.
XMLREF property parameter (0 or 1)
REFNAMESPACE property parameter
XMLPREFIX class parameter
空字符串和空值XMLUSEEMPTYELEMENT class parameter (0 or 1)
XMLIGNORENULL class parameter (0, 1, "INPUTONLY", or "RUNTIME")
XMLNIL class parameter (0 or 1)
XMLNIL property parameter (0 or 1)
如果 XMLIGNORENULL“RUNTIME”,则可以在导出或导入时覆盖 XMLNIL
XMLNILNOOBJECT class parameter (0 or 1)
XMLNILNOOBJECT property parameter (0 or 1)
转义 XML 特殊字符。CONTENT parameter ("STRING", "ESCAPE", "ESCAPE-C14N", or "MIXED")
ESCAPE parameter ("XML" or "HTML")
时区XMLTIMEZONE property parameter ("UTC" or "IGNORE")
XML 类型详细信息,包括限制XMLTYPECONSTRAINT property parameter ("EXPLICIT", "CHOICE", or "SUBSTITUTIONGROUP")
XMLINCLUDEINGROUP class parameter (0 or 1)
XMLCHOICELIST property parameter
XMLINHERITANCE class parameter ("left" or "right")
许多 IRIS 数据类型属性参数
使用属性进行输入、输出或两者XMLIO property parameter ("INOUT", "IN", "OUT", or "CALC")
控制可用的 XML 文档格式XMLFORMAT class parameter ("LITERAL", "ENCODED", or null for both formats)
多个元素具有相同的名称XMLSEQUENCE class parameter (0 or 1)
流属性XMLSTREAMMODE property parameter ("BLOCK" or "LINE")
意外的元素和属性。XMLIGNOREINVALIDTAG class parameter (0 or 1)
XMLIGNOREINVALIDATTRIBUTE class parameter (0 or 1)
Namespace prefixesXMLPREFIX class parameter
指定模式中的模式限制XMLPATTERN property parameter

还有一个附加参数:XMLELMENTREF 属性参数,该参数已弃用(由 XMLREF 替代),但将无限期支持。

0
0 64
文章 姚 鑫 · 十二月 19, 2023 2m read

第四十章 其他特殊主题 - 处理导入时意外的元素和属性

处理导入时意外的元素和属性

由于源 XML 文档可能包含意外的元素和属性,因此支持 XML 的类提供两个参数来指定导入此类文档时如何反应。例如,考虑以下类定义:

Class GXML.TestImportParms.Person Extends (%Persistent,%XML.Adaptor) 
{

Property Name As %Name [ Required ];
Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h") [ Required ];
}

另请考虑以下 XML 文档:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <Person employeeID="450">
      <Name>Dillard, Daniel</Name>
      <DOB>1962-09-18</DOB>
      <UserID>fr0078</UserID>
      <Address>
         <Street>810 Main Street</Street>
         <City>Reston</City>
         <State>NJ</State>
         <Zip>02641</Zip>
      </Address>
   </Person>
</Root>

employeeID 属性和 <Address> 元素与类中的属性不对应,因此是意外的。

要指定如何处理意外的属性和元素,请使用支持 XML 的类的以下参数:

XMLIGNOREINVALIDATTRIBUTE

控制如何处理意外属性。如果此参数为 1(默认值),则忽略此类属性。如果为0,则视为错误,导入失败。

XMLIGNOREINVALIDTAG

控制如何处理意外元素。如果此参数为 1,则忽略此类元素。如果为 0(默认值),则它们被视为错误,并且导入失败。

这些参数仅影响导入。

注意:xmlns 属性、数组键名称属性和架构实例 (xsi) 属性始终被忽略。此外,如果 XMLSEQUENCE1,则忽略 XMLIGNOREINVALIDTAG 参数。

0
0 63
文章 姚 鑫 · 十二月 18, 2023 2m read

第三十九章 其他特殊主题 - 映射 IRIS ID 以供导出

当在顶层映射 IRIS 对象(而不是作为另一个对象的属性)时,其内部 IDOID 和全局唯一 ID 不能用作对象属性,因此不会映射这些 ID。但是,在某些情况下,可能希望使用对象 ID 作为唯一标识符。然后,例如,可以在更新存储的对象之前将传入(已更改)的对象与相应的存储对象进行匹配。

IRIS XML 支持提供了多个帮助程序类,可用于将 IRIS 对象标识符投影到 XML 文档: %XML.Id 中(对于内部 ID)、%XML.Oid(对于 ``)和 %XML.GUID(用于全局唯一 ID)。

要使用这些类,请向支持 XML 的类添加一个特殊属性,该属性的用途是包含要导出的 ID。该属性的类型必须为 %XML.Id%XML.Oid%XML.GUID。确保该属性已映射,并将其标记为瞬态,以便它不包含在该类的 SQL 映射中。

当导出到 XML 时,将支持 XML 的类的对象带入内存。当对象位于内存中时,添加的特殊属性将从 IRIS 内部存储中检索请求的 ID 并包含该值(以便您可以导出它)。

例如,考虑以下类:

Class MyApp4.Obj.Person4 Extends (%Persistent,%Populate,%XML.Adaptor) 
{

Property IdForExport As %XML.Id 
(XMLNAME="IRISID", XMLPROJECTION="ELEMENT") [Private, Transient];

Property Name As %Name;

Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h");

}

在此类中,特殊属性是 IdForExport。该属性专门用 IRISIDXML 元素名称进行映射。

该类的示例输出如下:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <Person>
      <IRISID>1</IRISID>
      <Name>Marks,Jules F.</Name>
      <DOB>1989-04-02</DOB>
   </Person>
   <Person>
      <IRISID>2</IRISID>
      <Name>Palmer,Angelo O.</Name>
      <DOB>1937-11-15</DOB>
   </Person>
...

控制导出时的命名空间前缀

当为对象生成 XML 输出时,系统会根据需要生成命名空间前缀,但可以根据需要指定前缀。为此,请在支持 XML 的对象的类定义中设置以下参数:

XMLPREFIX

指定与此类的命名空间关联的前缀。

0
0 78
文章 Tete Zhang · 十二月 18, 2023 2m read

最近在多家现场都遇到了备机长时间宕机导致镜像日志写满磁盘的问题。在这里我将对这个问题发生的原因、发生后的处理、和如何预防这类问题发生进行一些讨论。

问题的发生一般始于一些原因导致的主机(如,01)宕机,进而触发镜像的主备切换。切换后备机(如,02)成为主机,并无缝接管业务。由于业务不受影响,如果不注意监控环境的话,很可能现场技术人员长时间都注意不到镜像的备机(01)是宕机状态。

备机长时间宕机会导致如下问题:

1. 这种情况下如果主机(02)再次遇到问题宕机,镜像将无法发挥其高可用性,无法保持业务稳定运行。

2. 主机(02)产生的镜像日志将无法同步到备机(01)。未同步的日志将一直被保存在主机(02)上不被删除。长此以往镜像日志磁盘将被写满,同样导致主机(02)宕机。

问题发现时切记不要手动从文件夹直接删除主机(02)上的镜像日志。未同步的日志一旦手动删除,镜像将无法自动同步,需要重做主备镜像。

问题发现时如果主机(02)还未宕机,此时尝试解决备机(01)问题,启动备机(01),等待镜像自动同步即可。同步完成之后镜像日志将可以被定时任务定时清除。如果遇到较为复杂的情况,现场请第一时间联系您的软件供应商,软件供应商将协同系联软件全球响应中心一起来解决您遇到的具体问题。

为了避免以上的问题发生,现场运维需要对镜像的状态和磁盘的状态配置监控。

0
0 151
文章 姚 鑫 · 十二月 17, 2023 2m read

第三十八章 其他特殊主题

类和属性参数

  • XMLNAME
  • XMLSEQUENCE
  • XMLUNSWIZZLE
  • XMLPREFIX
  • XMLIGNOREINVALIDTAG
  • XMLIGNOREINVALIDATTRIBUTE

控制元素的关闭

XML 中,仅包含属性的元素可以用以下任一方式表示:

<tag attribute="value" attribute="value" attribute="value"></tag>
<tag attribute="value" attribute="value" attribute="value"/>

IRIS 认为这些形式是等效的。当使用 %XML.Writer 导出对象时,可以控制关闭形式,但不能通过修改 XML 投影本身来控制。

处理具有多个同名标签的文档

XML 中的给定元素可以包含多个具有相同名称的元素;这些元素通过它们的顺序彼此区分。例如,以下是一个合法的 XML 文档:

<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <Person>
      <Name>Able, Andrew</Name>
      <DOB>1977-10-06</DOB>
      <Address>
         <Street>6218 Clinton Drive</Street>
         <City>Reston</City>
         <State>TN</State>
         <Zip>87639</Zip>
      </Address>
      <Address>
         <Street>110 High Street</Street>
         <City>Zanesville</City>
         <State>OR</State>
         <Zip>80719</Zip>
      </Address>
   </Person>
</Root>

将这样的文档映射到 IRIS 类有点棘手,因为每个类属性都必须有唯一的名称。

要将此类文档映射到 IRIS 类,请执行以下操作:

  • 根据需要设置 XMLNAME 属性参数,以将不同的类属性映射到相同的 XML 名称。
  • XMLSEQUENCE 类参数设置为 1。作为预防措施,这可确保映射遵循类定义中列出的属性顺序。
  • 确保类定义中列出的属性的顺序与 XML 文档中的顺序相同。

例如,考虑以下类定义:

Class GXML.TestSequence.Person Extends (%Persistent, %XML.Adaptor)
{

Property Name As %Name [ Required ];
Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h") [ Required ];
Property HomeAddress As GXML.TestSequence.Address(XMLNAME = "Address");
Property WorkAddress As GXML.TestSequence.Address(XMLNAME = "Address");

/// If the XMLSEQUENCE = 1, then the order of the XML elements must match the 
/// order of the class properties.  This allows us to deal with XML where the 
/// same field appears multiple times and is distinguished by the order.
Parameter XMLSEQUENCE = 1;

}

该类定义映射到前面显示的 XML 文档。

注意:如果 XMLSEQUENCE1,则忽略 XMLIGNOREINVALIDTAG 参数。

导出后控制 Unswizzling

当使用 IRIS XML 工具导出支持 XML 的持久对象时,系统会像往常一样自动将所有需要的信息混合到内存中;此信息包括对象值属性。导出对象后,IRIS 会取消混合任何对象列表,但不会(默认情况下)取消混合单个对象引用。对于大型对象,这可能会导致 <STORE> 错误。

要使任何单个对象引用在这种情况下不被混淆,请在支持 XML 的类中设置 XMLUNSWIZZLE 参数,如下所示:

Parameter XMLUNSWIZZLE = 1;

该参数的默认值为 0

0
0 69
文章 姚 鑫 · 十二月 16, 2023 4m read

第三十七章 XML 模式的高级选项 - 超类如何表示为类型

超类如何表示为类型

如果需要 XML 架构来显示特定的类型层次结构,则需要了解映射如何解释 IRIS 类层次结构。

类层次结构代表了有意义的数据组织等。该层次结构尽可能地反映在相应的 XML 类型定义中。

例如,假设有以下类:

  • 名为 Base 的类,定义了三个公共属性(Property1Property2Property3)。
  • 名为 Addition1 的类,它扩展 Baseand 并定义一个附加公共属性 (Addition1)。
  • 名为 Addition2 的类,它扩展 Addition1 并定义一个附加公共属性 (Addition2)。

Addition2 的架构应包含什么?它必须代表所有五个属性。另外,因为这些类都是用户定义的,所以 Addition2 的架构应该显示类层次结构的详细信息;相反,如果 BaseIRIS 类库扩展一个类,而 IRIS 类库又从该库扩展其他类,那么这些细节就不那么有趣了。

相应地,Addition2XML 模式默认如下所示:

<s:complexType name="Addition2">
    <s:complexContent>
        <s:extension base="Addition1">
            <s:sequence>
                <s:element name="Addition2" type="s:decimal" minOccurs="0" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="Addition1">
    <s:complexContent>
        <s:extension base="Base">
            <s:sequence>
                <s:element name="Addition1" type="s:string" minOccurs="0" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="Base">
    <s:sequence>
        <s:element name="Property1" type="s:string" minOccurs="0" />
        <s:element name="Property2" type="s:decimal" minOccurs="0" />
        <s:element name="Property3" type="s:date" minOccurs="0" />
    </s:sequence>
</s:complexType>

由于 XML 类型定义不支持多重继承,因此 IRIS XML 支持做出了某些简化假设。对于扩展多个超类的类,假定该类的类型是第一个列出的超类。一个例子如下所示。考虑以下三个类定义。 AddressPart1 包含一个属性:

Class GXML.Writer.ShowMultiple.AddressPart1 Extends %XML.Adaptor
{
Property Street As %String [ Required ];
}

AddressPart2 类包含另一个属性:

Class GXML.Writer.ShowMultiple.AddressPart2 Extends %XML.Adaptor
{
Property City As %String [ Required ];
}

最后,Address 继承了这两个类(AddressPart1 作为第一个超类)并添加了更多属性:

Class GXML.Writer.ShowMultiple.Address Extends 
(GXML.Writers.ShowMultiple.AddressPart1, 
GXML.Writers.ShowMultiple.AddressPart2)
{
Property State As %String(MAXLEN = 2, PATTERN = "2u") [ Required ];
Property Zip As %String(MAXLEN = 10, PATTERN = "5n.1(1""-""4n)") [ Required ];
}

AddressXML 架构如下:

<s:complexType name="Address">
    <s:complexContent>
        <s:extension base="AddressPart1">
            <s:sequence>
                <s:element name="City" type="s:string" />
                <s:element name="State" type="s:string" />
                <s:element name="Zip" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="AddressPart1">
    <s:sequence>
        <s:element name="Street" type="s:string" />
    </s:sequence>
</s:complexType>

请注意以下事项:

  • 第一个列出的超类 AddressPart1 由相应的 XML 类型表示,其中包含所有预期的详细信息。
  • 除了 AddressPart1 类型包含的属性之外,所有剩余属性都分配给 Address 类型。一旦 AddressPart1 类被映射,这是这些属性的唯一可能的表示。
  • AddressPart1AddressPart2 都是 %XML.Adaptor 的子类,但没有公开 %XML.Adaptor 的结构。重点是自定义类,这是合适的。

以下规则控制当查看给定类的架构时如何处理超类:

  • 如果超类继承自 %XML.Adaptor,则它由 XML 类型表示,该类型表示此类的所有映射属性。短类名被视为属性的 XML 类型。如果该类为 XMLTYPE 参数指定了一个值,则该值将用作类型名称。
  • 如果超类不是从%XML.Adaptor 继承,则它不由 XML 类型表示。如果它有任何属性,它们将被分配给继承类(正在查看其架构的类)。
  • 如果给定的类继承自多个超类,则会为第一个超类创建 XML 类型(如果适用;请参阅前面的规则)。不属于第一个超类的所有属性都将分配给继承类,如前面的示例所示。

基于多个支持 XML 的超类的类

在某些情况下,给定的类可能基于多个支持 XML 的超类。在这种情况下,相应的 XML 模式会考虑这些类的列出顺序。例如,考虑以下类,它继承自两个支持 XML 的超类:

Class Test.Default Extends (Test.Superclass1, Test.Superclass2)
{
///additional class members ...
}

此类的 XML 架构在从 Test.Superclass2 派生的 XML 类型之前列出了从最左侧的类 Test.Superclass1 派生的 XML 类型。当为此类的对象生成 XML 输出时,也会发生相同的顺序。

如果希望从右到左确定 XML 架构(和输出),请将 XMLINHERITANCE 参数指定为“right”。例如:

Class Test.Default Extends (Test.Superclass1, Test.Superclass2)
{
Parameter XMLINHERITANCE = "right";

///additional class members ...
}
0
0 55
文章 姚 鑫 · 十二月 16, 2023 2m read

第三十六章 XML 模式的高级选项 - 创建子类型的替换组

创建子类型的替换组

XML 模式规范还允许定义替换组,这可以是创建选择的替代方法。语法有些不同。无需制作明确的集中类型列表,而是注释可能的替代项,如下所示:

<s:complexType name="Example3">
    <s:sequence>
        <s:element ref="Person" minOccurs="0" />
    </s:sequence>
</s:complexType>
<s:element name="Person" type="Person"/>
<s:element name="Employee" type="Employee" substitutionGroup="Person"/>
<s:complexType name="Employee">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="EmployeeID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="Person">
    <s:sequence>
        <s:element name="Name" type="s:string" />
        <s:element name="DOB" type="s:date" />
    </s:sequence>
</s:complexType>
<s:element name="Patient" type="Patient" substitutionGroup="Person"/>
<s:complexType name="Patient">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="PatientID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>

为了创建这个模式,我们使用以下类:

Class UsingSubclasses.Example3 Extends (%Persistent, %XML.Adaptor)
{
Property Person As UsingSubclasses.Person
(XMLTYPECONSTRAINT = "SUBSTITUTIONGROUP");
}

将子类限制在替换组中

对于给定属性,如果将 XMLTYPECONSTRAINT 属性参数设置为等于“SUBSTITUTIONGROUP”,则该组将自动包含该属性类型的所有子类,如前面的示例所示。可以使用 XMLINCLUDEINGROUP 参数来标记给定的子类,以便它不包含在替换组中。例如,假设我们添加 Person 类的另一个子类:

Class UsingSubclasses.Other Extends UsingSubclasses.Person
{

Parameter XMLINCLUDEINGROUP = 0;

Property OtherID As %String [ Required ];
}

在这种情况下,该类不包含在替换组中。并且因为已经以这种方式显式标记了此类,所以它根本不包含在架构中。

0
0 80
文章 姚 鑫 · 十二月 14, 2023 2m read

第三十五章 XML 模式的高级选项 - 创建子类型的选择列表

创建子类型的选择列表

根据 XML Schema 规范,复杂类型可以由类型(特别是相关类型)的选择列表组成。假设我们希望架构允许使用 <Person>, <Patient>, or <Employee> 元素,而不是 <Person> 元素。要定义这样的架构,我们会将 Person 属性的 XMLTYPECONSTRAINT 属性参数设置为等于“CHOICE”,如下所示:

Class UsingSubclasses.Example2 Extends (%Persistent, %XML.Adaptor)
{
Property Person As UsingSubclasses.Person(XMLTYPECONSTRAINT = "CHOICE");
}

默认情况下,选择列表由 Person 类的所有子类组成。示例 2 的架构如下:

<s:complexType name="Example2">
    <s:sequence>
        <s:choice minOccurs="0">
            <s:element name="Employee" type="Employee" />
            <s:element name="Patient" type="Patient" />
            <s:element name="Person" type="Person" />
        </s:choice>
    </s:sequence>
</s:complexType>
<s:complexType name="Employee">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="EmployeeID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="Person">
    <s:sequence>
        <s:element name="Name" type="s:string" />
        <s:element name="DOB" type="s:date" />
    </s:sequence>
</s:complexType>
<s:complexType name="Patient">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="PatientID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>

与前面的示例相反,Example2 的类型是一个由 PersonPatientEmployee 组成的选择列表。后三种类型的定义方式与前面的示例相同。

XMLINCLUDEINGROUP=0 的选择列表示例

假设我们添加 Person 类的另一个子类,并通过将 XMLINCLUDEINGROUP 设置为 0 来限制它:

Class UsingSubclasses.Other Extends UsingSubclasses.Person
{
Parameter XMLINCLUDEINGROUP = 0;
}

在这种情况下,此类不包含在选择列表中,也不包含在架构中。

0
0 79
文章 姚 鑫 · 十二月 13, 2023 2m read

第三十四章 XML 模式的高级选项

类和属性参数

  • XMLTYPECONSTRAINT

  • XMLINCLUDEINLIST

  • XMLINHERITANCE

自动创建子类的类型

当为一个类定义 XML 投影时,它的所有子类都会自动映射到单独的类型,所有这些类型都使用超类作为基类型。这意味着无论何时使用超类型,都可以使用其中一种子类型。还可以使用子类型在 XML 模式中定义选择列表或替换组。

请注意,可以为抽象类定义 XML 映射;该类在任何派生类模式中都显示为基类型,尽管它是抽象的,无法实例化。

考虑一个例子。我们从一个简单的 Person 类开始:

Class UsingSubclasses.Person Extends (%Persistent, %XML.Adaptor)
{
Property Name As %String [Required];
Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h") [Required];
}

假设我们有两个直接基于 Person 类的类。首先是 Patient 类:

Class UsingSubclasses.Patient Extends UsingSubclasses.Person
{
Property PatientID As %String [Required];
}

接下来是 Employee 类:

Class UsingSubclasses.Employee Extends UsingSubclasses.Person
{
Property EmployeeID As %String [Required];
}

最后,考虑一个使用 Person 作为属性的类:

Class UsingSubclasses.Example1 Extends (%Persistent, %XML.Adaptor)
{
Property Person As UsingSubclasses.Person;
}

当生成 Example1 类的架构时,结果如下:

<s:complexType name="Example1">
    <s:sequence>
        <s:element name="Person" type="Person" minOccurs="0" />
    </s:sequence>
</s:complexType>
<s:complexType name="Employee">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="EmployeeID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>
<s:complexType name="Person">
    <s:sequence>
        <s:element name="Name" type="s:string" />
        <s:element name="DOB" type="s:date" />
    </s:sequence>
</s:complexType>
<s:complexType name="Patient">
    <s:complexContent>
        <s:extension base="Person">
            <s:sequence>
                <s:element name="PatientID" type="s:string" />
            </s:sequence>
        </s:extension>
    </s:complexContent>
</s:complexType>

请注意以下事项:

  • Example1 的类型是 Person
  • Employee类型和Patient类型均已定义;这是默认设置。 (作为参考,这对应于将 XMLTYPECONSTRAINT 属性参数设置为等于“EXPLICIT”。)
  • 根据 XML 架构规范,前面的架构意味着无论何时包含 <Person> 元素,都可以包含 <Employee> 元素或 <Patient> 元素。
0
0 74
文章 姚 鑫 · 十二月 12, 2023 3m read

第三十三章 控制到 XML 模式的映射 - 其他支持 XML 的类到 XML 类型的映射

其他支持 XML 的类到 XML 类型的映射

对于支持 XML 的类或基于支持 XML 的类的属性,XML 类型按如下方式确定: 如果该类具有 XMLTYPE 参数的值,则该值将用作类型名称。否则,短类名将被视为 XML 类型名。

例如,考虑以下类定义:

Class GXML.PersonWithAddress Extends (%Persistent, %XML.Adaptor) 
{
Parameter XMLTYPE = "PersonType";

Property Name As %Name;

Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h");

Property HomeAddress As GXML.Address;

}

对于此类的实例,XML 类型是 PersonType,它取自 XMLTYPE 参数。

假设 GXML.Address 类不包含 XMLTYPE 参数。在本例中,对于 <HomeAddress> 元素,XML 类型是 Address,它是短类名。

指定类型的命名空间

XML 类型分配给命名空间,如下所示:

  1. 如果相应的类定义定义了 XSDTYPE 类参数,则该类型位于以下 W3 命名空间中:
http://www.w3.org/2001/XMLSchema

只能在数据类型类中指定 XSDTYPE

注意:数据类型类不继承 XSDTYPE 类参数。也就是说,如果对现有数据类型类进行子类化,并且该类应映射到 XSD 类型之一,则必须指定此参数。

  1. 如果类定义未定义 XSDTYPE 但定义了 NAMESPACE,则该类型位于 NAMESPACE 指定的命名空间中。
  2. 否则该类型不在任何命名空间中。

但是,可以在生成架构时指定命名空间。请参阅使用 XML 工具中的从类生成 XML 模式。

要查看分配类型的命名空间,必须使用 %XML.Schema 并使用 %XML.Write。有关详细信息,请参阅使用 XML 工具中的从类生成 XML 模式。

抑制类型 QName 的命名空间前缀

如使用 XML 工具中所述,当使用 %XML.Writer 生成输出时,可以包含 XML 类型属性;为此,请将 writerOutputTypeAttribute 属性指定为 1

默认情况下,类型属性被写为 QName(限定名称),它指示类型的名称以及该类型所属的命名空间。例如:

<TeamA xmlns:s01="http://mynamespace" xsi:type="s01:TeamA">

可以定义相应的 IRIS 类定义,以便抑制命名空间前缀。例如:

<TeamB xsi:type="TeamB">

例如,考虑以下类定义:

Class STP.TeamA Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter NAMESPACE = "http://mynamespace";

Property Member1 as %String;

Property Member2 as %String;

}

STP.TeamB(未显示)具有相同的定义,但还将 SUPPRESSTYPEPREFIX 指定为 1

这两个类都用作第三个类中的属性:

Class STP.Container Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter NAMESPACE = "http://mynamespace";

Property TeamA As STP.TeamA;

Property TeamB As STP.TeamB;

}

当我们为 STP.Container 实例生成输出(并且启用 XML 类型属性的输出)时,我们会看到如下内容:

<?xml version="1.0" encoding="UTF-8"?>
<Container xmlns="http://mynamespace" 
           xmlns:s="http://www.w3.org/2001/XMLSchema" 
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <TeamA xmlns:s01="http://mynamespace" xsi:type="s01:TeamA">
    <Member1 xsi:type="s:string">Jack O'Neill</Member1>
    <Member2 xsi:type="s:string">Samantha Carter</Member2>
  </TeamA>
  <TeamB xsi:type="TeamB">
    <Member1 xsi:type="s:string">Jasper O'Nelson</Member1>
    <Member2 xsi:type="s:string">Sandra Chartres</Member2>
  </TeamB>
</Container>

请注意,<TeamA> 元素包含 xsi:type 属性,该属性等于“s01:TeamA”。该元素中的命名空间声明表明 s01 前缀引用命名空间 http://mynamespace

但是<TeamB> 元素在 xsi:type 属性中不包含前缀。

注意:SUPPRESSTYPEPREFIX 不会影响 XML 类型所属的命名空间。它只是抑制类型前缀的写入。

0
0 88
文章 姚 鑫 · 十二月 11, 2023 1m read

第三十二章 控制到 XML 模式的映射 - %ListOfObjects

本部分显示了从支持 XML 的类生成的 XML架构的一部分,该类包含定义为 %ListOfObjects 的属性。例如,考虑以下属性定义:

Property PropName As list Of %Integer(XMLITEMNAME = "MyXmlItemName");

如果此属性位于名为 Test.DemoObjList1 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" 
targetNamespace="mytypes">
  <complexType name="DemoObjList1">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNameRegisteredObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNameRegisteredObject">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:RegisteredObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
...
</schema>

类型的命名规则请参见类名列表。注意集合项类型是RegisteredObject,没有定义:

<element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:RegisteredObject" xmlns:s01="mytypes"/>

因此,该架构无法使用。

%ArrayOfObjects

本部分显示了从支持 XML 的类生成的 XML 架构的一部分,该类包含定义为 %ArrayOfObjects的属性。例如,考虑以下属性定义:

Property PropName As %ArrayOfObjects(XMLITEMNAME = "MyXmlItemName", XMLKEYNAME = "MyXmlKeyName");

如果此属性位于名为 Test.DemoObjArray1 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="mytypes">
  <complexType name="DemoObjArray1">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNamePairOfMyXmlKeyNameRegisteredObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNamePairOfMyXmlKeyNameRegisteredObject">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:PairOfMyXmlKeyNameRegisteredObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="PairOfMyXmlKeyNameRegisteredObject">
    <complexContent>
      <extension base="s01:RegisteredObject" xmlns:s01="mytypes">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </complexContent>
  </complexType>
...
</schema>

类型的命名规则请参见类名列表。注意,集合项类型是基于RegisteredObject的,没有定义:

  <complexType name="PairOfMyXmlKeyNameRegisteredObject">
    <complexContent>
      <extension base="s01:RegisteredObject" xmlns:s01="mytypes">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </complexContent>
  </complexType>

因此,该架构无法使用。

0
0 48
文章 姚 鑫 · 十二月 10, 2023 2m read

第三十一章 控制到 XML 模式的映射 - %ListOfDataTypes

%ListOfDataTypes

本部分显示从支持 XML 的类生成的 XML 架构的一部分,该类包含中定义为%ListOfDataTypes 的属性。例如,考虑以下属性定义:

Property PropName As %ListOfDataTypes(XMLITEMNAME = "MyXmlItemName");

如果此属性位于名为 Test.DemoList 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="mytypes">
  <complexType name="DemoList">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNameString" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNameString">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s:string"/>
    </sequence>
  </complexType>
</schema>

类型的命名规则请参见类名列表。请注意,集合项(本示例中的 PropNameItem)基于 XSD 字符串类型:

<element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s:string"/>

%ArrayOfDataTypes

本部分显示从支持 XML 的类生成的 XML 架构的一部分,该类包含定义为 %ArrayOfDataTypes 的属性。例如,考虑以下属性定义:

Property PropName As %ArrayOfDataTypes(XMLITEMNAME = "MyXmlItemName", XMLKEYNAME = "MyXmlKeyName"); 

如果此属性位于名为 Test.DemoArray 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" 
targetNamespace="mytypes">
  <complexType name="DemoArray">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNamePairOfMyXmlKeyNameString" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNamePairOfMyXmlKeyNameString">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:PairOfMyXmlKeyNameString" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="PairOfMyXmlKeyNameString">
    <simpleContent>
      <extension base="s:string">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </simpleContent>
  </complexType>
...
</schema>

有关类型名称的规则,请参阅类名数组。请注意,集合项(本例中为 PairOfMyXmlKeyNameString)基于 XSD 字符串类型:

  <complexType name="PairOfMyXmlKeyNameString">
    <simpleContent>
      <extension base="s:string">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </simpleContent>
  </complexType>

即,假设集合项是字符串。

0
0 76
文章 姚 鑫 · 十二月 9, 2023 2m read

第三十章 控制到 XML 模式的映射 - Array of Classname

Array of Classname

本部分显示了从启用 XML 的类生成的XML 架构的一部分,此时该类包含定义为类名数组的属性。例如,考虑以下属性定义:

Property PropName As array Of %Integer(XMLITEMNAME = "MyXmlItemName", XMLKEYNAME = "MyXmlKeyName");

如果此属性位于名为 Test.DemoArray1 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="mytypes">
  <complexType name="DemoArray1">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNamePairOfMyXmlKeyNameLong" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNamePairOfMyXmlKeyNameLong">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:PairOfMyXmlKeyNameLong" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="PairOfMyXmlKeyNameLong">
    <simpleContent>
      <extension base="s:long">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </simpleContent>
  </complexType>
...
</schema>

以下规则管理类型的名称:

  • 对于 PropName 属性,相应的类型被命名为 ArrayOfXMLItemNamePairOfXMLKeyNameType,其中:

    • XMLItemName 是集合中项目的名称,如控制数组类型属性的元素和属性名称中所述。对于数据类型属性,默认项目名称是在属性名称末尾附加 Item 的名称。 (对于对象属性,默认项名称是短类名称。)
    • XMLKeyName 是集合的键名称,如控制数组类型属性的元素和属性名称中所述。默认为属性名称,末尾连接有 Key
    • Type 是属性类映射到的 XML 类型。
    <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNamePairOfMyXmlKeyNameLong" xmlns:s01="mytypes"/>
    

    注意:如果 XMLKeyNameType 相同,则对于 PropName 属性,相应的类型将命名为 ArrayOfXMLItemNamePairOfXMLKeyName。即,从类型名称中删除冗余数组项。要使类型名称包含冗余名称,请将( %XML.Schema实例)AllowRedundantArrayName 属性指定为 1。同样,在 Web 服务类中,要在类型中包含冗余数组项名称在 WSDL 中,将(Web 服务类的)ALLOWREDUNDANTARRAYNAME 参数指定为 1

  • 类型 ArrayOfXMLItemNamePairOfXMLKeyNameType 被定义为另一种类型的 <sequence>,名为 PairOfXMLKeyNameType

     <complexType name="ArrayOfMyXmlItemNamePairOfMyXmlKeyNameLong">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:PairOfMyXmlKeyNameLong" xmlns:s01="mytypes"/>
    </sequence>
    </complexType>
    
    
  • PairOfXMLKeyNameType 类型是给定 XSD 类型的扩展。此扩展添加了一个名为 XMLKeyName 的属性:

      <complexType name="PairOfMyXmlKeyNameLong">
    <simpleContent>
      <extension base="s:long">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </simpleContent>
    </complexType>
    

Classname 引用对象类时,适用相同的规则。例如,考虑以下属性定义:

Property PropName As %ArrayOfObjects(XMLITEMNAME = "MyXmlItemName", XMLKEYNAME = "MyXmlKeyName");

其中 Simple.Object 包含两个属性:MyPropAnotherProp。如果此属性位于名为 Test.DemoObjArray 的启用 XML 的类中,则该类的 XML 架构包含以下内容:

<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:s="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" 
targetNamespace="mytypes">
  <complexType name="DemoObjArray">
    <sequence>
      <element minOccurs="0" name="PropName" type="s01:ArrayOfMyXmlItemNamePairOfMyXmlKeyNameSimpleObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="ArrayOfMyXmlItemNamePairOfMyXmlKeyNameSimpleObject">
    <sequence>
      <element maxOccurs="unbounded" minOccurs="0" name="MyXmlItemName" nillable="true" type="s01:PairOfMyXmlKeyNameSimpleObject" xmlns:s01="mytypes"/>
    </sequence>
  </complexType>
  <complexType name="PairOfMyXmlKeyNameSimpleObject">
    <complexContent>
      <extension base="s01:SimpleObject" xmlns:s01="mytypes">
        <attribute name="MyXmlKeyName" type="s:string" use="required"/>
      </extension>
    </complexContent>
  </complexType>
  <complexType name="SimpleObject">
    <sequence>
      <element minOccurs="0" name="MyProp" type="s:string"/>
      <element minOccurs="0" name="AnotherProp" type="s:string"/>
    </sequence>
  </complexType>
</schema>
0
0 55
文章 Qiao Peng · 十二月 7, 2023 5m read

TCP作为OSI 7层的传输层的通信协议,其使用上不像更上层的通信协议那么方便,因为TCP操作的不是数据包,它操作的是数据流。因此有多种将TCP数据流“解释”为数据包(消息)的方法。

InterSystems IRIS提供了多种TCP适配器,用于不同的“解释”,例如EnsLib.TCP.FramedInboundAdapter使用特定的首尾字符做为分隔、EnsLib.TCP.CountedInboundAdapter使用固定的长度进行分隔...

同时,InterSystems IRIS提供了多种开箱即用的TCP业务服务和业务操作,方便接入和发送TCP数据。这里我们介绍常见的使用特定的首尾字符做为分隔的TCP业务服务和业务操作。

1. 通用TCP业务服务和业务操作

EnsLib.TCP.Framed.PassthroughService和EnsLib.TCP.Framed.PassthroughOperation是一组使用特定的首尾字符做为分隔TCP数据流的通用业务服务和业务操作。EnsLib.TCP.Framed.PassthroughService业务服务会将TCP数据封装在Ens.StreamContainer发送给业务流程或业务操作;而EnsLib.TCP.Framed.PassthroughOperation业务操作发送并接收Ens.StreamContainer类型的数据。

0
0 222
文章 Qiao Peng · 十二月 4, 2023 10m read

1. 通用RESTful业务服务和业务操作


InterSystems IRIS 提供了一组通用的RESTful 业务服务和业务操作类,用户无需开发自定义的业务服务和业务操作类,就可以直接向外提供RESTful服务和调用外部的RESTful API。

BS EnsLib.REST.GenericService 通用REST业务服务
BS EnsLib.REST.SAMLGenericService 检查SAML令牌的签名和时间戳的REST业务服务
BO EnsLib.REST.GenericOperation 通用REST业务操作
BO EnsLib.REST.GenericOperationInProc 用于透传模式的通用REST业务操作

2. 通用RESTful 消息

通用的RESTful 业务服务和业务操作类使用一个通用的RESTful消息类 - EnsLib.REST.GenericMessage,它是EnsLib.HTTP.GenericMessage的子类,二者数据结构都是

0
2 227
文章 Lilian Huang · 十一月 24, 2023 3m read

我们继续使用FHIR适配器的示例,在本文中,我们将回顾如何在我们的IRIS实例中进行配置以及安装的结果。

配置项目的步骤与官方文档中所示的相同,您可以直接在此处查看。好吧,让我们开始工作吧!

安装

正如您在与本文相关的项目中看到的,我们将 IRIS 实例部署在 Docker 中,因此初始配置的主要部分将在 Dockerfile 中完成。别担心,我们不会详细介绍 Docker 配置。

要安装 FHIR 适配器,我们只需:

  1. 在我们的 IRIS 实例中使用互操作性功能创建一个名为ADAPTER的命名空间。
  2. 从 IRIS 终端访问我们的命名空间并执行以下命令。
set status = ##class (HS.FHIRServer.Installer).InteropAdapterConfig( "/Adapter/r4" )

在我们的例子中,我们定义了将接收 REST 请求的 IRIS 端点的 URL 为/Adapter/r4

安装结果

FHIR 适配器安装完成后,我们可以查看 IRIS 实例中发生的情况。为此,我们首先查看 Web 应用程序菜单(系统管理 -> 安全 -> 应用程序 -> Web 应用程序

正如我们所看到的,一个新的 Web 应用程序已添加到列表中,表明它对应于我们的 ADAPTER 命名空间。让我们访问它以更详细地查看其配置。

0
0 190
文章 Lilian Huang · 十一月 24, 2023 2m read

想必大家都听说过 FHIR 是解决系统间所有互操作性和兼容性问题的灵丹妙药和解决方案。就在这里,我们可以看到他手持一份 FHIR 资源,愉快地享受其中:

但对于我们这些普通人,我们将做一个小小的介绍。

什么是 FHIR?

让我们直接进入定义:FHIR(Fast Healthcare Interoperability Resource)是由HL7(Health Level 7标准组)开发的一种互操作性标准,旨在实现医疗行业中不同系统之间的电子医疗数据交换。

FHIR 从根本上基于哪些技术?

主要是通过 REST API 和 JSON 格式进行 HTTP 调用的结合(尽管它可以是 XML 以及我们可用的任何其他通信,具体根据我们的使用情况)。

我们如何与 FHIR 合作?

一般来说,最简单的方式是拥有一个FHIR服务器,我们将使用诸如GET(从服务器获取数据)、PUT(更新数据)、POST(保存数据)和DELETE(删除数据)等HTTP调用与其通信。

FHIR处理了“资源”(Resource)的概念,用于在服务器和客户端之间发送和接收数据。这些资源旨在涵盖系统间80%的互通性需求。在这里,我们可以看到默认情况下可用的资源图示。

如您所见,每个资源都附带一个表示资源成熟度的数字或字母(其中N = 正式)。如果您访问官方的FHIR文档,您将能够获取到众多示例。

0
0 216
文章 Meng Cao · 十一月 23, 2023 2m read
  • 前言 

        随着网络安全日益被重视,特别是等级保护制度的大环境下,SSL加密传输越来越被重视,本文介绍如何使用支持SSL的ODBC连接IRIS数据库。

        数据库版本:IRISHealth-2023.1

  • 1. 服务器端配置

       1)新建SSL服务器配置。

    

         2)开启超级端口的SSL支持,这里我们选择启用即可,如所有超级端口的连接都支持SSL可选要求。

       

  • 2.Windows客户端配置

       1)创建 SSLDefs.ini 配置文件,并编辑内容:

       [TLS to an InterSystems IRIS instance]
       Address=127.0.0.1
       Port=51773
       SSLConfig=DHCC

5
3 572
文章 姚 鑫 · 十一月 16, 2023 17m read

浅谈一下个人基于IRIS后端业务开发框架的理解

现状

由于国内使用基于M语言IRIS平台几乎都在医疗行业。医疗系统又非常的庞大和复杂。前期由于快速占领市场,系统数量越来越多,到了临界点后就产生了质变,所以前期基于功能的线性开发注重效率,所以导致大量的产品业务代码有如下集中情况:

  • 系统交互乱如麻,各系统的交互关系变成了网状。
  • 系统规模庞大,内部耦合严重,牵一发而动全身,后续修改和扩展困难,开发效率低。
  • 关键功能逻辑复杂,容易出现问题,出现问题后很难排查和修复,开发成本高。
  • 功能越来越多,导致系统复杂度指数级上升。
  • 重复造轮子,相似的功能不断重复开发。

image

如上图所示,这仅仅是展示了五个模块之前的交互,在此基础上继续增加模块则复杂度成指数级上升,并且如果每个模块之间如果没有做好接口管理,维护起来也是地狱级别。

如何解决这个问题,一般会分为两派:优化派与架构派。

优化派的思想是将现有的系统优化。例如重构某个方法,优化某个SQL。优化派的优势是针对系统改动较小,可以保持系统的稳定性,可以快速实施,缺点是治标不治本,随着需求越来越多,增加的代码量越多,后期还是会撑不住。

架构派的核心思想是调整系统架构,将原来的大系统拆分为多个互相配合的小系统,例如药库系统拆分为,采购模块,订单模块,查询模块,分析模块等。架构派的优势是一次调整可以支撑比较长期的业务发展,缺点是动作较大,耗时较长,稳定性需要项目考验。

所以这是个长痛短痛的问题,相信很多公司在遇到这种情况时,大部分情况下优化派会赢,因为“优化”是最快、最稳定的方式。除非当前架构支撑不了当前业务的阶段,才会考虑重新架构。


复杂性的一个主要原因就是系统越来越庞大,业务越来越多。降低复杂性最好的方式就是“拆”,化整为零,分而治之,将整体复杂性分散到多个子业务或子系统里去。

基于此我们设计一个简单的接口隔离方案,模块之间交互一般都会抽离个接口类,使用中介者模式或门面模式思想设计:

  • 门面模式:

每个模块之间,有单独的门面来专门处理接口,有效防止接口散布在模块内部的类中,起到接口隔离作用。

image

  • 中介者模式:

因为模块之间交互频繁,随着模块的增加,接口越来越多,会导致中介者类越来越庞大。好处是可以统一做接口管理。

image

这里的门面类可以进步一划分为提供接口与调用接口,可以类比为读写分离。

image

以上是处理多模块之间的常规交互处理方案。


下面我想针对单独模块内部的功能业务进行一些探讨:

在现实环境中,我们不可能一开始就设计出一套完美的架构,都会随着业务的发展,暴露出的问题,在不断的的迭代中,保留优秀的设计,修复有缺陷的设计,逐渐完善。不断进行重构,所以没有一劳永逸的框架。

笔者也曾开发了一些产品,基于此有一些基于M语言IRIS后端的一些框架想法分享给大家。 热门的语言JavaPython等有各种成熟的框架。对比关于IRIS相关的开发框架也比较少。借助相关的框架学习其中的思想,设计出一套适合基于IRIS平台M开发语言的医疗行业系统的开发框架。 尽量把框架做到,适合、简单。

  • 结合设计模式思想。
  • 根据功能进行模块划分。
  • 进行接口隔离,让产品、甚至功能做到高内聚,低耦合,可插拔。

方案

无论何种方案和框架,几乎都是为了一个统一的目标,让业务模块之间的耦合度尽量的低,做到可插拔,相互之间不影响,包括微服务,SOA。或者一些常见的架构MVCMVP。其思想都是根据职责或业务划分成不同的模块。

我们以药库的,订单,采购,入库的业务功能为例,具体展示一下业务功能的一些划分。

image

下面我们就针对如上这张图每个模块类来进行讨论。

无论采取何种分层维度,分层架构设计最核心的一点就是需要保证各层之间的差异足够清晰,边界足够明显。否则如果两个层的差异不明显,就会出现程序员A认为某个功能应该放在A层,而程序员B认为同样的功能应该放在B层,这样就导致了分层混乱。如果这样的架构进入实际开发落地,则A层和B层就会乱成一锅粥,也就失去了分层的意义。

按照如上分层就能够较好地完成系统扩展,本质在于:隔离关注点。即每个层中的组件只会处理本层的逻辑。比如说,展示层只需要处理Api相关。业务层只需要处理业务逻辑。数据层只提供数据。SQL层只处理SQL。接口层只处理各种服务。这样我们拓展某层时,其他层是不受影响的,通过这种方式可以支持系统在某曾上的快读扩展。

按照上图功能划分,好处是在于将业务功能强制分层,依赖限定为两两依赖,降低了系统复杂度。但是分层结构的代码特点就是冗余,也就是说,不管这个业务有多么简单,每层都必须要参与处理,有时甚至调用一个接口,要通过类包装函数层层传递。

我们是否应该自由选择绕过分层的约束呢?答案是不建议这样做,因为在传统的代码当中,我们已经见识到当接口方法在各种类中相互调用,时间一长,架构就会变的混乱,牵一发动全身。除此之外,虽然分层架构的实现在某些场景下看起来有些烦琐和冗余,但复杂度却很低。也不会增加太多工作量。无非在类多添加几个引用。

这个方案的另外一个典型缺点就是性能,因为每一次业务功能请求有可能需要穿越多个接口层,多少都会有一些性能的浪费。当然,这里所谓的性能缺点只是理论上的分析,实际上带来的性能损失,可以忽略不计。有时了为了保证方法类接口的单一性,牺牲一些性能也是在所难免的,最常见的例子就是用Gloabl取数据比写SQL要快,但是SQL在语义上比GLoabl要明确一些,实际上0.000010.0001性能上的感知几乎没有。但这并也并非说写SQL一定比Global要好。要根据情况而定,例如复杂SQL有多个JOIN,这种性能肯定会很比Global要慢很多。

具体实现

借用MVP架构和微服务的思想,后台将按每个大模块进行划分,考虑数据抽离、可复用性、原子性、安全性等原则,模块内部建立不同类进行区分。

注:一旦遵守框架开发,需要严格执行,否则过不了多久,又会乱成一锅粥。在现实环境中,多人开发确实很难达到一致性。

Base

image

其中:

  • His.Common.Base - 作为整个医院业务模块最底层包,存放一些所有产品组都可以用的公方法或开关。例如:获取pid,获取院级锁方法,抽象出一写全局方法,编译自动备份方法等。
  • Pharmacy.Common.Base - 其中Pharmacy作为His单独模块的包名,例如医生Doctor、护士Nurse、药房Medicine。该模块可以实现产品组级公共方法,实现His级的抽象方法,例如产品组级的锁。模块级的公共参数,字典变量等。
  • Pharmacy.Stock.Base - 该层为产品组级下的产品线,例如药房包括药库Stock、门诊药房Outpatient等。该层可以实现产品线级的公共方法,实现Pharmacy.Common.Base的抽象方法等。例如产品线级的锁。
  • 其中所有的BASE模块都可以包含对应层级的公用方法、公用包、inc文件,属性、参数、常量。每一个模块继承自己的上一级Base类。

注:可以把共有算法抽象到父类,子类写具体的模块的细节、模版方法模式。

代码示例:

  • His.Common.Base
    • 导入了通用包。例如:SQL,Util
    • 全局的参数。例如:当前日期时间等。
    • 定义抽象锁方法提子类重写。
/// 导入公共表与工具类
Import (SQLUser, Util)
/// todo:引入院级inc文件
Include His.Common.Base


Class His.Common.Base Extends %RegisteredObject
{

/// todo:院级公共属性
/// todo:院级公共参数
/// todo:院级公共方法
Parameter sysDate = {+$h};

Parameter sysTime = {$p($h, ",", 2)};

ClassMethod NewPid() As %String
{
    q $i(^HisPid)
}

ClassMethod Lock(lockName, lockTime) [ CodeMode = expression ]
{
..AbstractLock(lockName, lockTime)
}

ClassMethod AbstractLock(lockName, lockTime) [ Abstract ]
{
}

ClassMethod Unlock(lockName) [ CodeMode = expression ]
{
..AbstractUnlock(lockName)
}

ClassMethod AbstractUnlock(lockName) [ Abstract ]
{
}

}


  • Pharmacy.Common.Base
    • 实现模块级公用方法。此处为药房模块。
Class Pharmacy.Common.Base Extends His.Common.Base
{

/// todo:产品组模块公共属性
/// todo:产品组模块公共参数
/// todo:产品组模块公共方法
ClassMethod AbstractLock(lockName, lockTime)
{
    l +^PharmacyLock(lockName):lockTime e  q $$$NO
    q $$$OK
}

ClassMethod AbstractUnlock(lockName)
{
  	l -^PharmacyLock(lockName)
    q 0
}

}

  • Pharmacy.Stock.Base
    • 实现产品级公用方法。药房下药库产品。
Class Pharmacy.Stock.Base Extends Pharmacy.Common.Base
{

/// todo:产品线模块公共属性
/// todo:产品线模块公共参数
/// todo:产品线模块公共方法
ClassMethod AbstractLock(lockName, lockTime)
{
    l +^PharmacyStockLock(lockName):lockTime e  q $$$NO
    q $$$OK
}

ClassMethod AbstractUnlock(lockName)
{
  	l -^PharmacyStockLock(lockName)
    q 0
}

}


  • His.Common.Base.inc
    • 全局通用的inc文件。关于inc文件的使用可以参考百讲宏的使用.
#define HIS "HIS"

Biz

传统方式里我们习惯按建立的表类来写业务,这种方式的问题是当有主子表时我们的业务逻辑过于分散,一般主子表的业务操作都是联动的。所以我们可以根据业务的属性来划分到一个Biz里,这样做提高了内聚的属性。

image

  • 实现业务的主要逻辑代码,此类中不应包含具体的SQL语句、详细的过滤条件、复杂算法等。
  • Pharmacy.Stock.Ord.Biz - 实现该药库下订单模块的业务逻辑。
  • Biz的方法,应按照六大原则来书写,单独的功能抽离方法,业务方法应该是单一原则的方法嵌套。
Class Pharmacy.Stock.Ord.Biz Extends Pharmacy.Stock.Base
{

/// 保存订单主表业务逻辑
ClassMethod SaveMain(params)
{
	//todo:过滤条件
	
	q:(##class(Filter).IsSave()) $$$ERROR($$$GeneralError, "没有保存")
	q:(##class(Filter).IsAuit()) $$$ERROR($$$GeneralError, "没有通过审核")
	q:(##class(Filter).IsExist()) $$$ERROR($$$GeneralError, "不存在")
	
	//todo: 插入sql
	s id = ##class(Sql).SaveMain()
	q id
}

/// 保存订单子表业务逻辑
ClassMethod SaveDetail(params)
{
	//todo:过滤条件
	q ##class(Sql).SaveDetail()
}

/// 返回多条数据
ClassMethod Query(params)
{
}

/// 返回单条数据
ClassMethod QueryById(params)
{
	q ##class(Data).GetMainData()
}

}


  • Pharmacy.Stock.Base - 药库基本都涉及到保存主子表所以可以根据自己产品的需求抽象出一些公用方法,供其他功能模块重写。
Class Pharmacy.Stock.Base Extends Pharmacy.Common.Base
{


/// 公共保存方法
ClassMethod Save(params)
{
	// todo:算法,过滤条件
	#; 保存主算
	s ret = ..SaveMain(params)
	// todo:算法,过滤条件
	#; 保存明细数据
	s ret = ..SaveDetail(params)
	q $$$OK
}

/// 抽象保存主表算法
ClassMethod SaveMain(params) [ Abstract ]
{
}

/// 抽象保存子表算法
ClassMethod SaveDetail(params) [ Abstract ]
{
}

}



Data

image

在传统的书写习惯里可以看到几乎每个索引查询都会单独写一遍取数据。而且取数据的查询方法散布了很多类里。在后期维护时非常费时费力。所以每个业务模块都应有自己的Data类,取数据时都在统一的Data类里去取,目的是提高数据复用性。

  • Biz类返回数据,是模块内获取详细数据的唯一类,数据以动态JSON形式传递,抽离取值、提高复用。

注意:动态JSON非常的高效方便。

代码示例:

  • Pharmacy.Stock.Ord.Data
Class Pharmacy.Stock.Ord.Data Extends %RegisteredObject
{

/// 获取主数据
ClassMethod GetMainData(id)
{
	s data = ^OrdD(id)
	
	s ret = {}
	s ret.no = $lg(data, 1)
	s ret.createUserName = $lg(data, 2)
	s ret.finished = "Y"
	q ret
}

/// 获取明细数据
ClassMethod GetDetailData(id)
{
	s data = ^OrdD(+id, "I", $p(id,"||",2))
	
	s ret = {}
	s ret.name = $lg(data, 1)
	s ret.code = $lg(data, 2)
	s ret.desc = $lg(data, 3)
	q ret
}

}

注意:这里一定要区分好哪些是业务模块数据,哪些是公用数据,如果是公用数据不要放到业务模块数据里,要提到产品级的公用数据,依次类推。

例如:取登录人名称,登录人代码,就要去上一级的公共数据取。

Pharmacy.Stock.Ord.Data 的上一级Pharmacy.Stock.Common.Data存放药库级的公用数据。这里不应该放入取登录人方法。应该再次往上找Pharmacy.Common.Data,这里存放药房公用数据。实际上这里也不应该存放登录人方法,但是实际上每个产品组都是互为独立,大多数都把取登录人的方法放到这里。实际上应该放到His.Common.Data

  • His.Common.Data
Class His.Common.Data Extends %RegisteredObject
{

/// desc: 获取登录用户ID
ClassMethod GetUserId()
{
	q:'$d(%session) ""
	q:'$d(%session.Data("login.user.id")) ""
	s data = %session.Data("login.user.id")
	q data
}

/// desc: 获取登录用户ID
ClassMethod GetUsername()
{
	q:'$d(%session) ""
	q:'$d(%session.Data("login.user.name")) ""
	s data = %session.Data("login.user.name")
	q data
}

}

注:取数据类包保持单一原则,做到取desccodeid等数据时。可以直接取数据,而不是取到id再去根据id取对应Global做转换。

当遇到方法不知道放到哪里时,应该咨询有经验的组里资深程序员。

Filter

image

在传统的书写代码中,几乎看不到为专门的过滤条件建立的类,每次写业务逻辑时,尤其在循环中过滤条件都会复写一次,在字段过滤中最为明显,每次先查表找到过滤字段的位置,再取过滤字段,然后判断该字段是否满足条件,满足则过滤。

这种方式的问题很明显,每次过滤时查表费时费力,字段位置容易出错,大量的重复判断代码,当数据库字段有变动时,每个地方都需要改。而且一个业务可能会出现多个过滤条件,写新业务时可能会遗漏过滤条件。其他产品需要判断过滤条件时无接口可提供,往往是通过口头的方式告知这个表哪个字段为Y时过滤,他人还得需要再取一次。

Biz类提供条件过滤,优先考虑返回布尔类型结果。类中方法要保持原子性(不可拆分),只拓展不修改,提高复用,复杂过滤条件通过的单个方法组合的形式实现。注意保持单一原则,相同条件只允许有一个。

Class Pharmacy.Stock.Ord.Filter Extends %RegisteredObject
{

/// desc:判断是否保存
ClassMethod IsSave(ID As %Integer) As %Boolean
{
  s compFlag = $p(^OrdD(ID ), "^", 4)  
  q:(compFlag = "Y") $$$YES
  q $$$NO
}

/// desc:判断是否审核
ClassMethod IsAuit(ID As %Integer) As %Boolean
{
  s aduitFlag = $p(^OrdD(ID ), "^", 5)  
  q:(aduitFlag = "Y") $$$YES
  q $$$NO
}

/// desc:  判断是否存在
ClassMethod IsExist(ID As %String) As %Boolean
{
  s id = $p(^OrdD(ID), "^", 6)  
  q:(id '= "") $$$YES
  q $$$NO
}

/// desc:  是否允许
ClassMethod IsAllow(ingr As %String) As %Boolean
{
  q:..IsSave(ingr) $$$YES
  q:..IsAuit(ingr) $$$YES
  q:..IsExist(ingr) $$$YES
  q $$$NO
}

}

通过过滤条件的组合的好处是。为某个业务提供过滤时,我们仅提供组合好的接口即可,例如入库复杂的业务,需要考虑很多条件。这里只需要提供一个组合好的过滤接口,方便高效。

注意:这里一定要区分好哪些过滤条件是功能级别、产品线级别、模块组级别、全院级别。

Sql

image

传统类中SQL语句,我们可以发现有很多的相同的业务SQL语句散落在各个类中,也可以看到不同的方法有相同的SQL语句,这种碎片化的SQL语句有很多,也非常不好管理。所以可以根据小三层的思想把SQL语句放到专门的SQL类里,这样既增强了SQL语句的复用,也方便管理该业务下的所有相关SQL语句。当有新功能时,可以一览所有已有SQL,避免重复开发。

如果想避免重复的大量的简单SQL,可以参考这篇文章 只需要改造一下实体类,以后再也不用写SQL了

注意:后续对于数据操作都将仅仅存在SQL一种形式,不再使用对象存储。

Class Pharmacy.Stock.Ord.Sql Extends %RegisteredObject
{

ClassMethod SaveMain(params)
{
	
	//todo: sql
}

ClassMethod SaveDetail(params)
{
	//todo: sql
}

ClassMethod Delete(params)
{
	
	//todo: sql
}

ClassMethod Update(params)
{
	
	//todo: sql
}

ClassMethod Insert(params)
{
	
	//todo: sql
}

}

ImpRef

image

我们这里把接口分为了提供接口Imp与引用接口Ref,顾名思义Imp为对外的的接口,Ref为对内引用的第三方接口。

这样做的好处是隔离了功能模块间的耦合度,也统一了接口管理。接口做到了功能模块级,对于功能模块可以做到单独部署,统一了内外部引用,借用了微服务思想。

否则在传统模式下接口可能会散落在各个类中,接口级的管理顶多做到了产品组级。功能模块内部耦合严重。

模块向其他模块提供访问的接口类,所有业务模块之间的访问都应通过ImpRef类访问,模块间应保持独立性,尽可能降低依赖关系,降低业务间耦合度。此类不具备具体业务逻辑。

迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

  • Pharmacy.Stock.Ord.Imp
    • 例如对外提供接口判断订单是否存在。只需要引用Filter类即可。
Class Pharmacy.Stock.Ord.Imp Extends %RegisteredObject
{

// todo:对外提供接口

/// desc:  对外提供接口是否存在
ClassMethod IsOrdExist(ID As %String) [ CodeMode = expression ]
{
##class(Pharmacy.Stock.Ord.Filter).IsExist()
}

}

  • Pharmacy.Stock.Ord.Ref
    • 引用外部接口判断入库是否存在。
Class Pharmacy.Stock.Ord.Ref Extends %RegisteredObject
{

// todo:引用外部接口

/// desc:  引用外部接口,判断入库单是否存在
ClassMethod IsOrdExist(ID As %String) [ CodeMode = expression ]
{
##class(Pharmacy.Stock.Ins.Imp).IsInsExist()
}

}

Api

image

仅供前端直接访问的接口类,借用设计模式门面的思想。前端不允许访问除Api类之外的任何其他类方法,减少耦合度,针对接口编程。 Api可选择直接调用Biz方法,亦可覆写。对于前台需要的特殊格式的JsonXml,可以统一做转换。

Class Pharmacy.Stock.Ord.Api Extends Pharmacy.Stock.Ord.Biz
{

/// desc:查询明细
ClassMethod Query() [ CodeMode = expression ]
{
##super()
}

/// desc:根据保存单据
ClassMethod QueryById(params As %String) [ CodeMode = expression ]
{
##super(params)
}

}

注:这里应该当区分Api类与接口ImpRef的区别。Api是供前端调用的接口例如给webandroidios这些前端提供的数据接口。ImpRef为模块之间调用的数据接口。

Util

image

这里把Util类单独拿出来说,是因为防止重复造轮子,例如国密算法,每个组都可能用到,难道每个组都要去自己去实现吗?简单的还好,复杂的无疑增加了时间成本。

这里一定要明确Util类的概念,不包含任何业务逻辑方法,数据,过滤条件。与业务相关的,不能放到Util类里。Uti类更多是针对与编程方面,获取环境变量,公用算法等。

Util类是单独的包,面相所有产品,功能,模块的类,不通过接口来外部引用。这也说明了Util类一旦公布就只允许拓展,不允许修改。

传统的方式是,每个产品组各自为营,个搞个的,或者说压根就没有工具类,用到时在业务里随便复制粘贴个方法做为引用。所以就造成单个类,什么方法都有。

非常重要的一点是,工具类新增方法要定期公示,否则其他开发人员并不知道工具类有什么方法,或者建立索引文档提供查找。

总结

系统重构是大动作,持续是加比较长,而且会占用一定的资源,开发和测试。花费大量的沟通成本。

一旦决定重构,一开始就制定好各种规范,每个人都严格遵守。把解决的问题根据优先级、重要性。实施难度划分、

重构时可以遵循先易后难原则,这也是笔者的的一个重要体会。因为如果先攻克最难的问题,往往都耗时比较长,可能一两个月都没有什么进展和成果,会影响相关人员对项目的评价和看法,更会打击自信心。最后,刚开始的分析并不一定全面,所以一开始对最难的或最关键的是问题的判断可能会出错。

采取先易后难能够比较快速地看到成果,对后续项目的推进与提升士气有很大的好处。随着项目的进行,原来遗漏的点,或者分析和判断错误的点,会逐渐显示出来,到最后最难的问题也许迎刃而解。

以上是针对医疗领域与后端的一些业务开发框架的个人想法,现在回过头来看,感觉是理所当然,但实际上当时做分析和决策时远远没有这么简单。

以上是个人对基于IRIS后端的医疗框架理解,由于个人能力有限,欢迎大家提出意见,共同交流。

1
0 242
文章 Yinhang Hao · 十一月 20, 2023 2m read

前言

在日常工作中经常会遇到大量的接口开发需求,对于没有IRIS开发经验的同事来说很不友好,需要求助于公司开发人员来做接口开发,对项目联调进度多少会有些影响,本文站在没有IRIS开发经验的工作人员角度来阐述一下如何利用xslt转换文件自动生成接口联调所需要的Message模型。

基本思路是首先定义一套通用的数据模型,用来接收定义消息所需要的基础属性,包括类名,请求&响应(对应继承Ens.Request&Ens.Response),节点名称、节点长度、是否必填、默认值、字段约束等等。

1
0 165
文章 Michael Lei · 十一月 20, 2023 2m read

这是Java 编程比赛的相关文章。
我决定推出一个基于 IRIS Native API for Java 的 CRUD++  Global编辑器。
++因为它不仅仅是C reate、 R ead、 U pdate、 D elete
Global可视化对于立即查看结果始终很重要。

  • 为此,我使用模仿 ZWrite 的树查看器扩展了 API,并且还允许检查子树。
  • $Query Style Navigator 正向和反向操作可轻松找到感兴趣的全局节点。
  • 最后,ZKill 添加了一个选项,可以删除全局节点的内容而不删除下面的子树。

这需要在服务器端有一个小的帮助器类作为默认 API 的扩展

我的策略是拥有一个可从命令行使用的相当适度的界面
就像在 Docker 控制台或终端上一样,并使其尽可能简单。
花哨的图形界面只会分散示例的基本内容。

如果有任何默认值或先前的值,则会在输入提示中显示。
在 Docker 容器中,编辑器已经可以使用了。

  • docker-compose exec iris java gedi docker-compose exec iris java gedi

您首先连接到服务器

0
0 141
公告 Hao Ma · 十一月 14, 2023

我们最近发布了一份关于在镜像环境中使用报告节点(完整的“异步报告镜像成员”)的新白皮书。越来越多的客户正在研究这种机制,将其作为一种快速、简单的方法来设置保持最新的生产数据副本,但可以用于分析查询或重型报告工作负载,而不影响源系统。 请在此处阅读白皮书

与往常一样,我们很想听听您对如何在组织中使用此镜像选项的反馈,以及您是否对我们如何提高其效率有想法。

0
0 72
文章 姚 鑫 · 五月 20, 2021 9m read

第一章 发送HTTP请求

本主题介绍如何发送HTTP请求(如POSTGET)和处理响应。

HTTP请求简介

可以创建%Net.HttpRequest的实例来发送各种HTTP请求并接收响应。此对象相当于Web浏览器,可以使用它发出多个请求。它会自动发送正确的cookie,并根据需要设置Referer标头。

要创建HTTP请求,请使用以下常规流程:

  1. 创建%Net.HttpRequest的实例。
  2. 设置此实例的属性以指示要与之通信的Web服务器。基本属性如下:
  • 服务器指定Web服务器的IP地址或计算机名称。默认值为localhost

注意:不要将http://https://作为服务器值的一部分。这将导致错误#6059:无法打开到服务器http:/的TCP/IP套接字

  1. 可以选择设置HTTP请求的其他属性和调用方法,如指定其他HTTP请求属性中所述。
  2. 然后,通过调用%Net.HttpRequest实例的get()方法或其他方法来发送HTTP请求,如“发送HTTP请求”中所述。

可以从实例发出多个请求,它将自动处理cookie和Referer标头。

注意:如果创建此HTTP请求是为了与生产出站适配器(EnsLib.HTTP.Outbound Adapter)一起使用,那么请改用该适配器的方法来发送请求。

  1. 如果需要,使用%Net.HttpRequest的同一实例发送其他HTTP请求。默认情况下,InterSystems IRIS使TCP/IP套接字保持打开状态,以便可以重复使用套接字,而无需关闭和重新打开它。

以下是一个简单的示例:

/// w ##class(PHA.TEST.HTTP).Get()
ClassMethod Get()
{
	set request=##class(%Net.HttpRequest).%New()
	set request.Server="tools.ietf.org"
	set request.Https=1
	set request.SSLConfiguration="yx"
	set status=request.Get("/html/rfc7158")
	d $System.Status.DisplayError(status) 

	s response = request.HttpResponse
	s stream = response.Data
	q stream.Read()
}

提供身份验证

如果目标服务器需要登录凭据,则HTTP请求可以包括提供凭据的HTTP Authorization标头。

如果使用的是代理服务器,还可以指定代理服务器的登录凭据;为此,请设置ProxyAuthorization属性

使用HTTP 1.0时对请求进行身份验证

对于HTTP 1.0,要验证HTTP请求,请设置%Net.HttpRequest实例的用户名和密码属性。然后,该实例使用基本访问身份验证基于该用户名和密码创建HTTP Authorization标头(RFC 2617)。此%Net.HttpRequest发送的任何后续请求都将包括此头。

重要提示:请确保还使用SSL。在基本身份验证中,凭据以base-64编码形式发送,因此易于读取。

在使用HTTP 1.1时对请求进行身份验证

对于HTTP 1.1,要验证HTTP请求,在大多数情况下,只需设置%Net.HttpRequest实例的用户名和密码属性。当%Net.HttpRequest的实例收到401 HTTP状态代码和WWW-Authenticate标头时,它会尝试使用包含支持的身份验证方案的Authorization标头进行响应。使用为IRIS支持和配置的第一个方案。默认情况下,它按以下顺序考虑这些身份验证方案:

  1. 协商(SPNEGO和Kerberos,根据RFC 4559和RFC 4178)
  2. NTLM(NT LAN Manager身份验证协议)
  3. 基本认证(RFC 2617中描述的基本接入认证)

重要:如果有可能使用基本身份验证,请确保也使用SSL(参见“使用SSL进行连接”)。 在基本身份验证中,凭据以base-64编码的形式发送,因此很容易读取。

在Windows上,如果没有指定Username属性,IRIS可以使用当前登录上下文。 具体来说,如果服务器使用401状态码和用于SPNEGOKerberosNTLMWWW-Authenticate头响应,那么IRIS将使用当前操作系统用户名和密码创建Authorization头。

具体情况与HTTP 1.0不同,如下所示:

  1. 如果认证成功,IRIS更新%NetCurrentAuthenticationScheme属性。 HttpRequest实例来指示它在最近的身份验证中使用的身份验证方案。
  2. 如果尝试获取方案的身份验证句柄或令牌失败,IRIS会将基础错误保存到%Net.HttpRequest实例的AuthenticationErrors属性中。此属性的值为$List,其中每一项都具有格式scheme ERROR: message

仅HTTP 1.1支持协商和NTLM,因为这些方案需要多次往返,而HTTP 1.0要求在每个请求/响应对之后关闭连接。

Variations

如果知道服务器允许的一个或多个身份验证方案,则可以通过包括Authorization标头来绕过服务器的初始往返行程,该标头包含所选方案的服务器的初始令牌。为此,请设置%Net.HttpRequest实例的InitiateAuthentication属性。对于此属性的值,请指定服务器允许的单个授权方案的名称。使用下列值之一(区分大小写):

  • Negotiate
  • NTLM
  • Basic

如果要自定义要使用的身份验证方案(或更改其考虑顺序),请设置%Net.HttpRequest实例的AuthenticationSchemes。对于此属性的值,请指定以逗号分隔的身份验证方案名称列表(使用上一个列表中给出的准确值)。

直接指定授权标头

对于HTTP 1.0或HTTP 1.1(如果适用于场景),可以直接指定HTTP Authorization标头。具体地说,可以将Authorization属性设置为等于正在请求的资源的用户代理所需的身份验证信息。

如果指定Authorization属性,则忽略用户名和密码属性。

启用HTTP身份验证的日志记录

要启用HTTP身份验证的日志记录,请在终端中输入以下内容:

 set $namespace="%SYS"
 kill ^ISCLOG
 set ^%ISCLOG=2
 set ^%ISCLOG("Category","HttpRequest")=5

日志条目将写入^ISCLOG global中.。要将日志写入文件(以提高可读性),请输入以下内容(仍在%SYS命名空间内):

 do ##class(%OAuth2.Utils).DisplayLog("filename")

其中,filename是要创建的文件的名称。该目录必须已存在。如果该文件已经存在,它将被覆盖。

要停止日志记录,请输入以下内容(仍在%SYS命名空间内):

 set ^%ISCLOG=0
 set ^%ISCLOG("Category","HttpRequest")=0

指定其他HTTP请求属性

在发送HTTP请求之前(请参阅发送HTTP请求),可以指定其属性,如以下各节所述:

可以为%Net.HttpRequest的所有属性指定默认值,如最后列出的部分中所指定。

Location属性

Location属性指定从Web服务器请求的资源。如果设置此属性,则在调用Get(), Head(), Post(), 或 Put()方法时,可以省略location参数。

例如,假设正在向url http://machine_name/test/index.html发送一个HTTP请求

在这种情况下,将使用下列值:

%Net.HttpRequest的示例属性

PropertiesValue
Servermachine_name
Locationtest/index.html

指定Internet媒体类型(Media Type)和字符编码(Character Encoding)

可以使用以下属性指定%Net.HttpRequest实例及其响应中的Internet媒体类型(也称为MIME类型)和字符编码:

  • Content-Type指定Content-Type标头,该标头指定请求正文的Internet媒体类型。默认类型为None。

可能的值包括application/jsonapplication/pdfapplication/postscriptimage/jpegimage/pngmultipart/form-datatext/htmltext/plantext/xml等等

  • ContentCharset属性控制请求的任何内容(例如,text/htmltext/xml)类型时所需的字符集。如果不指定此属性,InterSystems IRIS将使用InterSystems IRIS服务器的默认编码。

注意:如果设置此属性,则必须首先设置ContentType属性。

  • NoDefaultContentCharset属性控制在未设置ContentCharset属性的情况下是否包括文本类型内容的显式字符集。默认情况下,此属性为False。

如果此属性为true,则如果有文本类型的内容,并且没有设置ContentCharset属性,则内容类型中不包括任何字符集;这意味着字符集iso-8859-1用于消息输出。

  • WriteRawMode属性影响实体正文(如果包含)。它控制请求正文的写入方式。默认情况下,此属性为False,并且InterSystems IRIS以请求标头中指定的编码写入正文。如果此属性为true,则InterSystems IRIS以原始模式写入正文(不执行字符集转换)。
  • ReadRawMode属性控制如何读取响应正文。默认情况下,此属性为False,并且InterSystems IRIS假定正文在响应标头中指定的字符集中。如果此属性为true,InterSystems IRIS将以原始模式读取正文(不执行字符集转换)。

使用代理服务器

可以通过代理服务器发送HTTP请求。要设置此设置,请指定HTTP请求的以下属性:

  • ProxyServer指定要使用的代理服务器的主机名。如果此属性不为空,则将HTTP请求定向到此计算机。
  • ProxyPort指定代理服务器上要连接到的端口。
  • ProxyAuthorization指定Proxy-Authorization标头,如果用户代理必须使用代理验证其自身,则必须设置该标头。对于该值,请使用正在请求的资源的用户代理所需的身份验证信息。
  • ProxyHTTPS控制HTTP请求是针对HTTPS页面还是针对普通HTTP页面。如果未指定代理服务器,则忽略此属性。此属性将目标系统上的默认端口更改为代理端口443。
  • ProxyTunes指定是否通过代理建立到目标HTTP服务器的隧道。如果为true,则请求使用HTTP CONNECT命令建立隧道。代理服务器的地址取自ProxyServerProxyPort属性。如果ProxyHttps为true,则隧道建立后,系统间IRIS将协商SSL连接。在这种情况下,由于隧道与目标系统建立直接连接,因此将忽略https属性。

使用SSL进行连接

%Net.HttpRequest类支持SSL连接。要通过SSL发送请求,请执行以下操作:

  1. SSLConfiguration属性设置为要使用的已激活SSL/TLS配置的名称。

  2. 还要执行以下操作之一,具体取决于是否使用代理服务器:

  • 如果未使用代理服务器,请将https属性设置为true。
  • 如果使用的是代理服务器,请将ProxyHTTPS属性设置为true。

在这种情况下,要使用到代理服务器本身的SSL连接,请将https属性设置为true。

请注意,当使用到给定服务器的SSL连接时,该服务器上的默认端口假定为443(HTTPS端口)。例如,如果没有使用代理服务器,并且https为true,则会将Default Port属性更改为443。

服务器身份检查

默认情况下,当%Net.HttpRequest实例连接到SSL/TLS安全的Web服务器时,它会检查证书服务器名称是否与用于连接到服务器的DNS名称匹配。如果这些名称不匹配,则不允许连接。此默认行为可防止“中间人”攻击,在RFC 2818的3.1节中进行了描述;另请参阅RFC 2595的2.4节。

若要禁用此检查,请将SSLCheckServerIdentity属性设置为0。

HTTPVersionTimeoutWriteTimeoutFollowRedirect属性

%Net.HttpRequest还提供以下属性:

HTTPVersion指定请求页面时使用的HTTP版本。默认值是"HTTP/1.1"。你也可以使用“HTTP/1.0”

Timeout指定等待web服务器响应的时间,以秒为单位。 缺省值是30秒。

WriteTimeout指定等待Web服务器完成写入的时间(以秒为单位)。默认情况下,它将无限期等待。可接受的最小值为2秒。

FollowRedirect指定是否自动跟踪来自Web服务器的重定向请求(由300-399范围内的HTTP状态代码发出信号)。如果使用的是GET或HEAD,则默认值为TRUE;否则为FALSE。

指定HTTP请求的默认值

可以为%Net.HttpRequest的所有属性指定默认值。

  • 要指定适用于所有名称空间的默认值,请设置全局节 ^%SYS("HttpRequest","propname"),其中“PropName”是属性的名称。
  • 要为一个名称空间指定默认值,请转到该名称空间并设置节点^SYS("HttpRequest","propname")

(^%SYS全局设置会影响整个安装,^SYS全局设置会影响当前命名空间。)

例如,要为所有名称空间指定默认代理服务器,请设置全局节^%SYS("HttpRequest","ProxyServer")

1
0 323