站长承接各类型网站制作,包括:企业网站、个人网站、教育网站等

疯狂Activiti6.0连载(四)——流程引擎数据源配置

全部 chinarui 6℃ 0评论

4 配置文件读取与数据源配置

        本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti

        要点

             掌握Activiti的配置文件读取方式

             掌握Activiti的数据源配置

4.1流程引擎配置对象

        ProcessEngineConfiguration对象代表一个Activiti流程引擎的全部配置,该类提供一系列创建ProcessEngineConfiguration实例的静态方法,这些方法用于读取和解析相应的配置文件,并返回ProcessEngineConfiguration的实例。除这些静态方法外,该类为其他可配置的引擎属性提供相应的setter和getter方法。本小节主要讲解如何使用这些静态方法创建ProcessEngineConfiguration实例。

4.1.1 读取默认的配置文件

        ProcessEngineConfiguration的createProcessEngineConfigurationFromResourceDefault方法,使用Activiti默认的方式创建ProcessEngineConfiguration的实例。这里所说的默认方式,是指由Activiti决定读取配置文件的位置、文件的名称和配置bean的名称这些信息。Activiti默认到ClassPath下读取名为“activiti.cfg.xml”的Activiti配置文件,启动并获取名称为“processEngineConfiguration”的bean的实例。解析XML与创建该bean实例的过程,由Spring代为完成。

        使用过Spring的朋友可以知道,只需要指定Spring的XML配置文件,创建相应的BeanFactory实例,再通过getBean(bean名称)方法即可获取相应对象的实例,ProcessEngineConfiguration使用Spring框架的DefaultListableBeanFactory作为BeanFactory。

    代码清单4-1使用createProcessEngineConfigurationFromResourceDefault方法创建ProcessEngineConfiguration实例。

        代码清单4-1:codes/04/4.1/create-default/src/org/crazyit/activiti/CreateDefault.java

		//使用Activiti默认的方式创建ProcessEngineConfiguration
		ProcessEngineConfiguration config = ProcessEngineConfiguration
                  .createProcessEngineConfigurationFromResourceDefault();

        代码清单4-1中,Activiti默认到ClassPath下读取activiti.cfg.xml文件,如果找不到该配置文件则抛出FileNotFoundException,如果找不到名称为processEngineConfiguration的bean,则抛出org.springframework.beans.factory.NoSuchBeanDefinitionException,本例中的activiti.cfg.xml内容如代码清单4-2所示。

        代码清单4-2:codes/04/4.1/create-default/resource/activiti.cfg.xml

	<!-- 只配置相应的数据库属性 -->
	<bean id="processEngineConfiguration" 
	class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
		<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUsername" value="root" />
		<property name="jdbcPassword" value="123456" />
	</bean>

    此处需要注意的是,代码清单4-2中所使用的ProcessEngineConfiguration为StandaloneProcessEngineConfiguration类,ProcessEngineConfiguration为抽象类,不能直接作为bean的class进行配置,ProcessEngineConfiguration的子类将在下面章节进行描述。

4.1.2 读取自定义的配置文件

        在4.1.1章节中可知,默认情况下Activiti将到ClassPath下读取activiti.cfg.xml文件,如果希望Activiti读取另外名称的配置文件,可以使用createProcessEngineConfigurationFromResource方法创建ProcessEngineConfiguration,该方法参数为一个字符串对象,调用该方法时,需要告诉Activiti配置文件位置。代码清单4-3调用createProcessEngineConfigurationFromResource(String resource)方法。

       代码清单4-3:codes/04/4.1/create-resource/src/org/crazyit/activiti/CreateFromResource_1.java

		// 指定配置文件创建ProcessEngineConfiguration
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource("my-activiti1.xml");

    代码清单4-3中,Activiti会到ClassPath下查找my-activiti1.xml配置文件,并创建名称为“processEngineConfiguration”的bean,此处创建bean的过程与4.1.1中描述一致,my-activiti1.xml文件的配置内容与代码清单4-2一致。

        ProcessEngineConfiguration中还有一个createProcessEngineConfigurationFromResource的重载方法,该方法需要提供两个参数来创建ProcessEngineConfiguration,第一个参数为Activiti配置文件的位置,第二个参数为创建bean的名称。

    代码清单4-4调用createProcessEngineConfigurationFromResource(String resource, String beanName)的方法。

    代码清单4-4:codes/04/4.1/create-resource/src/org/crazyit/activiti/CreateFromResource_2.java

		// 指定配置文件创建ProcessEngineConfiguration
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource(
						"my-activiti2.xml", "test2");
		System.out.println(config.getProcessEngineName());

        代码清单4-4中,告诉Activiti需要到ClassPath下查找my-activiti2.xml文件,并且创建名字为“test”的bean。如果找不到名称为“test”的bean,则抛出NoSuchBeanDefinitionException,以下的代码会抛该异常,因为找不到名称为test2的bean。

ProcessEngineConfiguration config = ProcessEngineConfiguration
    .createProcessEngineConfigurationFromResource("my-activiti2.xml", "test2");

4.1.3 读取输入流的配置

        ProcessEngineConfiguration中提供了一个createProcessEngineConfigurationFromInputStream方法,该方法使得Activiti配置文件的加载不再局限于项目的ClassPath,只要得到配置文件的输入流,即可创建ProcessEngineConfiguration。

        同样的,createProcessEngineConfigurationFromInputStream方法也提供了两个重载的方法,可以指定在解析XML时bean的名称。

        代码清单4-5中使用createProcessEngineConfigurationFromInputStream方法(没有指定bean名称)。

        代码清单4-5:codes/04/4.1/create-stream/src/org/crazyit/activiti/CreateInputStream.java

		File file = new File("resource/input-stream.xml");
		// 得到文件输入流
		InputStream fis = new FileInputStream(file);
		// 使用createProcessEngineConfigurationFromInputStream方法创建ProcessEngineConfiguration
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromInputStream(fis);

 

4.1.4 使用createStandaloneInMemProcessEngineConfiguration方法

        使用该方法创建ProcessEngineConfiguration,并不需要指定任何参数,该方法直接返回一个StandaloneInMemProcessEngineConfiguration实例,该类为ProcessEngineConfiguration的子类。使用该方法创建ProcessEngineConfiguration,并不会读取任何的Activiti配置文件,这样意味着流程引擎配置的全部属性,都会使用默认值,与其他子类不一样的是,创建的StandaloneInMemProcessEngineConfiguration实例,只特别指定了databaseSchemaUpdate属性和jdbcUrl属性,详细请见代码清单4-6。

        代码清单4-6:

        codes/04/4.1/create-standalone-inmem/src/org/crazyit/activiti/CreateStandaloneInMem.java

		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createStandaloneInMemProcessEngineConfiguration();
		// 值为create-drop
		System.out.println(config.getDatabaseSchemaUpdate());
		// 值为jdbc:h2:mem:activiti
		System.out.println(config.getJdbcUrl());

        该方法不需要读取任何的配置文件,ClassPath下也没有任何的Activiti配置文件,如果需要改变相关的配置,可以调用ProcessEngineConfiguration中相应的setter方法进行修改。方法createStandaloneInMemProcessEngineConfiguration返回的是一个org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration实例,如果使用配置文件的方式创建ProcessEngineConfiguration,可以将该类配置为bean的class,但使用时需要注意该类中属性的默认值。

        注:ProcessEngineConfiguration的各个属性及其作用,将在下面章节中逐一描述。

4.1.5 使用createStandaloneProcessEngineConfiguration方法

        与4.1.4类似的是,createStandaloneProcessEngineConfiguration方法返回的是一个StandaloneProcessEngineConfiguration实例,并且需要注意的是,4.1.4中的StandaloneInMemProcessEngineConfiguration类是本小节StandaloneProcessEngineConfiguration类的子类。代码清单4-7中输出了StandaloneProcessEngineConfiguration类的databaseSchemaUpdate和jdbcUrl的值。

        代码清单4-7:codes/04/4.1/create-standalone/src/org/crazyit/activiti/CreateStandalone.java

		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createStandaloneProcessEngineConfiguration();
		// 默认值为 false
		System.out.println(config.getDatabaseSchemaUpdate());
		// 默认值为 jdbc:h2:tcp://localhost/~/activiti
		System.out.println(config.getJdbcUrl());

    从代码清单4-7中可以明显看出,父类StandaloneProcessEngineConfiguration的databaseSchemaUpdate和jdbcUrl属性值分别为“false”和“jdbc:h2:tcp://localhost/~/activiti”,而其子类StandaloneInMemProcessEngineConfiguration(4.1.4章节),这两个属性值分别为“create-drop”和“jdbc:h2:mem:activiti”。

4.2数据源配置

        Activiti在启动时,会读取数据源配置,用于对数据库进行相应的操作。在前面章节中得知,Activiti会先读取配置文件,然后取得配置的bean,并对其进行初始化,本小节将讲解配置bean的一系列参数,并了解其作用。

4.2.1Activiti支持的数据库

        Activiti默认H2数据库,H2是一个开源的嵌入式数据库,使用Java语言编写。使用H2数据库并不需要另外安装服务器或者客户端,只需要提供一个jar包即可使用。在实际的企业应用中,很少会使用这种轻量级的嵌入式数据库,因此H2数据更适合使用于单元测试。除H2数据库,Activiti还为以下的数据库提供支持:

              MySQL:主流数据库之一,它是一个开源的小型关系型数据库,由它体积小、速度快,得到相当多开发者的青睐,并且最重要的是,它是免费的。

              Oracle:目前世界上最流行的商业数据库,价格昂贵,但是它高效的性能、可靠的数据管理,仍令不少企业心甘情愿为其掏钱。

              Postgres:PostgreSQL是另外一款开源的数据库。

              DB2:由IBM公司研发的一款关系型数据库,其良好的伸缩性、数据库的高效性,让它成为继Oracle之后,又一应用广泛的商业数据库。

              MSSQL:微软研发的一款数据库产品,目前也支持在Linux下使用。

4.2.2Activiti与Spring

        Spring是目前非常流行的一个轻量级J2EE框架,它提供了一套轻量级的企业应用解决方案,它包括IoC容器、AOP面向切面技术以及Web MVC框架等。

        使用Activiti的项目,并不意味着一定要使用Spring,Activiti可以在没有Spring的环境中使用。虽然Activiti并不需要使用Spring环境,但是Activiti在创建流程引擎时,使用了Spring的XML解析与依赖注入功能,ProcessEngineConfiguration对应的配置,即为Spring中的一个bean。

        使用过Spring的读者,看到ProcessEngineConfiguration对应的配置,会感到非常熟悉,没有使用过的读者也不必感到气馁,因为Activiti也可以在一个完全没有Spring的环境中运行。

4.2.3JDBC配置

        JDBC连接数据库,需要使用jdbc url、jdbc驱动、数据库用户名和密码,以下代码为连接MySQL的配置:

<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <!-- JDBC url -->
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
    <!-- JDBC驱动 -->
    <property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
    <!-- 数据库用户名 -->
    <property name="jdbcUsername" value="root" />
    <!-- 数据库密码 -->
    <property name="jdbcPassword" value="123456" />
</bean>

        以上代码配置一个bean,表示一个ProcessEngineConfiguration,并且使用“设值注入”的方式将四个数据库属性设置到该bean中,换言之,该ProcessEngineConfiguration类中,肯定有相应属性的setter方法。该bean的实现类以及这些属性,将在下面章节中作详细讲解。

4.2.4DBCP数据源配置

        DBCP是Apache提供的一个数据库连接池。ProcessEngineConfiguration中提供了一个dataSource属性,如果用户不希望将JDBC的相关连接属性交给Activiti,可以自己创建数据库连接,然后通过这个dataSource属性设置到ProcessEngineConfiguration中。为Activiti的ProcessEngineConfiguration设置dataSource,可以采用配置或者编写代码的方式。代码清单4-8为使用配置方式使用DBCP数据源。

        代码清单4-8:codes/04/4.2/ds-dbcp/resource/dbcp-config.xml

	<!-- 使用DBCP数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/act" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
	</bean>

	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="dataSource" ref="dataSource" />
	</bean>

   代码清单4-8中的dataSource,配置了一个DBCP的dataSource bean,然后在processEngineConfiguration的bean中注入该dataSource。在初始化流程引擎配置时,只需根据情况调用ProcessEngineConfiguration的createXXX方法即可,如以下代码所示:

// 读取 dbcp-config.xml配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("dbcp-config.xml");
// 能正常输出,即完成配置
DataSource ds = config.getDataSource();
// 查询数据库元信息,如果能查询则表示连接成功
ds.getConnection().getMetaData();
// 结果为 org.apache.commons.dbcp.BasicDataSource
System.out.println(ds.getClass().getName());

        本例使用了createProcessEngineConfigurationFromResource方法读取Activiti的配置文件。除使用配置外,也可以通过编码方式设置相应的dataSource,只需要先创建一个DataSource对象,然后设置到ProcessEngineConfiguration中即可,代码清单4-9为通过编码方式设置DBCP数据源。

        代码清单4-9:codes/04/4.2/ds-dbcp/src/org/crazyit/activiti/DBCPConfig.java

		// 读取 dbcp-config.xml配置
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource("dbcp-config.xml");
		// 能正常输出,即完成配置
		DataSource ds = config.getDataSource();
		// 查询数据库元信息,如果能查询则表示连接成功
		ds.getConnection().getMetaData();
		// 结果为 org.apache.commons.dbcp.BasicDataSource
		System.out.println(ds.getClass().getName());

        代码清单4-9中,先创建DataSource对象,然后为该对象设置相应的数据库连接属性,然后读取Activiti配置文件,得到ProcessEngineConfiguration对象,并将DataSource设置到该对象中。ProcessEngineConfiguration的bean配置不需要设置任何属性:

<!-- 不初始化任何属性 -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
</bean>

        在笔者成书时,DBCP项目已经发展到2.1版本,本小节的案例就是基于该版本。

4.2.5C3P0数据源配置

        与DBCP类似,C3P0也是一个开源的数据库连接池,它们都被广泛的应用到开源项目以及企业用中同。与DBCP类似,可以在Activiti中使用C3P0数据源,配置方式大致相同,代码清单4-10为C3P0 bean的配置。

        代码清单4-10:codes/04/4.2/ds-c3p0/resource/config/c3p0-config.xml

	<!-- 使用C3P0数据源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
		<property name="user" value="root" />
		<property name="password" value="123456" />
	</bean>

	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="dataSource" ref="dataSource" />
	</bean>

        注:此处需要注意的是,DBCP与C3P0的属性名称不一样,可以到两个数据源的官方文档查看更详细的配置。

        除了配置方式外,也可以像DBCP一样使用编码方式创建数据源,设置方式基本与DBCP一致,只是创建DataSource实例的方式不一样而已。代码清单4-11展示如何创建C3P0数据源。

        代码清单4-11:codes/04/4.2/ds-c3p0/src/org/crazyit/activiti/C3P0Coding.java:

		// 创建C3P0数据源
		ComboPooledDataSource ds = new ComboPooledDataSource();
		// 设置JDBC连接的各个属性
		ds.setUser("root");
		ds.setPassword("123456");
		ds.setJdbcUrl("jdbc:mysql://localhost:3306/act");
		ds.setDriverClass("com.mysql.jdbc.Driver");
		// 验证是否连接成功
		ds.getConnection().getMetaData();

		// 读取Activiti配置文件
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource("config/c3p0-coding.xml");
		// 为ProcessEngineConfiguration设置dataSource属性
		config.setDataSource(ds);
		System.out.println(config.getDataSource());

 

4.2.6 Activiti其他数据源配置

        如果不使用第三方数据源,直接使用Activiti提供的数据源,那么还可以指定其他一些数据库属性。Activiti默认使用的是myBatis的数据连接池,因此ProcessEngineConfiguration中也提供了一些MyBatis的配置:

              jdbcMaxActiveConnections:在数据库连接池内最大的活跃连接数,默认值为10。

              jdbcMaxIdleConnections:连接池最大的空闲连接数。

              jdbcMaxCheckoutTime:当连接池内的连接耗尽,外界向连接池请求连接时,创建连接的等待时间,单位为毫秒,默认值为20000,即20秒。

              jdbcMaxWaitTime:当整个连接池需要重新获取连接的时候,设置等待时间,单位为毫秒,默认值为20000,即20秒。

4.2.7 数据库策略配置

        ProcessEngineConfiguration提供了databaseSchemaUpdate属性,该项可以设置流程引擎启动和关闭时数据库执行的策略。Activiti的官方文档中,databaseSchemaUpdate有以下三个值:

              false:false为默认值,设置为该值后,Activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配时,将在启动时抛出异常。

              true:设置为该值后,Activiti会对数据库中所有的表进行更新,如果表不存在,则Activiti会自动创建。

              create-drop:Activiti启动时,会执行数据库表的创建操作,在Activiti关闭时,执行数据库表的删除操作。

        代码清单4-12将databaseSchemaUpdate配置为false。

        代码清单4-12:codes/04/4.2/schema-update/resource/schemaUpdate-false.xml

	<!-- 将databaseSchemaUpdate设置为false -->
	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
		<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUsername" value="root" />
		<property name="jdbcPassword" value="123456" />
		<property name="databaseSchemaUpdate" value="false"/>
	</bean>

        使用以下代码启动Activiti流程引擎:

//读取Activiti配置
ProcessEngineConfiguration config = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("schemaUpdate-false.xml");
//启动Activiti
config.buildProcessEngine();

        如果没有数据表,则会抛出异常。这里需要注意的是,如果想看到抛出异常的效果,需要将相应数据库里面的表全部删除。如果想执行数据库表结构更新,可以将该配置设置为true,将全部数据库表删除后,再启动Activiti,即可看到Activiti已经建好全部的表,值为true的配置与代码在此不再赘述,读者可看以下配置文件与Java类:

              配置文件:codes/04/4.2/schema-update/resource/schemaUpdate-true.xml

              Java类:

                codes/04/4.2/schema-update/src/org/crazyit/activiti/DatabaseSchemaUpdateTrue.java

        将databaseSchemaUpdate设置为create-drop后,Activiti会先检查数据表是否存在,如果表已经存在,则抛出异常并停止创建流程引擎。代码清单4-13使用create-drop属性启动Activiti。

        代码清单4-13:

        codes/04/4.2/schema-update/src/org/crazyit/activiti/DatabaseSchemaUpdateCreateDrop.java

		// 读取Activiti配置
		ProcessEngineConfiguration config = ProcessEngineConfiguration
				.createProcessEngineConfigurationFromResource("schemaUpdate-create-drop.xml");
		// 启动Activiti
		ProcessEngine engine = config.buildProcessEngine();
		// 关闭流程引擎
		engine.close();

        注意代码清单4-13中的close方法,如果想要Activiti执行“drop”操作,必须要调用ProcessEngine的close方法,否则将不会删除表。一般情况下,将databaseSchemaUpdate配置为create-drop,更适合在单元测试中使用。

        除了false、true和create-drop三个值外,databaseSchemaUpdate还有一个drop-create值,跟create-drop类似,drop-create会在流程引擎启动时,先将原来全部的数据表删除,再进行创建,与create-drop不同的是,不管是否调用ProcessEngine的close方法,都会执行create操作。同样地,该值在单元测试中使用比较合适,在流程引擎初始化时将原有的数据删除,在实际应用中,此举会带来较大的风险,Activiti的官方文档并没有提供该项配置,读者知道即可。

        注:使用各种方法读取Activiti配置,均不会创建数据库表,Activiti的数据库表只会在流程引擎创建的时候,才会按照配置的策略进行创建。

4.2.8 databaseType配置

        根据前一小节得知,将databaseSchemaUpdate设置为create-drop或者drop-create时,Activiti在启动和初始化时,会执行相应的创建表和删除表操作,Activiti支持多种数据库,每种数据库的创建表与删除表的语法有可能不一样,因此,需要指定databaseType属性,来告诉Activiti,目前使用了何种数据库(当然,如果设置true而数据库中没有表的话,也需要知道使用哪种数据库)。databaseType属性支持这些值:h2, mysql, oracle, postgres, mssql, db2,没有指定值时,databaseType为null。指定databaseType属性,目的是为了确定执行创建(或删除)表的SQL脚本。

        代码清单4-14,将该属性设置为oracle。

        代码清单4-14:codes/04/4.2/db-type/resource/database-type.xml

	<!-- 将databaseType设置为oracle -->
	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" />
		<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUsername" value="root" />
		<property name="jdbcPassword" value="123456" />
		<property name="databaseSchemaUpdate" value="create-drop"/>
		<property name="databaseType" value="oracle"/>
	</bean>

        使用以上配置,然后启动和关闭Activiti,会抛出MySQL异常,因为Activiti会根据该值去使用Oracle创建表和删除表的脚本,Oracle的SQL脚本在MySQL上面执行,肯定会出错。

        实际上,可以根本不需要指定该属性,Activiti就可以知道使用的是哪种数据库,因为配置数据源时就提供了JDBC连接属性给Activiti,根据这些属性创建JDBC连接,得到Connection对象后,可以调用getMetaData方法获取当前数据库的元数据,完全可以判断出当前所使用的数据库。的确,Activiti也是这样做的,但是为什么另外提供一个databaseType属性如此多此一举呢?笔者认为,Activiti为防止适配数据库类型出现异常,就提供多一个这样的值来给使用者选择,确保能适配到准确的数据库类型。

        注:没有配置databaseType属性,Activiti会使用Connection的getMetaData方法获取数据库元数据,但是一旦配置了databaseType属性,将会以该值为准。

        本文节选自《疯狂Workflow讲义(第2版)》。

该文章由WP-AutoPost插件自动采集发布

原文地址:https://my.oschina.net/JavaLaw/blog/1545227

转载请注明:喜乐从心,不应由人 » 疯狂Activiti6.0连载(四)——流程引擎数据源配置

喜欢 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址