工作流学习(三)

Osworkflow 配置解释

Osworkflow jar 包(略)

数据源配置

Ø         <Context path="/osworkflow" docBase="osworkflow"

Ø         debug= "99" reloadable="true" crossContext="true" verbosity="DEBUG">

Ø         <Logger className="org.apache.catalina.logger.FileLogger"

Ø         prefix="OSWorkflow." suffix=".log" timestamp="true"/>

Ø         <Resource name="jdbc/DefaultDS" auth="Container"

Ø         type="javax.sql.DataSource" password="nyber"

Ø         driverClassName="com.mysql.jdbc.Driver"

Ø         maxIdle="2"

Ø         maxWait="5000"

Ø         username="root"

Ø         url="jdbc:mysql://localhost:3306/workflow?autoReconnect=true"

Ø         maxActive="4"/>

Ø         </Context>

 

 

MySQL 配置

< osworkflow >

<!--

<persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>

-->

< persistence class = "com.opensymphony.workflow.spi.jdbc.MySQLWorkflowStore" >

 

    < property key = "datasource" value = "jdbc/DefaultDS" />

    < property key = "entry.sequence"

                      value = "SELECT nextVal('seq_os_wfentry')" />

<!--   <property key="entry.sequence"-->

<!--                      value="select count(*) + 1 from os_wfentry"/>-->

    < property key = "entry.table" value = "OS_WFENTRY" />

    < property key = "entry.id" value = "ID" />

    < property key = "entry.name" value = "NAME" />

    < property key = "entry.state" value = "STATE" />

     

    < property key = "step.sequence"

value = "select sum(c1) + 1 from (select 1 as tb, count(*) as c1

       from os_currentstep union

       select 2 as tb, count(*) as c1 from os_historystep) as TabelaFinal" />

    < property key = "history.table" value = "OS_HISTORYSTEP" />

    < property key = "current.table" value = "OS_CURRENTSTEP" />

    < property key = "historyPrev.table" value = "OS_HISTORYSTEP_PREV" />

    < property key = "currentPrev.table" value = "OS_CURRENTSTEP_PREV" />

    < property key = "step.id" value = "ID" />

    < property key = "step.entryId" value = "ENTRY_ID" />

    < property key = "step.stepId" value = "STEP_ID" />

    < property key = "step.actionId" value = "ACTION_ID" />

    < property key = "step.owner" value = "OWNER" />

    < property key = "step.caller" value = "CALLER" />

    < property key = "step.startDate" value = "START_DATE" />

    < property key = "step.finishDate" value = "FINISH_DATE" />

    < property key = "step.dueDate" value = "DUE_DATE" />

    < property key = "step.status" value = "STATUS" />

    < property key = "step.previousId" value = "PREVIOUS_ID" />

 

    < property key = "step.sequence.increment"

    value = "INSERT INTO OS_STEPIDS (ID) values (null)" />

  < property key = "step.sequence.retrieve"

    value = "SELECT max(ID) FROM OS_STEPIDS" />

    < property key = "entry.sequence.increment"

    value = "INSERT INTO OS_ENTRYIDS (ID) values (null)" />

  < property key = "entry.sequence.retrieve"

    value = "SELECT max(ID) FROM OS_ENTRYIDS" />

</ persistence >

 

    < factory class = "com.opensymphony.workflow.loader.XMLWorkflowFactory" >

        < property key = "resource" value = "workflows.xml" />

    </ factory >

</ osworkflow >

 

MS SQL 配置

 
 Ø        



 
 <osworkflow>
<!--
<persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>
-->
<persistence class="com.opensymphony.workflow.spi.jdbc.JDBCWorkflowStore">
 
        
<property key="datasource" value="jdbc/DefaultDS"/>
        
<property key="entry.sequence"
                      
value="select count(*) + 1 from os_wfentry"/>
        
<property key="entry.table" value="OS_WFENTRY"/>
        
<property key="entry.id" value="ID"/>
        
<property key="entry.name" value="NAME"/>
        
<property key="entry.state" value="STATE"/>
        
<property key="step.sequence"
value="select sum(c1) + 1 from (select 1 as tb, count(*) as c1 
       
from os_currentstep union 
       
select 2 as tb, count(*) as c1 from os_historystep) as TabelaFinal"/>
        
<property key="history.table" value="OS_HISTORYSTEP"/>
        
<property key="current.table" value="OS_CURRENTSTEP"/>
        
<property key="historyPrev.table" value="OS_HISTORYSTEP_PREV"/>
        
<property key="currentPrev.table" value="OS_CURRENTSTEP_PREV"/>
        
<property key="step.id" value="ID"/>
        
<property key="step.entryId" value="ENTRY_ID"/>
        
<property key="step.stepId" value="STEP_ID"/>
        
<property key="step.actionId" value="ACTION_ID"/>
        
<property key="step.owner" value="OWNER"/>
        
<property key="step.caller" value="CALLER"/>
        
<property key="step.startDate" value="START_DATE"/>
        
<property key="step.finishDate" value="FINISH_DATE"/>
        
<property key="step.dueDate" value="DUE_DATE"/>
        
<property key="step.status" value="STATUS"/>
        
<property key="step.previousId" value="PREVIOUS_ID"/>
</persistence>
 
    
<factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
        
<property key="resource" value="workflows.xml" />
    
</factory>
</osworkflow>

 

 

workflows.xml

存储方式的选择

Ø        通过ejb 存储

Ø        通过jdbc 进行存储

Ø        内存存储, 主要用于测试

Ø        通过Hibernate 进行存储

Ø        通过ofbiz 提供的方式进行存储

Ø        通过ojb 进行存储

Ø        通过prelayer 进行存储.

Ø        <osworkflow>
  <persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>

 

工作流定义文件存储方式

Ø        存储在数据库中

Ø        存储在XML

Ø        存储在URL.

Ø        存储在资源文件中

Ø        <factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
   <property key="resource” value="workflows.xml” />
  </factory>

 

 

 

 

pre function post function

Ø        pre functionpost functionosworkflow 提供的又一特色,它为某项执行逻辑提供了前驱和后继处理,运用十分灵活。并且,osworkflow 为许多元件的 执行逻辑都配备了pre functionpost function 的调用时机。这一点也可以从AbstractWorkflow.doAction 的执行逻辑中看到。可以使用和pre functionpost function 的元件包括:actionresult/unconditional resultstepsplitjoinScriptVariableParser>> 作为osworkflow 的一个util classScriptVariableParser 的主要功能是将给定字串中的${var} 替换成相应的value 。这意味着我们可以在许多地方使用类似于Ant 中引用property 的语法,来进一步提高灵活性。比如:
<results>
 <unconditional -result old-status="Finished” status="Underway” step="1″ owner="${caller}"/>
</results>
在这里,unconditional resultowner 属性将被caller 的实际值所替代。TransientVarsPropertySet>>osworkflow 的流程流转过程中,时常会用到Transient VarsProperty Set 。这两个工具用来暂存一些临时信息或者在step 间传递一些共享信息,比如:context 信息,workflow entry 信息,以及上面提到的${var}value ,等等。Transient Vars 实际上就是一个普通的Map ,至于Property Set ,则是opensymphony 的另一个独立模块,需要单独下载jar 包。与Transient Vars 将信息暂存与内存不同,Property Set 还支持数据库存储(JDBCPropertySetRegister>> 为 了更进一步提高灵活性,osworkflow 还提供了Register 功能。我们可以定义自己的Register ,以执行特殊任务,并在流程定义文件中注 明,该Register 便会被动态注册到Transient Vars 中,以备随时取用。以下便是一个典型的使用场景:<registers>
 <register type="class” variable-name="log">
  <arg name="class.name">com.opensymphony.workflow.util.LogRegister</arg>
  <arg name="addInstanceId">true</arg>
 </register>
</registers> <function type="beanshell” name="bsh.function">
 <arg name="script">transientVars.get("log").info("function called");</arg>
</function>
此 外,为了方便使用,osworkflowutil 包中还预定义了大量的ConditionFunctionProvider ,以及其他的一些辅助类, 比如:StatusConditionAllowOwnerOnlyConditionBeanShellConditionCallerEJBInvokerScheduleJob 。借助这些设施,osworkflow 的扩展性、灵活性、易用性,得到了极大的体现。

 

限制条件设置

Ø         OSUserGroupCondition :限制由隸屬某指定Group 的人執行。

Ø         StatusCondition :限制stepstatus 為某個值時才能執行

Ø         AllowOwnerOnlyCondition :只允許Owner 執行。

Ø         DenyOwnerCondition :只有Owner 不能執行。

<restrict-to>
  
<conditions type="AND">
    
<condition type="class">
      
<arg name="class.name">
         
com.opensymphony.workflow.util.StatusCondition
      
</arg>
      
<arg name="status">Queued</arg>
    
</condition>
    
<condition type="class">
      
<arg name="class.name">
        
com.opensymphony.workflow.util.OSUserGroupCondition
      
</arg>
      
<arg name="group">A0001</arg>
    
</condition>
  
</conditions>
</restrict-to>

 

傳值儲值

Ø        Property sets :持續性變數,會將變數值存入Table OS_PROPERTYENTRY ,在設定檔中以propertySet 來存取。

Ø         Transient Map :臨時性變數,僅在workflow 中有效,在設定檔中以transientVars 來存取。

<function type="class">
  
<arg name="class.name">
    
tw.idv.idealist.OutputPropertySet
  
</arg>
  
<arg name="author">Steven Shi</arg>
</function>

 

 

校驗器

Ø          MyValidator ,如有錯誤拋出exception 。接下來在流 程設定檔中加入 <validators>

  <validator type="class">

    <arg name="class.name">

      tw.idv.idealist.MyValidator

    </arg>

  </validator>

</validators>

 

 

 

Osworkflow 工作原理

简介

Ø         osworkflow 中有关工作流流转的所有核心代码都在 AbstractWorkflow 中, BasicWorkflow 就是派生自它,不过这个 BasicWorkflow 基本上没做什么事情。也许我们还可以从 AbstractWorkflow 派生自己的 Workflow 类以加入扩展功能,大概这也算是 osworkflow 所体现的一种灵活性了,即:允许对工作流流转的执行逻辑进行修改。 AbstractWorkflow 实现了 Workflow 接口,该接口包含了有关工作流 的核心方法,最重要的是 doAction 方法, AbstractWorkflow 实现了该方法,后面会提及,其他还有一些 getter query method

 

初始配置加载及工作流定义文件加载

Ø        AbstractWorkflow 首先会取得一个Configuration 实例,缺省时为DefaultConfiguration (实现了Configuration 接口),该实例负 责系统配置的加载。AbstractWorkflow 会调用其load 方法,该方法内部会查找一个名为osworkflow.xml 的配置文件,并对其解 析。osworkflow.xml 文件中一般会指定persistence classfactory class ,比如:<osworkflow>
 <persistence class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore"/>
 <factory class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
  <property key="resource” value="workflows.xml” />
 </factory>
</osworkflow> load
方法会动态加载这些class ,还可以指定一些参数,在它们初始化的时候传入。在这里,我们指定了XMLWorkflowFactory ,实际上还有其他 种类的WorkflowFactory ,比如JDBCWorkflowFactoryURLWorkflowFactory ,它们均派生自 AbstractWorkflowFactory ,其共同职责是通过某种媒介加载流程定义。以XMLWorkflowFactory 为例,其相应实例在load 方法内部完成初始化的过程中,将会查找一个名为workflows.xml 的文件,可以在该文件中定义多个流程,每个流程指明其对应的xml 定义文件。比如:<workflows>
 <workflow name="example” type="resource” location="example.xml"/>
</workflows>
XMLWorkflowFactory 内部维护了一个Map ,保存着流程名称与其对应的文件路径(实际上是一个WorkflowConfig 实例,不过这只是细节)。然后, DefaultConfiguration 调用XMLWorkflowFactory.getWorkflow 方法,并传入流程名称。getWorkflow 方法内部又会将流程加载的具体执行逻辑转交给一个名为WorkflowLoader 的类(调用WorkflowLoader.load 方法),由 WorkflowLoader 实现流程定义文件读取。不过,真正的xml 文件解析是由WorkflowDescriptor 类完成的。它将平面的xml 流 转化为osworkflow 内部所使用的具有真正意义的对象。此类有很多get 方法,在osworkflow 的许多地方都将会用到这个类的get 方法,以 获取具体的对象,比如:getActiongetJoingetStep 等等。最终,代表example.xml 流程定义文件的WorkflowDescriptor 实例将会被逐层返回,直至AbstractWorkflow 。至此,流程定义文件加载完毕,整个初始化过程也就基本完成了。

 

流程启动

Ø        前面分析流程流转的执行逻辑时,并没有讲到流程的启动。实际上,有了前面的基础,再加上对流程定义文件加载的过程有清晰认识之后,流程启动逻辑是很容易理解的。大致情况如下:
-
调用DefaultConfigurationgetWorkflow ,传入流程名称,然后返回一个WorkflowDescriptor 实例,流程定义文件的加载就是在这个时候完成的;
-
然后做一些准备工作,比如:获取WorkflowStore 实例、准备好transientVarspropertySet 等等;
-
调用WorkflowDescriptorgetInitialAction 方法,获取initial action (如果有的话),注意此前的第一步中,流程定义文件已经成功加载;
-
调用transitionWorkflow ,执行流程的流转(就是前面提到doAction 执行逻辑时调用的那个重要的方法);在transitionWorkflow 方法中,与流程启动后step 间的流转稍有不同的是:当发现actioninitial action 时,将会把流程设置为activated 状态,表示流程启动。就是下面这段代码:if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
 changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
} WorkflowStore
WorkflowEntry>> 前 面提到过,在AbstractWorkflow 中,包含流程流转关键逻辑的transitionWorkflow 方法,会创建新的step 。而这一创建工 作是通过调用另一个member method 实现的,也就是createNewCurrentStep 。其执行逻辑大致如下:int nextStep = theResult.getStep();
if (nextStep == -1) {
 if (currentStep != null) {
  nextStep = currentStep.getStepId();
 }
}

if (currentStep != null) {
 store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
 store.moveToHistory(currentStep);
}

Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
从这段代码中,我们首先可以看到,osworkflow 可以支持step 节点自身再次被 激活 的行为,也就是重复执行某个step 。而另一方面,随后的创建工作则是调用了store.createCurrentStep 。这 个store 变量就是一个实现了WorkflowStore 接口的实例变量,缺省是MemoryWorkflowStoreWorkflowStore 除了创建step 之外,还提供了一系列findquery 方法。在其内部分别保存着step 的历史记录(history steps )以及当前处于 激活 状态的stepcurrent steps ),以MemoryWorkflowStore 为例,分别对应了两个HashMap ,而JDBCWorkflowStore 则利用数据库表来记 录这些信息。实际上,WorkflowStore 可以同时保存多个流程的记录,这样就可以满足同时存在多个流程的情况。为此, osworkflow 提供了一个WorkflowEntry 接口用来描述流程信息,其中包含了流程名称、流程ID 、当前状态等等。 WorkflowEntry 中定义了如下几个流程状态常量:public static final int CREATED = 0; // 创建
public static final int ACTIVATED = 1; //
激活
public static final int SUSPENDED = 2; //
挂起
public static final int KILLED = 3; //
异常终止
public static final int COMPLETED = 4; //
正常结束
public static final int UNKNOWN = -1;
WorkflowStore 提供的接口方法中有如下几个方法供维护WorkflowEntry 使用:public WorkflowEntry createEntry(String workflowName) throws StoreException;
public WorkflowEntry findEntry(long entryId) throws StoreException;
public void setEntryState(long entryId, int state) throws StoreException;
具体的方法实现,各个具现类有所不同。比如。在MemoryWorkflowStore 中,是通过维护一个存储SimpleWorkflowEntry 实例(实现了WorkflowEntry 接口)的HashMap 达到目的的。

 

 

 

 

流程流转的执行逻辑

Ø        当流程执行到的某个step 时,可能有一个或多个action 可供用户选 择执行。一旦确定执行某个action 后,我们需要调用AbstractWorkflow.doAction ,并传入流程idactionid 。以下 是对doAction 的执行逻辑的一个不太严紧的算法描述:-  根据流程id ,获得所有当前的step ,这种情况往往发生在有split 的时候,此时会有多个step 等待执行;
-
 根据传入的actionid ,检查是否是global action
-
 若不是global action ,则遍历所有当前的step ,对每个step 的每个action 调用isActionAvailable 方法,检查该action 是否可用(记住stepaction 是一对多的关系);
-
 所谓可用是指,通过执行passesConditions ,逐个检查actioncondition :若是OR 的关系,则有一个condition 为真即为可用,AND 关系则类推;
-
 若action 可用,则调用transitionWorkflow ,这是流程流转处理的关键部分;
 执行transitionWorkflow 时:
 -  首先获取当前step ,存在有多个当前step 的情况,比如split ,此时获取首个isAvailableAction 为真的step
 -  调用verifyInputs 验证输入(如果actionvalidator 的话);
 -  执行当前steppost function (因为该step 即将结束);
 -  执行actionpre function
 -  判断当前step 所属的result 中的所有condition 是否满足要求,判断方法类似actioncondition
 -  一旦满足,则获取resultpre functionpost function
 -  否则即是unconditional result ,获取相应的pre functionpost function
 -  在没有splitjoin 的情况下
  -   会根据在result 中指定的下一个stepid ,创建一个新的step ,作为当前的step
  -  从current steps 中移除原来的当前step ,并添加到history steps 中;
  -  如果新的steppre function ,则会马上执行;
 -  执行resultpost function
 -  执行actionpost function
 -  若actionintial action ,则将流程设置为activated 状态;
 -  若actionfinish action ,则将流程设置为completed 状态,返回true
 -  寻找auto action ,若有的话,则执行之,执行方法是调用doAction 本身;
 -  返回false
-
 根据transitionWorkflow 的返回值判断流程是否结束;
-
 若返回false ,则调用checkImplicitFinish 检查是否存在implicit finish ,即:当前没有一个stepaction 可用时,就认为流程应该结束;*   *   *-  若存在split ,则会创建多个新的step ,并且在创建之前先执行splitpre function ,在创建之后执行splitpost function
-
 创建step 的过程和上面描述的普通状况相同:维护好current stepshistory steps ,并执行新的steppre function*   *   *-  若存在join ,先结束当前step ,并将该step 添加至history stepsjoin steps
-
 查找history steps ,对每个已完成的step ,查看是否在其resultunconditional result 中有join 一项,若有则加入join steps 中;
-
 检查join 是否已经满足:可以使用Bean Shell ,在xml 定义文件的join 节点中,通过引用一个名为“jn” 的特殊变量来指定join 的满足条件,jn 记录了有关join 的关键信息;
-
 若条件满足,则执行joinpre function ,维护好history steps ,并创建下一个step ,然后执行joinpost function*   *   *-  对于条件循环的情况,可以通过将result 的某个action 的下一个step 指定为自身来加以实现,这只是在xml 定义文件中做文章,流程执行逻辑无需做特殊处理;

 

定时执行功能

Ø        osworkflow 中提供了定时执行某项任务的功能,这主要得益于opensymphony 的另一个项目——Quatz ,该项目为工作的定期执行提供了底层设施支持。为此,我们需要引用quatrz.jar 包,好在osworkflow 的下载包中已经包含了该文件。为了实现任务定时,我们需要在流程定义文件中做类似如下的配置:<function type="class">
 <arg name="class.name">com.opensymphony.workflow.util.ScheduleJob</arg>
 <arg name="triggerId">1</arg>
 <arg name="jobName">testJob</arg>
 <arg name="triggerName">testTrigger</arg>
 <arg name="groupName">test</arg>
 <arg name="repeat">10</arg>
 <arg name="repeatDelay">2000</arg>
 <arg name="cronExpression">0,5,10,15,20,25,30,35,40,45,50,55 * * * * ?</arg>
 <arg name="username">test</arg>
 <arg name="password">test</arg>
 <arg name="local">true</arg>
 <arg name="schedulerStart">true</arg>
</function> ScheduleJob
是一个FunctionProiver ,因此具有execute 方法。在该方法执行期间,ScheduleJob 将会读取这些配置参数,创建好job 实例 (实际上是一个JobDetail 实例)和trigger 实例,然后启动schedule 。大致流程如下:
-
根据传入的shedulerName 参数,利用org.quartz.impl.StdSchedulerFactorygetScheduler 方法创建sheduler 实例,该实例实现了org.quartz.Scheduler 接口;
-
根据传入的jobClass 参数,决定创建何种Job 实例,osworkflow 自身提供了两种选择:WorkflowJobLocalWorkflowJob 。前者支持SOAP 协议,后者则是本地调用,它们都实现了org.quartz.Job 接口。
-
创建一个描述Job 信息的JobDetail 实例,并做好初始设置;
-
若传入参数中未指定cronExpression ,则创建SimpleTrigger ,并设置好startDateendDaterepeat ,否则创建CronTrigger
-
jobDetailtrigger 准备完毕后,就可以启动schedule 了:s.addJob(jobDetail, true);
s.scheduleJob(trigger);
s.start(); scheduler
中应该可以同时维护多个jobtrigger ,当trigger 的触发条件满足后,将会激活真正的job 实例。由于,scheduler 中只保存了 jobDetail 的实例,因此我猜想,job 实例的真正创建是由jobDetail 完成的。job 实例(WorkflowJob LocalWorkflowJob 或者是其他自定义扩展类)激活后,其excute 方法将会被执行。其内部的执行逻辑大体是获得指定的Workflow 实 例,然后执行该实例的executeTriggerFunction 方法。trigger function 的执行与先前在流程定义文件中所出现过的普通function 大同小异。当然,我们还需要在流程定义文件中加入对trigger function 的描述,大致格式如下:<trigger-functions>
 <trigger-function id="1″ >
 <function>
  
 </function>
</trigger-functions>

 

查询功能

WorkflowExpressionQuery 參數

 

這個類別建立時所需的四個參數,第二個對應到資料庫的Table 如下所示,第一個則對應到Table 中的欄位, 欄位如何對應由名稱應可明瞭,不另作說明。

常數

Table

FieldExpression.ENTRY

OS_WFENTRY

FieldExpression.CURRENT_STEPS

OS_CURRENTSTEP

FieldExpression.HISTORY_STEPS

OS_HISTORYSTEP


第三個參數為運算元,只有四種如下:

運算元

說明

FieldExpression.EQUALS

等於

FieldExpression.NOT_EQUALS

不等於

FieldExpression.GT

大於

FieldExpression.LT

小於

 

 

 

Osworkflow Xml 加载过程

Ø         abstractworkflow 中的 getconfiguration 方法调用了 configuration 中的 load null )方法

Ø         configuration 中的 load null )方法调用了 getInputStream ()方法

Ø         load null )方法调用了流程描述文件的工厂并进行实例华并将其初始化

Ø         factory initDone ()方法中主要是获取到 osworkflows Xml 的名字并进行解析

Ø         factory getinputstream name )方法可以看到他寻找 osworkflows Xml 顺序

Ø         找到流程定义文件后,就是往 workflows HashMap )中加入流程名称为参数的静态内部类 workflowconfig ,这个类将会为下面 workflowdescriptor 对象加载作好准备

 

Workflowdescriptor 对象加载过程

Ø         首先在 workflow 接口中 getworkflowdescriptor string workflowname )方法调用 configuration 对象中的 getworkflow workflowname )方法

Ø         Configuration 对象调用 factory 中的 getworkflow name )方法

Ø         factory 中的 getworkflow name )方法调用同类中的 getworkflow name true )方法

Ø         getworkflow name true )方法首先用 name 取得 workflow 对象,这个 workflowconfig 对象在 factory 初始化的时候已经被生成了,紧接着调用 loadworkflow c validate )方法去解析 xml 并最终生成 workflowdescriptor 对象

 

workflowstore 对象加载过程

Ø         首先 Abstractworkflow 中的 getPersistence 方法调用 configuration 对象中的 getworkflowstore ()方法

Ø         如果 store 为空,从预先用 load URL url )加载 persistenceClass 中取得实现类,然后再创建新的实例并初始化参数,最后返回 workflowstore 对象

 

Osworkflow 高级功能

全局条件

 

全局动作

 

通用动作

 

自动动作

 

发送邮件

 

注册器

 

触发器

 

定时器

 

验证器

 

 

Osworkflow 应用

和现有系统结合

 

 

参考

史蒂夫 OSWorkflow 入門

陈刚 osworkflow 指南

网上其他资料

如果转载需要经过作者同意

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值