#Ensemble

0 关注者 · 177 帖子

InterSystems Ensemble 是一个完整且易于使用的集成平台,使用户能够在有史以来最短的时间内连接人员、流程和应用程序。

了解更多信息

文档

文章 Nicky Zhu · 十月 28, 2025 2m read

InterSystems 常见问题解答标题

^%GCMP 实用工具可用于比较两个全局变量的内容。

例如,要比较 USER 和 SAMPLES 命名空间中的 ^test 和 ^test,过程将与下面类似:
*以下示例在这两个命名空间中创建了 700 个相同的全局变量,并更改了其中一个的内容,使其成为检测目标。

USER>kill^test
USER>for i=1:1:100 { forj=1:1:7 { set^test(i,j)="Test"_i } }

USER>zn"samples"// change namespace to SAMPLES SAMPLES>kill^test SAMPLES>for i=1:1:100 { forj=1:1:7 { set^test(i,j)="Test"_i } }

SAMPLES>set^test(50,5,1)=1// Change one of the globals created in the SAMPLES namespace. SAMPLES>do^%GCMP Compare global ^test// Global to compare. on directory set: (this system) // Enter in namespace: SAMPLES => // Enter (if this namespace is OK) with global ^test=> // Global to compare on directory set: (this system) // Enter in namespace: SAMPLES => USER // Namespace to compare Output differences on Device: // Destination for output results. Press <Enter> to view in a terminal.// If you enter the full path of the log file name, the output will be sent there. Right margin: 80 =>

Compare global ^test in SAMPLES with global ^test in USER

^test(50,5,1) exists in ^|"SAMPLES"|test but not in ^|"USER"|test // Detects differing globals Time=.001822 SAMPLES>

如果要在不同服务器上的实例之间进行比较,而不是在同一实例中,请使用 ^DATACHECK 实用工具。 有关如何使用 ^DATACHECK 实用工具的说明,请参阅以下相关文章:

如何比较两个数据库中的多个全局变量和例程

0
0 19
InterSystems 官方 Claire Zheng · 六月 19, 2025 4m read

互操作性用户界面现在包括可以在所有互操作性产品中使用的 DTL 编辑器生产配置应用程序的现代化用户体验。您可以在现代化视图与标准视图之间切换。所有其他互操作性屏幕仍采用标准用户界面。请注意,仅对这两个应用程序进行了更改,我们在下面确定了当前可用的功能。

要在升级前试用新屏幕,您可以点击这里,从我们的社区工具包网页中下载 2025.1 版:https://evaluation.intersystems.com/Eval/。请观看“学习服务”中的简短教程构建集成:一种新的用户体验,了解对这些屏幕进行的用户增强!

生产配置 - 配置任务简介
  • 生产配置:在以下版本的生产配置中受支持:
    • 创建/编辑/复制/删除主机
    • 停止/启动主机
    • 编辑生产设置
    • 停止/启动生产
  • 源代码控制集成:支持上述配置功能的源代码控制集成。
  • 分屏 视图:用户可以直接从“生产配置”屏幕打开“规则编辑器”和“DTL 编辑器”,在分屏视图中查看和编辑产品中包含的规则和转换。
  • 增强的筛选功能:使用顶部的搜索框,您可以搜索和筛选各种业务组件,包括多种类别、DTL 和子转换。 使用左侧边栏可以独立于主面板进行搜索,查看各种主机和类别中的搜索结果。
  • 批量编辑主机类别:通过从生产配置中添加主机,您可以为生产添加新类别或编辑现有类别。
  • 可展开路由器:可以展开路由器,内联查看所有规则、转换和连接。
  • 重新设计的主机连接:现在,在选择业务主机时,将呈现直接连接和间接连接,您可以查看消息能够采用的完整路径。 将鼠标悬停在任何出站或入站主机上可以进一步区分连接。如果开启仅显示连接的主机开关,将仅筛选所选主机及其连接。
DTL 编辑器 - DTL 工具简介
  • 源代码控制集成:支持源代码控制集成。
  • VS Code 集成:用户可以在其 VS Code IDE 中查看此版本的 DTL 编辑器。
  • 嵌入式 Python 支持:此版本的 DTL 编辑器现在支持嵌入式 Python。
  • DTL 测试:可以在此版本的 DTL 编辑器中使用 DTL 测试实用工具。
  • 切换面板布局:DTL 编辑器支持侧面到侧面和顶部到底部布局。 点击顶部功能区的布局按钮可以体验此功能。
  • 撤消/重做:用户可以使用撤消/重做按钮撤消和重做所有尚未保存为代码的操作。
  • “生成空段”参数:GENERATEEMPTYSEGMENTS 参数可用于为缺失的字段生成空段。
  • 子转换查看:用户可以点击眼睛图标,在新选项卡中打开子转换 DTL,查看子转换。
  • 滚动
    • 单独滚动:将光标放置在 DTL 的左右两部分(源和目标)其中之一的上方,并用滚轮或触控板垂直移动各段,可以单独滚动各个部分。
    • 联合滚动:将光标放置在图的中间,可以联合滚动源部分和目标部分。
  • 字段自动补全:自动补全适用于:“源”、“目标”和“条件”字段以及源类、源文档类型、目标类、目标文档类型。
  • 顺序编号:使用可视化编辑器,您可以打开和关闭查看每个段的序数和完整路径表达式的功能。
  • 轻松引用:当操作编辑器中的某个字段获得焦点时,在图形化编辑器中双击某个段会在操作编辑器中的当前光标位置插入相应的段引用。
  • 同步:点击可视化编辑器中的一个元素,可以在操作编辑器中高亮显示相应的行。
📣号召性用语📣

如果您有任何反馈,请通过以下途径提供给我们:

  • 跨所有互操作性的新功能:在 Ideas 门户中输入想法,或在 InterSystems Ideas 门户中参与其他想法。 对于新想法,请在您的帖子上添加“互操作性”标签或对列表中已提出的功能进行投票!
  • 💻跨所有互操作性的一般用户体验反馈:请在下面输入您的反馈或参与其他评论。
  • 🗒对现代化应用程序的建议/反馈(如上所述):请在下面输入您的反馈或参与其他评论。

请考虑利用 Global Masters 的机会,与团队进行互动,参与不公开的指导反馈会议,并获得积分! 点击此处,通过 Global Masters 报名参加这些会议。

如果您希望以私人形式提供任何其他反馈,请通过电子邮件将您的想法或问题发送至:ux@intersystems.com  
 

0
0 47
文章 Cryze Zhang · 十月 23, 2024 1m read
file2Xml 
一个将文件转换成Studio导出的xml格式的工具

通过此工具可以将本地文件转换成xml格式文件,然后通过Studio导入到服务器中,而不再需要其他工具将文件传至服务器。

下面以为ensemble2016自带的一个示例界面增加背景图为例。
http://localhost:57772/csp/samples/cinema/Cinema.csp

1.选择数据版本信息并录入web应用程序(SMP--系统--安全管理--Web 应用程序)

确定此示例界面的Web应用程序为:/csp/samples

2.点击“选择文件”,选择需要转换的文件
选择本地电脑要作为背景图的图片

3.录入每个文件导入后相对于web应用程序的路径和导入后的文件名

想要放到cinema下,故相对于web应用程序的路径为:cinema
想要将文件命名为:search_bg.jpg

4.勾选最终需要转换的文件,并点击“转换”

5.录入转换出的xml文件名,点击“确定”

6.转换出的xml文件就可以通过Studio直接导入库中了

然后修改下Search.csp的样式代码,就可以使用此背景图了

工具地址:

0
0 122
问题 water huang · 九月 3, 2024

我使用%SYSTEM.WorkMgr的多进程(multicompile=1)来处理数据,但是我发现执行完WaitForComplete后,%SYSTEM.WorkMgr创建的进程没有马上结束,

他们的状态是evtw,如果我执行了很多次这样的操作,进程就会越来越多,它会导致服务器卡顿等异常,但是过一会儿他们会消失,留一个它不会消失。它产生的子进程其实是可以使用$System.Process.Terminate(pid) 来结束,但是这样结束术后,它会有错误信息我的问题是如何正确的结束%SYSTEM.WorkMgr产生的子进程,如何避免服务器因此受到影响甚至宕机,为什么他会导致服务器出问题,有什么办法能再使用完成后,把 %SYSTEM.WorkMgr相关的进程正确的快速结束掉

0
0 112
问题 liu bo · 八月 19, 2024

1.我在java中发布了一个服务提供给调用,postman中可以请求解析出数据

请求信息如下:

POST /uploadPersonExcel HTTP/1.1
Host: localhost:8017
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
cache-control: no-cache
Postman-Token: 1c23edfe-1e83-44c6-8f91-e06bccfd4af3

Content-Disposition: form-data; name="file"; filename="C:\Users\elite\Desktop\personinfo.xlsx

------WebKitFormBoundary7MA4YWxkTrZu0gW--

2.Ensemble中请求代码如下:

2
0 94
文章 Cryze Zhang · 八月 18, 2024 1m read

由于个人习惯,更喜欢将代码文件使用xml格式导入导出,但是有时后翻代码,xml格式的阅读体验没那么好,如果要是导入到studio又太麻烦,所以实现了一个在线工具,解析xml内容,将其转换为在Studio看到的内容,即所谓的UDL(Universal Definition Language)格式。
小工具地址

https://ttykx.com/demos/cos/xml2Udl.html

1.选择xml文件,读取内容。

 

2.解析项目,按项目显示原xml内容

 

3.转为UDL格式内容

 

0
0 99
文章 Michael Lei · 七月 25, 2024 1m read

InterSystems 常见问题FAQ 

要编译包含映射修饰符的类rountine,请指定编译器修饰符“/mapped=1”或“/mapped”。例如,执行以下操作:

[示例 1] 获取类列表并编译

do$System.OBJ.GetClassList(.list,"/mapped")
 // build your classes starting from .listdo$System.OBJ.Compile(.list) 

[示例 2] 编译所有类 

do$system.OBJ.CompileAll("/mapped") 
0
0 83
文章 Michael Lei · 七月 18, 2024 1m read

InterSystems 常见问题系列

可以通过 TRY-CATCH 来完成:

#dim ex As%Exception.AbstractExceptionTRY {
    //Code that causes an error
  }
  CATCH ex {
     do ex.Log()
  }

如果用了 ^%ETN, 从BACK 接入点 (BACK^%ETN)处调用.

请参考另外一篇文章: 如何使用命令获得应用错误 (^ERRORS)

0
0 77
文章 liu bo · 七月 7, 2024 1m read

问题:锁管理里边包含很多WorkQueueMgr的锁,我想批量去移除,比较多,一个一个移除比较慢

解决:查询所有的进程,过滤routine不包含某个routine的进程,进行终止

代码:

ClassMethod BatchTerminalProcessQuery()
{     Set Rset = ##class(%ResultSet).%New("%SYS.ProcessQuery:ListPids")
Rset.Execute()
While Rset.Next() {
   CurrentLineAndRoutine=""
   &sql(SELECT CurrentLineAndRoutine INTO :CurrentLineAndRoutine FROM %SYS.ProcessQuery WHERE Pid = :Rset.GetData(1))
   continue:CurrentLineAndRoutine'[".WorkQueueMgr"
   CurrentLineAndRoutine,!
   pid=Rset.GetData(1)
   sc= $System.Process.Terminate(pid)
   sc,!
   ;w $ZU(4,pid,1)
}
Rset.Close()
}

1
0 130
文章 Michael Lei · 七月 7, 2024 1m read

InterSystems 常见问题系列FAQ

如果要让超时功能失效, 在DSN设置查询超时为disabled:

Windows Control Panel > Administrative Tools > Data Sources (ODBC) > System DSN configuration

如果勾选了Disable query timeout , 超时就会失效.

如果想在应用侧修改,你可以在ODBC API 层设置:在连接数据源之前,调用ODBC SQLSetStmtAttr功能设置SQL_ATTR_QUERY_TIMEOUT 属性 

0
0 91
文章 Michael Lei · 七月 7, 2024 1m read

InterSystems 常见问题FAQ

如果您想在InterSystems 产品启动时执行一个操作系统可执行文件,命令或者程序,可以在SYSTEM^%ZSTART routine里面写明流程 ( %ZSTART routine在 %SYS 命名空间里面创建).

在 SYSTEM^%ZSTART 里面写代码之前, 请确保他可以在任何情况下能正常工作

如果 ^%ZSTART routine 写的不对,或者没有响应或者发生错误,InterSystems 产品可能会无法启动。

更多信息,请参考一下文档。

About writing %ZSTART and %ZSTOP routines [IRIS]
About writing %ZSTART and %ZSTOP routines

0
0 92
文章 Michael Lei · 七月 7, 2024 4m read

InterSystems  常见问题系列FAQ

InterSystems 产品里数据 (表、对象、实例数据) 是存在global 变量里的。
每个global 的数据大小可以从管理门户中中点击属性查看Management Portal > System > Configuration > Local Database > Globals page, 然后在global 属性页点击计算大小Calculate Size 按钮。
你可以在终端上调用^%GSIZE  来在命名空间里显示数据大小,方法如下.

0
0 83
InterSystems 官方 Claire Zheng · 五月 30, 2024

从发布InterSystems IRIS®数据平台2022.3开始,InterSystems修改了许可证强制执行机制,以包括REST和SOAP请求。由于这种变化,在升级后,使用REST或SOAP的非处理器核数的许可证环境下,用户可能会遇到更高的许可证消耗。要确定此警报是否适用于您的InterSystems许可证,请按照下面链接的FAQ中的说明进行操作。

下表总结了强制执行变更情况:

产品

许可证强制执行中是否包含REST & SOAP 请求?

InterSystems Caché®

InterSystems Ensemble®

InterSystems IRIS, InterSystems IRIS® for Health, and Health Connect prior to 2022.3

InterSystems IRIS, InterSystems IRIS for Health, and Health Connect 2022.3 and later

1
0 142
问题 water huang · 五月 10, 2024

我新建了命名空间,然后新加了用户,新加了角色,给角色授权了%DB_%DEFAULT资源和%Ens_Portal(因为新加的命名空间默认应用就是这个必要资源),然后访问这个空间下的webservice,报错为权限不足,如果再加上%EnsRole_WebDeveloper就可以成功访问,但是加上它后,用户就能登录portal里面的,我不想让它访问这个 ensemble,请问我应该怎么配置权限资源

2
0 88
文章 Hao Ma · 三月 6, 2024 2m read

[翻译文章:大型数据集的存储注意事项]这篇文章其实很有用, 但恐怕注意的人不多,特意写文章介绍一下。

USEEXTENTSET翻译过来就是"使用EXTENT集合“,不够直白,它的工作简单说就是:通过哈希类名,使用更短的Global名字

ObjectScript的持久类编译后会默认使用"^类名D" 和"^类名I"的global来存储数据和索引, 当在类定义里加入`Parameter USEEXTENTSET = 1;` 这句话后, 会使用一个自动生成的短类名。 比如*Class User.EMR.MSG.Items.FirstPageCostsItem1*, 默认的存储为类似“^User.EMR.MEC31.FirstPageCoC0D4D”, 使用USEEXTENTSET“后会定义为“^U3Dx.EOXs.1”。

短Global名字的好处 

global名字缩短了,数据和索引的占用并不会改变。改变的是Journal的尺寸。 同样的表和数据插入, 之前的Journal是这样的:

 

使用短global名字后的journal记录是这样的:

 

我的测试中, 使用USEEXTENTSET减少了30%的Journal尺寸。

0
0 114
文章 聆严 周 · 九月 30, 2022 11m read

使用Prometheus监控Cache集群

Executive Summary

生产级别的Cache集群往往由多个Cache实例组成,而Cache自带的管理界面不能满足对整个集群的监控,因此在实际使用中,往往需要投入人力对实例进行性能巡检。即便如此,这种巡检模式实时性低、告警的漏报错报率高、对既往数据追溯能力差。针对Cache/IRIS集群管理的这一缺憾,本文提出以Prometheus监控Cache集群的方案,最终实现了对集群全实例监控指标的自动化采集,以及准实时监控数据展示和告警提示。

监控大屏3.主机实例监控

Prometheus及Grafana简介

Prometheus是一个开源的监控收集框架。它内置了一个数据收集服务、一个时序数据库、和一个Web UI管理界面。 Prometheus采用PULL模式拉取监控数据,使得它在众多监控解决方案中脱颖而出。这种PULL模式只需被监控对象暴露出符合Prometheus要求的接口即可,而无需配置监控服务器地址,也无需管理定时任务,对应用程序的侵入性极小。 Prometheus也有一个健全的生态。它提供各种管理接口API,如自动服务发现、告警、查询等接口,和已有开源软件互操作性好,也方便接入机构内部系统。其中Grafana是最重要的生态伙伴,它是一个开源的数据可视化工具,支持动态的更改数据源、灵活地配置报表、编写Prometheus查询语句、定义告警等。因为这些特性,它常用于性能监控领域。

下图是Prometheus的典型架构,本文只关注其中红色部分。

Prometheus Architecture

Cache常用监控指标

本文总结了License使用量、Ensemble队列消息排队数量、事务持续时长、数据库可用空间,这4个常用的数值类型的监控指标,这四个指标直接影响或指示出系统的健康程度。以下是这四个指标的获取方法。

  • License使用量
s LicenseUsed=##class(%SYSTEM.License).LUConsumed()
s LicenseAvailable=##class(%SYSTEM.License).LUAvailable()
s LicenseTotal=##class(%SYSTEM.License).GetUserLimit()
  • Ensemble队列消息排队数量
Set statement=##class(%SQL.Statement).%New(1)
Set sc=statement.%PrepareClassQuery("Ens.Queue","Enumerate")
Set rset=statement.%Execute()
  • 事务持续时长
set rs=##class(%ResultSet).%New()
set rs.ClassName="%SYS.Journal.Transaction"
set rs.QueryName="List"
set sc=rs.Execute(0)
  • 数据库可用空间
Set statement=##class(%SQL.Statement).%New(1)
Set sc=statement.%PrepareClassQuery("%SYS.DatabaseQuery","FreeSpace")
Set rset=statement.%Execute()

Cache中Prometheus接口定义

本文实现了Prometheus接口。该接口中的jsonToPrometheus()方法将JSON转换为Prometheus需要的格式,为后期扩展出通用的Prometheus监控指标采集和埋点提供了基础。接口定义如下,

Class HospModule.HxeyPrometheusMetrics.RestController Extends %CSP.REST
{

XData UrlMap
{
<Routes>
    <Route Url="/prometheus" Method="GET" Call="MetricsPrometheus" />
    <Route Url="/json" Method="GET" Call="MetricsJson" />
  </Routes>
}

ClassMethod MetricsPrometheus() As %Status
{
    set %response.ContentType="text/plain;version=0.0.4;charset=utf-8"
    s json=..getMetricsJson()
    s iter=json.%GetIterator()
    while iter.%GetNext(.key, .value) { 
        s promStr = ..jsonToPrometheus(value)
        w promStr
    }
    return $$$OK
}

ClassMethod MetricsJson() As %Status
{
    s json=..getMetricsJson()
    w json.%ToJSON()
    return $$$OK
}

/// Requires JSON format in this form: {"name":"hxey_ics_license_used","tags":[{"name","","value":""}],"desc":"已用License数","value":0}
ClassMethod jsonToPrometheus(json) As %String
{
    s retStr = ""
    s name=json.name
    s desc=json.desc
    s value=json.value
    s tags=json.tags

    s retStr = retStr_"# HELP "_name_" "_desc
    s retStr = retStr_$CHAR(10)
    s retStr = retStr_"# TYPE "_name_" gauge"
    s retStr = retStr_$CHAR(10)
    if (tags.%Size()=0) {
        s retStr = retStr_name_" "_value
        s retStr = retStr_$CHAR(10)
    } else {
        s retStr = retStr_name_"{"
        s iter2=tags.%GetIterator()
        while iter2.%GetNext(.key, .tag) { 
            s tagName=$REPLACE(tag.name,"""","")
            s tagValue=$REPLACE(tag.value,"""","")
            s retStr = retStr_tagName_"="""_tagValue_""","
        }
            
        s retStr = retStr_"} "_value
        s retStr = retStr_$CHAR(10)
    }
    return retStr
}

ClassMethod getMetricsJson() As %DynamicAbstractObject
{
    s json=[]
    IF (##class(%Dictionary.CompiledClass).%ExistsId("%SYSTEM.License")) {
        s LicenseUsed=##class(%SYSTEM.License).LUConsumed()
        s item={"name":"hxey_ics_license_used","tags":"","desc":"已用License数","value":0}
        s item.tags = []
        s item.value = LicenseUsed
        d json.%Push(item)

        s LicenseAvailable=##class(%SYSTEM.License).LUAvailable()
        s item={"name":"hxey_ics_license_avail","tags":"","desc":"可用License数","value":0}
        s item.tags = []
        s item.value = LicenseAvailable
        d json.%Push(item)

        s LicenseTotal=##class(%SYSTEM.License).GetUserLimit()
        s item={"name":"hxey_ics_license_total","tags":"","desc":"License总量","value":0}
        s item.tags = []
        s item.value = LicenseTotal
        d json.%Push(item)

        s item={"name":"hxey_ics_license_load","tags":"","desc":"License占用率","value":0}
        s item.tags = []
        s item.value = (LicenseUsed/(LicenseAvailable+LicenseUsed)) 
        d json.%Push(item)
    }

    s maxDuration=0
    IF (##class(%Dictionary.CompiledClass).%ExistsId("%SYS.Journal.Transaction")) {
        set rs=##class(%ResultSet).%New()
        set rs.ClassName="%SYS.Journal.Transaction"
        set rs.QueryName="List"
        set sc=rs.Execute(0)
        while (rs.%Next()) {
            s pid = rs.Data("Pid")
            s StartTime = rs.Data("StartTime")
            s now=$H

            s item={"name":"hxey_ics_transaction_duration","tags":"","desc":"事务持续时间","value":0}
            s item.tags = []
            s tag={}
            s tag.name="pid"
            s tag.value=pid
            d item.tags.%Push(tag)
            s tag={}
            s tag.name="ip"
            s tag.value=##class(%SYSTEM.Process).ClientIPAddress(pid)
            d item.tags.%Push(tag)
            s duration = (+now)*3600*24+$P(now,",",2)-((+StartTime)*3600*24+$P(StartTime,",",2))
            s:duration>maxDuration maxDuration=duration
            s item.value = duration
            d json.%Push(item)
        }
    }

    s item={"name":"hxey_ics_transaction_maxduration","tags":"","desc":"最长事务持续时间","value":0}
    s item.tags = []
    s item.value = maxDuration
    d json.%Push(item)

    IF (##class(%Dictionary.CompiledClass).%ExistsId("%SYS.DatabaseQuery")) {
        Set statement=##class(%SQL.Statement).%New(1)
        Set sc=statement.%PrepareClassQuery("%SYS.DatabaseQuery","FreeSpace")
        Set rset=statement.%Execute()
        while (rset.%Next()) {
            s dbName = rset.%Get("DatabaseName")
            s freeRate = rset.%GetData(7)
            s dbAvailMB = rset.%Get("AvailableNum")
            s diskAvailMB = rset.%Get("DiskFreeSpaceNum")

            s item={"name":"hxey_ics_db_load","tags":"","desc":"数据库可用空间占用率","value":0}
            s item.tags = []
            s tag={}
            s tag.name="name"
            s tag.value=dbName
            d item.tags.%Push(tag)
            s item.value = (100-freeRate) / 100
            d json.%Push(item)
        }
    }

    IF (##class(%Dictionary.CompiledClass).%ExistsId("Ens.Queue")) {
        Set statement=##class(%SQL.Statement).%New(1)
        Set sc=statement.%PrepareClassQuery("Ens.Queue","Enumerate")
        Set rset=statement.%Execute()
        while (rset.%Next()) {
            s name = rset.%Get("Name")
            s count = rset.%Get("Count")

            s item={"name":"hxey_ics_queue_load","tags":"","desc":"队列等待数量","value":0}
            s item.tags = []
            s tag={}
            s tag.name="name"
            s tag.value=name
            d item.tags.%Push(tag)
            s item.value = count
            d json.%Push(item)
        }
    }
    return json
}

}

接口服务配置如下,

Web Application Config

安装并运行Prometheus

Prometheus下载地址为https://prometheus.io/download/

安装过程如下,

  • 解压缩到安装目录。本文使用/opt/prometheus作为安装目录。下述子目录及文件均相对于该安装目录。

  • 修改配置文件prometheus.yml

    • 主要配置内容为采集间隔和采集接口列表,如下, imageimage

    • 其他配置,如告警管理服务器和告警规则,可以按需配置。本文使用Grafana配置告警,不在这里配置。

    • Prometheus启动后,会定时、或者在收到SIGHUP时重新加载该配置。

  • 启动后

    • 访问Web端口,默认ip:9090,可查看配置、服务运行情况。 image
  • 使用Systemd托管服务

    • 新增文件/usr/lig/systemd/system/prometheus.service,并写入如下内容,

      [Unit]
      Description=Prometheus Service
      After=network.target
      
      [Service]
      Type=simple
      User=app
      Restart=on-failure
      RestartSec=40s
      ExecStart=/opt/prometheus/prometheus \
              --config.file=/opt/prometheus/prometheus.yml \
              --storage.tsdb.path=/opt/prometheus/data \
              --storage.tsdb.retention=90d 
      ExecReload=/bin/kill -HUP $MAINPID
      KillMode=process
      
      [Install]
      WantedBy=multi-user.target
      
    • 添加为开机自动启动服务,在命令行执行 systemctl enable prometheus

    • 立刻启动服务,在命令行执行 systemctl start prometheus

    • 需要查看服务状态时,在命令行执行 systemctl status prometheus

    • 需要服务动态更新配置时,在命令行执行 systemctl reload prometheus

    • 需要关闭服务时,在命令行执行 systemctl stop prometheus

安装并运行Grafana

Grafana下载地址为https://grafana.com/grafana/download?pg=oss-graf&plcmt=resources

安装过程如下,

  • 解压缩到安装目录。本文使用/opt/grafana作为安装目录。下述子目录及文件均相对于该安装目录。

  • 修改配置文件conf/defaults.ini

    • 按需要修改文件存储地址,例如数据地址、日志地址。 image
    • 其他配置例如监听端口(http_port)、监听地址(http_addr)、数据库([database]章节)、接口安全配置([security]章节)都可以按需修改。
    • 默认情况,Grafana Web服务在ip:3000,使用内置SQLite数据库保存配置,接口无鉴权,默认管理员为admin
  • 启动后

    • 访问Web端口,默认ip:3000,可配置数据源、用户、报表,如下图。 image
  • 使用Systemd托管服务

    • 新增文件/usr/lig/systemd/system/grafana.service,并写入如下内容,

      [Unit]
      Description=Grafana Service
      After=network.target
      
      [Service]
      Type=simple
      User=app
      Restart=on-failure
      RestartSec=5s
      ExecStart=/opt/grafana/bin/grafana-server \
              -homepath=/opt/grafana \
              -config=/opt/grafana/conf/defaults.ini
      KillMode=process
      [Install]
      WantedBy=multi-user.target
      
    • 添加为开机自动启动服务,在命令行执行 systemctl enable grafana

    • 立刻启动服务,在命令行执行 systemctl start grafana

    • 需要查看服务状态时,在命令行执行 systemctl status grafana

    • 需要关闭服务时,在命令行执行 systemctl stop grafana

安装并运行Node Exporter

Prometheus生态中有一个Node Exporter,用于收集各操作系统的常用监控数据并暴露PULL接口。其下载地址和安装指南请参考https://grafana.com/oss/prometheus/exporters/node-exporter/

在实际应用时,Node Exporter提供的数据对不同操作系统略有差异,并且缺少必要的数据,例如与NTP时间服务器的时间偏置量。因此本文用Java实现了适合本机构的Node Exporter。为了实现稳定的跨操作系统的性能指标收集,使用 com.github.oshi:oshi-core:5.8.4库,示例代码如下,

    public double getLoad() {
        SystemInfo systemInfo = new SystemInfo();
        double load1 = systemInfo.getHardware().getProcessor().getSystemCpuLoadBetweenTicks(ticks);
        double load2 = systemInfo.getHardware().getProcessor().getSystemCpuLoadBetweenTicks(ticksLast);
        return Math.max(load1, load2);
    }

为了实现计算与NTP时间服务器的时间偏置量,使用commons-net:commons-net:3.8.0库,示例代码如下,

    public long getOffset() {
        NTPUDPClient ntpudpClient = new NTPUDPClient();
        ntpudpClient.setDefaultTimeout(5000);
        ntpudpClient.open();
        System.out.println(System.currentTimeMillis());
        InetAddress hostAddr = InetAddress.getByName(HOSTNAME);
        final TimeInfo info = ntpudpClient.getTime(hostAddr);
        ntpudpClient.close();
        info.computeDetails();
        return info.getOffset()
    }

接口实现效果如下,

# HELP hxey_memory_load 内存负载率
# TYPE hxey_memory_load gauge
hxey_memory_load{application="sysmetrics",hxey="hxey",} 0.42
# HELP disk_total_bytes Total space for path
# TYPE disk_total_bytes gauge
disk_total_bytes{application="sysmetrics",hxey="hxey",path="C:\\Users\\Administrator\\Desktop\\servermetrics\\.",} 2.78802722816E11
# HELP hxey_memory_total 内存总大小
# TYPE hxey_memory_total gauge
hxey_memory_total{application="sysmetrics",hxey="hxey",} 4.0
# HELP system_cpu_count The number of processors available to the Java virtual machine
# TYPE system_cpu_count gauge
system_cpu_count{application="sysmetrics",hxey="hxey",} 2.0
# HELP hxey_timestamp 服务器时间戳
# TYPE hxey_timestamp gauge
hxey_timestamp{application="sysmetrics",hxey="hxey",} 1.664506300883E12
# HELP hxey_disk_used 硬盘使用大小
# TYPE hxey_disk_used gauge
hxey_disk_used{application="sysmetrics",disk_label="C:\\[]",hxey="hxey",} 27.38
hxey_disk_used{application="sysmetrics",disk_label="D:\\[]",hxey="hxey",} NaN
hxey_disk_used{application="sysmetrics",disk_label="A:\\[]",hxey="hxey",} NaN
# HELP hxey_disk_total 硬盘总大小
# TYPE hxey_disk_total gauge
hxey_disk_total{application="sysmetrics",disk_label="C:\\[]",hxey="hxey",} 259.66
hxey_disk_total{application="sysmetrics",disk_label="D:\\[]",hxey="hxey",} NaN
hxey_disk_total{application="sysmetrics",disk_label="A:\\[]",hxey="hxey",} NaN

结果

搭建完成的Prometheus监控框架,能够准实时的收集和展示Cache中事务、Lisence使用率、消息队列等待长度、数据库使用率,以及操作系统时间偏置量、磁盘使用率、CPU使用率、内存使用率等指标。

image

image

image

讨论

任何监控系统,都是定时采集的,采集得到的指标是对现实中的连续指标的一个抽样。管理人员不能通过监控指标定位到故障原因,甚至不能精确定位到故障的实际发生时间。 为了弥补监控指标的缺憾,后期应为Cache集群添加日志采集系统,如Loki日志采集工具,建立起监控指标的异常和日志输出的对应关系,让异常定位更准确。

8
0 978
文章 Qiao Peng · 一月 31, 2024 21m read

InterSystems IRIS、Health Connect和上一代的Ensemble提供了优秀的互操作架构,但即便有低代码开发能力,很多开发者还是希望能用自己的技术栈语言在InterSystems的产品上开发互操作产品。

考虑到互操作产品本身的开放性要求和各个技术栈背后庞大的生态价值,InterSystems IRIS和Health Connect提供了Production EXtension (PEX)架构,让开发者使用自己的技术栈语言来开发互操作解决方案。目前PEX支持Java、.net、Python。

这里我们介绍使用Java利用PEX进行互操作产品的开发。

一 InterSystems IRIS上使用Java开发的基础

在进入PEX主题前,需要简单介绍一下Java在InterSystems IRIS上开发的各种技术选项,因为PEX也是以这些技术选项为基础的。

0
1 274
文章 Michael Lei · 一月 26, 2024 2m read

InterSystems 常见问题解答

如果系统24小时没有停止,旧的日志文件将根据“日志文件删除设置”在0:30删除。

导致日志文件保留的时间早于“日志文件删除设置”的一个可能原因是存在仍处于开放状态的事务。

在这种情况下,您将能够通过搜索执行事务的进程并完成事务来删除日志文件。

下面的示例检查是否存在未完成的事务,如果存在,则输出目标文件名和日志记录信息。

(示例可以从这里下载

*注意*如果要检查的日志文件较大或日志文件较多,则执行需要时间,因此请联系我们的支持中心。

0
0 86
文章 Michael Lei · 一月 15, 2024 2m read

作为针对数据导入处理性能和错误(锁定表已满)的衡量标准,可能需要调整常规内存堆 (gmheap) 和锁定表大小 (locksiz) 参数。

事实上,您可以使用终端和管理门户来检查当前分配了多少通用内存堆。


★终端用

// 一般メモリヒープサマリUSER> w $system .Config.SharedMemoryHeap.GetUsageSummary() 4992226 , 6029312 , 59441152

通用内存堆摘要以使用量、分配量和配置量(字节)的形式显示返回值。

使用量是分配的锁表、进程表等实际使用的量。
分配量是gmheap区域中锁表、进程表等分配的量。
配置量为gmheap(KB)+IRIS系统附加区,即当前最大可用量(实际通用内存堆区值)。

如上所述,配置数量与配置参数 gmheap 的独立值不匹配。
这是因为IRIS自动将内部使用的内存区域添加到配置参数gmheap中来配置通用内存堆区域。详情请参阅下面的文档。

关于gmheap

您可以使用以下命令获取锁表的使用情况:
返回值显示为可用量、用户可用量和已用量(字节)。详情请参阅这篇文章

%SYS > w##class (SYS. Lock ).GetLockSpaceInfo() 16772624 , 16764624 , 4592


★用于管理门户

您可以从“系统操作”>“系统使用情况”>“共享内存堆使用状态”进行检查。  

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

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

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

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

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

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

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

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

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

0
0 151
问题 water huang · 十一月 28, 2023

我创建了一个类,里面有个属性OPDT 是 %Library.DateTime类型的,类继承了%XML.Adaptor,我是用 d obj.XMLExportToString(.xml)  导出为xml后,OPDT的值是 2023-11-28T13:57:26  这样的,我需要的值是 2023-11-28 13:57:26   这样的,T需要换成“ ”,有没有什么方式能设置导出的数据格式?

1
0 116
文章 Yuxiang Niu · 十一月 12, 2023 3m read

在日常Cache运维过程中可能会由于数据或者程序等原因造成锁的异常增长,导致数据库性能受到影响会出现程序报错或卡顿无法正常运行的问题。遇到此类问题需查看数据库当前锁列表情况,找到出现次数最多关键锁,根据关键锁对应的进程来判断处理。总结有以下三种方式查看关键锁。

  1. 可在portal[Locks]中查看;
  2. 可在terminal端的%sys下使用Do ^LOCKTAB命令下查看;
  3. 通过自定义程序查看。

查看方式

优点

缺点

第一种

易操作、方式简便

慢、锁数量太多无法显示

第二种

快、不受网页限制

易忘、需要输入准确命令

第三种

快、灵活、直接显示关键锁信息

需定位准确命名空间

下面给出自定义程序实例,程序逻辑为按命名空间循环所有锁信息,通过计数器方式记录所有锁当中出现次数最多的一个,输出其信息。入参为数据库中不同命名空间,输出结果为锁名称及锁的所有者,所有者一般为进程IDECP

2
0 202
文章 liu bo · 九月 19, 2023 4m read

前言 {#1}

ensemble里边实现分页比较麻烦,毕竟对于sql的书写比较麻烦,单表的查询相对简单,对于多表的关联查询单纯的sql不好查询,我们使用sql进行先查询出主表满足条件的rowId,在根据根据满足条件的rowid进行遍历取值。

思路

我们先取对比一下其他数据库实现的原理。

  1. Mysql的实现原理 总数:SELECT COUNT(*) AS total FROM person WHERE (name LIKE ?) 分页:SELECT id,name,age,email FROM person WHERE (name LIKE ?) LIMIT ?,?

  2. ORACLE的实现原理 rownum 总数:SELECT COUNT() AS total FROM person WHERE (name LIKE ?) 分页:SELECT * FROM ( SELECT TMP., ROWNUM ROW_ID FROM ( SELECT id,name,age,email FROM person WHERE (name LIKE ?) ) TMP WHERE ROWNUM <=?) WHERE ROW_ID > ?

  3. 由于cache没有limit关键字,看看有没有和oracle里边rownum一样的原理。Cache的实现原理和oracle类似 %VID 只查询主键id,在遍历取值 总数:select count() FROM Design_Page.Person WHERE birth<'1988-12-1' 分页:SELECT * FROM ( SELECT %VID ROWNUM ,TMP. FROM ( SELECT * FROM Design_Page.Person WHERE birth<'1988-12-1' ) TMP WHERE %VID <=15) WHERE ROWNUM > 5

代码构建

  1. 构建查询的抽象的AbstractQueryWrapper包装类 `

    Class Design.Page.V1.AbstractQueryWrapper Extends %RegisteredObject {

        /// 构建sql的运算符号
        Parameter AND = "AND";
    
        Parameter OR = "OR";
    
        Parameter NOT = "NOT";
    
        Parameter IN = "IN";
    
        Parameter NOTIN = "NOT IN";
    
        Parameter LIKE = "LIKE";
    
        Parameter NOTLIKE = "NOT LIKE";
    
        Parameter EQ = "=";
    
        Parameter NE = "!=";
    
        Parameter GT = ">";
    
        Parameter GE = ">=";
    
        Parameter LT = "<";
    
        Parameter LE = "<=";
    
        Parameter ISNULL = "IS NULL";
    
        Parameter ISNOTNULL = "IS NOT NULL";
    
        Parameter GROUPBY = "GROUP BY";
    
        Parameter HAVING = "HAVING";
    
        Parameter ORDERBY = "ORDER BY";
    
        Parameter EXISTS = "EXISTS";
    
        Parameter NOTEXISTS = "NOT EXISTS";
    
        Parameter BETWEEN = "BETWEEN";
    
        Parameter NOTBETWEEN = "NOT BETWEEN";
    
        Parameter ASC = "ASC";
    
        Parameter DESC = "DESC";
    
        /// 抽象类
        Method addCondition(coloumParams As %String) [ Abstract ]
        {
        }
    
        /// 添加字段之间的条件连接
        Method addConditionOperate(operate As %String) [ Abstract ]
        {
        }
    
        /// 等于的条件
        Method eq(column As %String, val As %String)
        {
           d ..addCondition(" "_column_..#EQ _"'"_val_"' ")
           q $this
        }
    
        /// 不等于
        Method ne(column As %String, val As %String)
        {
           d ..addCondition(" "_ column_..#NE _"'"_val_"' ")
           q $this
        }
    
    /// 大于的条件
    Method gt(column As %String, val As %String)
    {
       d ..addCondition( " "_column_..#GT _"'"_val_"' ")
       q $this
    }
    
    /// 大于等于的条件
    Method ge(column As %String, val As %String)
    {
       d ..addCondition( " "_column_..#GE _"'"_val_"' ")
       q $this
    }
    
    /// 小于的条件
    Method lt(column As %String, val As %String)
    {
       d ..addCondition(" "_column_..#LT _"'"_val_"' ")
       q $this
    }
    
    /// 小于等于条件
    Method le(column As %String, val As %String)
    {
       d ..addCondition( " "_column_..#LE _"'"_val_"' ")
       q $this
    }
    
    /// like 模糊匹配
    Method like(column As %String, val As %String)
    {
       d ..addCondition( " "_column_" "_..#LIKE _" '%"_val_"%' ")
       q $this
    }
    
    /// not like 模糊匹配
    Method notLike(column As %String, val As %String)
    {
       d ..addCondition( " "_column_" "_..#NOTLIKE _" '%"_val_"%' ")
       q $this
    }
    
    /// 左匹配 模糊匹配
    Method likeLeft(column As %String, val As %String)
    {
       d ..addCondition( " "_column_" "_..#LIKE _" '"_val_"%' ")
       q $this
    }
    
    /// 右匹配
    Method likeRight(column As %String, val As %String)
    {
       d ..addCondition( " "_column_" "_..#NOTLIKE _" '%"_val_"' ")
       q $this
    }
    
    /// between 拼接
    Method between(column As %String, startVal As %String, endVal As %String)
    {
       d ..addCondition( " "_column_" "_..#BETWEEN _" '"_startVal_"' "_..#AND_"'"_endVal_"' ")
       q $this
    }
    
    /// notBetween 拼接
    Method notBetween(column As %String, startVal As %String, endVal As %String)
    {
       d ..addCondition( " "_column_" "_..#NOTBETWEEN _" '"_startVal_"' "_..#AND_"'"_endVal_"' ")
       q $this
    }
    
    /// 字段值为空
    Method isNull(column As %String)
    {
       d ..addCondition( " "_column_" "_..#ISNULL _" ")
       q $this
    }
    
    /// 非空
    Method isNotNull(column As %String)
    {
       d ..addCondition( " "_column_" "_..#ISNOTNULL_" ")
       q $this
    }
    
    /// in 
    Method in(column As %String, valueList As %String, Separator As %String = "^")
    {
       d ..addCondition( " "_column_" "_..#IN_"("_..ConcatListParams(valueList,Separator)_")")
       q $this
    }
    
    /// not in 
    Method notIn(column As %String, valueList As %String, Separator As %String = "^")
    {
       d ..addCondition( " "_column_" "_..#NOTIN_"("_..ConcatListParams(valueList,Separator)_")")
       q $this
    }
    
    /// in list的参数拼接
    Method ConcatListParams(valList As %String, Separator As %String)
    {
    	s paramsLen=$l(valList,Separator)
    	q:paramsLen=1 "'"_valList_"'"
    	s paramsList =$lb("")
    	for i=1:1:paramsLen{
    	  if (i=1)  s $LIST(paramsList,1)=$p(valList,Separator,i)
    	  else  s $LIST(paramsList,*+1)=$p(valList,Separator,i)
    	}
    	q "'"_$LISTTOSTRING(paramsList,"','")_"'"
    }
    
    }
    

`

  1. 构建查询的包装类queryWrapper `

    /// 包装查询的列和查询条件以及运算符 Class Design.Page.V1.QueryWrapper Extends AbstractQueryWrapper {

    /// sql查询的列
    Property SelectColoums As %String;
    
    /// 查询的表对应的schema
    Property querySchema As %String [ InitialExpression = "SQLUser" ];
    
    /// 查询的表
    Property queryTable As %String;
    
    /// 查询条件
    Property queryCondition As %String;
    
    /// 字段,值,关系运算符,逻辑运算符构建查询条件
    Method addCondition(coloumParams As %String)
    {
    	s ..queryCondition=..queryCondition_" "_coloumParams
    }
    
    /// and 连接字段
    Method and()
    {
       s ..queryCondition=..queryCondition_" "_..#AND
       q $this
    }
    
    Method or()
    {
       s ..queryCondition="("_..queryCondition_") "_..#OR
       q $this
    }
    
    }
    

3. 构建查询总数和过滤条件的sqlBuilder

/// 构建查询的sql语句
Class Design.Page.V1.SqlBuilder Extends %RegisteredObject
{

Property queryWrapper As QueryWrapper;

Method %OnNew(queryWrapper As QueryWrapper) As %Status [ Private, ServerOnly = 1 ]
{
	s ..queryWrapper=queryWrapper
	Quit $$$OK
}

/// 构建查询的总数据的sql
Method bulidCountSql()
{
	q "select count(*) totalcount from "_..queryWrapper.querySchema_"."_..queryWrapper.queryTable_" where"_..queryWrapper.queryCondition
}

/// 构建查询执行查询业务的sql
Method bulidBusiSql()
{
	q "select "_..queryWrapper.SelectColoums_" from "_..queryWrapper.querySchema_"."_..queryWrapper.queryTable_" where"_..queryWrapper.queryCondition
}

Method bulidPageSql(stOffset As %Integer, endOffset As %Integer)
{
	s businessSql=..bulidBusiSql()
	q "SELECT * FROM ( SELECT  %VID ROWNUM ,TMP.* FROM ( "_businessSql_") TMP WHERE %VID <= "_endOffset_" ) WHERE ROWNUM > "_stOffset
}

}

4. 构建返回数据列表的IPage

/// 分页插件
Class Design.Page.V1.IPage Extends %RegisteredObject
{

/// 数据列表
Property Data As list Of %RegisteredObject;

/// 查询列表总记录数 0
Property total As %Integer [ InitialExpression = 0 ];

/// 总页数
Property pages As %Integer [ InitialExpression = 0 ];

/// 每页显示条数,默认 10
Property pageSize As %Integer [ InitialExpression = 10 ];

/// 当前页
Property currentPage As %Integer [ InitialExpression = 1 ];

/// 当前计数器
Property currentCount As %Integer [ InitialExpression = 0 ];

/// 单页分页条数限制
Property maxLimit As %Integer;

/// 分页的最后一次循环的ID
Property currentId As %String;

/// /插入数据
Method InternalInsert(obj As %ObjectHandle)
{
   q ..Data.Insert(obj)
}

/// 执行往list里边插入对象的操作
Method doInsert(obj As %ObjectHandle) As %Status
{
    s currentPage=..currentPage
	s pageSize=..pageSize
	s currentCount=..currentCount+1
	s ..currentCount=currentCount
	d:(currentPage=1) ..InternalInsert(obj)
    d:((currentCount>((currentPage-1)*pageSize))&&(pageSize>0)&&(currentPage>1)) ..InternalInsert(obj)
    ;实际的页数大于等于分页的数 退出循环
    q ..Data.Count()>=pageSize
}

/// 根据计算起始数和限制查询的条数
Method getOffset(Output stOffset, Output endOffset)
{
	 ;分页数
	 i ..total # ..pageSize=0  d
	 .s ..pages= ..total/..pageSize
	 e  s ..pages=$System.SQL.FLOOR(..total/..pageSize) +1
	 ;当前页数
     s currentPage = ..currentPage
     i currentPage=1{
	     s stOffset=0
	     s endOffset=..pageSize
     }else{
	     s stOffset=(currentPage-1)*..pageSize
	     s endOffset=currentPage*..pageSize
     }
     q $$$OK
}

/// 获取查询的结果的ID
Method selectPage(queryWrapper As QueryWrapper, Output ok) As %ArrayOfDataTypes
{
	s ret = ##Class(%ArrayOfDataTypes).%New()
	//拼接sql执行查询总数
	s ok=$$$OK
	s sqlBuilder=##class(Design.Page.V1.SqlBuilder).%New(queryWrapper)
	s countTotalSql=sqlBuilder.bulidCountSql()
	d ..exeCountTotalSql(countTotalSql)
	q:..total=0 ret
	///计算分页
	d ..getOffset(.stOffSet,.edOffSet)
	///获取分页执行sql
	s pageSql=sqlBuilder.bulidPageSql(stOffSet,edOffSet)
    ///返回结果集的ID
	q ..exePageSql(pageSql)
}

/// 执行查询分页sql
Method exePageSql(sql) As %ArrayOfDataTypes
{
	s ret = ##Class(%ArrayOfDataTypes).%New()
    s rset = ##class(%ResultSet).%New()
	d rset.Prepare(sql)
	d rset.Execute()
	i rset.QueryIsValid() d
	.While (rset.Next()) {
	.d ret.SetAt(rset.GetData(1),rset.GetData(2))
	.}
	q ret
}

/// 执行查询总数的sql
Method exeCountTotalSql(sql) As %Status
{
    s rset = ##class(%ResultSet).%New()
	d rset.Prepare(sql)
	s sc= rset.Execute()
	i rset.QueryIsValid() d
	.While (rset.Next()) {
	. s ..total= rset.GetData(1)
	.}
	q $$$OK
}

}

`

测试

  1. 自定义的objlist需要继承IPage `

           ///定义返回的对象列表
            Class Design.Page.ObjList Extends (Design.Page.V1.IPage, %XML.Adaptor)
               {
    
                        /// 数据列表
                        Property Data As list Of Object;
    
               }
                 ///单个对象
                Class Design.Page.Object Extends (%RegisteredObject, %XML.Adaptor)
                {
    
                Property PatientName As %String;
    
                Property PatientNo As %String;
    
                }
    

`

2.测试代码

`

/// 分页查询
ClassMethod selectPage()
{
	s $zt="Err"
	//当前页数
	s currentPage=1
	//每页的大小
	s pageSize=10
	s objlist=##class(Design.Page.ObjList).%New()
	s objlist.currentPage=currentPage
	s objlist.pageSize=pageSize
	//构建查询的条件
	s queryWrapper =##class(Design.Page.QueryWrapper).%New()
	s queryWrapper.SelectColoums="ID"
	s queryWrapper.querySchema="Design_Page"
	s queryWrapper.queryTable="Person"
	d queryWrapper.lt("birth",$zdh("2023-12-1",3)).and().like("name","in")
	;执行查询查询获取Id
	s rset=objlist.selectPage(queryWrapper,.ok)
	q:ok'=1 "调用出现异常"
	q:objlist.total=0 "未查询到数据!"
	q:rset.Count()=0 "未查询到数据!"
	s RowId=""
	while(rset.GetNext( .RowId)){
		continue:'$d(^Design.Page.PersonD(RowId))
		s obj=##class(Design.Page.Object).%New()
		s obj.PatientName=$lg(^Design.Page.PersonD(RowId),2)		;患者姓名
	    s obj.PatientNo=$lg(^Design.Page.PersonD(RowId),3)		;病人ID号
	    d objlist.Data.Insert(obj)
	}
	w objlist.Data.Count(),!
	d objlist.XMLExportToString(.xml)
	w xml,!
	q
Err
   w $ze,!
   q $$$OK
}

`

3.查询结果 `

<ObjList>
	<total>23</total>
	<pages>3</pages>
	<pageSize>10</pageSize>
	<currentPage>1</currentPage>
	<currentCount>0</currentCount>
	<Data>
		<Object>
			<PatientName>Ingrahm,Michelle X.</PatientName>
			<PatientNo>436244981</PatientNo>
		</Object>
		<Object>
			<PatientName>Koivu,Clint W.</PatientName>
			<PatientNo>473036353</PatientNo>
		</Object>
		<Object>
			<PatientName>Avery,Josephine F.</PatientName>
			<PatientNo>815934238</PatientNo>
		</Object>
		<Object>
			<PatientName>Thompson,Clint M.</PatientName>
			<PatientNo>970071592</PatientNo>
		</Object>
		<Object>
			<PatientName>Ingersol,Diane S.</PatientName>
			<PatientNo>949798228</PatientNo>
		</Object>
		<Object>
			<PatientName>Quince,Sally E.</PatientName>
			<PatientNo>643134733</PatientNo>
		</Object>
		<Object>
			<PatientName>Novello,Clint Y.</PatientName>
			<PatientNo>612491568</PatientNo>
		</Object>
		<Object>
			<PatientName>Ingrahm,Buzz O.</PatientName>
			<PatientNo>72704061</PatientNo>
		</Object>
		<Object>
			<PatientName>Ihringer,Chris M.</PatientName>
			<PatientNo>112730429</PatientNo>
		</Object>
		<Object>
			<PatientName>Anderson,Vincent V.</PatientName>
			<PatientNo>507161056</PatientNo>
		</Object>
	</Data>
</ObjList>

`

2
0 355