主要分为一下几个步骤:

  1.画图

  2.部署流程-把图的信息转入到数据表格中

  3.创建流程实例-开始一个流程-实际发起了一个流程

  4.执行任务:获取任务+完成任务

1.画图

  画了一个简单的流程图,图形文件名称是:qj01.bpmn   id:myProcess_1

     流程: 请假(qingjia)--->审批(shenpi)--->结束

  

指定了这一步骤的名称和执行人   Assignee:dcc  执行人时dcc

指定了第二步的名称和执行人  Assignee:jcc  执行人时jcc

 2.部署流程

2.1方法1

  (本地测试可以用下,实际项目不使用)

  private ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    //流程定义
@Test
public void deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment de = repositoryService.createDeployment().addClasspathResource("bpmn/qj01.bpmn").name("qj").deploy(); //这里name里面传入中文,到act_re_deployment表可能出现乱码
System.out.println("部署id"+de.getId()); /*
流程定义:
流程部署表 re_deployment
流程定义表 re_procdef
资源信息表 ge_bytearray
*/
}

addClasspathResource("bpmn/qj01.bpmn"),这里文件是bpmn图形文件的路径

执行,在数据库中查看,影响到3张数据表格:流程部署表 re_deployment  流程定义表 re_procdef  资源信息表 ge_bytearray

2.2方法2:上传压缩文件的方式

2.2.1第一步:把bpmnt图形文件压缩成zip文件

  

2.2.2编码

  这里我省略了上传zip文件的过程,直接从本地读取的


@Test
public void deploy2() throws FileNotFoundException {
RepositoryService repositoryService = processEngine.getRepositoryService();
InputStream in = new FileInputStream("C:\\Users\\Administrator\\IdeaProjects\\jsnhcopy\\src\\main\\resources\\bpmn\\qj01.zip"); //zip文件路径
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deploy = repositoryService.createDeployment().addZipInputStream(zipInputStream).name("qjlc").deploy(); System.out.println("部署id"+deploy.getId()); /*
流程定义:
流程部署表 re_deployment
流程定义表 re_procdef
资源信息表 ge_bytearray
*/
}

2.3中文乱码问题
  
存在中文的时候,出现乱码,尝试了各种方法,都没有生效
    1)idea安装目录下两个文件加入配置:-Dfile.encoding=UTF-8,没有用
    
  

    2)配置idea,无效

      file-setting-editor-fileencoding

  

    3)pom文件加入配置,无效

  <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.4.RELEASE</spring.version>
</properties>
      <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

  最后没有办法,只能不用中文

3.创建流程实例-启动流程

  //启动流程实例
@Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myProcess_1");//表re_procdef里面的key字段 System.out.println("实例id"+myProcess_1.getId());
System.out.println("流程定义id"+myProcess_1.getProcessDefinitionId()); /*
启动流程实例
运行时任务表:ru_task
运行时流程执行表:ru_execution
运行时执行主体信息表:ru_identitylink
*/
}

其中 myProcess_1的值是有图形的id决定的。会成为表act_re_procdef里面的KEY_的值,这个属性用来标识流程。相同的KEY_的流程表示同一流程的不同版本。默认启动的是同一流程的最新版本

执行,影响到三张表 运行时任务表:ru_task   运行时流程执行表:ru_execution   运行时执行主体信息表:ru_identitylink,此时一个流程被发起,有了任务act_ru_task

除了使用KEY_来创建流程实例,还可以通过流程的id等其它属性来创建流程实例 runtimeService.startProcessInstanceById(1)

4.执行任务

4.1获取任务

    @Test
public void findtask(){
//获取任务
TaskService taskService = processEngine.getTaskService();
List<Task> task_list = taskService.createTaskQuery().taskAssignee("dcc").list();//查询dcc的任务
task_list.stream().forEach((x)->{
System.out.println("任务id"+x.getId());
System.out.println("任务名称"+x.getName());
});

这里是通过任务执行人来获取的任务,获取的是dcc的任务。这里获取到id为25005的任务

4.2完成任务

   @Test
public void complete() { TaskService taskService = processEngine.getTaskService();
taskService.complete("25005");
System.out.println("完成任务"); /*
任务id2505
完成任务id2505
*/
/*
完成任务:
ru的几张表的数据清楚了
历史相关表:
              历史任务表:hi_taskinst

                历史流程实例表:hi_procinst

                历史流程参与者表:hi_identitylink

                历史活动节点表:hi_actinst
*/
}

  这里通过任务id来完成任务

  在完成了这个任务后,流程就走到了审批流程,表中ru_task表中id为25005任务没有了,出现了一个新的任务-审批

  上面执行,完成了第一个步骤-请假,再依照上面,获取任务-完成任务,完成第二个步骤-审批,整个流程就走完了。

   流程走完后,此时act_ru_的几张表的数据会清空,历史信息进入历史表,涉及到一下四张表

        历史任务表:hi_taskinst

          历史流程实例表:hi_procinst

           历史流程参与者表:hi_identitylink

           历史活动节点表:hi_actinst

  

 5.流程的CRUD

5.1新增

  新增一个流程也就是流程部署,也就是上面标题2的内容

5.2修改

  流程本身是不能修改的。只能通过发布新的版本的流程来实现。

  

  这个属性,也就是表act_re_procdef的KEY_属性值,相同表示同一流程,版本号增加。

  如下图,我共画了3张图,重复发布,总共发布了7次,但是没修改id,流程的KEY_是相同的,都是"myProcess_1",所以KEY_值相同,版本号增加。如果此时通过KEY_来创建流程实例,默认是创建的最新版本的流程实例。

5.3删除

  不建议删除

5.4查询

  可以通过多个条件去查询KEy_ 、id等

   @Test
public void query(){
     ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
ProcessDefinitionQuery processDefinitionQuery = processEngine.getRepositoryService().createProcessDefinitionQuery();
List<ProcessDefinition> myProcess_1 = processDefinitionQuery.processDefinitionKey("myProcess_1").list();
if(!myProcess_1.isEmpty()){
myProcess_1.stream().forEach(x->{
System.out.println(x.getKey());
System.out.println(x.getId());
});
}
}

6.历史的查询

6.1查询历史任务

  可以通过多种条件查询 id、时间等

  @Test
public void queryHi(){
HistoricTaskInstanceQuery historicTaskInstanceQuery = processEngine.getHistoryService().createHistoricTaskInstanceQuery();
List<HistoricTaskInstance> myProcess_11 = historicTaskInstanceQuery.processDefinitionKey("myProcess_1").list(); if(!myProcess_11.isEmpty()){
myProcess_11.stream().forEach(x->{
System.out.println(x.getName());
System.out.println(x.getId());
});
}
}

7.查询流程实例

  可以通过多个条件查询,KEY_、id、name等

@Test
public void queryDeployVo(){
ProcessInstanceQuery processInstanceQuery = processEngine.getRuntimeService().createProcessInstanceQuery();
List<ProcessInstance> myProcess_1 = processInstanceQuery.processDefinitionKey("myProcess_1").list(); if(!myProcess_1.isEmpty()){
myProcess_1.stream().forEach(x->{
System.out.println(x.getName());
System.out.println(x.getId());
});
}
}

8.流程中的参数传递

8.1基本说明

  这里的参数是指创建流程实例-执行任务中的参数传递。支持的数据类型包括基本数据类型、序列化对象

   参数分为两类:任务节点本地变量和全局流程实例变量,前者在流程的指定节点有效,后者整个流程实例过程有效,一般使用后面的

8.2实例

8.2.1流程图

 8.2.2部署流程

  @Test
public void deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment de = repositoryService.createDeployment().addClasspathResource("bpmn/qj01.bpmn").name("qj").deploy(); }

8.2.3启动一个流程实例并传递参数

    @Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String,Object> pa = new HashMap<>();
pa.put("start","qidong");
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myid2",pa);//表re_procdef里面的key字段 }

这里传递了一个名为start的参数

8.2.4执行任务

先创建一个对象PoTe,序列化,这样子就可以作为参数传递

public class PoTe implements Serializable {  //需要序列化

    private String username;

    private String password;

    public PoTe() {
} public PoTe(String username, String password) {
this.username = username;
this.password = password;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} @Override
public String toString() {
return "PoTe{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}

  执行请假任务

@Test
public void complete() { TaskService taskService = processEngine.getTaskService(); Object start = taskService.getVariable("40006", "start");
System.out.println("启动变量"+start); //启动变量qidong taskService.setVariable("40006","username","hahaha");
taskService.setVariable("40006","password","123");
taskService.setVariable("40006","po",new PoTe("hahaha","123"));
/*
Map<String,Object> pa = new HashMap<>();
pa.put("username","hahaha");
pa.put("password","123");
pa.put("po",new PoTe("hahaha","123"));
taskService.setVariables("25005",pa);
*/ taskService.complete("40006");
System.out.println("完成任务"); }

  获取到了前面添加的名称为start的变量。并且又添加了三个变量

  执行审批任务

    @Test
public void complete2() { TaskService taskService = processEngine.getTaskService(); String username = taskService.getVariable("42507", "username", String.class);
String password = taskService.getVariable("42507", "password", String.class);
String start = taskService.getVariable("42507", "start", String.class);
PoTe po = taskService.getVariable("42507", "po", PoTe.class);
System.out.println(username); //hahaha
System.out.println(password); //123
System.out.println(start); //qidong
System.out.println(po); //PoTe{username='hahaha', password='123'}
Map<String, VariableInstance> variableInstances = taskService.getVariableInstances("42507");
variableInstances.keySet().stream().forEach(x->{
VariableInstance variableInstance = variableInstances.get(x);
System.out.println(variableInstance.toString());
//VariableInstanceEntity[id=42502, name=password, type=string, textValue=123]
//VariableInstanceEntity[id=40002, name=start, type=string, textValue=qidong]
//VariableInstanceEntity[id=42501, name=username, type=string, textValue=hahaha]
//VariableInstanceEntity[id=42504, name=po, type=serializable, byteArrayValueId=42503]
});
taskService.complete("42507");
System.out.println("完成任务 42507"); }

  这里获取到了start等4个参数。流程结束。

9.流程不同的分支走向-连线中的表达式

9.1画图

图的文件名:qj02.bpmn    图的id:myid3  

这里的流程是:

    请假(qjia)--->部门审批(shenpi)--->天数小于等于5->流程结束

                                     --->天数大于5->领导审批(shenpi2)->流程结束

  

在这条连线设置了条件表达式:days>5

这里连线添加了条件:day<=5

9.2部署流程

   //流程定义
@Test
public void deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment de = repositoryService.createDeployment().addClasspathResource("bpmn/qj02.bpmn").name("qj").deploy();
System.out.println("部署id"+de.getId()); }

9.3创建流程实例

 @Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String,Object> pa = new HashMap<>();
pa.put("start","qidong"); ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myid3",pa);//表re_procdef里面的key字段 System.out.println("实例id"+myProcess_1.getId());
System.out.println("流程定义id"+myProcess_1.getProcessDefinitionId()); }

9.4.执行任务

9.4.1第一个任务-请假

此时任务表,有一个任务,id为67507,执行人时dcc,也就是请假这一步

执行任务

    @Test
public void complete() { TaskService taskService = processEngine.getTaskService();
      taskService.setVariable("67507","days",3);
      taskService.complete("67507"); System.out.println("完成任务");
}

注意,这里传递了一个参数days为3

9.4.2第二个任务-部门审批

上面任务执行完成后,再看任务表。有一个任务,id为70002,执行人为jcc,也就是走到了第一个审批

 @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("70002");
}

执行任务,执行完成后,发现任务表为空,整个流程完成了,因为day为3,所以没有走第二个审批

下面我们把days换做6

9.5创建流程实例

    @Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String,Object> pa = new HashMap<>();
pa.put("start","qidong");
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myid3",pa);//表re_procdef里面的key字段
}

9.6执行任务

9.6.1第一个任务-请假

  任务表有一个任务,id为75007,执行人为dcc,也就是第一步-请假

   @Test
public void complete() {
TaskService taskService = processEngine.getTaskService();
taskService.setVariable("85006","days",6);
taskService.complete("85006");
System.out.println("完成任务");
}

注意,这里传递的days的参数是6

9.6.2第二个任务-部门审批

 @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("87503");
System.out.println("完成任务");
}

9.6.3第三个任务-领导审批

    @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("90002");
System.out.println("完成任务");
}

执行后,任务表没有任务了,流程走完了

10条件表达式

activiti支持两个UEL表达式:UEL-value和UEL-method

11.网关

11.1排他网关

  几条不同的线,只走其中一条线

11.1.1画图

  图形名称:qj03.bpmn  图形id:myid4

  流程: 请假(qjia)--->请假天数<=5天--->部门审批(shenpi)--->结束

          --->请假天数>5天--->领导审批(shenpi2)--->结束

 11.1.2代码测试

  和上面的代码差不多,我这里就不贴出来了。先测试3天,再测试6天。发现3天时,是部门审批,6天时,是领导审批

11.2并行网关

  并行网关是指,几条线都需要执行通过,才能往下走

11.2.1画图

图名:qj04.bpmn  id:myid6

流程:  请假(qjia)--->部门审批(shenpi1)+领导审批(shenpi2)--->老板审批(shenpi3)--->结束

这里部门审批和领导审批都需要审批,这里就没有设计条件表达式了,因为都要去审批

11.2.1部署流程

   @Test
public void deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment de = repositoryService.createDeployment().addClasspathResource("bpmn/qj04.bpmn").name("qj").deploy();
System.out.println("部署id"+de.getId());
}

11.2.2创建流程实例

    @Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String,Object> pa = new HashMap<>();
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myid6",pa);//表re_procdef里面的key字段
}

11.2.3执行任务

11.2.3.1第一步任务-请假

有一个任务 请假(qjia)

执行任务

    @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("107505");
System.out.println("完成任务");
}

11.2.3.2第二步-部门审批(shenpi)+领导审批(shenpi2)

  上面完成后,任务里面有两个,分别是部门审批(shenpi)和领导审批(shenpi2)

先执行一个审批,随便先执行哪一个都可以

    @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("110004");
System.out.println("完成任务");
}

一个审批完成后,发现第三步审批任务还没有,任务里面还剩下一个领导审批

继续执行领导审批

   @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("110007");
System.out.println("完成任务");
}

11.2.3.3第三步-老板审批(shenpi3)

上面2个审批都执行完后,任务里面出现了老板审批(shenpi3)的任务

执行

   @Test
public void complete2() {
TaskService taskService = processEngine.getTaskService();
taskService.complete("115003");
System.out.println("完成任务");
}

执行完成后,没有任务了,流程执行完

12.任务分配

  前面我们画图的时候,每一步任务由谁执行,都是固定写死的,现在我们使用表达式

   写死的:

    

  表达式:

    

12.1画图

  图形bpmn文件名称:qj05.bpmn  id:myid11

  流程:员工(employee)请假--->部门领导审批(leader)--->结束

 12.2部署流程

  //流程定义
@Test
public void deploy(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment de = repositoryService.createDeployment().addClasspathResource("bpmn/qj05.bpmn").name("qj").deploy();
System.out.println("部署id"+de.getId());
}

12.3启动流程实例

    //启动流程实例
@Test
public void run(){
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String,Object> pa = new HashMap<>();
pa.put("employee","ddd");
ProcessInstance myProcess_1 = runtimeService.startProcessInstanceByKey("myid10",pa);//表re_procdef里面的key字段
}

启动流程的时候,设置哪些人可以请假,这里设置ddd可以请假

此时有任务,执行人就是ddd

12.4执行任务

12.4.1第一步-请假

ddd可以获取到任务,并执行

获取

   @Test
public void findtask(){
//获取任务
TaskService taskService = processEngine.getTaskService();
List<Task> task_list = taskService.createTaskQuery().taskAssignee("ddd").list();//查询dcc的任务
}

执行,执行时指定由谁来审批,这里指定的是jjj

  @Test
public void complete() {
TaskService taskService = processEngine.getTaskService();
taskService.setVariable("150006","leader","jjj");
taskService.complete("150006");
System.out.println("完成任务");
}

12.4.2第二步-审批

  jjj获取任务并执行

 @Test
public void findtask(){
//获取任务
TaskService taskService = processEngine.getTaskService();
List<Task> task_list = taskService.createTaskQuery().taskAssignee("jjj").list();//查询dcc的任务
}

执行

 @Test
public void complete() {
TaskService taskService = processEngine.getTaskService();
taskService.setVariable("152503","leader","jjj");
taskService.complete("152503");
System.out.println("完成任务");
}

流程走完了,任务没有了

Activiti02流程基本功能使用的更多相关文章

  1. H3 BPM让天下没有难用的流程之功能介绍

    H3 BPM10.0功能地图如下:  图:H3 BPM 功能地图 一.流程引擎 H3  BPM 流程引擎遵循WFMC 标准的工作流引擎技术,设计可运行的流程和表单,实现工作任务在人与人.人与系统.系统 ...

  2. PHP实现流程管理功能

    核心逻辑:流程管理,在各种系统中扮演很重要的地位,可以把设定好的流程放入系统中,规定好几个节点,只要所有节点都通过,就可以通过. 建立四张数据库表: 1.我们首先做一个新建流程页面 flow.php, ...

  3. 金蝶EAS——我的EAS报销流程怎么能让另一个人看到呢?即如何设置流程传阅功能?设置“代理报销”

    代理的话只能看到被代理人能看到的流程.设置"代理报销":应用--财务会计--费用管理--代理报销 选择报销人公司--"他人代理我报销"--选择报销人(zhaof ...

  4. 使用gitflow流程完成功能时报错

    报错 fatal: could not read Username for 'https://github.com': ······ 原因 使用https方式的时候 在https url 里面没有用户 ...

  5. 流程开发Activiti 与SpringMVC整合实例

    流程(Activiti) 流程是完成一系列有序动作的概述.每一个节点动作的结果将对后面的具体操作步骤产生影响.信息化系统中流程的功能完全等同于纸上办公的层级审批,尤其在oa系统中各类电子流提现较为明显 ...

  6. H3 BPM让天下没有难用的流程之技术特性

    一.集成性  H3 BPM可以与其它系统进行多个层面的集成,满足企业的针对不同系统的集成需求. 图:多种集成维度 Ø  用户集成 可与企业现有系统进行组织架构同步或调用,也可以直接与AD 进行集成. ...

  7. django博客功能实现——标签功能

    标签功能添加流程 0.功能概括 标签作为文章中的分类标记,会显示出该文章是关于哪一方面的文章,比如是关于python的还是关于django的. 当我们点击该标签的时候,会出现该博客中所有属于该标签的文 ...

  8. F2工作流引擎之-纯JS Web在线可拖拽的流程设计器(八)

          Web纯JS流程设计器无需编程,完全是通过鼠标拖.拉.拽的方式来完成,支持串行.并行.分支.异或分支.M取N路分支.会签.聚合.多重聚合.退回.传阅.转交,都可以非常方便快捷地实现,管理员 ...

  9. 知方可补不足~用CDC功能来对数据库变更进行捕捉

    回到目录 如果我们希望监视一个数据表的变化,在sql2008之前的版本里,在数据库端可能想到的只有触发器,或者在程序端通过监视自己的insert,update,delete来实现相应的功能,这种实现无 ...

  10. 纯JS Web在线可拖拽的流程设计器

    F2工作流引擎之-纯JS Web在线可拖拽的流程设计器 Web纯JS流程设计器无需编程,完全是通过鼠标拖.拉.拽的方式来完成,支持串行.并行.分支.异或分支.M取N路分支.会签.聚合.多重聚合.退回. ...

随机推荐

  1. Day11.2:标签的使用

    标签的使用 当我们在嵌套语句中,例如当我们在for的嵌套循环语句中,想要终止或重新开始当前循环以外的循环的时候,单独仅靠break和continue和还不够,需要在我们想要作用的循环语句处加上一个标签 ...

  2. Redis的攻击手法

    目录 Redis概述 Redis未授权 漏洞发现 漏洞验证 Redis写shell 漏洞利用 Redis写公钥 漏洞利用 主从复制RCE 漏洞简介: 漏洞利用 计划任务反弹shell 漏洞利用 Red ...

  3. Thinkphp6使用腾讯云发送短信步骤

    1.前提条件国内短信地址:https://console.cloud.tencent.com/smsv2 已开通短信服务,具体操作请参见 国内短信快速入门.如需发送国内短信,需要先 购买国内短信套餐包 ...

  4. 读书笔记《A Philosophy of Software Design - John Ousterhout 软件设计哲学》

    软件设计哲学这本书很薄,值得一读.这本书将大家平时碰到的很多软件问题从更深刻的层面进行了抽象分析,同时又给出了具体的解决方案.可以说既有理论高度,又能贴近实践. 但针对软件问题,这本书并没有提出太多与 ...

  5. 一张VR图像帧的生命周期

    "VR 应用程序每帧渲染两张图像,一张用于左眼,一张用于右眼."人们通常这样来解释 VR 渲染,虽然没有错,但可能过于简单化了.对于 Quest 开发人员来说,了解全貌是有益的,这 ...

  6. js- day03- 将数据变成柱形图

    柱形图的渲染 * {             margin: 0;             padding: 0;         }  .box {             display: fle ...

  7. 【Java面试指北】Exception Error Throwable 你分得清么?

    读本篇文章之前,如果让你叙述一下 Exception Error Throwable 的区别,你能回答出来么? 你的反应是不是像下面一样呢? 你在写代码时会经常 try catch(Exception ...

  8. python爬虫爬取网易云音乐(超详细教程,附源码)

    一. 前言 先说结论,目前无法下载无损音乐,也无法下载vip音乐. 此代码模拟web网页js加密的过程,向api接口发送参数并获取数据,仅供参考学习,如果需要下载网易云音乐,不如直接在客户端下载,客户 ...

  9. 解析【.mdb】文件

    有一些项目用的是微软的access软件,这里面存放数据用的是mdb结尾的文件 有的时候,客户想开发一个新的系统,但是数据需要从这些文件中获取,因此得解析这些文件,来提取数据 一.解析时用到的依赖 1. ...

  10. 11、ON DUPLICATE KEY UPDATE实现插入更新操作

    一.插入与更新操作: MySQL中,采用ON DUPLICATE KEY UPDATE语句对不存在的数据进行INSERT插入操作,对已存在的数据进行UPDATE更新操作: 总结: 1.ON DUPLI ...