#工具

0 关注者 · 29 帖子

编程工具软件开发工具软件开发人者用来创建、调试、维护或以其他方式支持其他程序和应用程序的计算机程序。

文章 Lilian Huang · 十月 24, 2025 14m read

学习如何使用 LangGraph 设计结合了推理、矢量搜索和工具集成的可扩缩自主 AI 智能体。

cover

概括

  • AI 智能体是一种超越简单的聊天机器人的自主系统,它结合了记忆库、上下文,并具有自动完成任务的主动性。
  • LangGraph 是一种框架,它使我们能够利用具有内置状态管理的节点(任务)和边缘(连接),构建复杂的 AI 工作流。
  • 本指南将指导您构建 AI 赋能的客户支持智能体,该智能体可以划分优先级,识别相关主题,并确定是上报还是自动回复。

那么,AI 智能体究竟是什么?

让我们直面它吧 —“AI 智能体”听起来就像可以接管会议室的机器人。 实际上,它们是您得力的助手,可以简化复杂的工作流,消除重复性任务。 您可以把它们看作是聊天机器人的下一个进化阶段:它们不只是简单地等待提示;它们可以发起行动,协调多个步骤,并随时进行调整。

过去,打造一个“智能”系统意味着兼顾语言理解、代码生成、数据查找等各种不同的模型,然后将它们粘合在一起。 您的一半时间花在了集成上,另一半时间则花在了调试上。

智能体彻底颠覆了这一切。 它们将上下文、主动性和适应性融合在一个精心编排的流程中。 它们不仅实现了自动化,更是肩负使命的智者。 借助 LangGraph 之类的框架,我相信,组建一支自己的智能体团队实际上会很有趣。

image

LangGraph 究竟是什么?

LangGraph 是一种创新型框架,它彻底改变了我们构建涉及大语言模型 (LLM) 的复杂应用程序的方式。

想象一下,您正在指挥一支管弦乐队:每种乐器(或“节点”)都需要知道何时演奏,声音有多大,顺序如何。 这种情况下,LangGraph 就是您的指挥棒,为您提供以下内容:

  • 图结构:它采用具有节点和边缘的图结构,使开发者能够设计适应各种分支和循环的灵活非线性工作流。 它可以反映复杂的决策过程,类似于神经通路的运作方式。
  • 状态管理:LangGraph 提供了各种内置工具,可以实现状态保持和错误恢复,简化了应用程序中各个阶段的上下文数据的维护。 借助 Zep 等工具,它可以在短期记忆和长期记忆之间高效切换,提高了交互质量。
  • 工具集成:借助 LangGraph,LLM 智能体可以轻松与外部服务或数据库协作,获取真实的数据,从而改进应用程序的功能和响应性。
  • 人机协同:除了可以实现自动化外,LangGraph 还可以适应工作流中的人为干预,这对于需要分析监督或伦理考虑的决策过程至关重要。

无论您是在构建具有真实记忆的聊天机器人、交互式故事引擎,还是能够处理复杂问题的智能体团队,LangGraph 都可以将令人头疼的管道工程转变成简单明了、直观的状态机。

开始

要开始使用 LangGraph,您需要进行基本的设置,通常包括安装 langgraph 和 langchain-openai 等必要的库。 然后,您可以定义图中的节点(任务)和边缘(连接),有效地实现短期记忆的检查点,并利用 Zep 满足更持久的记忆需求。

操作 LangGraph 时,请记住以下几点:

  • 具有灵活性的设计:利用强大的图结构来解释并非严格线性的潜在工作流分支和交互。
  • 以深思熟虑的方式与工具交互:利用外部工具增强 LLM 功能,而不是取而代之。 为每个工具提供全面的描述,以实现精确使用。
  • 采用丰富的记忆解决方案:高效地使用记忆库,留意 LLM 的上下文窗口,并考虑集成外部解决方案,以实现自动事实管理。

现在,我们已经介绍 LangGraph 的基础知识,我们来看一个实例。 为此,我们将开发一个用于提供客户支持的 AI 智能体。

这个智能体将接收电子邮件请求,分析电子邮件正文中的问题描述,然后确定请求的优先级和适当的主题/类别/部门。

系好安全带,我们开始吧!

buckle up

首先,我们需要定义什么是“工具”。 可以把它看作是智能体的专属“助理”,使其能够与外部功能进行交互。

@tool 装饰器在这里必不可少。 LangChain 简化了自定义工具的创建,这意味着首先,定义一个 Python 函数,然后应用 @tool 装饰器。

tools

为了进行说明,我们来创建第一个工具。 这个工具将帮助智能体根据电子邮件内容划分 IT 支持工单的优先级:

    from langchain_core.tools import tool
    
    @tool
    def classify_priority(email_body: str) -> str:
        """Classify the priority of an IT support ticket based on email content."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and classify its priority as High, Medium, or Low.
            
            High: System outages, security breaches, critical business functions down
            Medium: Non-critical issues affecting productivity, software problems
            Low: General questions, requests, minor issues
            
            Email: {email}
            
            Respond with only: High, Medium, or Low"""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

太棒了! 现在,我们有一个提示,指示 AI 接收电子邮件正文,对其进行分析,并将其优先级分为“高”、“中”或“低”。

就是这样! 您刚刚编写了一个智能体可以调用的工具!

接下来,我们创建一个类似的工具来识别支持请求的主要主题(或类别):


    @tool
    def identify_topic(email_body: str) -> str:
        """Identify the main topic/category of the IT support request."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and identify the main topic category.
            
            Categories: password_reset, vpn, software_request, hardware, email, network, printer, other
            
            Email: {email}
            
            Respond with only the category name (lowercase with underscores)."""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

现在,我们需要创建一个状态,在 LangGraph 中,这一小部分非常重要。

把它想象成图的中枢神经系统。 这就是节点之间的通信方式,就像优等生在课堂上传递纸条一样。

文档中显示:

“状态是表示应用程序当前快照的共享数据结构。”

在实践中呢? 状态是在节点之间移动的结构化消息。 它将一个步骤的输出作为下一个步骤的输入。 基本上,它是将整个工作流粘合在一起的粘合剂。

因此,在构建图之前,我们必须先定义我们的状态结构。 本例中,我们的状态包括以下内容:

  • 用户请求(电子邮件正文)
  • 指定的优先级
  • 确定的主题(类别)

它简单明了,因此您可以像专业人士一样浏览图。

    from typing import TypedDict

    # Define the state structure
    class TicketState(TypedDict):
        email_body: str
        priority: str
        topic: str
        
    
    # Initialize state
    initial_state = TicketState(
        email_body=email_body,
        priority="",
        topic=""
    )

节点与 边缘:LangGraph 的关键组成部分

LangGraph 的基本要素包括节点边缘

  • 节点:它们是图中的操作单元,执行实际工作。 节点通常由可以执行任何逻辑(从计算到与语言模型 (LLM) 或外部集成交互)的 Python 代码组成。 从本质上讲,节点就像传统编程中的个别函数或智能体。
  • 边缘:边缘定义节点之间的执行流,决定接下来会发生什么。 它们充当连接器,允许状态根据预定义条件从一个节点转换到另一个节点。 在 LangGraph 中,边缘在协调节点之间的序列和决策流方面至关重要。

为了掌握边缘的功能,我们来看一个消息传递应用程序的简单类比:

  • 节点类似于积极参与对话的用户(或他们的设备)。
  • 边缘代表着用户之间能够促进沟通的聊天主题或连接。

当用户选择一个聊天主题来发送消息时,会有效地创建一个边缘,将他们与另一个用户连接在一起。 与 LangGraph 状态的结构化模式类似,每次交互(无论是发送文本、语音还是视频消息)都遵循预定义的顺序。 它确保了沿边缘传递的数据的一致性和可解释性。

不同于事件驱动型应用程序的动态特性,LangGraph 采用在整个执行过程中保持一致的静态模式。 它简化了节点之间的通信,使开发者可以依赖稳定的状态格式,从而确保无缝的边缘通信。

设计基本工作流

可以将 LangGraph 中的流工程理解为设计一个状态机。 在这个情境中,每个节点代表一个不同的状态或处理步骤,而边缘定义了这些状态之间的转换。 这种方式对想要在 AI 的确定性任务序列与动态决策能力之间取得平衡的开发者特别有用。 我们来使用前面定义的 TicketState 类初始化 StateGraph,开始构建流程。

    from langgraph.graph import StateGraph, START, END
    
    workflow = StateGraph(TicketState)

节点添加:节点是基本要素,用于执行划分工单优先级或识别其主题等特定任务。

每个节点函数均接收当前状态,执行其操作,并返回一个字典以更新状态:

   def classify_priority_node(state: TicketState) -> TicketState:
        """Node to classify ticket priority."""
        priority = classify_priority.invoke({"email_body": state["email_body"]})
        return {"priority": priority}

    def identify_topic_node(state: TicketState) -> TicketState:
        """Node to identify ticket topic."""
        topic = identify_topic.invoke({"email_body": state["email_body"]})
        return {"topic": topic}
        
        
    workflow.add_node("classify_priority", classify_priority_node)
    workflow.add_node("identify_topic", identify_topic_node)

classify_priority_node 和 identify_topic_node 方法将更改 TicketState 并发送参数输入。

边缘创建:定义连接节点的边缘:


    workflow.add_edge(START, "classify_priority")
    workflow.add_edge("classify_priority", "identify_topic")
    workflow.add_edge("identify_topic", END)

classify_priority 确定起点,而 identify_topic 确定到目前为止工作流的终点。

编译与执行:配置节点和边缘后,编译并执行该工作流。


    graph = workflow.compile()
    result = graph.invoke(initial_state)

太好了! 您还可以生成 LangGraph 流的可视化表示。

graph.get_graph().draw_mermaid_png(output_file_path="graph.png")

如果将代码运行到此点,您就会看到一个与下面类似的图:

first_graph.png

这幅图直观地显示了一次顺序执行:开始,然后划分优先级,接着确定主题,最后结束。

LangGraph 最强大的一个方面是它的灵活性,这让我们可以创建更复杂的流程和应用程序。 例如,我们可以修改工作流,使用以下行将 START 中的边缘添加到两个节点:

    workflow.add_edge(START, "classify_priority")
    workflow.add_edge(START, "identify_topic")

这一更改意味着智能体将同时执行 classify_priority 和 identify_topic。

LangGraph 中另一个非常有用的功能是能够使用条件边缘。 它们允许工作流根据对当前状态的评估进行分支,实现任务的动态路由。

我们来增强工作流。 我们将创建一个新工具,分析请求的内容、优先级和主题,以确定它是否为需要上报(例如,为人工团队提交工单)的高优先级问题。 如果不需要,将为用户生成一个自动响应。


    @tool
    def make_escalation_decision(email_body: str, priority: str, topic: str) -> str:
        """Decide whether to auto-respond or escalate to IT team."""
        prompt = ChatPromptTemplate.from_template(
            """Based on this IT support ticket, decide whether to:
            - "auto_respond": Send an automated response for simple/common or medium priority issues
            - "escalate": Escalate to the IT team for complex/urgent issues
            
            Email: {email}
            Priority: {priority}
            Topic: {topic}
            
            Consider: High priority items usually require escalation, while complex technical issues necessitate human review.
            
            Respond with only: auto_respond or escalate"""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "priority": priority,
            "topic": topic
        })
        return response.content.strip()
        

此外,如果请求被确定为低优先级或中等优先级(导致“auto_respond”决策),我们将执行矢量搜索来检索历史回答。 然后,将使用此信息来生成适当的自动回答。 不过,这需要两个额外的工具:


    @tool
    def retrieve_examples(email_body: str) -> str:
        """Retrieve relevant examples from past responses based on email_body."""
        try:
            examples = iris.cls(__name__).Retrieve(email_body)
            return examples if examples else "No relevant examples found."
        except:
            return "No relevant examples found."

    @tool
    def generate_reply(email_body: str, topic: str, examples: str) -> str:
        """Generate a suggested reply based on the email, topic, and RAG examples."""
        prompt = ChatPromptTemplate.from_template(
            """Generate a professional IT support response based on:
            
            Original Email: {email}
            Topic Category: {topic}
            Example Response: {examples}
            
            Create a helpful, professional response that addresses the user's concern.
            Keep it concise and actionable."""
        )
        chain = prompt | llm
        response = chain.invoke({
            "email": email_body,
            "topic": topic,
            "examples": examples
        })
        return response.content.strip()

现在,我们为这些新工具定义相应的节点:

    
    def decision_node(state: TicketState) -> TicketState:
        """Node to decide on escalation or auto-response."""
        decision = make_escalation_decision.invoke({
            "email_body": state["email_body"],
            "priority": state["priority"],
            "topic": state["topic"]
        })
        return {"decision": decision}
        
    
    def rag_node(state: TicketState) -> TicketState:
        """Node to retrieve relevant examples using RAG."""
        examples = retrieve_examples.invoke({"email_body": state["email_body"]})
        return {"rag_examples": examples}

    def generate_reply_node(state: TicketState) -> TicketState:
        """Node to generate suggested reply."""
        reply = generate_reply.invoke({
            "email_body": state["email_body"],
            "topic": state["topic"],
            "examples": state["rag_examples"]
        })
        return {"suggested_reply": reply}
        
    
    def execute_action_node(state: TicketState) -> TicketState:
        """Node to execute final action based on decision."""
        if state["decision"] == "escalate":
            action = f"🚨 ESCALATED TO IT TEAM\nPriority: {state['priority']}\nTopic: {state['topic']}\nTicket created in system."
            print(f"[SYSTEM] Escalating ticket to IT team - Priority: {state['priority']}, Topic: {state['topic']}")
        else:
            action = f"✅ AUTO-RESPONSE SENT\nReply: {state['suggested_reply']}\nTicket logged for tracking."
            print(f"[SYSTEM] Auto-response sent to user - Topic: {state['topic']}")
        
        return {"final_action": action}
        
        
        
    workflow.add_node("make_decision", decision_node)
    workflow.add_node("rag", rag_node)
    workflow.add_node("generate_reply", generate_reply_node)
    workflow.add_node("execute_action", execute_action_node)

然后,条件边缘将使用 make_decision 节点的输出来定向流:

    workflow.add_conditional_edges(
        "make_decision",
        lambda x: x.get("decision"),
        {
            "auto_respond": "rag",
            "escalate": "execute_action"
        }
    )

如果 make_escalation_decision 工具(通过 decision_node)产生“auto_respond”,工作流将继续通过 RAG 节点(检索示例),然后是 generate_reply 节点(设计回答),最后是 execute_action 节点(记录 auto-response)。

相反,如果决策是“escalate”,流程将绕过 RAG 和生成步骤,直接转到 execute_action 来处理上报。 要添加剩余的标准边缘来完成图,请执行以下操作:

    workflow.add_edge("rag", "generate_reply")
    workflow.add_edge("generate_reply", "execute_action")
    workflow.add_edge("execute_action", END)

数据集注释:对于此项目,我们用于支持检索增强生成 (RAG) 的数据集来自 Hugging Face 上的 Customer Support Tickets 数据集。 对该数据集进行了筛选,以便只包含分类为技术支持并限制为英语的条目。 它确保 RAG 系统只为技术支持任务检索高度相关的特定领域的示例。

此时,我们的图应当与下图类似:

graph.png

当您使用一封电子邮件来执行此图,并导致高优先级分类和“escalate”决策时,您将看到以下响应:

image.png

同时,被分类为低优先级并导致“auto_respond”决策的请求将触发与下面类似的回复:

image.png

那么… 这一路都很顺利吗?

并不完全是。 有一些问题需要注意:

  • 数据隐私:小心敏感信息 — 这些智能体需要防护。
  • 计算成本:某些高级设置需要大量资源。
  • 幻觉:LLM 偶尔也会编造一些内容(不过仍然比大多数实习生聪明)。
  • 非确定性:相同的输入可能会返回不同的输出,这对创造力来说是好事,但对严格的流程来说却很棘手。

不过,大多数缺点都可以通过良好的规划、合适的工具和一点点思考加以控制。

LangGraph 将 AI 智能体从流行语变成实实在在的有效解决方案。 无论您是要自动执行客户支持,处理 IT 工单,还是构建自主应用程序,这个框架都让操作变得可行,甚至有趣。

您有任何问题或反馈吗? 请说出来。 AI 革命需要像您这样的建设者。

0
0 9
文章 Lilian Huang · 四月 16, 2025 7m read

Hi 大家好
在本文中,我讲介绍我的应用 iris-AgenticAI .

代理式人工智能的兴起标志着人工智能与世界互动方式的变革性飞跃--从静态响应转变为动态、目标驱动的问题解决方式。参看 OpenAI’s Agentic SDK ,  OpenAI Agents SDK使您能够在一个轻量级、易用且抽象程度极低的软件包中构建代理人工智能应用程序。它是我们之前的代理实验 Swarm 的生产就绪升级版。

该应用展示了下一代自主人工智能系统,这些系统能够进行推理、协作,并以类似人类的适应能力执行复杂任务。

应用功能

  • Agent Loop 🔄 一个内置循环,可自主管理工具的执行,将结果发回 LLM,并迭代直至任务完成。
  • Python-First 🐍 利用本地 Python 语法(装饰器、生成器等)来协调和连锁代理,而无需外部 DSL。
  • Handoffs 🤝 通过在专业代理之间委派任务,无缝协调多代理工作流程。
  • Function Tools ⚒️ 用 @tool 修饰任何 Python 函数,可立即将其集成到代理的工具包中。
  • Vector Search (RAG) 🧠 原生集成向量存储(IRIS),用于 RAG 检索。
  • Tracing 🔍 内置跟踪功能,可实时可视化、调试和监控代理工作流(想想 LangSmith 的替代方案)。
  • MCP Servers 🌐 通过 stdio 和 HTTP 支持模型上下文协议(MCP),实现跨进程代理通信。
  • Chainlit UI 🖥️ 集成 Chainlit 框架,可使用最少的代码构建交互式聊天界面。
  • Stateful Memory 🧠 跨会话保存聊天历史、上下文和代理状态,以实现连续性和长期任务。
0
0 118
文章 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
文章 Michael Lei · 八月 6, 2024 2m read

开发新的互操作性Production时,最初在Production中添加设置是很自然的做法。

不过,一旦要将Production从开发环境移动到测试或其他环境,你就会发现 HTTP 服务器、IP 地址和/或端口之类的设置都需要更改。 为了避免这些设置在后续重新部署时被覆盖,必须将这些设置从Production得设置中移动到系统默认设置(System Default Settings)。

虽然系统默认设置可以手动创建,但是当生产中有大量业务组件时会难以处理。 因此,@Wietze Drost 让我开发一个工具自动执行此流程,通过筛选表达式指定哪些设置必须创建为系统默认设置。

  • 这个表达式可以定义为“*:HTTPServer,SSLConfig*”,其中“\*”表示“为任何主机类名”。 冒号后面是需要移动的设置列表。 所以,这个表达式的意思是“为所有名为 HTTPServer 和 SSLConfig 的设置创建或更新系统默认设置”。
  • 可以定义多个筛选表达式,用分号分隔,例如 "*:HTTPServer,SSLConfig;FullClassName2:xxx,yyy"

根据他的请求,我编写了名为 GetSettingsFromProduction 的类方法,

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

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


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

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

2. 通用RESTful 消息

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

0
2 227
文章 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
文章 Hao Ma · 十一月 22, 2023 5m read

介绍

由于InterSystems最近宣布从2023.2版本开始停止对InterSystems Studio的支持,转而独家开发Visual Studio Code(VSC)IDE的扩展,相信后者比Studio提供了更优越的体验,我们很多开发者都已切换或开始使用 VSC。很多人可能想知道如何打开终端进行操作,因为VSC没有像Studio那样的输出面板,也没有集成的功能来打开IRIS终端,除非下载InterSystems开发的插件。

概括

  • 介绍
  • 解决方案
    • 对于至少具有 IRIS 2020.1 或 IRIS IRIS 2021.1.2 的用户– 使用 Web 终端
    • 对于至少具有 IRIS 2023.2 的用户 – 使用 WebSocket 终端
    • 对于使用基于 Docker 的 IRIS 的用户
    • 对于在本地计算机上使用 2023.2 之前的 IRIS 版本的用户
    • 对于使用 SSH 连接在基于远程服务器的 IRIS 上进行编码的用户

解决方案

在 VSC 中打开终端的方法有多种,具体取决于您使用的具体配置,我在这里总结了适合任何情况的最佳解决方案:

对于至少具有 IRIS 2020.1.1 或 IRIS 2021.1.2 的用户 – 使用 Web 终端

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

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

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

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

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

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

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

您首先连接到服务器

0
0 141
文章 water huang · 十月 6, 2023 4m read

一般情况下,我们根据iris的portal向导创建数据库,然后创建命名空间。这个过程比较花时间,如果是已经存在的数据库,还需要再装载。翻阅portal调用的方法后,我整合了这几个方法。把这几个方法拷贝到任意已经存在的命名空间,通过执行CNNS(路径,命名空间),就可以快速创建好命名空间。方法的大概过程是,进入到%sys命名空间,然后依次创建数据库,创建命名空间,创建web应用。创建完成后,回到当前命名空间。

0
0 192
文章 liu bo · 九月 21, 2023 4m read

前言

对于第三方接口进行交互的时候,往往需要大量的进行参数合法性校验。以前的方法就是对每个参数进行验证。如下截图: image

上图的会存在大量的if else if else..,如果字段很多,那导致一个方法存在大量的验证的代码,那我们考虑是否可以进行统一的验证参数的合法性。

思路

平时建立类的时候我们可以写参数MAXLEN=100,TRUNCATE=1 是否截取等,那找找这些参数的定义地方。如截图:

image 那我们想要定义自己的参数,该如何定义呢?根据面向对象设计原则之一:

里氏替换原则(Liskov Substitution Principle,LSP):子类型必须能够替换掉他们的基类型。即,在任何父类可以出现的地方,都可以用子类的实例来赋值给父类型的引用。当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有是一个 (is-a) 关系

那我们可以自定义数据类型,继承%Library.String,这样子类继续使用父类的参数,还可以自定义自己的参数。此处以字符串为例,其他的数据类型一样的原理。 自定义类型就为String.

实现

  1. 自定义数据类型

`

/// 自定义数据类型实现继承的String
Class Design.DataType.String Extends %Library.String
{

/// 是否为空 1 必填 0 可以为空
Parameter NOTBLANK = 0;

/// 代码值,写取global的表达式? $XECUTE 执行?
Parameter DICCODE;

/// 不为空的错误消息
Parameter MESSAGE;

/// 错误码值错误
Parameter CODEERRMESSAGE;

/// 类型 INT,STRING,FLOAT,NUMBER,DATE,DATETIME
Parameter TYPE = "STRING";

/// 是否时间类型
Parameter ISDATE = 0;

/// 时间格式:yyyy-MM-dd=>3  yyyyMMdd=8 dd/MM/yyyy=1	默认
Parameter DATEFORMAT = 3;

/// 条件取值验证
Parameter CONDITION;

/// 是否需要在当前时间之后,比如预约时间
Parameter ISAFTER;

/// 是否在当前直接之前 比如出生日期
Parameter ISBEFORE;

}

`

2.定义模型类 `

Class Design.DataType.Person Extends (%RegisteredObject, %XML.Adaptor)
{

/// 姓名
Property pname As String(MAXLEN = 100, MESSAGE = "人员姓名不能为空!", NOTBLANK = 1, TRUNCATE = 1);

/// 生日
Property birth As String(DATEFORMAT = 3, ISBEFORE = 1, ISDATE = 1, MESSAGE = "出生日期不能为空!", NOTBLANK = 1, TRUNCATE = 1);

/// 性别
Property sexCode As String(CODEERRMESSAGE = "性别代码错误!", DICCODE = "$O(^CT(""SEX"",0,""Code"",", MAXLEN = 100, MESSAGE = "性别代码不能为空!", NOTBLANK = 1);

/// ...此处省略  民族,国籍,学历等等
/// 工作描述
Property job As Job;

}

`

3.统一验证的方法代码 `

/// 校验对象的工具类
Class Design.DataType.ValidUtil Extends %RegisteredObject
{

/// 判断对象是否有效
/// TODO:嵌套对象可以自己研究研究
ClassMethod IsValid(obj As %ObjectHandle, Output errmsg As %String) As %Status
{
	s ClsName=obj.%ClassName(1)
	s validFlag=$$$OK
	s Name=""
	f{
		s Name=$o(^oddDEF(ClsName,"a",Name)) 
		q:Name="" 
	    s Val=$Property(obj,Name)
	    w Name_"="_Val,!
	    ;获取属性对应的参数
	    ;是否为空
	    s NOTBLANK=$g(^oddDEF(ClsName,"a",Name,"P","NOTBLANK"))
	    i (NOTBLANK=1)&&(Val=""){
		  s errmsg= $g(^oddDEF(ClsName,"a",Name,"P","MESSAGE"))  
		  s validFlag=0
		  q 
		}
		;是否码值校验
		s DICCODE=$g(^oddDEF(ClsName,"a",Name,"P","DICCODE"))
		i DICCODE'=""{
		   SET cmd="(out){ s out="_DICCODE_""""""_$$ALPHAUP^SSUTIL4(Val)_""",""""))"_" q 1}"
		   SET rtn=$XECUTE(cmd,.rowId)
		   i rowId="" {
			  s errmsg=$g(^oddDEF(ClsName,"a",Name,"P","CODEERRMESSAGE"))
			  s validFlag=0
			  q 
		   } 
		}
		;是否时间格式校验
	    s ISDATE=$g(^oddDEF(ClsName,"a",Name,"P","ISDATE"))
	    if ISDATE=1 {
		   s DATEFORMAT=$g(^oddDEF(ClsName,"a",Name,"P","DATEFORMAT"))
		   s result=$zdh(Val,DATEFORMAT,"","","","","","-1","时间格式错误","")
           if result="时间格式错误"{
	          s errmsg="字段"_Name_"["_Val_"]"_"时间格式错误"
	          s validFlag=0
			  q  
	       }
	       //时间与当前时间的验证
	       s ISBEFORE=$g(^oddDEF(ClsName,"a",Name,"P","ISBEFORE"))
	       if ISBEFORE=1{
		      if result>+$h {
			      s errmsg="字段"_Name_"["_Val_"]"_"不能超过当前日期!"
			      s validFlag=0
				  q  
		      }
		   }
		   s ISAFTER=$g(^oddDEF(ClsName,"a",Name,"P","ISAFTER"))
	       if ISAFTER=1{
		      if result<+$h {
			      s errmsg="字段"_Name_"["_Val_"]"_"不能小于当前日期!"
			      s validFlag=0
				  q  
		      }
		   }
		}
	}
    q validFlag
}

}

`

测试

  1. 测试不为空 image

  2. 测试code错误 image

3.测试时间格式错误 image

4.测试时间的值先后 image

[^1]

**参数还需要进行大量的验证,此处只是示例,可能存在错误,欢迎批评纠正**

[^1]:

1
1 206
文章 Michael Lei · 九月 17, 2023 2m read

增强的密码管理:无缝编辑密码

在不断发展的数字安全领域,强大的密码管理工具已变得不可或缺。我们的密码管理应用程序旨在简化和保护您的在线生活,现在提供了一项增强功能 - 轻松编辑密码的能力。

为什么这个功能会改变游戏规则?

  1. 灵活性:生活是动态的,我们的在线帐户也是如此。借助新的编辑密码功能,您可以在需要时灵活地修改已保存的密码。无论您是出于安全考虑想要更改密码还是只是更新密码,此功能都可以让您轻松适应。
  2. 简化的体验:编辑密码是无缝且用户友好的。不再需要繁琐的过程或从头开始创建新条目。只需点击几下,您的密码就会更新,从而使您的记录保持井然有序且最新。
  3. 增强安全性:我们将安全性放在首位。编辑密码功能可确保使用现有加密密钥对更新后的密码进行加密。这意味着即使修改密码,您的数据仍然受到保护。
  4. 个性化:您的密码,您做主。根据需要自定义标题、登录名和密码。此功能使您能够使您的密码管理器真正个性化,适合您独特的偏好和组织风格。

怎么运行的:

  • 登录到您的帐户。
  • 导航到您要编辑的密码。
  • 单击“编辑”图标。
  • 修改密码标题、登录名或密码本身。
  • 保存您的更改。
  • 您更新的密码现已安全存储并可供使用。

保持安全,保持井井有条:

凭借增强的编辑密码功能,我们的密码管理器为您的安全需求提供了更全面的解决方案。确保安全、井井有条并放心地管理您的密码。

下一步是什么:

0
0 145
公告 Michael Lei · 六月 8, 2023

如果您已经使用%UnitTest 框架构建了单元测试,或者正在考虑这样做,请查看InterSystems 测试管理器Test Manager

无需离开 VS Code,您现在可以浏览单元测试、运行或调试它们,并查看之前的运行结果。

InterSystems 测试管理器适用于 ObjectScript 扩展支持的两种源代码位置范例。您的单元测试类可以在 VS Code 的本地文件系统(“客户端编辑”范例)或服务器命名空间(“服务器端编辑”)中掌握。在这两种情况下,实际测试运行都发生在服务器命名空间中。

欢迎反馈。

0
0 139
文章 water huang · 三月 27, 2023 9m read

一、背景

1.1 我遇到了几个项目,他们的接口服务器崩溃了。 项目上希望尽快恢复服务器。他们的服务器在局域网上运行,他们不能使用git,服务器中有多个命名空间运行不同的服务,而且通常只有一台平台服务器。

1.2 如果消息中有字符流类型的属性,消息搜索页面不支持使用字符流属性进行过滤,因此很难找到想要的消息。

1.3 其他同事可能会更新服务器上的代码,代码中可能有些错误。

2.挑战

2.1 如何快速恢复?

2.2 如何支持字符流属性过滤消息?

2.3 如何在编译类时自动备份?

3.解决方案

1.编译时自动导出为备份文件

首先,我们定义一个名为“SYS.base”的类,它只有一个名为“ CLSBAKPATH”的参数,并设置它的值

Class SYS.Base Extends%RegisteredObject 
{
   Parameter CLSBAKPATH = "D:\IRIS\CLSBAK";
}

然后,定义一个名为“SYS.Projection”的类,它继承了 base和%Projection.AbstractProjection,添加“Projection Reference As SYS.Projection”,重写类方法“CreateProjection”;

代码如下:

1
0 209
公告 Jingwei Wang · 一月 26, 2023

以下是 2023 年 InterSystems 开发者工具大赛的技术红利,您可以在投票中获得额外加分:

  • 嵌入式 Python的使用
  • Docker容器的使用
  • ZPM 包部署
  • 在线演示
  • 代码质量通过
  • 开发者社区文章
  • 开发者社区第二篇文章
  • 上传 YouTube 视频
  • 第一次贡献

请参阅下面的额外加分项详细信息:

嵌入式 Python的使用 - 3 分

在您的应用程序中使用嵌入式 Python,可以获得 3 分加分。您至少需要 InterSystems IRIS 2021.2及以上版本。

Docker 容器的使用 - 2 分

如果应用程序使用在 docker 容器中运行的 InterSystems IRIS,则该应用程序将获得“Docker 容器”奖励。这是最简单的模板

ZPM 包部署 - 2 分

如果您为您的全栈应用程序构建和发布 ZPM(ObjectScript 包管理器)包,您可以获得2分加分,以便它可以使用如下方式部署:

zpm "install your-multi-model-solution"

已安装的ZPM 客户端命令,请参考:

ZPM客户端。文档

项目的在线演示 - 2 分
如果您将项目作为在线演示提供给云,则可额外获得 3 个奖励积分。您可以自己完成,也可以使用此模板- 这是一个 示例。这是有关如何使用它的视频

代码质量通过且零错误 - 1 分

0
0 150
公告 Michael Lei · 一月 23, 2023

嘿开发者,

我们想邀请您参加我们的下一场比赛,该比赛致力于创造有用的工具,让您的开发伙伴们的生活更轻松

🏆 InterSystems 开发者竞赛:Tool(工具)🏆

提交有助于加快开发速度、贡献更多定性代码并有助于使用 InterSystems IRIS 测试、部署、支持或监控您的解决方案的应用程序。

时间: 2023 年 1 月 23 日至 2 月 12 日(美国东部时间)

奖金池: 13,500 美元

 

0
1 303
文章 Hao Ma · 三月 25, 2021 2m read

在Caché时代, 比较受欢迎的IRIS数据库客户端是Sqldbx和Winsql, 这两者的共同点是提供ODBC兼容的连接,而且免费。限制也差不多:只能用于Windows环境,只能用ODBC连接。 

DBeaver是我最近使用的免费SQL客户端, 推荐给各位。它有几个好处:

1
1 610
文章 Michael Lei · 八月 9, 2022 1m read

在测试你的代码时,你经常会遇到需要检查对象的实际内容。无论是使用 ZWRITE 还是 $system.OBJ.Dump()

你会得到一个简单的属性图片,"--- 属性值---"

而 "--- swizzled references ---" 更容易让人混淆 

用“--- calculated references ---" 你只是被留在了后面。

0
0 391
公告 Michael Lei · 四月 29, 2022

我们很高兴向您介绍我们新的反馈门户网站--InterSystems Ideas!

我们的目标是改善我们的反馈机制,使您可以建议我们的产品如何发展以满足您的业务挑战。在开发者社区提问是与您的同行就特定的代码问题进行互动的好方法,而我们的客户支持网站WRC则一如既往地是解决实时问题的方法。

这个新的门户是为了获得您更高层次的想法。不是关于今天如何,更多是关于未来,您想看到我们的产品在未来如何更好地工作。您可以发布您自己的反馈,也可以对其他人提供的反馈进行评论/投票。InterSystems将查看你提交的任何反馈,直接回应你的反馈,并且如果您的建议有了任何进展,我们将及时更新状态。

所以,欢迎提出您的想法,为了我们共同的未来!谢谢!

0
0 80
文章 Johnny Wang · 十一月 21, 2021 3m read

    在医疗领域,开发创新可以挽救更多的生命。

    这也是为什么我们更需要去倾听负责构建未来的人:开发人员。 他们需要什么工具才能更有效地使应用程序更加高效? 他们面对着什么样的障碍?

    InterSystems 不想去做无用的猜测,因此我们推动进行了一项研究,该研究综合了 200 名医疗行业开发者的反馈,深入了解了他们的最大需求。我们认为,这些研究结果为医疗单位和医疗技术公司提供了一个机会,可以帮助他们的开发团队为业务带来新机遇,同样也为临床医生和患者带来更光明的未来。

    以下是三个关键要点:

    1. 开发人员想要一个统一的医疗平台。

    在接受本次研究采访的 200 名开发人员中,有88% 的受访者表示他们是医疗 IT 领域的专家或该领域的技术人员——他们都希望能有最好的、为他们的行业量身定制的开发工具。 这就是为什么一半的受访者将统一的、专注于医疗的数据平台列为购买新开发工具的关键原因。

    一个合适的医疗行业开发平台应该包括互操作性/集成引擎、分析工具、面向医疗行业的自然语言处理功能、机器学习工具和 FHIR 服务器,以及其他组件。

    如果一家公司能够提供一个包含所有上述组件的平台,那么超过 90% 的开发人员将对这项技术非常感兴趣。

    2. 临床数据模型必不可少

0
0 176
文章 Hao Ma · 四月 18, 2021 5m read

IRIS相比Caché在部署上的一个进步是支持docker。即便不是云部署, 使用docker也带来非常多的便利。 尤其是在开发测试环节,由于docker的使用更便捷,除非要模拟客户的环境或者做规定的性能测试,我在测试中基本已经不再使用本机的实例或者虚机。IRIS的联机文档有详细的IRIS docker安装使用指导,本文只是一个简单的,快速上手的在测试环境安装IRIS docker的简单步骤,尤其适合初学者。

注意Windows上docker可能会遇到这样那样的问题,因此通常还是推荐在Linux或者Mac OS上使用。正式的生产环境的IRIS docker container也是不支持Windows系统的。

Referrence

##1. 在操作系统上安装Docker环境

Docker官方文档的安装步骤非常清晰,我按照上面的步骤在MAC和CentOS上安装docker从来没有出现过问题。恰恰是这个文档中没有的Redhat上的安装,过程中曾碰到过几个问题,在网上搜索了答案,也不算困难。

简单的复述一下步骤:root用户的最简单安装,没有权限问题, 没有docker网络修改等等:

  1. 安装yum-util, 用于添加yum源,如果您的系统中已经有了yum-utils包这步可以省略

     sudo yum install -y yum-utils
    
  2. 添加docker的Repo到yum源并确认

     sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
     sudo yum repolist
    
  3. 安装3个docker组件

     sudo yum install docker-ce docker-ce-cli containerd.io
    
  4. 启动Docker

     sudo systemctl start docker
    
  5. 确认安装成功。'docker run'命令会去本地的库里找'hello-world',没有找到就去网上下一个"images",然后创建一个docker容器给你使用。

     sudo docker run hello-world
    

安装结束您想要到https://hub.docker.com/注册一个账户,用来下载上传docker image。 下载命令使用'docker pull', 比如

docker pull nginx

2. 下载IRIS Docker image

在线文档Container Images Available from InterSystems中介绍了https://containers.intersystems.com网站上可以下载的IRIS images的列表,其中community版本不需要license,其他的版本需要从InterSystems处获得IRIS docker版的专用license.

下面是登录并下载iris docker image的记录。从网页登录https://containers.intersystems.com,您会得到docker的登录密码"使用的密码"jaRWSBJjcUcNprCKTuMX10PYHNq2IYPrAQoYdp6Siokb"。

[root@centos7 ~]# docker login -u="hma" -p="jaRWSBJjcUcNprCKTuMX10PYHNq2IYPrAQoYdp6Siokb" containers.intersystems.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
[root@centos7 ~]# docker pull containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
2020.3.0.304.0: Pulling from intersystems/iris-ml
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
d21839215a64: Pull complete
7995f836674b: Pull complete
841ee3aaa7aa: Pull complete
739c318c2223: Pull complete
d76886412dda: Pull complete
Digest: sha256:4c62690f4d0391d801d3ac157dc4abbf47ab3d8f6b46144288e0234e68f8f10e
Status: Downloaded newer image for containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0
[root@centos7 ~]#

另外,您也可以从InterSystems的技术支持网站下载iris docker的压缩包"iris*.tar.gz",然后使用"docker load"安装, 比如:

CNMBPHMA:~ hma$ docker load -i iris-2020.4.0.524.0-docker.tar.gz

3. 创建并运行IRIS Container

###简单的办法

如果是只是简单的测试,可以直接运行,但如果不是community版本的docker, 您还需要把license拷贝到container内部。 比如下面的步骤

CNMBPHMA:~ hma$ docker run -d -i -p 52773:52773 -p 51773:51773 --name iris20204 intersystems/irishealth:2020.2.0.211.0
CNMBPHMA:~ hma$ docker cp iris.key iris20204:/usr/irissys/mgr/iris.key

需要注意的是,不同版本的超级端口可能不同,有些是1972,有些是51773。为了省事,您也许可以使用"-P"把所有container内部的端口都映射出来。

除了要拷贝license到container内部,在做测试的时候,您可能还要经常的使用"docker cp"把各种测试文件拷入container。而且,当查看和修改container内部文件的使用,还需要使用下面的命令进入container内部:

CNMBPHMA:~ hma$ docker exec -it iris20204 /bin/sh

因此,如果是您需要一个经常使用的iris container环境,我还是建议您使用下面的方法。

IRIS Container使用外部存储

简单的说,下面的命令

  • 使用"--volume"参数为container创建外部存储,将host上的"/root/data/dur"文件夹和container内部的"/dur"文件夹链接起来。(/root/data/dur文件夹要在host上手工创建,这只是一个示意,您可以使用任何可用的目录)

  • ISC_DATA_DIRECTORY环境设置会在创建iris container时在/dur文件夹下创建一个子目录”/dur/irisepy",所有IRIS运行的日志,客户配置,mgr目录下的用户数据都会保存在这个目录。因为"/dur"是外部存储,即使docker container被删除数据也不会丢失。这对container的重新创建或者iris升级都带来很大的方便。在线文档上对如果覆盖或者升级带有外部存储的iris container有详细的介绍。

  • “key"参数定义了在IRIS container创建时会把"/dur/license"目录下的iris.key拷贝到iris安装目录的mgr下并激活。也就是说,在运行命令前, 您需要在host上在"/root/data/dur"目录下创建"license"子目录并将iris.key拷贝进去。

      docker run --name irisepy --init -d\
           -p 9091:1972 -p 9092:52773\
           -v /root/data/dur:/dur\ 
           --env ISC_DATA_DIRECTORY=/dur/irisepy \
           containers.intersystems.com/intersystems/iris-ml:2020.3.0.304.0 \
           --key /dur/license/iris.key	
    

希望您喜欢使用docker

0
1 834
公告 Claire Zheng · 三月 17, 2021

亲爱的社区开发者们,大家好!

我们很高兴地宣布,新一轮InterSystems开发者竞赛开启了!

🏆 InterSystems 编程大赛:开发者工具 🏆

请提交具有如下特性的应用程序——能够加速开发、贡献更多高质量代码、帮助用户测试、部署、支持或监控基于InterSystems IRIS的解决方案。

间: 2021年3月29日- 4月25日

Total prize: $8,500 

3
0 374
文章 Qiao Peng · 二月 6, 2021 10m read

在本文中,我们将讨论一个我每天都会使用的应用程序,当监控 InterSystems IRIS 平台上的应用程序和集成解决方案并查找所发生的错误时,我就会用到它。

在查找用来记录 InterSystems IRIS、Ensemble 和 Caché DBMS 中对象变化的解决方案时,我发现了一篇关于使用宏进行日志记录的好文章。 受到该文章的启发,我对其介绍的项目进行了分叉,并做了相应调整以满足一些特定需求。 生成的解决方案以面板子类 %CSP.Util.Pane 的形式实现,它具有主命令窗口、“Run”(运行)按钮和已启用的命令配置。

该应用程序允许查看和编辑 global 数组、执行查询(包括 JDBC 和 ODBC)、通过电子邮件发送搜索结果(压缩的 XLS 文件)、查看和编辑对象,以及用几个简单图表来表示系统协议。

该 apptools-admin 应用程序基于 jQuery-UI、UiKit、chart.js 和 jsgrid.js。 欢迎查看源代码

安装

仓库中详细介绍了所有安装方法。 但是,最简单的方法是使用包管理器命令:

zpm "install apptools-admin"

[apptools-admin]        Reload START
[apptools-admin]        Reload SUCCESS
[apptools-admin]        Module object refreshed.
[apptools-admin]        Validate START
[apptools-admin]        Validate SUCCESS
[apptools-admin]        Compile START
[apptools-admin]        Compile SUCCESS
[apptools-admin]        Activate START
[apptools-admin]        Configure START

http://hp-msw:52773/apptools/apptools.core.LogInfo.cls
http://hp-msw:52773/apptools/apptools.Tabs.PanelUikitPermissMatrx.cls?autoload=Matrix
[apptools-admin]        Configure SUCCESS
[apptools-admin]        Activate SUCCESS

第一个建议链接必须在浏览器的地址字段中打开。 在加载的面板中输入 ? ,然后按“Execute”(执行)按钮。 应用程序随后会显示命令示例。

命令

在面板中,您可以运行实用工具,查看和编辑 global,以及执行查询。 每次启动都保存在命名空间上下文的历史记录中,因此可以找到并重复。 在此上下文中,“启动”表示开始执行命令,命令表示我们在面板中输入的所有内容。 下面的屏幕截图显示了 global 数组 ^%apptools.History 查看命令的示例

如您所知,自动错误检测和通知可以由流行的解决方案(例如 Prometheus)处理。 但通常可以通过视觉评估错误的严重性。

我经常需要快速获取所有命名空间中的生产错误信息。 为此,我实现了一个实用工具:

##class(apptools.core.Production).FindAndDrawAllErr

这会启动一个每日搜索请求,用于查找每个包含工作产品的命名空间中的错误,并允许通过快速转换到可视化跟踪来查看这些错误。 您可以在 apptools 面板中像运行其他任何程序一样运行此实用工具,只需加上 xec 前缀。

所有有用命令都可以保存在作用域上下文的 global 扩展中,以便随时查找和重复。

Global

apptools-admin 应用程序的很大一部分专门用于处理 global。 可以按倒序查看 global,还可以对链接和数据应用筛选。 显示的注释可以进行编辑或删除。

您可以在 global 名称后输入 * 通配符,以获得具有附加特征的 global 列表。

第二个 * 将添加一个新字段“Allocated MB”(已分配 MB)。

第三个将添加“Used MB”(已使用 MB)字段。 此语法解析为两个报告的结合,星号将通常较长的报告分成易管理的部分。

当您获取 global 列表形式的报告时(上面的屏幕截图中),可以点击活动链接来查看 global 本身。 还可以在管理门户中点击“Permission”(权限)字段中的 RW,以标准方式查看和编辑 global。

通常,在调试项目时,对 global 的写操作用于记录变量和对象的状态。 为此,我使用特殊宏:

set $$$AppL("MSW","anyText")=$$$AppObJs(%request)

在此示例中,$$$AppL 为一个带 ^log 前缀的 glob 以及索引值中的日期和时间形成链接。

$$$AppObJs 是对象序列化宏。

您可以在面板中查看协议 global,对象可以在窗口中以完全格式化的形式显示。

查询

与 global 的使用几乎一样多的功能是查询。 通过输入命令形式的语句来运行此功能。

例如,可以执行一个 SQL 语句。

还可以将结果保存在 global ^mtempSQLGN 中。

随后,global 中保存的结果可以显示在面板中。 ![

将报告转换为 Excel 格式

标准管理门户缺少的一项功能是执行数据库 JDBC 或 ODBC 源中配置的查询,输出 XLS 格式的结果,然后将文件存档并通过电子邮件发送。

要在应用程序中实现此功能,只需在执行命令之前选中“Upload to Excel file”(上传到 Excel 文件)复选框。

这个功能为我的日常工作节省了很多时间,让我可以成功地将现成模块融入到新的应用程序和集成解决方案中。

要启用此功能,首先需要配置服务器上用于创建文件的路径、用户凭据以及邮件服务器。 为此,又需要编辑全局程序设置 ^%apptools.Setting 的节点。

全局保存报告

通常,您需要将报告执行的结果保存到 global。 为此,可以使用以下程序:

函数
对于 JDBC:##class(apptools.core.sys).SqlToDSN
对于 ODBC:##class(apptools.core.sys).SaveGateway
对于 SQL:##class(apptools.core.sys).SaveSQL
对于查询:##class(apptools.core.sys).SaveQuery

例如,使用 ##class(apptools.core.sys).SaveQuery 函数,将查询 %SYSTEM.License:Counts 的结果保存到 global ^mtempGN 中。

然后可以使用以下命令在面板中显示已保存的内容:

result ^mtempGN("%SYSTEM.License:Counts", 0)

https://lh5.googleusercontent.com/KCIekwZw3guq79GWxVdHYdAbWQc4u97-dr-hWT26lYE2oEzUTSkwCE4ki1zvNqRFBg6dKQshSqcy3YSgUbjFKgX3v7Ecpa5Bm_NEQuZhP8Fn8p1gzrmAdTR-Cg9jBeVcNWGukW3a

增强功能模块

还有什么简化和自动化了我的工作? 就是让我能够在形成查询字符串时执行自定义模块的更改。 我可以将新功能即时嵌入到报告中,例如用于对数据执行其他操作的活动链接。 让我们看一些示例。

我们使用以下函数在浏览器中显示查询结果:

##class(apptools.core.LogInfoPane).DrawSQL

让我们将字标记函数 ##class(apptools.core.LogInfo).MarkRed 添加到参数 5 中。 同样,可以为输出补充其他功能,例如,活动链接或工具提示。

本解决方案中的 global 编辑器根据同样的原理实现。

以下是以表格形式输出 global 和查询的函数列表:

函数
对于 global:##class(apptools.core.LogInfoPane).DrawArray("^mtempSQLGN")
对于 SQL:##class(apptools.core.LogInfoPane).DrawSQL("select * From %SYS.ProcessQuery")
对于查询:##class(apptools.core.LogInfoPane).DrawSQL("query %SYSTEM.License:Counts")
对于 global 结果:##class(apptools.core.LogInfoPane).DrawSQL("result ^mtempSQLGN")

使用 apptools.core.Parameter 类 在安装了 apptools-admin 的实例的上下文中,此链接将在浏览器中打开 CSP 应用程序。

http://localhost:52773/apptools/apptools.Form.Exp.cls?NSP=APP&SelClass=apptools.core.Parameter

或在面板中选择活动链接。 将加载 CSP 应用程序以编辑存储类的实例,在此示例中: apptools.core.Parameter

通过表导航器创建 apptools.core.Parameter

在安装了 apptools-admin 的实例的上下文中,如果在浏览器中打开此链接:

http://localhost:52773/apptools/apptools.Form.Exp.cls?panel=AccordionExp&NSP=APP

或在面板中选择活动链接。

将加载 CSP 应用程序以导航存储类,并能够编辑它们。

简单 CSP 应用程序的示例

在安装了 apptools-admin 的实例的上下文中,如果在浏览器中打开此链接:

http://localhost:52773/apptools/apptools.Tabs.PanelSample.cls

或在面板中选择活动链接。 此示例还显示了编辑类实例 apptools.core.Parameter 的能力。

图表

为了直观展示数据库的增长,应用程序提供了一个图表页面,显示每月测量的数据库大小。 该图表来源于 IRIS file.log(对于 Caché 则为 cconsole.log)中从当天向回“扩展”的记录。

程序会遍历协议,找到数据库扩展记录,并从当前数据库大小中减去增量的兆字节。 最终生成数据库增长的图表。

例如,下面的屏幕截图显示了 InterSystems IRIS 中通过协议文件形成的事件图。

下面是另一个示例:系统中的事件时间表,基于系统协议 file.log (cconsole.log)。

总结

我们在本文中讨论的应用程序旨在帮助我执行日常任务。 它包括一组模块,您可以将它们用作自定义管理员工具的构建块。 如果您觉得它对您的工作有用,我会非常高兴。 欢迎将愿望和建议作为任务添加到项目仓库

0
0 119
文章 Jeff Liu · 一月 8, 2021 5m read

你好!

本文简单介绍一款工具,帮您理解InterSystems产品(从IRISCachéEnsemble以及HealthShare)中的类及其结构。

简言之,它将类或整个包可视化,显示类之间的关系,并向开发人员和团队领导提供各种信息,而无需到 Studio 中检查代码。

如果您正在学习InterSystems产品,经常查看项目,或只对InterSystems技术解决方案中的新内容感兴趣,欢迎阅读ObjectScript类浏览器概述!

InterSystems 产品简介

IRIS(之前称为Caché) 是一个多模型DBMS。您可以使用SQL查询来访问它,也可以通过各种编程语言可用的接口与存储的对象和过程进行交互。但最多的还是使用DBMS原生内置语言--ObjectScript (COS) 开发应用程序。

Caché支持DBMS级别的类。有两种主要的类类型:Persistent(可以存储在数据库中)和 Registered(不存储在数据库中,扮演程序和处理程序的角色)。还有几种特殊的类类型:Serial(可集成到持久类中用于创建复杂数据类型(如:地址)的类),DataType(用于创建用户定义的数据类型)、IndexView Stream

进入类浏览器

0
1 473
文章 Louis Lu · 一月 8, 2021 3m read

在本篇短文中,我们将讨论如何让 Yape 在 docker 容器中运行,从而避免在本地计算机上安装设置 python。

距离本系列的上一篇文章已经有一段时间了,让我们快速回顾一下。

我们讨论了使用 matplotlib 创建基本图形。 之后我们介绍了使用 bokeh 生成动态图形。 在第三部分中,我们讨论了使用 monlbl 数据生成热图

在通过各种渠道获得的反馈中,有一个相同的难题是设置一个环境来运行上面的例子。 所以我们决定让实现变得更容易一些,我与 Murray 合作为他的优秀工具 Yape 创建了一个 Dockerfile。 Github 页面

当然,您必须在您的计算机上安装并运行 docker

Dockerfile

一个相当简单的基于官方 python 映像的 docker 定义:

FROM python:3

WORKDIR .

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

源码

Requirements.txt 包含运行 yape 所需的包:

altgraph==0.10.2
py-dateutil==2.2
bdist-mpkg==0.5.0
certifi==2017.7.27.1
cffi==1.10.0
chardet==3.0.4
idna==2.5
bokeh==0.12.6
macholib==1.5.1
matplotlib==2.0.2
pandas==0.20.3
modulegraph==0.10.4
numpy==1.13.1
py2app==0.7.3
pycparser==2.18
pyparsing==2.0.1
python-dateutil==1.5
pytz==2013.7
requests==2.18.3
six==1.4.1
urllib3==1.22
zope.interface==4.1.1

源码

要构建映像,只需从github 中check out,然后运行 docker build:

git clone https://github.com/murrayo/yape.git
docker build -t yape .

(在pull request被合并之前,使用 https://github.com/kazamatzuri/yape.git

这将需要几分钟的时间,具体取决于您的计算机/互联网连接的速度。

之后可以使用如下命令对 pButtons 文件运行 yape:

docker run -v `pwd`/in:/data  --rm --name yape-test yape  \
./extract_pButtons.py -o /data \ 
/data/pButtons.html

docker run -v `pwd`/in:/data  --rm --name yape-test yape  \ 
./graph_pButtons.py -o /data/charts /data

我们在当前工作目录中使用

    /in

并将其映射到容器中的 /data。 我们将从该目录获得 pButtons.html,同时图形也将输出到该目录。

注意

我必须向脚本添加参数,我们要将它们合并到官方 yape 仓库中(pull request

0
0 174