ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

activiti7 工作流——基础篇

2022-01-15 18:59:47  阅读:390  来源: 互联网

标签:定义 activiti 流程 基础 工作 activiti7 act processEngine id


该笔记是看黑马程序员课程,自己跟着敲记录的,可以去下载老师的笔记更全
百度网盘地址: https://pan.baidu.com/s/1rii67cdsIt_Y9p1lWQCcJg 提取码:506g

目录

一、activiti理解

工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。

一个软件系统中具有工作流的功能,我们把它称为工作流系统,一个系统中工作流的功能是什么?就是对系统的业务流程进行自动化管理,所以工作流是建立在业务流程的基础上,所以一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。即使没有工作流业务系统也可以开发运行,只不过有了工作流可以更好的管理业务流程,提高系统的可扩展性。

用BPMN的符号来定义我们的流程,定义好后的流程,可以用activiti提供的接口来简化开发。

二、开发准备

1、idea安装activiti插件

安装插件 actiBPM
1、在idea搜不到这个插件,直接去官网
链接: https://plugins.jetbrains.com/plugin/7429-actibpm/versions
在这里插入图片描述
2、idea从文件夹安装下载的插件jar包
在这里插入图片描述

三、java开发、理解部分

1、activiti环境

1)、引入activiti相关依赖

		<dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <!--  bpmn 模型处理-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-model</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-converter</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <!--  bpmn json数据转换-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-json-converter</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <!--   bpmn 布局-->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-bpmn-layout</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>
        <!--   activiti云支持-->
        <dependency>
            <groupId>org.activiti.cloud</groupId>
            <artifactId>activiti-cloud-services-api</artifactId>
            <version>7.0.0.Beta1</version>
        </dependency>

2)、activiti配置文件

在resources下创建activiti.cfg.xml文件,用于生成activiti表
注意要有mysql的相关依赖,才能连接

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    在默认方式下 bean的id 固定为processEngineConfiguration-->
    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!--        配置数据库相关信息-->
<!--        数据库驱动-->
        <property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
<!--        数据库连接-->
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;serverTimezone=UTC&amp;nullCatalogMeansCurrent=true" />
<!--        数据库账号密码-->
        <property name="jdbcUsername" value="root" />
        <property name="jdbcPassword" value="123456" />
<!--        activiti:数据库在生成时的策略  true:如果数据库中已经存在相应的表,要么直接使用,如果不存在,会创建-->
        <property name="databaseSchemaUpdate" value="true" />
    </bean>
</beans>

3)、activiti工具类生成25张表

	public static void main(String[] args) {
		// 需要使用activiti提供的工具类 ProcessEngine,使用getDefaultProcessEngine方法
		// getDefaultProcessEngine会默认从resource底下读取名字为activiti.cfg.xml
		ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
		System.out.println("defaultProcessEngine = " + defaultProcessEngine);
	}

遇到报Cause: java.sql.SQLSyntaxErrorException: Table ‘myactiviti.act_ge_property’ doesn’t exist错的,在数据库url加上nullCatalogMeansCurrent=true
.
url:
jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC&nullCatalogMeansCurrent=true

在这里插入图片描述

4)、理解activiti表

_ge : general 通用表,用在各种情况下

_hi : history 历史记录,保存的都是历史数据,比如执行过的流程实例、变量、任务,等等。Activit默认提供了4种历史级别

_re : repository 仓库,保存一些‘静态’信息,如流程定义和流程资源(如图片、规则等)

_ru : runtime 运行时,保存一些流程实例、用户任务、变量等的运行时数据。Activiti只保存流程实例在执行过程中的运行时数据,并且当流程结束后会立即移除这些数据,这是为了保证运行时表尽量的小并运行的足够快;

表分类表名称表含义
.act_evt_log事件处理日志表
一般数据act_ge_bytearray通用的流程定义和流程资源
.act_ge_property系统相关属性
流程历史记录act_hi_actinst历史的流程实例
.act_hi_attachment历史的流程附件
.act_hi_comment历史的说明性信息
.act_hi_detail历史的流程运行中的细节信息
.act_hi_identitylink历史的流程运行过程中用户关系
.act_hi_procinst历史的流程实例
.act_hi_varinst历史的流程运行中的变量信息
用户用户组表act_procdef_info死信任务
流程定义表act_re_deployment部署单元信息
.act_re_model模型信息
.act_re_procdef已部署的流程定义
运行实例表act_ru_deadletter_job执行失败任务表
.act_ru_event_subscr运行时事件
.act_ru_execution运行时流程执行实例
.act_ru_identitylink运行时用户关系信息
.act_ru_job运行时作业
.act_ru_suspended_job运行时暂停任务
.act_ru_task运行时任务
.act_ru_timer_job运行时定时任务
.act_ru_variable运行时变量表

2、activiti类关系图

1)、类关系图

在这里插入图片描述
新版IdentityService,FormService两个Serivce都已经删除了

2)、工作流引擎创建

1、默认方式

ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println("defaultProcessEngine = " + defaultProcessEngine);

2、一般创建方式

//先构建ProcessEngineConfiguration
ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
// 通过ProcessEngineConfiguration创建ProcessEngine,此时会创建数据库
ProcessEngine processEngine = configuration.buildProcessEngine();

3)、Service服务接口

1、Service创建方式

RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();

2、Service总览

service名称service作用
RepositoryServiceactiviti的资源管理类 : 流程仓库服务,管理流程仓库,比如部署、删除、读取流程资源
RuntimeServiceactiviti的流程运行管理类 : 运行服务,处理所有正在运行态的流程实例、任务等。
TaskServiceactiviti的任务管理类 : 任务服务,管理(签收、办理、指派等)、查询任务。
HistoryServiceactiviti的历史管理类 : 历史服务,管理所有历史数据
ManagerServiceactiviti的引擎管理类 : 引擎管理服务,和具体业务无关,管理引擎。

3、activiti入门、操作

创建activiti工作流主要包含以下几步:
1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来
2、部署流程,把画好的流程定义成文件,加载到数据库中,生成表的数据
3、启动流程,使用java代码操作数据库表中的内容

1)、流程符号

BPMN
排他网关(x)

只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;

如果多条线路计算结果都是true,则会执行第一个值为true的线路。如果所有网关计算结果没有true,则引擎会抛出异常。

排他网关需要和条件顺序流结合使用,default属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流

并行网关(+)

所有路径会被同时选择
.
拆分-----并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路
.
合并-----所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行

包容网关(+)

可以同时执行多条线路,也可以在网关上设置条件
.
拆分----计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行。
.
合并----所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行

事件网关(+)

专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。

在这里插入图片描述

2)、actiBPM使用

1、new–>bpmn文件
在这里插入图片描述

2、双击文件打开视图
3、拉取一个开始事件

4、再拉取Usertask,并取名Name、和受让人Assignee (Assignee 受让人:很重要,后面需要用这个查询这个人的任务列表)
在这里插入图片描述

5、再创建一个经理审批,并取名Name、和受让人Assignee
在这里插入图片描述

6、结束
流程可以自己设多一点,这里只做最简单的展示
在这里插入图片描述

7、流向
点住中间往下拖
在这里插入图片描述

8、点击空白,为流程指定keyname (很重要,后面的流程需要这个key当参数去查这个流程)
在这里插入图片描述

9、导出Png图片

拷贝一份bpmn文件,修改后缀名为.xml
右键查看视图,如果没有Diagrams,点击File–>settings–>Plugins–>UML勾上,重启
在这里插入图片描述点击导出png
在这里插入图片描述

3)、解决中文乱码

在这里插入图片描述在这里插入图片描述还有c盘配置文件也加上
在这里插入图片描述

-Dfile.encoding=UTF-8

4)、流程部署实现(将定义好的流程写到数据库)

在这里插入图片描述
代码

	/**
	 * 将流程保存到数据库
	 */
	public static void main(String[] args) {
		// 1、创建ProcessEngine
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 2、获取RespositoryService
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 3、使用service进行流程的部署,定义一个流程的名字,把bpmn和png部署到数据库中
		Deployment deploy = repositoryService.createDeployment().name("出差申请") // 设置名称
				.addClasspathResource("bpmn/evection.bpmn20.xml") // 加入我们的bpmn文件
				.addClasspathResource("bpmn/evection.png")
				.deploy();
		// 4、输出部署信息
		System.out.println("流程部署id="+deploy.getId());
		System.out.println("流程部署name="+deploy.getName());
	}

看输出信息主要操作了
在这里插入图片描述
在这里插入图片描述在这里插入图片描述总操作四张表
act_ge_property —— 修改系统相关属性
act_re_procdef ——流程定义表
act_re_deployment ——流程部署表:每部署一次会增加一条记录
act_ge_bytearray ——流程资源表

5)、开启流程示例

开启这个按钮
在这里插入图片描述

刚刚部署的流程添加到了流程定义表,通过key启动流程
key就是我们在上方创建流程的第8步
在这里插入图片描述
在这里插入图片描述

	/**
	 * 开始这个流程任务
	 * @param args
	 */
	public static void main(String[] args) {

		// 1、创建ProcessEngine
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 2、获取RunTimeService
		RuntimeService runtimeService = processEngine.getRuntimeService();
		// 3、根据流程定义的id启动流程
		/**
		 * 参数1:流程定义的key
		 * 参数2:businessKey,存出差申请单的id
		 */
		ProcessInstance evection = runtimeService.startProcessInstanceByKey("evection","1001");
		// 4、输出内容
		System.out.println("流程定义id " + evection.getProcessDefinitionId());
		System.out.println("流程示例id " + evection.getId());
		System.out.println("当前活动的id " + evection.getActivityId());
	}

activiti只有流程的操作,并没有我们流程中具体的字段,如出差申请:出差时间,出差目的地等信息都没有,下一步审批的人的没有信息看,不能审批
主要操作就是在 创建流程实例时,把业务表id(businessKey)添加到activiti表
在这里插入图片描述

6)、查询个人待执行的任务

可以查出这个人有什么任务待执行,任务id
在这里插入图片描述taskAssignee(“hmb”)就是我们创建流程第4步的受让人Assignee

	/**
	 * 获取个人待执行任务
	 * @param args
	 */
public static void main(String[] args) {
		// 1、获取流程执行引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 2、获取taskService
		TaskService taskService = processEngine.getTaskService();
		// 3、根据流程key 和 任务的负责人 查询任务
		List<Task> list = taskService.createTaskQuery().processDefinitionKey("evection")  // 流程的key
				.taskAssignee("hmb")  // 要查询的负责人
				.list();// 任务集合
		// 4、输出
		for (Task task:list){
			System.out.println("流程示例id = " + task.getProcessDefinitionId());
			System.out.println("任务id = " + task.getId());
			System.out.println("任务的负责人 = " + task.getAssignee());
			System.out.println("任务的名称 = " + task.getName());
		}
	}

主要执行了这个sql
在这里插入图片描述

7)、完成个人任务

通过任务id完成任务(就是上一步查出来的待执行的任务id)

	/**
	 * 完成个人任务
	 */
	public static void main(String[] args) {
		// 获取流程引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取taskService
		TaskService taskService = processEngine.getTaskService();
		// 根据任务id完成任务
		taskService.complete("2505");  
	}

通过用户完成任务
就是上两布的结合

/**
	 * 完成某个人任务
	 * @param args
	 */
	public static void main(String[] args) {
		// 获取流程引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取taskService
		TaskService taskService = processEngine.getTaskService();
		// 获取受让人的单个任务
		Task task = taskService.createTaskQuery().processDefinitionKey("evection")
				.taskAssignee("hwt")
				.singleResult(); // 单个流程结果
		// 完成该任务
		taskService.complete(task.getId());
	}

有兴趣的可以看打印的sql去看看它操作了什么表,操作了什么

8)、zip方式流程部署

文件少的可以用add一个个部署
在这里插入图片描述但是文件多的时候,会很耗时,zip方式可以进行批量的部署

	/**
	 * zip包进行批量的部署
	 */
	public  void main(String[] args) {
		// 获取流程引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取repositoryService
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 流程部署
		InputStream inputStream = this.getClass()  // 获取这个类
								.getClassLoader()    // 获取这个工程的内容
								.getResourceAsStream("bpmn/evection.zip");  // 获取resuorces目录的资源
		// 用inputStream 构造 ZipInputStream
		ZipInputStream zipInputStream = new ZipInputStream(inputStream);
		// 使用压缩包的流进行流程的部署
		Deployment deploy = repositoryService.createDeployment()
				.addZipInputStream(zipInputStream)
				.deploy();
		System.out.println("流程部署的id = " + deploy.getId());
		System.out.println("流程部署的name = " + deploy.getName());

	}

9)、流程定义信息查询

在这里插入图片描述

	/**
	 * 查询流程定义
	 */
	public static void main(String[] args) {
		// 获取引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取repositoryService
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 获取processDefinitionQuery对象
		ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
		// 查询当前所有的流程定义
		// processDefinitionKey(流程定义key)
		List<ProcessDefinition> evection = processDefinitionQuery.processDefinitionKey("evection")
																.orderByProcessDefinitionVersion().desc()// 排序
																.list();// 返回list集合
		// 输出信息
		for (ProcessDefinition processDefinition:evection){
			System.out.println("流程定义id = " + processDefinition.getId());
			System.out.println("流程定义名称 = " + processDefinition.getName());
			System.out.println("流程定义key = " + processDefinition.getKey());
			System.out.println("流程定义版本 = " + processDefinition.getVersion());
			System.out.println("流程定义部署id = " + processDefinition.getDeploymentId());
		}
	}

10)、流程定义删除

就是删除流程部署时放到表里的数据。
部署id就是上一步可以查出来的

1、通过部署id删除
deleteDeployment
参数1:部署id
参数2:加true 就是级联删除,如果没加,流程没结束删除不掉

	public static void main(String[] args) {
		// 获取引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取repositoryService
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 通过部署id来删除流程部署信息
		repositoryService.deleteDeployment("1",true);
	}

11)、下载资源文件

下载我们部署时上传的png文件、bpmn文件等
在这里插入图片描述用到IO流工具依赖

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
/**
	 * 下载 资源文件
	 * @param args
	 */
	public static void main(String[] args) throws IOException {
		// 获取引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取repositoryService
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 获取查询对象
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
															.processDefinitionKey("evection")
															.singleResult();
		// 通过流程定义信息,获取部署id、png的name,bpmn的name
		String deploymentId = processDefinition.getDeploymentId();
		String pngName = processDefinition.getDiagramResourceName();
		String bpmnName = processDefinition.getResourceName();
		// 通过repositoryService,传递部署id,读取资源信息(png、bpmn)
		// 获取图片的流
		InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, pngName);
		// 获取bpmn的流
		InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, bpmnName);

		// 构造OutPutStream流
		File pngFile = new File("d:/evection.png");
		File bpmnFile = new File("d:/evection.bpmn");
		FileOutputStream pngOutputStream = new FileOutputStream(pngFile);
		FileOutputStream bpmnOutputStream = new FileOutputStream(bpmnFile);

		// 输入流、输出流的转换
		IOUtils.copy(pngInput,pngOutputStream);
		IOUtils.copy(bpmnInput,bpmnOutputStream);
		// 关闭流
		IOUtils.close();
	}

12)、查看历史记录

操作act_hi_actinst表流程的历史记录
在这里插入图片描述

/**
	 * 查看历史信息
	 */
	public static void main(String[] args) {
		// 获取引擎
		ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
		// 获取historyService
		HistoryService historyService = processEngine.getHistoryService();
		// 获取actinst表的查询对象
		HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
		//查询 actinst表
		instanceQuery.processInstanceId("10001");
		//	instanceQuery.processDefinitionId("evection:1:7504") 或者根据DefinitionId查
		// 增加排序操作
		instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
		// 查询所有内容
		List<HistoricActivityInstance> list = instanceQuery.list();
		for (HistoricActivityInstance his:list){
			System.out.println(his.getActivityId());
			System.out.println(his.getActivityName());
			System.out.println(his.getProcessDefinitionId());
			System.out.println(his.getProcessInstanceId());
			System.out.println("=======================");
		}
	}

13)、流程挂起与激活

业务中:
有每月最后一天不处理出差申请
或者某种原因,转正申请被暂停
需要将流程挂起,等到激活时才能继续往下走

1、挂起或激活整个流程(全部流程实例)

/**
 * 全部流程实例挂起与激活
 * suspended 暂停
 * @param args
 */
public static void main(String[] args) {
	// 1、创建ProcessEngine流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 2、获取repositoryService
	RepositoryService repositoryService = processEngine.getRepositoryService();
	// 3、查询流程定义
	ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("evection").singleResult();
	// 4、获取当前流程定义的实例是否都挂起状态
	boolean suspended = processDefinition.isSuspended();
	// 5、获取流程定义id
	String definitionId = processDefinition.getId();
	// 如果是挂起状态,改为激活
	if (suspended){
		// 激活                                     参数1:流程定义Id  ,参数2:是否激活   ,参数3:激活时间
		repositoryService.activateProcessDefinitionById(definitionId,true,null);
	}else { // 如果是正常状态,改为挂起
		// 挂起                                   参数1:流程定义Id  ,参数2:是否暂停   ,参数3:暂停时间
		repositoryService.suspendProcessDefinitionById(definitionId,true,null);
	}
}

2、挂起或激活单个流程实例

/**
 * 全部单个流程实例挂起与激活
 * suspended 暂停
 * @param args
 */
public static void main(String[] args) {
	//        获取processEngine
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        RuntimeService
	RuntimeService runtimeService = processEngine.getRuntimeService();
//        查询流程定义的对象
	ProcessInstance processInstance = runtimeService.
			createProcessInstanceQuery().
			processInstanceId("15001").
			singleResult();
//        得到当前流程定义的实例是否都为暂停状态
	boolean suspended = processInstance.isSuspended();
//        流程定义id
	String processDefinitionId = processInstance.getId();
//        判断是否为暂停
	if(suspended){
//         如果是暂停,可以执行激活操作 ,参数:流程定义id
		runtimeService.activateProcessInstanceById(processDefinitionId);
		System.out.println("流程定义:"+processDefinitionId+",已激活");
	}else{
//          如果是激活状态,可以暂停,参数:流程定义id
		runtimeService.suspendProcessInstanceById( processDefinitionId);
		System.out.println("流程定义:"+processDefinitionId+",已挂起");
	}
}

标签:定义,activiti,流程,基础,工作,activiti7,act,processEngine,id
来源: https://blog.csdn.net/qq_48721706/article/details/122514133

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有