搜索​​​​

文章
Michael Lei · 十二月 7, 2022

ECP 与 Docker

大家好! 这是关于使用 Docker 初始化 IRIS 实例的系列文章中的第三篇。 这次,我们将关注企业缓存协议(**E**nterprise **C**ache **P**rotocol,ECP)。 ECP 允许以一种非常简单的方式将某些 IRIS 实例配置为应用程序服务器,将其他实例配置为数据服务器。 有关详细的技术信息,请参阅官方文档。 本文旨在介绍: * 如何编写数据服务器的初始化脚本,以及如何编写一个或多个应用程序服务器的初始化脚本。 * 如何使用 Docker 在这些节点之间建立加密连接。 为此,我们通常使用我们在以前的 Web 网关中已经看到的一些工具,以及描述 OpenSSL、envsubst 和 Config-API 等工具的镜像文章。 ## 要求 ECP 不适用于 IRIS 社区版。 因此,需要访问全球响应中心才能下载容器许可证并连接到 containers.intersystems.com 注册表。 ## 准备系统 系统必须与容器共享一些本地文件。 需要创建特定用户和组来避免出现“访问被拒绝”错误。 ```bash sudo useradd --uid 51773 --user-group irisowner sudo useradd --uid 52773 --user-group irisuser sudo groupmod --gid 51773 irisowner sudo groupmod --gid 52773 irisuser ``` 如果您还没有“iris.key”许可证,请从 WRC 下载,并将其添加到您的主目录中。 ## 检索示例存储库 除“iris.key”许可证外,您需要的所有其他文件都可以在公共存储库中找到,因此,首先将其克隆: ```bash git clone https://github.com/lscalese/ecp-with-docker.git cd ecp-with-docker ``` ## SSL 证书 为了加密应用程序服务器与数据服务器之间的通信,我们需要 SSL 证书。 可以使用现成的脚本(“gen-certificates.sh”)。 但是,您可以随意修改脚本,使证书设置与您的位置、公司等保持一致。 执行: ```bash sh ./gen-certificates.sh ``` 生成的证书现在位于“./certificates”目录中。 | 文件 | 容器 | 描述 | | ------------------------------ | ------------- | ---------------- | | ./certificates/CA_Server.cer | 应用程序服务器和数据服务器 | 机构服务器证书 | | ./certificates/app_server.cer | 应用程序服务器 | IRIS 应用程序服务器实例证书 | | ./certificates/app_server.key | 应用程序服务器 | 相关私钥 | | ./certificates/data_server.cer | 数据服务器 | IRIS 数据服务器实例证书 | | ./certificates/data_server.key | 数据服务器 | 相关私钥 | ## 构建镜像 首先,登录 Intersystems docker 注册表。 在构建期间,将从注册表中下载基础镜像: ```bash docker login -u="YourWRCLogin" -p="YourICRToken" containers.intersystems.com ``` 如果您不知道自己的Token,请使用您的 WRC 帐户登录 https://containers.intersystems.com/。 在此构建过程中,我们将向 IRIS 基础镜像添加一些软件实用程序: * **gettext-base**:它将允许我们使用“envsubst”命令替换配置文件中的环境变量。 * **iputils-arping**:如果我们想要镜像数据服务器,则需要使用此实用程序。 * **ZPM**:ObjectScript 软件包管理器。 [Dockerfile](https://github.com/lscalese/ecp-with-docker/blob/master/Dockerfile): ``` ARG IMAGE=containers.intersystems.com/intersystems/iris:2022.2.0.281.0 # Don't need to download the image from WRC. It will be pulled from ICR at build time. FROM $IMAGE USER root # Install iputils-arping to have an arping command. It's required to configure Virtual IP. # Download the latest ZPM version (ZPM is included only with community edition). RUN apt-get update && apt-get install iputils-arping gettext-base && \ rm -rf /var/lib/apt/lists/* USER ${ISC_PACKAGE_MGRUSER} WORKDIR /home/irisowner/demo RUN --mount=type=bind,src=.,dst=. \ iris start IRIS && \ iris session IRIS < iris.script && \ iris stop IRIS quietly ``` 此 Dockerfile 中除最后一行外没有什么特别之处。 它将 IRIS 数据服务器实例配置为最多接受 3 个应用程序服务器。 请注意,此配置需要重新启动 IRIS。 我们在构建过程中分配此参数的值,以避免稍后编写重新启动脚本。 开始构建: ```bash docker-compose build –no-cache ``` ## 配置文件 在配置 IRIS 实例(应用程序服务器和数据服务器)时,我们使用 JSON config-api 文件格式。 您会注意到这些文件包含环境变量 "${variable_name}"。 它们的值在“docker-compose.yml”文件的“environment”部分定义,我们稍后将在本文档中看到。 这些变量将在使用“envsubst”实用程序加载文件之前被替换掉。 ### 数据服务器 对于数据服务器,我们将: * 启用 ECP 服务并定义授权客户端(应用程序服务器)列表。 * 创建加密通信所需的“SSL %ECPServer”配置。 * 创建“myappdata”数据库。 它将用作来自应用程序服务器的远程数据库。 (data-serer.json)[https://github.com/lscalese/ecp-with-docker/blob/master/config-files/data-server.json] ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true, "ClientSystems":"${CLIENT_SYSTEMS}", "AutheEnabled":"1024" } }, "Security.SSLConfigs": { "%ECPServer": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_SERVER}", "Name": "%ECPServer", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "1", "VerifyPeer": 3 } }, "Security.System": { "SSLECPServer":1 }, "SYS.Databases":{ "/usr/irissys/mgr/myappdata/" : {} }, "Databases":{ "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/" } } } ``` 此配置文件由“init_datasrv.sh”脚本在数据服务器容器启动时加载。 连接到数据服务器的所有应用程序服务器都必须可信。 此脚本将在 100 秒内自动验证所有连接,以限制管理门户中的手动操作。 当然,可以对其进行改进以提高安全性。 ### 应用程序服务器 对于应用程序服务器,我们将: * 启用 ECP 服务。 * 创建通信加密所需的 SSL 配置“%ECPClient”。 * 配置与数据服务器的连接信息。 * 创建远程数据库“myappdata”的配置。 * 在“USER”命名空间中创建到“myappdata”数据库的全局映射“demo.*”。 它可以让我们稍后测试 ECP 的运行。 [app-server.json](https://github.com/lscalese/ecp-with-docker/blob/master/config-files/app-server.json): ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true } }, "Security.SSLConfigs": { "%ECPClient": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_CLIENT}", "Name": "%ECPClient", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "0" } }, "ECPServers" : { "${DATASERVER_NAME}" : { "Name" : "${DATASERVER_NAME}", "Address" : "${DATASERVER_IP}", "Port" : "${DATASERVER_PORT}", "SSLConfig" : "1" } }, "Databases": { "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/", "Name" : "${REMOTE_DB_NAME}", "Server" : "${DATASERVER_NAME}" } }, "MapGlobals":{ "USER": [{ "Name" : "demo.*", "Database" : "myappdata" }] } } ``` 配置文件由“[init_appsrv.sh](https://github.com/lscalese/ecp-with-docker/blob/master/init_appsrv.sh)”脚本在应用程序服务器容器启动时加载。 ## 启动容器 现在,我们可以启动容器: * 2 个应用程序服务器。 * 1 个数据服务器。 为此,请运行: docker-compose up –scale ecp-demo-app-server=2 请参阅 [docker-compose](https://github.com/lscalese/ecp-with-docker/blob/master/docker-compose.yml) 文件以了解详情: ``` # Variables are defined in .env file # to show the resolved docker-compose file, execute # docker-compose config version: '3.7' services: ecp-demo-data-server: build: . image: ecp-demo container_name: ecp-demo-data-server hostname: data-server networks: app_net: environment: # List of allowed ECP clients (application server). - CLIENT_SYSTEMS=ecp-with-docker_ecp-demo-app-server_1;ecp-with-docker_ecp-demo-app-server_2;ecp-with-docker_ecp-demo-app-server_3 # Path authority server certificate - CA_ROOT=/certificates/CA_Server.cer # Path to data server certificate - CA_SERVER=/certificates/data_server.cer # Path to private key of the data server certificate - CA_PRIVATE_KEY=/certificates/data_server.key # Path to Config-API file to initiliaze this IRIS instance - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/data-server.json ports: - "81:52773" volumes: # Post start script - data server initilization. - ./init_datasrv.sh:/home/irisowner/demo/init_datasrv.sh # Mount certificates (see gen-certificates.sh to generate certificates) - ./certificates/app_server.cer:/certificates/data_server.cer - ./certificates/app_server.key:/certificates/data_server.key - ./certificates/CA_Server.cer:/certificates/CA_Server.cer # Mount config file - ./config-files/data-server.json:/home/irisowner/demo/data-server.json # IRIS License - ~/iris.key:/usr/irissys/mgr/iris.key command: -a /home/irisowner/demo/init_datasrv.sh ecp-demo-app-server: image: ecp-demo networks: app_net: environment: # Hostname or IP of the data server. - DATASERVER_IP=data-server - DATASERVER_NAME=data-server - DATASERVER_PORT=1972 # Path authority server certificate - CA_ROOT=/certificates/CA_Server.cer - CA_CLIENT=/certificates/app_server.cer - CA_PRIVATE_KEY=/certificates/app_server.key - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/app-server.json ports: - 52773 volumes: # Post start script - application server initilization. - ./init_appsrv.sh:/home/irisowner/demo/init_appsrv.sh # Mount certificates - ./certificates/CA_Server.cer:/certificates/CA_Server.cer # Path to private key of the data server certificate - ./certificates/app_server.cer:/certificates/app_server.cer # Path to private key of the data server certificate - ./certificates/app_server.key:/certificates/app_server.key # Path to Config-API file to initiliaze this IRIS instance - ./config-files/app-server.json:/home/irisowner/demo/app-server.json # IRIS License - ~/iris.key:/usr/irissys/mgr/iris.key command: -a /home/irisowner/demo/init_appsrv.sh networks: app_net: ipam: driver: default config: # APP_NET_SUBNET variable is defined in .env file - subnet: "${APP_NET_SUBNET}" ``` ## 我们来测试一下! ### 访问数据服务器管理门户 容器已启动。 我们从数据服务器中检查一下状态。 端口 52773 映射到本地端口 81,因此可以使用此地址 [http://localhost:81/csp/sys/utilhome.csp](http://localhost:81/csp/sys/utilhome.csp) 进行访问 使用默认登录名\密码登录,然后转到 System -> Configuration -> ECP Params(系统 -> 配置 -> ECP 参数)。 点击“ECP Application Servers”(ECP 应用程序服务器)。 如果一切正常,您应该会看到 2 个状态为“Normal”(正常)的应用程序服务器。 客户端名称的结构为 "数据服务器名称":"应用程序服务器主机名":"IRIS 实例名称"。 本例中,我们没有设置应用程序服务器主机名,因此我们将获得自动生成的主机名。 ![应用程序服务器列表](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/app-server-list-en.png) ### 访问应用程序服务器管理门户 要连接到应用程序服务器的管理门户,首先需要获取端口号。 由于我们使用了“--scale”选项,我们无法在 docker-compose 文件中设置端口。 因此,必须使用 `docker ps` 命令检索它们: ``` docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1844f38939f ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:81->52773/tcp, :::81->52773/tcp ecp-demo-data-server 4fa9623be1f8 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49170->52773/tcp, :::49170->52773/tcp ecp-with-docker_ecp-demo-app-server_1 ecff03aa62b6 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49169->52773/tcp, :::49169->52773/tcp ecp-with-docker_ecp-demo-app-server_2 ``` 在本示例中,端口: * 49170,用于第一个应用程序服务器 http://localhost:49170/csp/sys/utilhome.csp * 49169,用于第二个应用程序服务器 http://localhost:49169/csp/sys/utilhome.csp ![数据服务器](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/data-server-status-en.png) ### 远程数据库上的读/写测试 我们在终端中执行一些读/写测试。 在第一个应用程序服务器上打开一个 IRIS 终端: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_1 iris session iris Set ^demo.ecp=$zdt($h,3,1) _ “ write from the first application server.” ``` 现在,在第二个应用程序服务器上打开一个终端: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_2 iris session iris Set ^demo.ecp(2)=$zdt($h,3,1) _ " write from the second application server." zwrite ^demo.ecp ``` 您应该会看到两个服务器中的响应: ``` ^demo.ecp(1)="2022-07-05 23:05:10 write from the first application server." ^demo.ecp(2)="2022-07-05 23:07:44 write from the second application server." ``` 最后,在数据服务器上打开一个 IRIS 终端并执行全局 demo.ecp 读取: ``` docker exec -it ecp-demo-data-server iris session iris zwrite ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(1)="2022-07-05 23:05:10 write from the first application server." ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(2)="2022-07-05 23:07:44 write from the second application server." ``` 希望大家喜欢这篇文章。 欢迎您发表评论。
文章
Frank Ma · 六月 13, 2022

使用IRIS IntegratedML(一体化机器学习)预测孕产妇风险的Web应用

孕产妇风险可以通过一些医学界众所周知的参数来测量。这样,为了帮助医学界和计算机系统,特别是人工智能,科学家Yasir Hussein Shakir发布了一个非常有用的数据集,用于训练检测/预测孕产妇风险的机器学习(ML)算法。这份出版物可以在最大和最知名的ML数据库Kaggle上找到,网址是 https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml. 关于数据集 由于缺乏怀孕期间和怀孕后的孕产妇保健信息,许多孕妇死于怀孕问题。这在农村地区和新兴国家的中下层家庭中更为常见。在怀孕期间,应时刻注意观察,以确保婴儿的正常成长和安全分娩 (来源: https://www.kaggle.com/code/yasserhessein/classification-maternal-health-5-algorithms-ml). 数据是通过基于物联网的风险监测系统,从不同的医院、社区诊所、孕产妇保健机构收集而来。 Age(年龄): 妇女怀孕时的年龄,以岁为单位。 SystolicBP (收缩压): 血压的最高值(mmHg),这是怀孕期间的另一个重要属性。 DiastolicBP(舒张压): 血压的较低值(mmHg),这是怀孕期间的另一个重要属性。 BS(血糖): 血糖水平是以摩尔浓度为单位,即mmol/L。 HeartRate(心率): 正常的静息心率,单位是每分钟的心跳次数。 Risk Level(风险等级): 基于前边的属性所预测的孕期风险强度水平。 从Kaggle获取孕产妇的风险数据 来自Kaggle的孕产妇风险数据可以通过Health-Dataset(健康数据集)应用程序加载到IRIS表中: https://openexchange.intersystems.com/package/Health-Dataset. 要做到这一点,在你的module.xml项目中,设置依赖关系(Health Dataset的ModuleReference): Module.xml with Health Dataset application reference <?xml version="1.0" encoding="UTF-8"?> <Export generator="Cache" version="25"> <Document name="predict-diseases.ZPM"> <Module> <Name>predict-diseases</Name> <Version>1.0.0</Version> <Packaging>module</Packaging> <SourcesRoot>src/iris</SourcesRoot> <Resource Name="dc.predict.disease.PKG"/> <Dependencies> <ModuleReference> <Name>swagger-ui</Name> <Version>1.*.*</Version> </ModuleReference> <ModuleReference> <Name>dataset-health</Name> <Version>*</Version> </ModuleReference> </Dependencies> <CSPApplication Url="/predict-diseases" DispatchClass="dc.predict.disease.PredictDiseaseRESTApp" MatchRoles=":{$dbrole}" PasswordAuthEnabled="1" UnauthenticatedEnabled="1" Recurse="1" UseCookies="2" CookiePath="/predict-diseases" /> <CSPApplication CookiePath="/disease-predictor/" DefaultTimeout="900" SourcePath="/src/csp" DeployPath="${cspdir}/csp/${namespace}/" MatchRoles=":{$dbrole}" PasswordAuthEnabled="0" Recurse="1" ServeFiles="1" ServeFilesTimeout="3600" UnauthenticatedEnabled="1" Url="/disease-predictor" UseSessionCookie="2" /> </Module> </Document> </Export> Web Frontend and Backend Application to Predict Maternal Risk Go to Open Exchange app link (https://openexchange.intersystems.com/package/Disease-Predictor) and follow these steps: 使用Clone/git 把repo拉到任一本地目录中: $ git clone https://github.com/yurimarx/predict-diseases.git 在该文件夹中打开Docker 终端并运行: $ docker-compose build 运行IRIS容器: $ docker-compose up -d 进入管理门户执行查询,训练AI模型: http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER 创建用于训练的VIEW(视图): CREATE VIEW MaternalRiskTrain AS SELECT BS, BodyTemp, DiastolicBP, HeartRate, RiskLevel, SystolicBP, age FROM dc_data_health.MaternalHealthRisk 使用视图创建AI模型: CREATE MODEL MaternalRiskModel PREDICTING (RiskLevel) FROM MaternalRiskTrain 训练模型: TRAIN MODEL MaternalRiskModel 访问 http://localhost:52773/disease-predictor/index.html ,使用 Disease Predictor(疾病预测器)前端进行疾病预测,如下: 幕后工作 预测孕产妇风险疾病的后端类方法 InterSystems IRIS允许你执行SELECT,使用之前创建的模型进行预测。 Backend ClassMethod to predict Maternal Risk /// Predict Maternal Risk ClassMethod PredictMaternalRisk() As %Status { Try { Set data = {}.%FromJSON(%request.Content) Set %response.Status = 200 Set %response.Headers("Access-Control-Allow-Origin")="*" Set qry = "SELECT PREDICT(MaternalRiskModel) As PredictedMaternalRisk, " _"age, BS, BodyTemp, DiastolicBP, HeartRate, SystolicBP " _"FROM (SELECT "_data.BS_" AS BS, " _data.BodyTemp_" As BodyTemp, " _data.DiastolicBP_" AS DiastolicBP, " _data.HeartRate_" AS HeartRate, " _data.SystolicBP_" As SystolicBP, " _data.Age_" AS age)" Set tStatement = ##class(%SQL.Statement).%New() Set qStatus = tStatement.%Prepare(qry) If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} Set rset = tStatement.%Execute() Do rset.%Next() Set Response = {} Set Response.PredictedMaternalRisk = rset.PredictedMaternalRisk Set Response.Age = rset.Age Set Response.SystolicBP = rset.SystolicBP Set Response.DiastolicBP = rset.DiastolicBP Set Response.BS = rset.BS Set Response.BodyTemp = rset.BodyTemp Set Response.HeartRate = rset.HeartRate Write Response.%ToJSON() Return 1 } Catch err { write !, "Error name: ", ?20, err.Name, !, "Error code: ", ?20, err.Code, !, "Error location: ", ?20, err.Location, !, "Additional data: ", ?20, err.Data, ! Return 0 } } 现在,任何web应用都可以进行预测并显示结果。请在预测疾病应用程序的前端文件夹中查看源代码。
文章
Michael Lei · 六月 1, 2022

部分IRIS 2022 年度编程大奖赛作品展示—— 利用IRIS 一体化机器学习IntegratedML来预测糖尿病的Web 应用

糖尿病可以从医学界熟知的一些参数中发现。这样,为了帮助医学界和计算机软件系统,特别是人工智能软件,美国国家糖尿病和消化道及肾脏疾病研究所发布了一个非常有用的数据集,用于训练糖尿病检测/预测的机器学习算法。这份出版物可以在最大和最知名的ML数据库Kaggle上找到,网址是https://www.kaggle.com/datasets/mathchi/diabetes-data-set。 该糖尿病数据集有以下元数据信息(来源:https://www.kaggle.com/datasets/mathchi/diabetes-data-set): 怀孕:怀孕次数 葡萄糖: 口服葡萄糖耐量试验中2小时的血浆葡萄糖浓度 Plasma glucose concentration a 2 hours in an oral glucose tolerance test 血压: 舒张压(mm Hg) 皮肤厚度: 肱三头肌皮褶厚度(mm) 胰岛素: 2小时血清胰岛素(mu U/ml) BMI: 体重指数 (体重 kg/(身高 m)^2) 糖尿病血统函数: 糖尿病血统函数(它提供了一些关于亲属中的糖尿病史以及这些亲属与病人的遗传关系的数据。这种对遗传影响的测量使我们了解到一个人可能有的遗传风险与糖尿病的发病有关--来源:https://machinelearningmastery.com/case-study-predicting-the-onset-of-diabetes-within-five-years-part-1-of-3/) 年龄: 结果: 类变量 (0 or 1) 实例数量: 768 属性数量: 8 + 1个类变量 对每个属性: (全部为numeric数字量化类型) 怀孕次数 口服葡萄糖耐量试验中2小时的血浆葡萄糖浓度 舒张压 (mm Hg) 三头肌皮褶厚度 (mm) 2小时血清胰岛素 (mu U/ml) BMI指数 (体重 kg/(身高 m)^2) 糖尿病血统函数 年龄 类变量 (0 or 1) 缺失属性值: 是 类分布: (类值为1解释为 "糖尿病测试阳性") 从Kaggle获取糖尿病数据 Kaggle的糖尿病数据可以通过Health-Dataset程序加载到IRIS表中:https://openexchange.intersystems.com/package/Health-Dataset。要做到这一点,在你的module.xml项目中,设置依赖关系(Health Dataset的ModuleReference)。 Module.xml with Health Dataset application reference <?xml version="1.0" encoding="UTF-8"?> <Export generator="Cache" version="25"> <Document name="predict-diseases.ZPM"> <Module> <Name>predict-diseases</Name> <Version>1.0.0</Version> <Packaging>module</Packaging> <SourcesRoot>src/iris</SourcesRoot> <Resource Name="dc.predict.disease.PKG"/> <Dependencies> <ModuleReference> <Name>swagger-ui</Name> <Version>1.*.*</Version> </ModuleReference> <ModuleReference> <Name>dataset-health</Name> <Version>*</Version> </ModuleReference> </Dependencies> <CSPApplication Url="/predict-diseases" DispatchClass="dc.predict.disease.PredictDiseaseRESTApp" MatchRoles=":{$dbrole}" PasswordAuthEnabled="1" UnauthenticatedEnabled="1" Recurse="1" UseCookies="2" CookiePath="/predict-diseases" /> <CSPApplication CookiePath="/disease-predictor/" DefaultTimeout="900" SourcePath="/src/csp" DeployPath="${cspdir}/csp/${namespace}/" MatchRoles=":{$dbrole}" PasswordAuthEnabled="0" Recurse="1" ServeFiles="1" ServeFilesTimeout="3600" UnauthenticatedEnabled="1" Url="/disease-predictor" UseSessionCookie="2" /> </Module> </Document> </Export> 预测糖尿病的前端和后端应用程序 访问 Open Exchange 应用连接 (https://openexchange.intersystems.com/package/Disease-Predictor) 并遵守以下步骤: Clone/git 把repo pull 到任何本地目录 $ git clone https://github.com/yurimarx/predict-diseases.git 打开 该目录下Docker终端并执行: $ docker-compose build 执行IRIS container: $ docker-compose up -d 在管理门户中执行查询来训练AI模型: http://localhost:52773/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER 创建用来训练的 VIEW : CREATE VIEW DiabetesTrain AS SELECT Outcome, age, bloodpressure, bmi, diabetespedigree, glucose, insulin, pregnancies, skinthickness FROM dc_data_health.Diabetes 利用View视图来创建 AI 模型: CREATE MODEL DiabetesModel PREDICTING (Outcome) FROM DiabetesTrain 训练模型: TRAIN MODEL DiabetesModel 访问 http://localhost:52773/disease-predictor/index.html 来使用疾病预测器qian frontend and predict diseases like this: 幕后工作 后端预测糖尿病的类方法 InterSystems IRIS 支持执行Select 并使用上一个创建的模型来预测。 Backend ClassMethod to predict Diabetes /// Predict Diabetes ClassMethod PredictDiabetes() As %Status { Try { Set data = {}.%FromJSON(%request.Content) Set qry = "SELECT PREDICT(DiabetesModel) As PredictedDiabetes, " _"age, bloodpressure, bmi, diabetespedigree, glucose, insulin, " _"pregnancies, skinthickness " _"FROM (SELECT "_data.age_" AS age, " _data.bloodpressure_" As bloodpressure, " _data.bmi_" AS bmi, " _data.diabetespedigree_" AS diabetespedigree, " _data.glucose_" As glucose, " _data.insulin_" AS insulin, " _data.pregnancies_" As pregnancies, " _data.skinthickness_" AS skinthickness)" Set tStatement = ##class(%SQL.Statement).%New() Set qStatus = tStatement.%Prepare(qry) If qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} Set rset = tStatement.%Execute() Do rset.%Next() Set Response = {} Set Response.PredictedDiabetes = rset.PredictedDiabetes Set Response.age = rset.age Set Response.bloodpressure = rset.bloodpressure Set Response.bmi = rset.bmi Set Response.diabetespedigree = rset.diabetespedigree Set Response.glucose = rset.glucose Set Response.insulin = rset.insulin Set Response.pregnancies = rset.pregnancies Set Response.skinthickness = rset.skinthickness Set %response.Status = 200 Set %response.Headers("Access-Control-Allow-Origin")="*" Write Response.%ToJSON() Return 1 } Catch err { write !, "Error name: ", ?20, err.Name, !, "Error code: ", ?20, err.Code, !, "Error location: ", ?20, err.Location, !, "Additional data: ", ?20, err.Data, ! Return 0 } } 现在,任何网络应用都可以使用预测并显示预测结果。欢迎在frontend 文件夹查看本应用的源代码。
文章
Michael Lei · 九月 13, 2022

使用 Globals存储思维导图

Globals是InterSystems IRIS的数据持久性的核心。它很灵活,允许存储JSON文档、关系数据、面向对象的数据、OLAP立方体和自定义数据模型,例如思维导图。要了解如何使用globals来存储、删除和获取思维导图数据,请遵循以下步骤: 1. 把repo Clone/git到任意本地目录 $ git clone https://github.com/yurimarx/global-mindmap.git 2. 在该目录下打开Docker 终端并执行: $ docker-compose build 3. 启动 IRIS 容器: $ docker-compose up -d 4. 访问 http://localhost:3000 来使用思维导图的前端并创建类似以上的思维导图 本例子的源代码 存储数据 (更多请访问: https://www.npmjs.com/package/mind-elixir): { topic: 'node topic', id: 'bd1c24420cd2c2f5', style: { fontSize: '32', color: '#3298db', background: '#ecf0f1' }, parent: null, tags: ['Tag'], icons: ['😀'], hyperLink: 'https://github.com/ssshooter/mind-elixir-core', } 注意parent属性,它被用来在mindmap节点之间建立父/子关系。 使用Globals 来存储思维导图的源代码 ClassMethod StoreMindmapNode /// Store mindmap node ClassMethod StoreMindmapNode() As %Status { Try { Set data = {}.%FromJSON(%request.Content) Set ^mindmap(data.id) = data.id /// set mindmap key Set ^mindmap(data.id, "topic") = data.topic /// set topic subscript Set ^mindmap(data.id, "style", "fontSize") = data.style.fontSize /// set style properties subscripts Set ^mindmap(data.id, "style", "color") = data.style.color Set ^mindmap(data.id, "style", "background") = data.style.background Set ^mindmap(data.id, "parent") = data.parent /// store parent id subscript Set ^mindmap(data.id, "tags") = data.tags.%ToJSON() /// store tags subscript Set ^mindmap(data.id, "icons") = data.icons.%ToJSON() /// store icons subscript Set ^mindmap(data.id, "hyperLink") = data.hyperLink /// store hyperLink subscript Set %response.Status = 200 Set %response.Headers("Access-Control-Allow-Origin")="*" Write "Saved" Return $$$OK } Catch err { write !, "Error name: ", ?20, err.Name, !, "Error code: ", ?20, err.Code, !, "Error location: ", ?20, err.Location, !, "Additional data: ", ?20, err.Data, ! Return $$$NOTOK } } 我们创建了一个名为^mindmap的Global。对于每个思维导图的属性,它被存储在一个Globals下标中。下标的键是mindmap的id属性。 删除思维导图节点的源代码 - kill the global ClassMethod DeleteMindmapNode /// Delete mindmap node ClassMethod DeleteMindmapNode(id As %String) As %Status { Try { Kill ^mindmap(id) /// delete selected mindmap node using the id (global key) Set %response.Status = 200 Set %response.Headers("Access-Control-Allow-Origin")="*" Write "Deleted" Return $$$OK } Catch err { write !, "Error name: ", ?20, err.Name, !, "Error code: ", ?20, err.Code, !, "Error location: ", ?20, err.Location, !, "Additional data: ", ?20, err.Data, ! Return $$$NOTOK } } 这个例子使用mindmap.id作为mindmap的Global Key,所以删除很容易: call Kill ^mindmap(<mindmap id>) 获得所有存储内容的源代码- 用 $ORDER循环globals ClassMethod GetMindmap - return all mindmap global nodes /// Get mindmap content ClassMethod GetMindmap() As %Status { Try { Set Nodes = [] Set Key = $Order(^mindmap("")) /// get the first mindmap node stored - the root Set Row = 0 While (Key '= "") { /// while get child mindmap nodes Do Nodes.%Push({}) /// create a item into result Set Nodes.%Get(Row).style = {} Set Nodes.%Get(Row).id = Key /// return the id property Set Nodes.%Get(Row).hyperLink = ^mindmap(Key,"hyperLink") /// return the hyperlink property Set Nodes.%Get(Row).icons = ^mindmap(Key,"icons") /// return icons property Set Nodes.%Get(Row).parent = ^mindmap(Key,"parent") /// return parent id property Set Nodes.%Get(Row).style.background = ^mindmap(Key,"style", "background") /// return the style properties Set Nodes.%Get(Row).style.color = ^mindmap(Key,"style", "color") Set Nodes.%Get(Row).style.fontSize = ^mindmap(Key,"style", "fontSize") Set Nodes.%Get(Row).tags = ^mindmap(Key,"tags") /// return tags property Set Nodes.%Get(Row).topic = ^mindmap(Key,"topic") /// return topic property (title mindmap node) Set Row = Row + 1 Set Key = $Order(^mindmap(Key)) /// get the key to the next mindmap global node } Set %response.Status = 200 Set %response.Headers("Access-Control-Allow-Origin")="*" Write Nodes.%ToJSON() Return $$$OK } Catch err { write !, "Error name: ", ?20, err.Name, !, "Error code: ", ?20, err.Code, !, "Error location: ", ?20, err.Location, !, "Additional data: ", ?20, err.Data, ! Return $$$NOTOK } } 用$Order(^mindmap("")) - empty "" - 得到第一个mindmap Global (根节点)。对于每个属性值,我们使用^mindmap(Key,<property name>)。最后,调用$Order(^mindmap(Key))来获得下一个事件。 前端 Mind-elixir和React被用来渲染和编辑mindmap,消耗使用IRIS构建的API后端。见mindmap的反应组件: Mindmap React component - consuming IRIS REST API import React from "react"; import MindElixir, { E } from "mind-elixir"; import axios from 'axios'; class Mindmap extends React.Component { componentDidMount() { this.dynamicWidth = window.innerWidth; this.dynamicHeight = window.innerHeight; axios.get(`http://localhost:52773/global-mindmap/hasContent`) .then(res => { if (res.data == "1") { axios.get(`http://localhost:52773/global-mindmap/get`) .then(res2 => { this.ME = new MindElixir({ el: "#map", direction: MindElixir.LEFT, data: this.renderExistentMindmap(res2.data), draggable: true, // default true contextMenu: true, // default true toolBar: true, // default true nodeMenu: true, // default true keypress: true // default true }); this.ME.bus.addListener('operation', operation => { console.log(operation) if (operation.name == 'finishEdit' || operation.name == 'editStyle') { this.saveMindmapNode(operation.obj) } else if (operation.name == 'removeNode') { this.deleteMindmapNode(operation.obj.id) } }) this.ME.init(); }) } else { this.ME = new MindElixir({ el: "#map", direction: MindElixir.LEFT, data: MindElixir.new("New Mindmap"), draggable: true, // default true contextMenu: true, // default true toolBar: true, // default true nodeMenu: true, // default true keypress: true // default true }); this.ME.bus.addListener('operation', operation => { console.log(operation) if (operation.name == 'finishEdit' || operation.name == 'editStyle') { this.saveMindmapNode(operation.obj) } else if (operation.name == 'removeNode') { this.deleteMindmapNode(operation.obj.id) } }) this.saveMindmapNode(this.ME.nodeData) this.ME.init(); } }) } render() { return ( <div id="map" style={{ height: window.innerHeight + 'px', width: '100%' }} /> ); } deleteMindmapNode(mindmapNodeId) { axios.delete(`http://localhost:52773/global-mindmap/delete/${mindmapNodeId}`) .then(res => { console.log(res); console.log(res.data); }) } saveMindmapNode(node) { axios.post(`http://localhost:52773/global-mindmap/save`, { topic: (node.topic == undefined ? "" : node.topic), id: node.id, style: (node.style == undefined ? "" : node.style), parent: (node.parent == undefined ? "" : node.parent.id), tags: (node.tags == undefined ? [] : node.tags), icons: (node.icons == undefined ? [] : node.icons), hyperLink: (node.hyperLink == undefined ? "" : node.hyperLink) }) .then(res => { console.log(res); console.log(res.data); }) } renderExistentMindmap(data) { let root = data[0] let nodeData = { id: root.id, topic: root.topic, root: true, style: { background: root.style.background, color: root.style.color, fontSize: root.style.fontSize, }, hyperLink: root.hyperLink, children: [] } this.createTree(nodeData, data) return { nodeData } } createTree(nodeData, data) { for(let i = 1; i < data.length; i++) { if(data[i].parent == nodeData.id) { let newNode = { id: data[i].id, topic: data[i].topic, root: false, style: { background: data[i].style.background, color: data[i].style.color, fontSize: data[i].style.fontSize, }, hyperLink: data[i].hyperLink, children: [] } nodeData.children.push(newNode) this.createTree(newNode, data) } } } } export default Mindmap;
文章
Claire Zheng · 十月 18, 2022

技能帖:更好地利用开发者社区的发帖功能!

各位开发者社区的同学们,大家好! 您想更好地获得帮助、讨论有趣的功能、发布公告或分享您的知识吗?在这篇文章中,我们将告诉你如何做到这一切。 我们将通过以下几部分来分享“如何做”: 一般发帖步骤 问题 文章或公告 讨论 一般发帖步骤 首先,你需要点击开发者社区网站顶部菜单中的“发布新帖”按钮: 之后,您将看到编辑器中显示创建一个问题、一则公告、一篇文章或一个讨论。不同类型的帖子有自己的一组必填字段和可选字段。 首先,让我们讨论所有类型的帖子的公共字段,然后继续讨论细节。 基本上,每篇文章都有一个标题*、正文*、组*、标签和一些额外的选项,你可以在其中添加调查或附加PDF文件。所有用星号(*)标记的文本字段都是必填项。因此,首先,你需要选择帖子的类型,可以像上面提到的问题,公告,文章或讨论。 接下来,请用最精确和简洁的方式表述你的问题的主要思想,并将其作为标题。 之后,在文章主体中,你可以写任何你想与他人分享的东西。在写文章的时候有两个选择。你可以使用编辑器“所见即所得”(WYSIWYG)模式或者Markdown。当然,这两种方法得到的结果是一样的。 vs. 在你写完文本后,你必须选择组,通常是InterSystems提供的技术、产品或服务。 在组字段之后,有一个标签字段,您可以在其中添加与文章内容相关的标签。有相当多的标签,所以请认真选择,因为其他成员会通过这些标签寻找或排序所需要的信息。 在标签下面,有一个链接可以查看更多选项。在那里,您可以附加一个pdf文档(例如,pdf格式的事件时间表)并提供您想要显示的名称。 你可以通过“更多选项”做的另一件事是添加投票。在字段中填写一个问题、可能的答案、选择持续时间等。 完成后,你可以预览你的帖子,看看它对其他人来说是什么样子,你可以保存它以便以后继续编辑,或立即发布它。 此外,您可以预约发布您的文章。只需点击向下箭头,选择安排帖子,并设置日期和时间。 一切都设置好后,只需点击安排帖子,就完成了。 基本上,这是创建帖子的常见功能。 问题 从它的名字来看,很明显,如果你需要别人的帮助,你应该选择这种类型的帖子。在这里,在开发者社区中,有很多专家,有些人可能已经遇到了相同的情况。所以不要犹豫,提出问题或回答问题吧:) 要寻求帮助,请阐明你的问题的主要思想,并将其作为一个标题写下来。接下来,请选择您正在使用的产品版本,因为不同版本具有不同的功能和类,一些建议可能对某些版本有用,而对其他版本无用。 更准确地说,您可以提供当前在$ZV文本框中使用的完整构建。要获得完整版本,可以打开Terminal并执行以下命令: write $ZV 在你正在使用的IDE中也可以执行相同的操作,或者你可以在管理门户(Management Portal)中看到: 其余字段与前面描述的相同。 文章或公告 要分享你的知识或发布公告,你应该分别选择一种类型的帖子——文章或公告。这些类型的帖子除了公共字段之外还有一些额外的字段。这些是上一篇文章、下一篇文章和打开Exchange应用程序链接。 因此,基本上,如果当前的文章/声明(或讨论的分支)链接到另一篇文章,您可以在“上一个公告”字段中添加链接,这样其他社区成员将在文章末尾看到以下相关文章块。 你不需要再打开上一篇文章去添加到下一篇文章的链接,它将自动链接。 添加完这些链接后,用户也可以通过使用链接文章右上角的导航按钮轻松地从一个帖子导航到另一个帖子。 如果你的帖子在Open Exchange上有一个项目链接到它,你可以在相应的字段中添加这个项目的链接。 讨论 要开始关于某个功能的对话,或者分享您使用该技术的经验并寻求反馈,您可以开始一个讨论。这种类型的文章有所有公共字段,也有到上一篇和下一篇的链接。 就这些! 这就是您在社区上开始发布一个新帖子时所需要知道的。 期待着看到您的精彩发帖:)
文章
Tete Zhang · 九月 14, 2022

集成平台消息相关的常见存储问题

从消息查看器看到清除周期以外的消息没有被正常清除 这种情况先抽查这些消息所处的会话中是否有未完成操作周期的消息(状态为除“Completed”“Error”“Discarded”之外的状态)。如有,且定期清除任务配置了“KeepIntegrity”,且该环境并不需要保留这些消息,可通过关闭清除任务中的“KeepIntegrity”配置清除这些会话和包含的消息。如果有这类消息,但是定期清除任务未配置“KeepIntegrity”,可能是定期清除任务的逻辑或消息数据问题导致清楚任务查找的时候没有覆盖这些消息,请联系WRC帮助排查具体原因。 有关定期清除任务的更多信息请参见文档 Purging Production Data | Managing Productions | InterSystems IRIS for Health 2022.1 从消息查看器看不到清除周期之外的消息,但是^%GSIZE显示有global占据了很大的磁盘空间 这种情况需要具体排查每个较大的global。可能有以下原因: 系统定义的global占用很大空间。您可以联系WRC帮助排查具体原因。 自定义的global占用很大空间。这可能是消息中嵌套的持久化数据或流数据,或者是和消息没有直接关系的独立的表里面的数据。请对创建这种global的代码进行复盘,找到创建逻辑并增加相应的数据管理逻辑。 孤立消息 (Orphan Messages) 孤立消息是指不存在配套Message Header的所有消息对象。 定期清除任务会根据Message Header里的时间信息和状态信息去判断一条消息是否符合清除条件。Message Header是在消息从一个组件发向另一个组件的时候被创建的。所以,当我们创建将被永久存储的对象之前,我们都要思考:这个对象会被保存吗?被保存后会被发送到另一个组件吗?如果不会被发送,该对象将不存在配套的Message Header,也就不会被定期清除。这种情况我们需要开发相应的自定义逻辑去定期管理该表中的数据,或确保该对象被发送到某个组件以创建Message Header。消息对象中嵌套对象或流的情况要尤其注意,对每一个嵌套的对象或流都要定义相对应的%OnDelete()删除逻辑。 在测试阶段我们可以做如下的测试: 测试前跑^%GSIZE报告并检查磁盘存储 跑一套测试消息 用清除任务删除系统上所有的消息(DaysToKeep=0) 跑^%GSIZE报告并检查磁盘存储 如果对比前后的^%GSIZE报告和磁盘空间之后,发现清除任务完成后没有遗留多余的数据,那么这就证明我们的逻辑中对消息及相关嵌套数据进行了很好的管理。反之如果发现了遗留数据,我们可以在研发测试阶段就对问题进行排查,尽量避免开放生产环境以后出现磁盘满或数据库过大的问题。 如果发现了环境中有孤立消息的问题,请联系WRC进行排查和消息清除管理。 HL7v2:孤立字段(Orphan Segment) HL7v2在数据库中的存储逻辑如下。 EnsLib.HL7.Message对象存在以下两个global里: ^EnsLib.H.MessageD ^EnsHL7.Segment 示例: HL7v2消息 (^EnsLib.H.MessageD global): 1: ^EnsLib.H.MessageD = 1257406 2: ^EnsLib.H.MessageD(1257406) = $lb("","","2.3:ORU_R01",0,"2019-06-03 15:28:38.819","2.3.1","C:\Support\inarchive\testoru.txt_2019-06-03_11.28.38.814","","") 3: ^EnsLib.H.MessageD(1257406,"segs") = 5 4: ^EnsLib.H.MessageD(1257406,"segs",1) = "11612,25" 5: ^EnsLib.H.MessageD(1257406,"segs",2) = "11612,26" 6: ^EnsLib.H.MessageD(1257406,"segs",3) = "11612,27" 7: ^EnsLib.H.MessageD(1257406,"segs",4) = "11612,28" 8: ^EnsLib.H.MessageD(1257406,"segs",5) = "11612,29" 其中, 125706是该HL7v2消息的Object ID。Global值"11612,25","11612,26"指向相应的HL7v2字段。 HL7v2字段 (^EnsHL7.Segment global): 1: ^EnsHL7.Segment(11612) = 30 2: ^EnsHL7.Segment(11612,25) = "|^~\&MSH|^~\&||GA0000||VAERS PROCESSOR|20010331605||ORU^R01|20010422GA03|T|2.3.1|||AL|" 3: ^EnsHL7.Segment(11612,25,0,1257406) = "" 其中,11612是创建该HL7v2消息的进程 ID (PID)。^EnsHL7.Segment(11612,25) 存储了该字段的具体数据。^EnsHL7.Segment(11612,25,0,1257406) 中的第四个值(1257406)是这个字段所属消息的Object ID。 从以上示例可以看出,HL7v2字段数据存储于^EnsHL7.Segment global。所以在^%GSIZE中看到^EnsHL7.Segment global比 ^EnsLib.H.MessageD global大是正常现象。使用平台自带的逻辑在最新版本上目前也没有已知问题会导致孤立字段。如果您持续观察^%GSIZE报告,发现^EnsHL7.Segment global的大小出现异常增长,可以联系WRC排查是否有孤立字段的情况。
文章
Michael Lei · 十月 10, 2022

互操作性--创建和连接业务主机Business Host的步骤一二三

Hi 大家好, 我最近开始学习InterSystems IRIS 的互操作性,我发现官方文档对理解它的工作原理很有帮助,尽管我自己在实现它时仍有一些困难。在我的同事的帮助下,我成功地创建了一个系统的Demo,并从实践中学习。因此,我决定写一下文章,分享我得到的帮助,来帮助更多的其他人。 介绍 首先,让我们掌握一些基本概念: 互操作性 - 这个词的含义并不像它的发音那样复杂--它基本上是把各种信息从一个系统带到另一个系统的“魔术”。 业务主机 - 如果把互操作性比作是魔术,那么业务主机Business Host就是魔术师的魔法帽--业务主机里有能够识别和接收信息的业务服务Business Service/BS,并将其作为消息发送给业务流程BP或业务操作BO。业务操作执行所需的操作(顾名思义)并传递信息。业务流程控制着消息的流动:它们定义了消息的去向(基于你所选择的任何东西)以及它是如何被传递的。 适配器 - 适配器是一些我们可以用来识别和操作我们可能要处理的各种信息的类。在实践中,我们把它们作为参数和(可选)属性来访问其方法和属性 准备搭建Production 从简单的开始比较容易--让我们先想想服务和操作--比如说你有一个接收一种信息的服务,它很容易被我们唯一的操作所识别。 当生产的目的和它的部分非常清楚时,开发就比较容易。如果你愿意,画一张图或写下你希望它完成的步骤可能会有帮助。 例如: 首先要问自己, "需要做什么?" - 在我的演示中,我需要操作一个SQL表--我要把一本书的标题和作者等信息,并将其插入到一个表中。 "那么,我需要Production做什么呢?- 它必须接收一个包含书名和作者的信息,并执行一个SQL INSERT。 "好的,如何让这件事发生?- 业务服务(BS)将接收标题和作者,将其传递给业务操作(BO)。BO执行SQL代码。 "现在我有了信息将遵循的路径,我需要理解信息。它是什么?" - 我有很多方法可以发送数据。我可以在一个文件中发送,或一个REST应用程序,甚至一个电子邮件。让我们选择文件来开始简单。我的BS将接收文件,读取它并将其信息发送给BO。BO执行查询。 开始干活 你可以从你对最有信心的代码部分开始。 业务服务 Business Service/BS 我是从业务服务BS开始的. 现在我很清楚,我需要实现Request(请求)类,以便它存储数据和将被发送到的操作。 业务操作Business Operation/BO 请求Request Request类很简单。我只用它来存储一些数据。%XML.Adaptor是为了在管理门户上显示Request以进行错误管理。 业务操作Business Operation BO有一个消息地图,为每一种到达的消息提供足够的方向。例如,如果在BS中,在SendRequestSync方法和Request参数中,我使用了不同的类型,如 "Demo.Books.BO.SearchTable.Request",我可以用这种消息类型创建另一个MapItem,引用一个Search方法。 方法Method 在这里,你实现了操作应该做的任何事情. 管理门户设置 最后,为了让事情顺利进行,请遵循以下步骤: 管理门户 > 互操作性 > 列表 > 生产 > 新建(Management Portal > Interoperability > List > Productions > New) 门户网站将用Production生产信息创建一个类。 然后,你用你创建的类添加一个服务,并设置文件路径(你将把输入文件放在哪里)和工作路径。 另外,用你创建的类添加一个操作,必要时指定其设置。 几点观察 业务操作可以接收一个同步请求,这意味着服务只有在BO检索到它的信息后才会响应,因为服务的响应取决于BO的响应,例如,如果它必须在表操作中执行一个搜索。因为Production生产只执行INSERT操作,BS只需要发送信息,BO就会INSERT;不需要响应,所以我们可以有一个异步请求。 讨论例如文件和SQL适配器等适配器的规格超出了本文的范围,本文的目的是对编码和步骤进行概述,以便更好地理解实际工作。 欢迎与我联系--我很乐意提供任何可能的帮助!
文章
Frank Ma · 三月 2, 2022

如何成为时间领主 - 诞生

好人不需要规则。 神秘博士 要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。 坐上TARDIS,我将把你变成一个时间领主。 让我们从基本知识开始 如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分: WRITE $HOROLOG > 66149,67164 第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。 在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。 感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。 如何将内部格式转换为更清晰的格式? 为此,我们将用到命令 $ZDATETIME 基本的命令是 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow) > 02/09/2022 18:39:24 默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4) > 09/02/2022 18:39:24 该选项使用我们在本地变量中定义的分隔符和年份格式。 如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4,3) > 09/02/2022 06:39:24PM WRITE $ZDATETIME(RightNow,4,4) > 09/02/2022 06:39PM 现在是不是更清楚了?那么让我们更深入地了解一下。 ODBC 格式 这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,3) > 2022-02-09 18:39:24 一周的日子,星期名称,一年中的某天 Value 值 描述 10 一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。 11 星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode) 12 长格式的星期名称,与11相同。 14 一年中的某一天,自1月1日以来的天数。 如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATE(RightNow,10) > 3 WRITE $ZDATE(RightNow,11) > Wed WRITE $ZDATE(RightNow,12) > Wednesday 那我如何将日期转换为内部格式? 好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。 这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。 格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。 SET MyDatetime = "2022-02-09 18:39:24" SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format SET MyDatetime = "09/02/2022 18:39:24" SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format SET MyDatetime = "02/09/2022 06:39:24PM" SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM WRITE Interna1,!,Interna2,!,Interna3 > 66149,67164 66149,67164 66149,67164 从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。 不要混合格式,这样以后会出现问题。 SET MyDatetime = "09/02/2022" /// American format SET InternalDate = $ZDATEH(MyDatetime, 1) /// European format SET OtherDate = $ZDATETIME(InternalDate, 4) WRITE InternalDate,!,OtherDate > 66354 02/09/2022 不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么? SET MyDatetime = "14/02/2022" SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!! ^ <ILLEGAL VALUE> 嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。 好吧,让我们用你已经学到的东西做一些事情。 READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth SET internalFormat = $ZDATEH(dateOfBirth, 4) SET dayOfWeek= $ZDATE(internalFormat, 10) SET nameOfDay = $ZDATE(internalFormat, 12) WRITE !,"The day of the week of your birth is: ",nameOfDay IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday 以后我们将看到其他的做事方法,以及如何处理错误。 下一章:如何进行时间旅行 好奇 如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。
文章
Michael Lei · 四月 17, 2022

翻译博客文章--浏览医疗保健的未来

在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。 这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。 医疗保健行业与图书行业的趋势 数字化 我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。 虚拟服务 书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院? 更智能的算法 分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。 合并和收购 无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。 对未来医疗的影响 对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。 新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。 没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。 关于作者: Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。 博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
姚 鑫 · 七月 12, 2022

第三章 嵌入式Python概述(三)

# 第三章 嵌入式Python概述(三) ## 使用 SQL `IRIS` 中的类被投影到 `SQL`,除了使用类方法或直接全局访问之外,还允许使用查询访问数据。 `iris` 模块为提供了两种从 `Python` 运行 `SQL` 语句的不同方式。 以下示例使用 `iris.sql.exec()` 运行 `SQL SELECT` 语句以查找类名称以“`%Net.LDAP`”开头的所有类定义,返回一个包含每个名称和超类的结果集每个班级。在这里,系统类 `%Dictionary.ClassDefinition` 将 `SQL` 投影为同名表。 ```java >>> rs = iris.sql.exec("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH '%Net.LDAP'") ``` 以下示例使用 `iris.sql.prepare()` 准备 `SQL` 查询对象,然后执行查询,将`“%Net.LDAP”`作为参数传入: ```java >>> stmt = iris.sql.prepare("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH ?") >>> rs = stmt.execute("%Net.LDAP") ``` 无论哪种情况,都可以按如下方式遍历结果集,并且输出相同: ```java >>> for idx, row in enumerate(rs): ... print(f"[{idx}]: {row}") ... [0]: ['%Net.LDAP.Client.EditEntry', '%RegisteredObject'] [1]: ['%Net.LDAP.Client.Entries', '%RegisteredObject,%Collection.AbstractIterator'] [2]: ['%Net.LDAP.Client.Entry', '%RegisteredObject,%Collection.AbstractIterator'] [3]: ['%Net.LDAP.Client.PropList', '%RegisteredObject'] [4]: ['%Net.LDAP.Client.Search.Scope', '%Integer'] [5]: ['%Net.LDAP.Client.Session', '%RegisteredObject'] [6]: ['%Net.LDAP.Client.StringList', '%RegisteredObject'] [7]: ['%Net.LDAP.Client.ValueList', '%RegisteredObject,%Collection.AbstractIterator'] ``` ## 使用Globals 在 `IRIS` 数据库中,所有数据都存储在全局变量中。全局数组是持久的(意味着它们存储在磁盘上)、多维的(意味着它们可以有任意数量的下标)和稀疏的(意味着下标不必是连续的)。当您在表中存储类的对象或行时,这些数据实际上存储在全局变量中,尽管您通常通过方法或 `SQL` 访问它们并且从不直接接触全局变量。 有时将持久数据存储在全局变量中会很有用,而无需设置类或 `SQL` 表。在 `IRIS` 中,全局变量看起来很像任何其他变量,但它在名称前用插入符号 (`^`) 表示。以下示例将工作日的名称存储在当前命名空间的全局 `^Workdays` 中。 ```java >>> myGref = iris.gref('^Workdays') >>> myGref[None] = 5 >>> myGref[1] = 'Monday' >>> myGref[2] = 'Tuesday' >>> myGref[3] = 'Wednesday' >>> myGref[4] = 'Thursday' >>> myGref[5] = 'Friday' >>> print(myGref[3]) Wednesday ``` 第一行代码 `mmyGref = iris.gref('^Workdays') ` 获取一个全局引用(或 `gref`),指向一个名为 `^Workdays` 的全局引用,它可能已经存在也可能不存在。 第二行 `myGref[None] = 5` 将工作日数存储在 `^Workdays` 中,不带下标。 第三行 `myGref[1] = 'Monday'` 将字符串 `Monday` 存储在位置 `^Workdays(1)` 中。接下来的四行将剩余的工作日存储在位置 `^Workdays(2)` 到 `^Workdays(5)` 中。 最后一行 `print(myGref[3])` 显示了如何在给定 `gref` 的情况下访问存储在全局中的值。 # 一起使用 ObjectScript 和 Python `IRIS` 让 `ObjectScript` 和 `Python` 程序员的混合团队轻松协作。例如,类中的一些方法可以用 `ObjectScript` 编写,而另一些可以用 `Python` 编写。程序员可以选择用他们最熟悉的语言编写,或者更适合手头任务的语言。 ## 创建混合 InterSystems IRIS 类 下面的类有一个用 `Python` 编写的 `Print()` 方法和一个用 `ObjectScript` 编写的 `Write()` 方法,但它们在功能上是等效的,并且可以从 `Python` 或 `ObjectScript` 调用这两种方法。 ```java Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor) { /// The company's name. Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ]; /// The company's mission statement. Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()"); /// The unique Tax ID number for the company. Property TaxID As %String [ Required ]; /// The last reported revenue for the company. Property Revenue As %Integer; /// The Employee objects associated with this Company. Relationship Employees As Employee [ Cardinality = many, Inverse = Company ]; Method Print() [ Language = python ] { print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID) } Method Write() [ Language = objectscript ] { write !, "Name: ", ..Name, " TaxID: ", ..TaxID } } ``` 此 `Python` 代码示例展示了如何使用 `%Id=2` 打开 `Company` 对象并调用 `Print()` 和 `Write()` 方法。 ```java >>> company = iris.cls("Sample.Company")._OpenId(2) >>> company.Print() Name: IntraData Group Ltd. TaxID: G468 >>> company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` 此 `ObjectScript` 代码示例展示了如何打开相同的 `Company` 对象并调用这两种方法。 ```java SAMPLES>set company = ##class(Sample.Company).%OpenId(2) SAMPLES>do company.Print() Name: IntraData Group Ltd. TaxID: G468 SAMPLES>do company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` ## 在 Python 和 ObjectScript 之间传递数据 虽然 `Python` 和 `ObjectScript` 在许多方面都兼容,但它们有许多自己的数据类型和结构,有时在将数据从一种语言传递到另一种语言时需要进行一些数据转换。之前看到了一个示例,即从 `ObjectScript` 向 `Python` 传递命名参数的示例。 `%SYS.Python` 类的 `Builtins()` 方法为提供了一种方便的方式来访问 `Python` 的内置函数,它可以帮助创建 `Python` 方法所期望的类型的对象。 以下 `ObjectScript` 示例创建两个 `Python` 数组 `newport` 和 `cleveland`,每个数组都包含一个城市的纬度和经度: ```java USER>set builtins = ##class(%SYS.Python).Builtins() USER>set newport = builtins.list() USER>do newport.append(41.49008) USER>do newport.append(-71.312796) USER>set cleveland = builtins.list() USER>do cleveland.append(41.499498) USER>do cleveland.append(-81.695391) USER>zwrite newport newport=11@%SYS.Python ; [41.49008, -71.312796] ; USER>zwrite cleveland cleveland=11@%SYS.Python ; [41.499498, -81.695391] ; ``` 下面的代码使用在前面的示例中看到的 `geopy` 包来计算纽波特,罗德岛和克利夫兰,俄亥俄州之间的距离。它使用 `geopy.distance.distance()` 方法创建一条路线,将数组作为参数传递,然后打印路线的英里属性。 ```java USER>set distance = $system.Python.Import("geopy.distance") USER>set route = distance.distance(newport, cleveland) USER>write route.miles 538.3904453677205311 ``` 注意: `geopy.distance.distance()` 方法实际上期望参数是 `Python` 元组数据类型,但数组也可以。 ## 运行 Python 命令 当开发或测试某些东西时,有时运行一行 `Python` 代码以查看它的作用或是否有效可能会很有用。在这种情况下,可以使用 `%SYS.Python.Run()` 方法,如下例所示: ```java USER>set rslt = ##class(%SYS.Python).Run("print('hello world')") hello world ```
文章
TZ Zhuang · 二月 3

PerfTools IO 测试套件

# 目的 这两个工具(RanRead 和 RanWrite)用于在数据库(或一对数据库)内生成随机读写事件,以测试每秒输入/输出的操作数 (IOPS)。它们可以一起使用或分开单独使用,以测试 IO 硬件容量、验证目标 IOPS 并确保系统拥有可接受的磁盘响应时间。从 IO 测试中收集的结果将因配置而异,具体取决于 IO 子系统。在运行这些测试之前,请确保相应的操作系统监控和存储级别监控已配置,这些捕获的 IO 性能指标可以为以后的分析提供帮助。我们推荐使用 IRIS 中捆绑的系统性能工具,例如^SystemPerformance。 请注意,这里使用的工具是对先前版本的更新。之前的版本可在[这里](https://community.intersystems.com/post/random-read-io-storage-performance-tool)找到。 # 安装 从 GitHub 下载 **PerfTools.RanRead.xml** 和 **PerfTools.RanWrite.xml** 工具 点击[这里](https://github.com/intersystems-community/perftools-io-test-suite)。 将工具导入 USER 命名空间。 USER> do $system.OBJ.Load("/tmp/PerfTools.RanRead.xml","ckf") USER> do $system.OBJ.Load("/tmp/PerfTools.RanWrite.xml","ckf") 运行帮助方法以查看所有入口点。所有命令都在 USER 中运行。 USER> do ##class(PerfTools.RanRead).Help() - do ##class(PerfTools.RanRead).Setup(Directory,DatabaseName,SizeGB,LogLevel) 创建具有相同名称的数据库和命名空间。日志级别必须在 0 到 3 的范围内,其中 0 是“无”,3 是“详细”。 - do ##class(PerfTools.RanRead).Run(Directory,Processes,Count,Mode) 运行随机读取 IO 测试。模式参数,1(默认)代表时间,以秒为单位 ,2是循环次数,用前面的 Count 参数控制。 - do ##class(PerfTools.RanRead).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanRead).Reset() 删除先前运行的统计信息。在测试之间运行这个很重要,否则之前运行的统计数据将平均到当前运行的统计数据中。 - do ##class(PerfTools.RanRead).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanRead).Export(Directory) 将所有随机读取测试历史的摘要导出到逗号分隔的文本文件。 USER> do ##class(PerfTools.RanWrite).Help() - do ##class(PerfTools.RanWrite).Setup(Directory,DatabaseName) 创建具有相同名称的数据库和命名空间。 - do ##class(PerfTools.RanWrite).Run(Directory,NumProcs,RunTime,HangTime,HangVariationPct,Global name length,Global node depth,Global subnode length) 运行随机写入 IO 测试。除目录外的所有参数都有默认值。 - do ##class(PerfTools.RanWrite).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanWrite).Reset() 删除先前运行的统计信息。 - do ##class(PerfTools.RanWrite).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanWrite).Export(Directory) 将所有随机写入测试历史的摘要导出到逗号分隔的文本文件。 # 配置 创建一个名为 RAN 的空(预扩展)数据库,其大小至少是要测试的物理主机内存的两倍。同时确保这个空数据库至少是存储控制器缓存大小的四倍。数据库需要大于物理内存以确保读取的数据不会缓存在文件系统缓存中。您可以手动创建或使用以下方法自动创建命名空间和数据库。 USER> do ##class(PerfTools.RanRead).Setup("/ISC/tests/TMP","RAN",200,1) Created directory /ISC/tests/TMP/ Creating 200GB database in /ISC/tests/TMP/ Database created in /ISC/tests/TMP/ 注意:RanRead 和 RanWrite 可以使用相同的数据库。如果需要一次测试多个磁盘或用于特定目的,也可以使用分开的数据库。 RanRead 代码允许指定数据库的大小,但 RanWrite 代码不允许,因此最好使用 RanRead Setup 命令来创建所需的任何预先确定大小的数据库,即使要创建 RanWrite 做测试的数据库也可以。 # 方法论 从少量进程和 30-60 秒运行时间开始测试。然后增加进程数,例如从 10 个作业开始,然后增加 10、20、40 等。继续运行单个测试,直到响应时间始终超过 10 毫秒或计算出的 IOPS 不再以线性方式增加。 该工具使用 ObjectScript VIEW 命令读取内存中的数据库块,因此如果您没有获得预期的结果,则可能所有数据库块都已在内存中。 作为指南,全闪存阵列通常可以接受以下 8KB 和 64KB 数据库随机读取(非缓存)的响应时间: * 平均 do ##class(PerfTools.RanRead).Export("/ISC/tests/TMP/ ") Exporting summary of all random read statistics to /usr/iris/db/zranread/PerfToolsRanRead_20221023-1408.txt Done. # 分析 建议使用内置的 [SystemPerformance 工具](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_systemperf)来获取被分析的系统的真实情况。 SystemPerformance 的命令需要在 %SYS 命名空间中运行。要切换到那个命名空间,请使用 ZN 命令: USER> ZN "%SYS" 要查找系统瓶颈的详细信息,或者如果需要系统如何以目标 IOPS 运行的更多详细信息,则应创建具有高频率数据采集的 SystemPerformance 配置文件: %SYS> set rc=$$addprofile^SystemPerformance("5minhighdef","A 5-minute run sampling every second",1,300) 然后运行该配置文件(从 %SYS)并立即切换回 USER 并使用“job”而不是“do”来启动 RanRead 和/或 RanWrite: %SYS> set runid=$$run^SystemPerformance("5minhighdef") %SYS> ZN “USER” USER> job ##class(PerfTools.RanRead).Run("/ISC/tests/TMP",5,60) USER> job ##class(PerfTools.RanWrite).Run("/ISC/tests/TMP",1,60,.001) 然后可以等待 SystemPerformance 作业结束,并使用 [yaspe](https://github.com/murrayo/yaspe) 等工具分析生成的 html 文件。 # 清理 运行完测试后,需要删除历史记录: %SYS> do ##class(PerfTools.RanRead).Reset()
文章
姚 鑫 · 六月 14, 2022

第九章 其他参考资料(二)

# 第九章 其他参考资料(二) # 特殊变量 (SQL) 系统提供的变量。 ```sql $HOROLOG $JOB $NAMESPACE $TLEVEL $USERNAME $ZHOROLOG $ZJOB $ZPI $ZTIMESTAMP $ZTIMEZONE $ZVERSION ``` SQL直接支持许多对象脚本特殊变量。这些变量包含系统提供的值。只要可以在SQL中指定文字值,就可以使用它们。 SQL特殊变量名不区分大小写。大多数可以使用缩写来指定。 Variable| Name| Abbreviation| Data Type Returned Use ---|---|---|--- $HOROLOG| $H| %String/VARCHAR |当前进程的本地日期和时间 $JOB| $J| %String/VARCHAR |当前进程的 job ID $NAMESPACE| none| %String/VARCHAR |当前命名空间名称 $TLEVEL| $TL| %Integer/INTEGER|| 当前事务嵌套级别 $USERNAME| none| %String/VARCHAR|当前进程的用户名 $ZHOROLOG| $ZH |%Numeric/NUMERIC(21,6)|自InterSystems IRIS启动后经过的秒数 $ZJOB| $ZJ| %Integer/INTEGER|当前进程的job状态 $ZPI| none| %Numeric/NUMERIC(21,18) |数值常量PI $ZTIMESTAMP |$ZTS| %String/VARCHAR |协调世界时间格式的当前日期和时间 $ZTIMEZONE| $ZTZ| %Integer/INTEGER| 当地时区与GMT的偏移量 $ZVERSION| $ZV| %String/VARCHAR| IRIS的当前版本 # 示例 ```sql SELECT TOP 5 Name,$H FROM Sample.Person ``` 以下示例仅在时区位于大陆内时才返回结果集: ```sql SELECT TOP 5 Name,Home_State FROM Sample.Person WHERE $ZTIMEZONE BETWEEN -480 AND 480 ``` # 字符串操作(SQL) 字符串操作函数和运算符。 SQL 支持多种类型的字符串操作: - 字符串可以通过长度、字符位置或子字符串值进行操作。 - 字符串可以通过指定的分隔符或分隔符字符串来操作。 - 字符串可以通过模式匹配和单词感知搜索来测试。 - 特殊编码的字符串(称为列表)包含嵌入的子字符串标识符,而不使用分隔符。各种 `$LIST` 函数对这些与标准字符串不兼容的编码字符串进行操作。唯一的例外是 `$LISTGET` 函数和 `$LIST` 的单参数和双参数形式,它们将编码字符串作为输入,但将单个元素值作为标准字符串输出。 SQL 支持字符串函数、字符串条件表达式和字符串运算符。 ObjectScript 字符串操作区分大小写。字符串中的字母可以转换为大写、小写或混合大小写。字符串排序规则可以区分大小写,也可以不区分大小写;默认情况下,SQL 字符串排序规则是不区分大小写的 `SQLUPPER`。 SQL 提供了许多字母大小写和排序规则函数和运算符。 当为数字参数指定字符串时,大多数 SQL 函数执行以下字符串到数字的转换: 非数字字符串转换为数字 0;将数字字符串转换为规范数字;并且混合数字字符串在第一个非数字字符处被截断,然后转换为规范数字。 # 字符串连接 以下函数将子字符串连接成字符串: - `CONCAT`:连接两个子字符串,返回一个字符串。 - `STRING`:连接两个或多个子字符串,返回单个字符串。 - `XMLAGG`:连接列的所有值,返回单个字符串。 - `LIST`:连接列的所有值,包括逗号分隔符,返回单个字符串。 - 连接运算符 (`||`) 也可用于连接两个字符串。 # 字符串长度 以下函数可用于确定字符串的长度: - `CHARACTER_LENGTH` 和 `CHAR_LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回 `NULL`。 - `LENGTH`:返回字符串中的字符数,不包括尾随空格。 `NULL` 返回 NULL。 - `$LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回为 0。 # Truncation and Trim 以下函数可用于截断或修剪字符串。截断限制字符串的长度,删除超出指定长度的所有字符。`Trim`从字符串中删除前导和/或尾随空格。 - `Truncation`: `CONVERT`, `%SQLSTRING`, and `%SQLUPPER`. - `Trimming`: `TRIM`, `LTRIM`, and `RTRIM`. # 子串搜索 以下函数在字符串中搜索子字符串并返回字符串位置: - `POSITION`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。 - `CHARINDEX`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点。 - `$FIND`:按子串值搜索,找到第一个匹配项,返回子串结束的位置。可以指定起点。 - `INSTR`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点和子串出现。 以下函数在字符串中按位置或分隔符搜索子字符串并返回子字符串: - `$EXTRACT`:按字符串位置搜索,返回由开始位置或开始和结束位置指定的子字符串。从字符串的开头搜索。 - `SUBSTRING`:按字符串位置搜索,返回由开始位置或开始和长度指定的子字符串。从字符串的开头搜索。 - `SUBSTR`:按字符串位置搜索,返回由起始位置或起始和长度指定的子字符串。从字符串的开头或结尾搜索。 - `$PIECE`:按分隔符搜索,返回第一个分隔的子字符串。可以指定起点或默认为字符串的开头。 - `$LENGTH`:按分隔符搜索,返回分隔子串的数量。从字符串的开头搜索。 - `$LIST`:在特殊编码的列表字符串上按子字符串计数搜索。它通过子串计数定位子串并返回子串值。从字符串的开头搜索。 - 包含运算符 (`[`) 也可用于确定子字符串是否出现在字符串中。 - `%STARTSWITH` 比较运算符将指定的字符与字符串的开头进行匹配。 # 子串搜索和替换 以下函数在字符串中搜索子字符串并将其替换为另一个子字符串。 - `REPLACE`:按字符串值搜索,用新的子字符串替换子字符串。从字符串的开头搜索。 - `STUFF`:按字符串位置和长度搜索,用新的子字符串替换子字符串。从字符串的开头搜索。 # 字符类型和 Word-Aware 比较 `%PATTERN` 比较运算符将字符串与指定的字符类型模式匹配。
文章
Guangliang Zhang · 十月 21, 2022

基于cconsole.log的cache数据库的实时监控

cache数据库自身带有系统监控Portal界面,但需要运维人员定期主动查看才能获取监控信息。当系统故障发生时,容易出现由于没有及时获取故障信息而不能及时处理,从而导致造成的影响扩大。本文研究通过解析cache数据库控制台日志(cconsole.log)进行监控信息获取并主动推送微信或短信实现cache数据库主动实时监控。 cache数据库在运行时会将所有控制台消息包括一般消息、系统错误、某些操作系统错误和网络错误都会发送到控制台日志文件,通过操作员控制台工具从其他系统远程启动的作业的成功或失败等信息也会写入控制台日志,因此通过对控制台日志的解析即可获取所需要监控信息。具体步骤方法如下: 解析控制台日志 控制台日志默认存储在install-dir\mgr路径下。 根据cache版本不同,使用的读取方法也不同。对于cache2016版本以上,系统提供了EnsLib.SQL.Snapshot类,可以直接获取日志的行和列信息,非常方便。对于cache2010及以下版本则无此方法,需要使用%File文件读取方法。 随着系统运行时间增加,控制台日志也会不断增大,造成每次检索时间加大,且不容易找出最近的监控信息,作出有效监控。较好的方式是每次解析时均从上次的节点继续进行,以便获取新的有效的监控信息。可以使用指定global来记录每次执行的最后一行ID,下次执行时从ID+1行开始解析来达到目的。对于控制台日志,EnsLib.SQL.Snapshot方法里提供了方法以获取当前行,对于cache2010及以下版本,则需要记录最后一次处理的内容,再次处理的时先找到本条内容的位置,从此位置继续处理。代码示例如下。 cache2016及以上版本代码示例: ClassMethod GetAlertInfo() As %String{ //cconsole.log存储路径 set FilePath="D:\InterSystems\HealthShare\mgr\cconsole.log" //使用EnsLib.SQL.Snapshot读取日志 set snap=##class(EnsLib.SQL.Snapshot).%New() set snap.MaxRowsToGet=-1 do snap.ImportFile(FilePath,," ") do Consolelogshow do snap.Clean() quit $$$OK //处理日志内容Consolelogshow set colsCount=snap.ColCount //获取上一次处理的最后一行行号 set snap.%CurrentRow=$g(^AlertCurrentRow("Consolelog","CurrentRow")) k ^AlertCurrentRow("Consolelog","PID") while snap.Next() { set alertLevel=snap.GetData(3) set pid=snap.GetData(2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) continue:pid="" //0级别信息不处理 continue:((alertLevel=0)) //进程号已存在不处理,即只获取每次每个进程的第一条告警/错误信息 continue:($d(^AlertCurrentRow("Consolelog","PID",pid))) //记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //定义并组装监控消息内容 set alertInfo="" i ((alertLevel=1)||(alertLevel=2)||(alertLevel=3)){ f i=4:1:colsCount{ set alertInfo=alertInfo_" "_snap.GetData(i) } set alertInfo=snap.GetData(1)_":"_alertLevel_":"_alertInfo continue:((alertLevel=1)&&(alertInfo'["Warning")) //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } //记录当前处理的行 set ^AlertCurrentRow("Consolelog","CurrentRow")=snap.%CurrentRow } quit $$$OK} cache2010及以下版本代码示例: ClassMethod GetAlertInfo() As %String{ //使用%File读取日志 set file=##class(%File).%New("/cachesys/mgr/cconsole.log") set Status= file.Open("R") k ^AlertCurrentRow("Consolelog","PID") //获取上次处理的信息内容 set LastPosition=$g(^AlertCurrentRow("Consolelog","LastPosition")) s:LastPosition="" LastPosition=0 set LastPosition=LastPosition+1 set int=0 while 'file.AtEnd{ i int=0{ //跳转到上次处理的最后位置 do file.MoveTo(LastPosition) } set file.LineTerminator=$c(10) set text= file.ReadLine(,.sc) set int=int+1 //解析日志 set pid=$p(text," ",2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) set alertLevel=$p(text," ",3) s:text'="" textLast=text //定义并组装监控消息内容 i ((alertLevel=1)&&(alertLevel["Warning"))||(alertLevel=2)||(alertLevel=3){ set length=$length(text," ") set alert="",alertInfo="" f i=4:1:length{ set alert=$p(text," ",i) i i=4{ set alertInfo=alert }else{ set alertInfo=alertInfo_" "_alert } } set alertInfo=$p(text," ",1)_": "_alertLevel_": "_pid_":"_alertInfo set alertInfo=$zcvt(alertInfo,"O","UTF8") i '$d(^AlertCurrentRow("Consolelog","PID",pid)){ //记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } } } //记录最后处理的行内容 set ^AlertCurrentRow("Consolelog","LastPosition")=file.FindAt(1,textLast) do file.Close() quit $$$OK} 控制台日志在记录信息时,经常会记录一系列问题。记录模式为时间+进程ID+信息级别+详细信息。如图1所示。信息级别从0级到3级,问题严重程度依次增加。为0的一般为正常运行信息,也是占日志主要部分的内容。问题级别为1的为警告信息。问题级别为2的为错误信息。问题级别为3的为严重错误信息。因此信息级别为1以上均是监控需要关注的信息。需要将此部分信息及时发出。 图1 cconsole.log普通记录示例 有时候控制台日志也会有其它格式的信息出现,如图2所示。此类信息说明有比较严重的异常行为出现,需要重点关注,因为一般将其全部发出。 图2 cconsole.log特殊记录示例 监控信息推送 控制台日志会对一个进程的多个操作进行记录,如果将所有信息发出,则会造成信息量过大而找不到重点。一般将每个进程的第一个告警或错误信息发出即可。处理方式见上述示例代码。 将获取到所需要发送的监控信息调取微信(如图3)或短信(如图4)或其它信息推送终端的接口即可进行信息推送。 图3 微信告警示例 图4 短信告警示例 任务部署 将程序部署成定时任务,按照所需自定义监控频率即可实现cache数据库的主动实时监控。如图5所示。 图5 监控任务示例
文章
姚 鑫 · 八月 15, 2022

第二章 使用管理门户(二)

# 第二章 使用管理门户(二) # 管理门户概述 本节介绍管理门户页面的一些常见布局元素。 注意:在管理门户中的任何位置,将光标移到菜单项上都会显示该项目的描述。 ## 管理门户主页 管理门户主页的标题是 `Welcome, `。在标题旁边,功能区包含以下选项: - 两个视图按钮,可让指定如何在菜单列中显示链接。 - 搜索栏,位于功能区的右侧。当指定一个词并按 Enter 键时,将显示包含该词的所有页面的列表;然后,可以单击要显示的目标页面,而无需浏览子菜单。 以下部分描述了主页的区域: ### 管理门户菜单栏 位于主页左边缘的菜单栏是导航门户的主要方法。 ### 管理门户欢迎窗格 欢迎窗格位于主页的中心,包括经常访问的页面的快捷方式。它包含以下字段: - 收藏夹`Favorites` — 列出选择为收藏夹的管理门户页面(请参阅操作窗格);可以单击每个页面标题直接转到该页面。 - 最近`Recent` — 列出自上次启动 IRIS 以来最近显示的页面。 - `Did you know?` — 显示提示。 - 链接 `Links` - 指向可能想要访问的页面的链接。 ### 管理门户消息窗格 位于主页右侧边缘的消息窗格显示一般系统信息并提供指向系统仪表板的链接。 如果实例是镜像成员,则消息窗格还显示它所属的镜像、其状态和成员类型以及指向镜像监视器的链接。 ## 管理门户标题 页眉位于管理门户中每个页面的顶部,可用于快速导航门户。 标题包含以下链接: - 主页`Home` — 显示管理门户主页。 - 关于`About` — 显示系统概览信息。 - 帮助`Help` — 显示正在查看的页面/主题的在线文档(帮助)。 - 联系方式`Contact` — 显示全球响应中心 (WRC) 的联系方式页面。 - 注销`Logout` — 注销您并带您进入管理门户的登录页面。 - 菜单`Menu` — 根据用户担任的角色显示常见任务列表。 标头还包含有用的信息,例如: - 服务器`Server` — 运行 IRIS 的服务器的名称。 - 命名空间`Namespace` — 当前使用的命名空间的名称。要在不同的命名空间中工作,请单击切换并选择所需的命名空间。 - 用户`User` — 登录到管理门户的用户的名称。要更改用户的密码,请单击名称。 - 许可证`Licensed to` — 出现在许可证密钥信息中的客户名称。 - 实例`Instance` — 服务器上运行的 `IRIS` 实例的名称。 此外,可以显示系统模式标签(例如,测试系统); 管理门户标题的左侧显示正在使用的产品的名称。 ## 管理门户功能区 功能区位于标题正下方,并显示特定于每个页面的不同内容。例如,数据库页面(`System Explorer` > `Databases`)的功能区如下图所示: 功能区的典型内容是: - 正在显示的管理门户页面的标题。 - 当前页面的面包屑,直接列在页面标题上方。路径中列出的每个页面都是一个活动链接,可以使用它返回到先前显示的子菜单/列表。当在页面上进行未保存的更改时,会在面包屑中附加一个星号,例如系统 > 配置 >内存和启动 —(配置设置)*。在离开未保存的更改之前,系统始终会提示进行确认。 注意:页签不会列出路径中的每个页面,并且页签中的页面并不总是与导航菜单中的页面匹配。始终可以通过单击主页返回到管理门户主页并使用搜索工具导航到特定页面,本节稍后将对此进行介绍。 - 允许在页面上执行操作的几个按钮。例如,`Databases` 页面包含按钮 `Integrity Check` 和 `Integrity Log`。 - 刷新按钮,包含有关页面上次更新时间的信息。 ## 系统概述信息 单击管理门户标题上的关于时,将显示一个表格,其中包含以下信息: - 版本 — 此 `IRIS` 实例的特定构建信息,包括平台、构建号和构建日期。 - 配置 - 此实例正在使用的配置 (`.cpf`) 文件的名称和位置。 - 数据库缓存 (`MB`) — 为数据库分配的空间例程缓存 (`MB`) — 为例程分配的空间。 - 日志文件 - 当前日志文件的名称和位置。 - `SuperServer` 端口 — 运行 `IRIS` 服务器的端口号。 - `Web`服务器端口 — 运行私有 `IRIS Web` 服务器的端口号。 - 许可证服务器地址/端口 — `IRIS` 许可证服务器的 `IP` 地址和运行它的端口号。 - 许可给 — 出现在许可密钥信息中的客户名称。 - 集群支持 - 指示此实例是否是集群的一部分。 - 镜像 — 指示此实例是否是镜像的成员。 - `Time System Started` — 上次启动 `InterSystems IRIS` 实例的日期和时间。 - 加密密钥标识符 — 如果激活加密,则为加密密钥的 `GUID`(全局唯一 `ID`)。 - `NLS` 区域设置 — 国家语言支持区域设置。 - 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
Michael Lei · 四月 24, 2022

基于Docker的Apache Web Gateway

# 基于Docker的Apache Web Gateway Hi 社区 在本文中,我们将基于Docker程序化地配置一个Apache Web Gateway,使用。: * HTTPS protocol. * TLS\SSL to secure the communication between the Web Gateway and the IRIS instance. ![image](/sites/default/files/inline/images/net-schema-01.png) 我们将使用两个镜像:一个用于Web网关,第二个用于IRIS实例。 所有必需的文件都在这 [GitHub repository](https://github.com/lscalese/docker-webgateway-sample). 我们从git clone开始: ```bash git clone https://github.com/lscalese/docker-webgateway-sample.git cd docker-webgateway-sample ``` ## 准备系统 为了避免权限方面的问题,你的系统需要一个用户和一个组: * www-data * irisowner 需要与容器共享证书文件。 如果你的系统中不存在这些文件,只需执行: ```bash sudo useradd --uid 51773 --user-group irisowner sudo groupmod --gid 51773 irisowner sudo useradd –user-group www-data ``` ## 生成证书 在这个示例中,我们使用以下三个证书: 1. HTTPS web server usage. 2. TLS\SSL encryption on Web Gateway client. 3. TLS\SSL encryption on IRIS Instance. 有一个随时可用的脚本来生成它们。. 然而,你应该自定义证书的主题;只需编辑这个文件 [gen-certificates.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/gen-certificates.sh) . 这是 OpenSSL `subj` argument的结构: 1. **C**: Country code 2. **ST**: State 3. **L**: Location 4. **O**: Organization 5. **OU**: Organization Unit 6. **CN**: Common name (basically the domain name or the hostname) 可以随意改动这些值. ```bash # sudo is needed due chown, chgrp, chmod ... sudo ./gen-certificates.sh ``` 如果一切都OK,应该能看到两个带证书的新目录 `./certificates/` and `~/webgateway-apache-certificates/` with certificates: | File | Container | Description | |--- |--- |--- | | ./certificates/CA_Server.cer | webgateway,iris | Authority server certificate| | ./certificates/iris_server.cer | iris | Certificate for IRIS instance (used for mirror and wegateway communication encryption) | | ./certificates/iris_server.key | iris | Related private key | | ~/webgateway-apache-certificates/apache_webgateway.cer | webgateway | Certificate for apache webserver | | ~/webgateway-apache-certificates/apache_webgateway.key | webgateway | Related private key | | ./certificates/webgateway_client.cer | webgateway | Certificate to encrypt communication between webgateway and IRIS | | ./certificates/webgateway_client.key | webgateway | Related private key | 请记住,如果有自签名的证书,浏览器会显示安全警报。 显然,如果你有一个由认证机构交付的证书,你可以用它来代替自签的证书(尤其是Apache服务器证书) ## Web Gateway 配置文件 让我们来看看配置文件. ### CSP.INI 你能看到在 `webgateway-config-files` 目录下 CSP.INI 文件. 将被推到镜像里, 但内容可以在runtime被修改. 可以把这个文件作为模版. 在这个示例中,以下参数将在容器启动时被覆盖: * Ip_Address * TCP_Port * System_Manager 更多细节请参考 [startUpScript.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/startUpScript.sh) . 大致上,替换是通过`sed`命令行进行的. 同时, 这个文件包含 SSL\TLS 配置来确保与 IRIS 实例的通信: ``` SSLCC_Certificate_File=/opt/webgateway/bin/webgateway_client.cer SSLCC_Certificate_Key_File=/opt/webgateway/bin/webgateway_client.key SSLCC_CA_Certificate_File=/opt/webgateway/bin/CA_Server.cer ``` 这些语句都比较重要. 我们必需确保证书文件可用. 我们稍后将在`docker-compose`文件中用一个卷来做这件事. ### 000-default.conf 这是一个Apache 配置文件. 允许使用HTTPS协议并将HTTP请求重定向到HTTPS. 证书和私钥文件在这个文件里设置: ``` SSLCertificateFile /etc/apache2/certificate/apache_webgateway.cer SSLCertificateKeyFile /etc/apache2/certificate/apache_webgateway.key ``` ## IRIS 实例 对我们 IRIS实例, 我们仅仅配置最低要求来允许SSL\TLS 和Web Gateway 之间的通信; 这涉及到: 1. `%SuperServer` SSL Config. 2. Enable SSLSuperServer security setting. 3. Restrict the list of IPs that can use the Web Gateway service. 为简化配置, config-api 用一个简单的JSON 配置文件. ```json { "Security.SSLConfigs": { "%SuperServer": { "CAFile": "/usr/irissys/mgr/CA_Server.cer", "CertificateFile": "/usr/irissys/mgr/iris_server.cer", "Name": "%SuperServer", "PrivateKeyFile": "/usr/irissys/mgr/iris_server.key", "Type": "1", "VerifyPeer": 3 } }, "Security.System": { "SSLSuperServer":1 }, "Security.Services": { "%Service_WebGateway": { "ClientSystems": "172.16.238.50;127.0.0.1;172.16.238.20" } } } ``` 不需要做任何动作. 在容器启动时这个配置会自动加载. ## tls-ssl-webgateway 镜像 ### dockerfile ``` ARG IMAGEWEBGTW=containers.intersystems.com/intersystems/webgateway:2021.1.0.215.0 FROM ${IMAGEWEBGTW} ADD webgateway-config-files /webgateway-config-files ADD buildWebGateway.sh / ADD startUpScript.sh / RUN chmod +x buildWebGateway.sh startUpScript.sh && /buildWebGateway.sh ENTRYPOINT ["/startUpScript.sh"] ``` 默认的 entry point是 `/startWebGateway`, 但是在启动webserver前需要执行一些操作. 记住我们的 CSP.ini 文件只是个 `模版`, 并且我们需要在启动时改变一些参数 (IP, port, system manager) . `startUpScript.sh` 将执行这些变化并启动初始 entry point 脚本 `/startWebGateway`. ## 启动容器 ### docker-compose 文件 启动容器之前, 必须修改好`docker-compose.yml` 文件: * `**SYSTEM_MANAGER**` 必须配好授权的IP来访问 **Web Gateway Management** https://localhost/csp/bin/Systems/Module.cxw 基本就是你自己的IP地址 (可以是一个用逗号分开的列表). * `**IRIS_WEBAPPS**` 必须配好 CSP 应用列表. 这个表用空格隔开, 例如: `IRIS_WEBAPPS=/csp/sys /swagger-ui`. 默认, 只有 `/csp/sys` 被暴露. * 80和 443 端口映射好. 如果你的系统中已经使用了这些端口,请将调整为其他端口. ``` version: '3.6' services: webgateway: image: tls-ssl-webgateway container_name: tls-ssl-webgateway networks: app_net: ipv4_address: 172.16.238.50 ports: # change the local port already used on your system. - "80:80" - "443:443" environment: - IRIS_HOST=172.16.238.20 - IRIS_PORT=1972 # Replace by the list of ip address allowed to open the CSP system manager # https://localhost/csp/bin/Systems/Module.cxw # see .env file to set environement variable. - "SYSTEM_MANAGER=${LOCAL_IP}" # the list of web apps # /csp allow to the webgateway to redirect all request starting by /csp to the iris instance # You can specify a list separate by a space : "IRIS_WEBAPPS=/csp /api /isc /swagger-ui" - "IRIS_WEBAPPS=/csp/sys" volumes: # Mount certificates files. - ./volume-apache/webgateway_client.cer:/opt/webgateway/bin/webgateway_client.cer - ./volume-apache/webgateway_client.key:/opt/webgateway/bin/webgateway_client.key - ./volume-apache/CA_Server.cer:/opt/webgateway/bin/CA_Server.cer - ./volume-apache/apache_webgateway.cer:/etc/apache2/certificate/apache_webgateway.cer - ./volume-apache/apache_webgateway.key:/etc/apache2/certificate/apache_webgateway.key hostname: webgateway command: ["--ssl"] iris: image: intersystemsdc/iris-community:latest container_name: tls-ssl-iris networks: app_net: ipv4_address: 172.16.238.20 volumes: - ./iris-config-files:/opt/config-files # Mount certificates files. - ./volume-iris/CA_Server.cer:/usr/irissys/mgr/CA_Server.cer - ./volume-iris/iris_server.cer:/usr/irissys/mgr/iris_server.cer - ./volume-iris/iris_server.key:/usr/irissys/mgr/iris_server.key hostname: iris # Load the IRIS configuration file ./iris-config-files/iris-config.json command: ["-a","sh /opt/config-files/configureIris.sh"] networks: app_net: ipam: driver: default config: - subnet: "172.16.238.0/24" ``` Build and start: ```bash docker-compose up -d --build ``` `tls-ssl-iris 和 tls-ssl-webgateway 容器应该启动好了.` ## 测试 Web Access ### Apache 默认页 打开网页 [http://localhost](http://localhost). 你将自动被重定向到[https://localhost](https://localhost). 浏览器显示安全警告. 如果是自签署的证书,这是正常的,接受并继续. ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-02.png) ### Web Gateway 管理页面 打开 [https://localhost/csp/bin/Systems/Module.cxw](https://localhost/csp/bin/Systems/Module.cxw) 并测试服务器连接. ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-03.png) ### 管理门户 打开 [https://localhost/csp/sys/utilhome.csp](https://localhost/csp/sys/utilhome.csp) ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-04.png) 赞! Web Gateway 例子跑起来了! ## IRIS Mirror 与vWeb Gateway 在上一篇文章中,我们建立了一个镜像环境,但网络网关是一个缺失的部分。 现在,我们可以改进这一点。 一个包括Web Gateway和一些更多改进的资源库就可以用了 [iris-miroring-with-webgateway](https://github.com/lscalese/iris-mirroring-with-webgateway) : 1. 证书不再是即时生成的,而是在一个单独的过程中生成的. 2. IP地址被docker-compose和JSON配置文件中的环境变量所取代, 变量被定义在'.env'文件中. 3. 这个repository 可以作为一个模板来使用. 查看 repository文件 [README.md](https://github.com/lscalese/iris-mirroring-with-webgateway) 来运行以下环境: ![image](https://github.com/lscalese/iris-mirroring-with-webgateway/blob/master/img/network-schema-01.png?raw=true)