jbpm5.1介绍(2)

简介:

快速开始 

首先下载jBPM,http://sourceforge.net/projects/jbpm/files/

可以有选择性的下载:

  • bin:jBPM的二进制文件和它们的依赖包
  • src:核心模块的源码
  • gwt-console:jBPM的控制台,包括服务端和客户端
  • docs:文档
  • examples:一些jBPM的例子可以导入到Eclipse
  • installer: jBPM的安装,下载安装一个jBPM的示例
  • installer-full:所有的包括demo,jar包等待的完整工程

一些有用的链接 

源码 

jBPM的现在使用它的源代码版本控制系统的git可以在这里找到jBPM项目的来源包括所有版本从jBPM5.0- CR1开始

https://github.com/droolsjbpm/jbpm

安装所需条件

 JDK 1.5+ (set as JAVA_HOME)

 Ant 1.7+

演示环境安装

到安装目录下运行

ant install.demo

将会执行

  • 下载JBoss AS
  • 下载Eclipse
  • JBoss的安装Drools的Guvnor
  • 到JBoss Oryx安装设置
  • 安装到JBoss jBPM的控制台
  • 安装jBPM的Eclipse插件
  • 安装Drools的Eclipse插件

如果你想看到报告在jBPM控制台上,那么需要修改build.properties文件的jBPM.birt.download属性设置为true

ant start.demo

启动示例

  • 启动H2数据库
  • 启动了JBoss AS
  • 启动Eclipse
  • 启动人工任务服务

使用Eclipse tools

导入示例工程下的sample/evaluation

导入之后可以看到工程中的示例程序

双击打开Evaluation.bpmn

可以运行ProcessTest进行测试

使用jBPM控制台

启动后输入如下链接

http://localhost:8080/jbpm-console

使用 krisv / krisv 登录

可以看到如下界面

你可以启动一个新的流程,查看一个正在运行的流程的实例的状态,查看你的任务,完成任务,监控流程的执行

使用Guvnor仓库和设计

作为一个过程Guvnor可用于存储业务流程它还提供了一个基于Web的界面来管理您的进程

输入如下地址可以进入

http://localhost:8080/drools-guvnor

核心引擎API

本节介绍的API你需要加载过程并执行它们对于如何界定的过程本身详细,查看检出的BPMN 2.0章节

你可以在知识库中定义一个流程实例,然后在知识库中产生一个实例的session对象,如下图所示

知识库可以共享会话之间通常创建一次启动应用程序知识库可以动态改变这样你就可以在运行过程中添加或删除

会话可以创建基于一个知识库用于执行过程与引擎交互你想创建一个会话被认为是相对较轻的你可以创造尽可能多独立会议如何创建许多会议是由你一般情况下,最简单情况开始创建一个会话然后您的应用程序各个地方你可以决定创建多个会话例如,如果你有多个独立处理单元例如,你想要的所有进程从一个客户完全独立另一个客户过程使您可以创建一个为每个客户独立会议如果你需要多个会话可扩展性的原因如果你不知道做什么只要简单地启动一个知识库,其中包含你所有的流程定义和创建会话然后使用执行你所有的流程

正如上文所述,jBPM的API,因此可用于(1)创建一个知识库,其中包含流程定义(2)创建一个会话启动新的进程实例信号现有注册侦听等。

1)知识库

通过知识库加载流程定义,通过以下代码实现

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource( "MyProcess.bpmn" ), ResourceType.BPMN2);
KnowledgeBase kbase = kbuilder.newKnowledgeBase();

 ResourceFactory类似方法来加载文件系统的文件从URLInputStream中

2)Session

一旦你加载知识库你应该创建一个会话与引擎交互本次会议可以被用来启动新的进程信号事件下面的代码片段显示它是多么容易创建较早创建知识库为基础的会话,并启动一个进程ID

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ProcessInstance processInstance = ksession.startProcess( "com.sample.MyProcess" );

 ProcessRuntime接口定义了所有的会议方法与流程交互,如下所示

/**
      * Start a new process instance.  The process (definition) that should
      * be used is referenced by the given process id.
      *
      * @param processId  The id of the process that should be started
      * @return the ProcessInstance that represents the instance of the process that was started
      */
     ProcessInstance startProcess(String processId);
 
     /**
      * Start a new process instance.  The process (definition) that should
      * be used is referenced by the given process id.  Parameters can be passed
      * to the process instance (as name-value pairs), and these will be set
      * as variables of the process instance.
      *
      * @param processId  the id of the process that should be started
      * @param parameters  the process variables that should be set when starting the process instance
      * @return the ProcessInstance that represents the instance of the process that was started
      */
     ProcessInstance startProcess(String processId,
                                  Map<String, Object> parameters);
 
     /**
      * Signals the engine that an event has occurred. The type parameter defines
      * which type of event and the event parameter can contain additional information
      * related to the event.  All process instances that are listening to this type
      * of (external) event will be notified.  For performance reasons, this type of event
      * signaling should only be used if one process instance should be able to notify
      * other process instances. For internal event within one process instance, use the
      * signalEvent method that also include the processInstanceId of the process instance
      * in question.
      *
      * @param type the type of event
      * @param event the data associated with this event
      */
     void  signalEvent(String type,
                      Object event );
 
     /**
      * Signals the process instance that an event has occurred. The type parameter defines
      * which type of event and the event parameter can contain additional information
      * related to the event.  All node instances inside the given process instance that
      * are listening to this type of (internal) event will be notified.  Note that the event
      * will only be processed inside the given process instance.  All other process instances
      * waiting for this type of event will not be notified.
      *
      * @param type the type of event
      * @param event the data associated with this event
      * @param processInstanceId the id of the process instance that should be signaled
      */
     void  signalEvent(String type,
                      Object event ,
                      long  processInstanceId);
 
     /**
      * Returns a collection of currently active process instances.  Note that only process
      * instances that are currently loaded and active inside the engine will be returned.
      * When using persistence, it is likely not all running process instances will be loaded
      * as their state will be stored persistently.  It is recommended not to use this
      * method to collect information about the state of your process instances but to use
      * a history log for that purpose.
      *
      * @return a collection of process instances currently active in the session
      */
     Collection<ProcessInstance> getProcessInstances();
 
     /**
      * Returns the process instance with the given id.  Note that only active process instances
      * will be returned.  If a process instance has been completed already, this method will return
      * null.
      *
      * @param id the id of the process instance
      * @return the process instance with the given id or null if it cannot be found
      */
     ProcessInstance getProcessInstance( long  processInstanceId);
 
     /**
      * Aborts the process instance with the given id.  If the process instance has been completed
      * (or aborted), or the process instance cannot be found, this method will throw an
      * IllegalArgumentException.
      *
      * @param id the id of the process instance
      */
     void  abortProcessInstance( long  processInstanceId);
 
     /**
      * Returns the WorkItemManager related to this session.  This can be used to
      * register new WorkItemHandlers or to complete (or abort) WorkItems.
      *
      * @return the WorkItemManager related to this session
      */
     WorkItemManager getWorkItemManager();

3)Events

可以使用ProcessEventListener注册自己的监听器

public  interface  ProcessEventListener {
 
   void  beforeProcessStarted( ProcessStartedEvent event  );
   void  afterProcessStarted( ProcessStartedEvent event  );
   void  beforeProcessCompleted( ProcessCompletedEvent event  );
   void  afterProcessCompleted( ProcessCompletedEvent event  );
   void  beforeNodeTriggered( ProcessNodeTriggeredEvent event  );
   void  afterNodeTriggered( ProcessNodeTriggeredEvent event  );
   void  beforeNodeLeft( ProcessNodeLeftEvent event  );
   void  afterNodeLeft( ProcessNodeLeftEvent event  );
   void  beforeVariableChanged(ProcessVariableChangedEvent event );
   void  afterVariableChanged(ProcessVariableChangedEvent event );
 
}

 默认支持下面的记录器实现

1。 控制台记录器此记录写入控制台所有事件
2。 文件记录器此记录写入到一个文件中使用XML表示所有事件此日志文件可能被用来在IDE中生成一个基于树的可视在执行过程中发生的事件
3。 线程文件记录因为文件记录器事件写入到磁盘中只有当关闭记录仪记录器事件数量达到预定水平不能被用来调试时,在运行过程一个线程文件记录器事件写入到一个文件后,在指定的时间间隔,使得可以使用记录以可视化的实时进展调试过程

KnowledgeRuntimeLoggerFactory 可以添加logger到你的session中

KnowledgeRuntimeLogger logger =
     KnowledgeRuntimeLoggerFactory.newFileLogger( ksession, "test"  );
// add invocations to the process engine here,
// e.g. ksession.startProcess(processId);
...
logger.close();

业务流程创建

使用eclipse创建流程

可以在生成好的文件上定义业务流程

节点类型描述

BPMN 2.0规范定义了三种主要类型的节点

事件它们用于模型中的特定事件发生这可能是一个开始事件(即用来指示的过程中开始结束事件定义过程结束子流中间事件指示的执行过程中可能出现事件过程)。

活动:这些定义需要执行过程中执行不同的动作存在不同类型任务活动的类型取决于您尝试模型(如人工的任务,服务任务等)actvities也可以嵌套使用不同类型子进程

网关:可以被用来定义多个路径的过程中根据网关类型,这些可能表明并行执行选择等

流程属性

一个BPMN2过程是不同类型的节点使用连接流程图这个过程本身暴露了以下属性

  • id:过程中唯一的ID
  • name:过程中显示名称。
  • Version: 版本号的过程
  • Package: 过程的包(命名空间
  • Variables:变量可以被定义为数据存储过程执行期间
  • Swimlanes:指定在这个过程中用于分配人工任务泳道

事件

1)开始事件

进程的开始一个过程应该有一个起始节点没有传入的连接只有传出的连接

每当一个进程启动后,开始执行此节点,并自动继续这个启动事件第一个节点,并依此类推包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称

2)结束事件

所有流程的结束,应该只有传入的连接没有传出的连接。包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称

Terminate: 结束事件可以在整个过程中终止路径一个流程实例被终止,这意味着它的状态设置为完成所有其他节点可能仍然活跃在这个过程实例并行路径被取消非终止结束事件只是这个路径执行这个分支在这里结束结束,但仍然可以继续其他平行的路径如果有流程实例没有更积极的路径(例如,如果一个流程实例到达结束节点,但终止流程实例没有活跃的分支将完成的过程实例一个流程实例自动完成反正)。终止结束事件可视化的事件节点内使用一个完整的圆非终止事件节点是空的。注意如果您使用一个子进程终止事件节点你是终止流程实例的顶层只是子进程

3)出错事件

错误处理事件,只能有传入事件没有传出事件,错误事件包含以下属性:

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • FaultName: 故障名称,使用些名称来处理故障
  • FaultVariable: 名称变量,它包含此故障相关数据这个数据也是通过异常处理程序如果找到

4)定时器事件

表示定时器,可以触发一个特定的时间内一次或多次计时器事件应该有一个传入的连接一个外向连接计时器延迟指定计时器之前应等待多久引发第一次计时器事件的过程中达到,它会启动相关的定时器如果定时器节点被取消(例如,通过完成或中止封闭过程实例定时器就会被取消

计时器事件包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Timer delay:延迟节点之前应等待触发第一次表达应的形式[#][#H] [#M] [#S] [[MS]这意味着您可以指定天,小时,分钟multiseconds这是默认的,如果你指定任何数量例如,表达1H”触发定时器将等待一个小时。表达式也可以使用{expr}的动态推导基于一些过程变量延迟 EXPR在这种情况下可能是一个过程变量,基于一个过程变量myVariable.getValue一个更复杂的表达式
  • Timer period: 随后的两个触发器之间期间如果期间为0时,定时器只能触发一次表达应的形式[#][#H] [#M] [#S] [[MS]这意味着您可以指定天,小时,分钟multiseconds这是默认的,如果你指定任何数量例如,表达1H”再次触发定时器将等待一个小时。也可以使用{expr}的动态推导基于一些过程变量期间的表达 EXPR在这种情况下可能是一个过程变量,基于一个过程变量myVariable.getValue一个更复杂的表达式

5)信号事件

可用于信号事件执行过程中内部或外部事件作出回应信号事件没有传入的连接一个外向连接它指定的事件类型,预计每当检测这种类型的事件此事件节点相连节点将被触发。包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • EventType:事件的类型
  • VariableName: variablename的变量将包含与此事件相关的数据(如有)此事件发生时名称

一个流程实例可以标志着一个特定的事件发生

ksession.signalEvent(eventType, data, processInstanceId)

这将触发所有给定的进程实例(活动)信号等待该事件类型事件节点事件相关数据可以通过使用数据参数如果事件节点指定一个变量名这个数据将复制到该变量在事件发生时

您还可以产生一个流程实例信号可以使用一个脚本(脚本任务进入或退出操作使用

kcontext.getKnowledgeRuntime().signalEvent(
   eventType, data, kcontext.getProcessInstance().getId());

6)活动

表示,应该在这个过程中执行的脚本脚本任务应该有一个传入的连接一个外向连接指定应执行相关的操作编码行动(即Java或MVEL使用的方言实际行动代码此代码可以访问的任何变量和全局还有一个预定变量kcontext引用ProcessContext对象,例如它可以用来访问当前流程实例NodeInstance并获得设置变量,获得ksession使用kcontext.getKnowledgeRuntime()当一个脚本任务的过程中达成它会执行的动作,然后继续下一个节点包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Action: 行动节点相关的动作脚本

请注意,您可以编写脚本节点任何有效的Java代码基本上允许你做这样一个脚本节点内部任何但是也有一些注意事项

  • 当试图建立一个更高级别的业务过程中,也应该企业用户了解它可能是明智的,里面的过程避免低层次实施细节,包括在这些脚本任务脚本任务仍然可以用于快速操作变量其他概念服务任务可以用来一个更高层次方式更复杂的行为模式
  • 应立即脚本他们使用的是引擎的线程来执行脚本也许应该仿照作为异步服务任务可能需要一些时间来执行脚本
  • 应尽量避免接触外部服务通过一个脚本节点这不仅平时违反两个警告,这与外部服务交互的发动机问题没有知识,特别是当使用持久性交易一般情况下,它可能是更明智的使用服务的任务与外部服务模式通信
  • 脚本不应该抛出异常。运行时异常应该被捕获和管理里面的脚本转换信号,然后将其内部的过程处理错误例子

7)服务任务

执行流程引擎之外所有工作应派代表参加以声明方式使用服务任务不同类型的服务预定义的,例如发送电子邮件,记录信息用户可以定义特定的服务或工作项目采用了独特的名称定义的参数(输入)和相关结果(输出)这种类型的工作检查特定于域的过程进行了详细的解释例子说明如何定义和使用你的流程工作项目当一个服务任务的过程中达成共识相关的工作执行一个服务的任务应该一个传入的连接一个外向连接包含如下属性:

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Parameter mapping:允许过程变量复制工作项目参数创建工作项目,该值将被复制。
  • Result mapping: 允许复制工作项目的结果参数的过程变量每种类型的工作可以定义结果,将(可能)已经完成的工作项目返回参数结果映射可用于复制给定的结果参数在这个过程中给定的变量例如,FileFinder工作项目返回结果参数文件匹配给定搜索条件文件清单这个文件列表然后可以绑定以便使用过程中一个过程变量工作项目完成,该值将被复制。
  • On-entry and on-exit actions:在进入退出这个节点分别执行动作
  • Additional parameters: 每个工作项目类型可以定义额外的参数这种类型的工作有关例如,“电子邮件”工作项目定义额外的参数,如发件人,收件人,主题和正文用户可以提供这些参数的值,直接定义一个参数映射将复制在这个过程中给定的变量给定的参数如果两者都指定映射将具有优先权 String类型参数可以使用#{表达式}嵌入在字符串值中创建工作项目值将被检索替换表达式将被替换变量上调用toString()方法结果表达式可以简单地一个变量的名字这种情况下,解析变量的值,但更先进MVEL表达式尽可能,如#{person.name.firstname}。

8)用户任务

 

过程也涉及用户需要执行任务用户任务代表一个原子一个人执行任务它应该有一个传入的连接一个外向连接可用于用户任务泳道分配多个人工任务相似的用户组合参阅有关详细信息,对用户任务用户任务实际上只是一个服务节点具体类型类型为“人工任务用户任务包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • TaskName: 任务的名称
  • Priority: 一个整数,指示任务优先
  • Comment:与任务相关的描述
  • ActorId: 负责执行人工任务ID一个演员的ID列表可以指定使用一个逗号(',')作为分隔
  • GroupId: 负责执行人工任务组ID作为分隔符使用一个逗号(',')可以指定组ID列表
  • Skippable: 指定是否可以跳过人工任务,即,这个角色可能决定不执行任务
  • Content: 此任务相关数据
  • Swimlane:泳道可以分配多个人工任务给相同的角色
  • On entry and on exit actions: 在进入和退出之前分别执行的动作
  • Parameter mapping: 允许人工任务参数复制过程变量创造人工任务,该值将被复制
  • Result mapping: 允许复制工作项目的结果参数的过程变量每种类型的工作可以定义结果,将(可能)已经完成的工作项目返回参数结果映射可用于复制给定的结果参数在这个过程中给定的变量例如,FileFinder工作项目返回结果参数文件匹配给定搜索条件文件清单这个文件列表然后可以绑定以便使用过程中一个过程变量工作项目完成,该值将被复制。

9)子流程

表示这个过程的另一个进程调用一个子流程节点都应该有一个传入的连接一个传出连接重复使用子流程节点的过程中达成发动机将开始给定ID的过程包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • ProcessId: 应执行的进程的ID
  • Wait for completion: 如果此属性为true这个子进程节点只会继续下去如果启动的子进程已终止执行完成或中止,否则将继续启动立即所以不会等待它的完成
  • Independent: 如果此属性为true子进程开始作为一个独立的过程,这意味着如果完成这个父进程子进程将不会被终止其他一些原因取消子流程节点;活跃​​的子进程将被取消(或取消子流程节点父进程终止
  • On entry and on exit actions: 在进入和退出之前分别执行的动作
  • Parameter in/out mapping: 子流程节点还可以定义映射变量启动进程在“在”映射的变量将被用作参数(相关参数名称所定义的”映射子进程的变量将被复制到这个过程中,变量当子进程已经完成请注意,您可以使用“出”映射只有当等待完成设置为true

10)业务规则任务

表示需要进行评估规则,到达节点时规则进行评估规则的任务应该一个传入的连接一个外向连接规则是被定义单独的文件中使用Drools规则格式规则可以
一个特定的规则流使用规则流组属性规则一部分当一个规则任务达成的过程,发动机将开始执行相应的规则流部分(如有)规则执行将自动继续到下一个
节点如果有没有更积极的规则在这个规则流这意味着执行一个规则流有可能属于当前活动规则流激活添加其他规则的事实变化,由于议程注意,这
一进程将立即继续下一个节点如果遇到一个规则流那里有当时没有活动的规则如果规则流组已经启动规则流保持活跃只会继续执行规则流所有活动的规则
如果已经完成包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • RuleFlowGroup: 表示这个RuleFlowGroup节点规则规则流名称

11)嵌入式子过程

一个子进程是一个节点,它可以包含其他节点,因此,它作为一个节点容器行为这使得不仅在这样一个子流程节点嵌入过程的一部分而且此容器内部的所有节点访问额外的变量定义一个子进程应该有一个传入的连接一个外向连接也应该包含一个起始节点定义启动(子进程当你到达子进程也应该包含一个或多个结束事件需要注意如果您使用里面一个子进程终止事件节点终止流程实例的顶层不只是子进程,所以一般你应该使用一个子进程非终止端节点当有活动的节点内的子进程没有子进程结束包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Variables: 变量可以被定义为执行节点数据存储

12)多实例的子进程

实例的子进程一种特殊子进程允许执行所包含的过程中多次分部为每一个集合元素一次实例子进程应该有一个传入的连接一个传出连接等待,直到完成嵌入式过程片段给定集合的每个元素,然后再继续包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • CollectionExpression: 一个变量,表示应该遍历元素集合名称集合变量应该是一个数组或类型的java.util.Collection如果集合表达式计算为null或空集合子进程立即完成多个实例,并按照传出连接
  • VariableName:variablename的变量的名称包含集合中的当前元素这给选定的元素复合节点访问节点

13)流向不同的网关

允许你创建你的进程分支发散网关应该有一个传入的连接两个或两个以上的传出连接目前支持网关节点有三个类型

  • 并行手段控制流同时继续所有传出连接
  • 完全传出连接将选择唯一手段这个决定是评估链接到每个传出连接约束选择优先级最低的数量计算结果为true约束约束可以指定使用不同的方言请注意,您应始终确保至少一个传出连接在运行时评估为true规则流在运行时会抛出一个异常,如果它不能找到至少一个传出连接
  • OR或选择条件计算结果为true所有传出连接手段条件相似的独家网关没有重点考虑除外注意因为这个过程在运行时会抛出一个异常,如果它不能确定传出连接你应该确保至少一个传出连接将评估在运行时也是如此

包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Type:分割节点,异或OR(见上文)类型
  • Constraints: 约束每个传出连接

14)合并网关

可以同步多个分支融合网关应该有两个或两个以上的传入连接一个外向连接有两种类型拆分目前支持

  • 并行的手段或者说是将等待,直到所有传入分支,然后再继续完成
  • 唯一手段它继续尽快传入分支之一已经完成。如果从多个传入的连接触发时,它会触发这些触发器下一个节点

包含以下属性

  • Id: 节点ID(这是一个节点容器唯一
  • Name: 节点的显示名称
  • Type:分割节点,异或OR(见上文)类型
目录
相关文章
|
Java 应用服务中间件 数据库连接
|
应用服务中间件 Android开发 Java
安装JBpm
原文 http://www.cnblogs.com/default/archive/2012/02/28/2370673.html   自动安装(ant start.demo)太慢,所以手动安装。
859 0
|
API
jBPM 5 的点滴
发布地址 http://sourceforge.net/projects/jbpm/files/jBPM%205/  http://www.jboss.org/jbpm/documentation   jBPM5引入了新的API、新的工具并支持BPMN 2.
1006 0
|
测试技术
jbpm5.1介绍(10)
Junit测试异常事件触发 下面的示例中测试在程序中触发异常事件的流程,流程如下 测试程序 public void testTimerBoundaryEventInterrupting() throws Exception { KnowledgeBase kbase = crea...
867 0
|
测试技术
jbpm5.1介绍(9)
Junit测试调用子流程 下面的示例中测试在程序中调用其它程序的子流程的示例,需要加载两个配置文件 和 需要指定属性 下面是调用的示例程序 public void testCallActivity() throws Exception { System.
883 0
|
JSON Java 数据格式
jbpm5.1介绍(11)
Jbpm-gwt-console源码编译 从svn下载,svn的下载地址是 http://anonsvn.jboss.org/repos/soag/bpm-console/tags/bpm-console-2.
876 0
jbpm5.1介绍(5)
看几个jbpm5中带的示例程序吧,包括了很多我们在日常生活中的场景 循环示例 本示例是一个在外部传入的变量,通过传入的变量来判断循环次数的演示程序,看一下流程定义的内容 如图: 初始化的时候设置变量i的值为0,然后进入流程结点,选择的是XOR,就是异或的意思,在循环中输出变量i的值并且加1, 在后面的选择节点上判断变量i的值,如果小于count则循环继续,如果大于i那么就进行完成结点,输出结点中的内容。
727 0