注冊用戶即可下載全站資源 關注Java幫幫微信公眾號
 

atomikos JTA/XA全局事務

9
發表時間:2018-12-11 15:17

FDDADEB1-C462-492D-B243-0BCEC7AB66B1.png

日本韩国三级aⅴ在线观看    Atomikos公司官方網址為:。其旗下最著名的產品就是事務管理器。產品分兩個版本:

TransactionEssentials:開源的免費產品

日本韩国三级aⅴ在线观看ExtremeTransactions:上商業版,需要收費。

日本韩国三级aⅴ在线观看這兩個產品的關系如下圖所示:

CF404B96-1F5C-4969-9C7F-21A83C4C54FA.png

TransactionEssentials:

1、實現了JTA/XA規范中的事務管理器(Transaction Manager)應該實現的相關接口,如:

   UserTransaction實現是com.atomikos.icatch.jta.UserTransactionImp,用戶只需要直接操作這個類

   TransactionManager實現是com.atomikos.icatch.jta.UserTransactionManager

   Transaction實現是com.atomikos.icatch.jta.TransactionImp

2、針對實現了JDBC規范中規定的實現了XADataSource接口的數據庫連接池,以及實現了JMS規范的MQ客戶端提供一層封裝。

    在上一節我們講解JTA規范時,提到過XADataSource、XAConnection等接口應該由資源管理器RM來實現,而Atomikos的作用是一個事務管理器(TM),并不需要提供對應的實現。而Atomikos對XADataSource進行封裝,只是為了方便與事務管理器整合。封裝XADataSource的實現類為AtomikosDataSourceBean。典型的XADataSource實現包括:

   1、mysql官方提供的com.mysql.jdbc.jdbc2.optional.MysqlXADataSource

   2、阿里巴巴開源的druid連接池,對應的實現類為com.alibaba.druid.pool.xa.DruidXADataSource

   3、tomcat-jdbc連接池提供的org.apache.tomcat.jdbc.pool.XADataSource

   而其他一些常用的數據庫連接池,如dbcp、dbcp2或者c3p0,目前貌似尚未提供XADataSource接口的實現。如果提供給AtomikosDataSourceBean一個沒有實現XADataSource接口的數據源,如c3p0的ComboPooledDataSource,則會拋出類似以下異常:

  1. com.atomikos.jdbc.AtomikosSQLException: The class 'com.mchange.v2.c3p0.ComboPooledDataSource'

  2. specified by property 'xaDataSourceClassName' does not implement the required interface  

  3. javax.jdbc.XADataSource.  

  4. Please make sure the spelling is correct, and check your JDBC driver vendor's documentation.

 

ExtremeTransactions在TransactionEssentials的基礎上額外提供了以下功能:

支持TCC:這是一種柔性事務

支持通過RMI、IIOP、SOAP這些遠程過程調用技術,進行事務傳播。

本文主要針對Atomikos開源版本的事務管理器實現TransactionEssentials進行講解,包括:

1、直接使用TransactionEssentials的API

2、TransactionEssentials與spring、mybatis整合

3、Atomikos配置詳解


直接使用TransactionEssentials的API

在maven項目的pom文件中引入以下依賴:

  1. <dependency>

  2.    <groupId>com.atomikos</groupId>

  3.    <artifactId>transactions-jdbc</artifactId>

  4.    <version>4.0.6</version>

  5. </dependency>

  6. <dependency>

  7.    <groupId>mysql</groupId>

  8.    <artifactId>mysql-connector-java</artifactId>

  9.    <version>5.1.39</version>

  10. </dependency>

新建mysql數據庫表

需要注意的是,在mysql中,只有innodb引擎才支持XA事務,所以這里顯式的指定了數據庫引擎為innodb。

  1. -- 新建數據庫db_user;

  2. create database db_user;

  3. -- db_user庫中新建user

  4. create table db_user.user(id int AUTO_INCREMENT PRIMARY KEY,name varchar(50)) engine=innodb;

  5. -- 新建數據庫db_account;

  6. create database db_account;

  7. -- db_account庫中新建account

  8. create table db_account.account(user_id int,money double) engine=innodb;

另外,在本案例中,db_user庫和db_account庫是位于同一個mysql實例中的。

     

案例代碼:

   在使用了事務管理器之后,我們通過atomikos提供的UserTransaction接口的實現類com.atomikos.icatch.jta.UserTransactionImp來開啟、提交和回滾事務。而不再是使用java.sql.Connection中的setAutoCommit(false)的方式來開啟事務。其他JTA規范中定義的接口,開發人員并不需要直接使用。

  1. import com.atomikos.icatch.jta.UserTransactionImp;

  2. import com.atomikos.jdbc.AtomikosDataSourceBean;

  3. import javax.transaction.SystemException;

  4. import javax.transaction.UserTransaction;

  5. import java.sql.Connection;

  6. import java.sql.PreparedStatement;

  7. import java.sql.ResultSet;

  8. import java.sql.Statement;

  9. import java.util.Properties;

  10. public class AtomikosExample {

  11.   private static AtomikosDataSourceBean createAtomikosDataSourceBean(String dbName) {

  12.      // 連接池基本屬性

  13.      Properties p = new Properties();

  14.      p.setProperty("url", "jdbc:mysql://localhost:3306/" + dbName);

  15.      p.setProperty("user", "root");

  16.      p.setProperty("password", "your password");

  17.      // 使用AtomikosDataSourceBean封裝com.mysql.jdbc.jdbc2.optional.MysqlXADataSource

  18.      AtomikosDataSourceBean ds = new AtomikosDataSourceBean();

  19.      //atomikos要求為每個AtomikosDataSourceBean名稱,為了方便記憶,這里設置為和dbName相同

  20.      ds.setUniqueResourceName(dbName);

  21.      ds.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");

  22.      ds.setXaProperties(p);

  23.      return ds;

  24.   }

  25.   public static void main(String[] args) {

  26.      AtomikosDataSourceBean ds1 = createAtomikosDataSourceBean("db_user");

  27.      AtomikosDataSourceBean ds2 = createAtomikosDataSourceBean("db_account");

  28.      Connection conn1 = null;

  29.      Connection conn2 = null;

  30.      PreparedStatement ps1 = null;

  31.      PreparedStatement ps2 = null;

  32.      UserTransaction userTransaction = new UserTransactionImp();

  33.      try {

  34.         // 開啟事務

  35.         userTransaction.begin();

  36.         // 執行db1上的sql

  37.         conn1 = ds1.getConnection();

  38.         ps1 = conn1.prepareStatement("INSERT into user(name) VALUES (?)", Statement.RETURN_GENERATED_KEYS);

  39.         ps1.setString(1, "tianshouzhi");

  40.         ps1.executeUpdate();

  41.         ResultSet generatedKeys = ps1.getGeneratedKeys();

  42.         int userId = -1;

  43.         while (generatedKeys.next()) {

  44.            userId = generatedKeys.getInt(1);// 獲得自動生成的userId

  45.         }

  46.         // 模擬異常 ,直接進入catch代碼塊,2個都不會提交

  47. //        int i=1/0;

  48.         // 執行db2上的sql

  49.         conn2 = ds2.getConnection();

  50.         ps2 = conn2.prepareStatement("INSERT into account(user_id,money) VALUES (?,?)");

  51.         ps2.setInt(1, userId);

  52.         ps2.setDouble(2, 10000000);

  53.         ps2.executeUpdate();

  54.         // 兩階段提交

  55.         userTransaction.commit();

  56.      } catch (Exception e) {

  57.         try {

  58.            e.printStackTrace();

  59.            userTransaction.rollback();

  60.         } catch (SystemException e1) {

  61.            e1.printStackTrace();

  62.         }

  63.      } finally {

  64.         try {

  65.            ps1.close();

  66.            ps2.close();

  67.            conn1.close();

  68.            conn2.close();

  69.            ds1.close();

  70.            ds2.close();

  71.         } catch (Exception ignore) {

  72.         }

  73.      }

  74.   }

  75. }

2、TransactionEssentials與spring、mybatis整合

在pom中添加以下依賴

  1. <dependency>

  2.    <groupId>org.springframework</groupId>

  3.    <artifactId>spring-jdbc</artifactId>

  4.    <version>4.3.7.RELEASE</version>

  5. </dependency>

  6. <dependency>

  7.    <groupId>org.springframework</groupId>

  8.    <artifactId>spring-context</artifactId>

  9.    <version>4.3.7.RELEASE</version>

  10. </dependency>

  11. <dependency>

  12.    <groupId>org.mybatis</groupId>

  13.    <artifactId>mybatis</artifactId>

  14.    <version>3.4.1</version>

  15. </dependency>

  16. <dependency>

  17.    <groupId>org.mybatis</groupId>

  18.    <artifactId>mybatis-spring</artifactId>

  19.    <version>1.3.1</version>

  20. </dependency>

新建User實體

  1. package com.tianshouzhi.atomikos;

  2. public class User {

  3.   private int id;

  4.   private String name;

  5.   // setters and getters

  6. }

日本韩国三级aⅴ在线观看新建Account實例

  1. package com.tianshouzhi.atomikos;

  2. public class Account {

  3.   private int userId;

  4.   private double money;

  5.   // setters and getters

  6. }

新建UserMapper日本韩国三级aⅴ在线观看接口,為了方便,這里使用了mybatis 注解方式,沒有編寫映射文件,作用是一樣的

  1. package com.tianshouzhi.atomikos.mappers.db_user;

  2. import org.apache.ibatis.annotations.Insert;

  3. import com.tianshouzhi.atomikos.User;

  4. import org.apache.ibatis.annotations.Options;

  5. public interface UserMapper {

  6.   @Insert("INSERT INTO user(id,name) VALUES(#{id},#{name})")

  7.   @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")

  8.   public void insert(User user);

  9. }

新建AccountMapper接口

  1. package com.tianshouzhi.atomikos.mappers.ds_account;

  2. import com.tianshouzhi.atomikos.Account;

  3. import org.apache.ibatis.annotations.Insert;

  4. public interface AccountMapper {

  5.    @Insert("INSERT INTO account(user_id,money) VALUES(#{userId},#{money})")

  6.    public void insert(Account account);

  7. }

日本韩国三级aⅴ在线观看新建使用JTA事務的bean,注意在使用jta事務的時候,依然可以使用spring的聲明式事務管理

  1. package com.tianshouzhi.atomikos;

  2. import com.tianshouzhi.atomikos.mappers.db_user.UserMapper;

  3. import com.tianshouzhi.atomikos.mappers.ds_account.AccountMapper;

  4. import org.springframework.beans.factory.annotation.Autowired;

  5. import org.springframework.transaction.annotation.Transactional;

  6. public class JTAService {

  7.   @Autowired

  8.   private UserMapper userMapper;//操作db_user庫

  9.   @Autowired

  10.   private AccountMapper accountMapper;//操作db_account庫

  11.   @Transactional

  12.   public void insert() {

  13.      User user = new User();

  14.      user.setName("wangxiaoxiao");

  15.      userMapper.insert(user);

  16.      

  17.      //    int i = 1 / 0;//模擬異常,spring回滾后,db_user庫中user表中也不會插入記錄

  18.      Account account = new Account();

  19.      account.setUserId(user.getId());

  20.      account.setMoney(123456789);

  21.      accountMapper.insert(account);

  22.   }

  23. }

編寫配置文件spring-atomikos.xml

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"

  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

  5.       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

  6.    <!--==========針對兩個庫,各配置一個AtomikosDataSourceBean,底層都使用MysqlXADataSource=====================-->

  7.    <!--配置數據源db_user-->

  8.    <bean id="db_user" class="com.atomikos.jdbc.AtomikosDataSourceBean"

  9.          init-method="init" destroy-method="close">

  10.        <property name="uniqueResourceName" value="ds1" />

  11.        <property name="xaDataSourceClassName"

  12.                  value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />

  13.        <property name="xaProperties">

  14.            <props>

  15.                <prop key="url">jdbc:mysql://localhost:3306/db_user</prop>

  16.                <prop key="user">root</prop>

  17.                <prop key="password">shxx12151022</prop>

  18.            </props>

  19.        </property>

  20.    </bean>

  21.    <!--配置數據源db_account-->

  22.    <bean id="db_account" class="com.atomikos.jdbc.AtomikosDataSourceBean"

  23.          init-method="init" destroy-method="close">

  24.        <property name="uniqueResourceName" value="ds2" />

  25.        <property name="xaDataSourceClassName"

  26.                  value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />

  27.        <property name="xaProperties">

  28.            <props>

  29.                <prop key="url">jdbc:mysql://localhost:3306/db_account</prop>

  30.                <prop key="user">root</prop>

  31.                <prop key="password">shxx12151022</prop>

  32.            </props>

  33.        </property>

  34.    </bean>

  35.    <!--=============針對兩個數據源,各配置一個SqlSessionFactoryBean============ -->

  36.    <bean id="ssf_user" class="org.mybatis.spring.SqlSessionFactoryBean">

  37.        <property name="dataSource" ref="db_user" />

  38.    </bean>

  39.    <bean id="ssf_account" class="org.mybatis.spring.SqlSessionFactoryBean">

  40.        <property name="dataSource" ref="db_account" />

  41.    </bean>

  42.    <!--=============針對兩個SqlSessionFactoryBean,各配置一個MapperScannerConfigurer============ -->

  43.    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  44.        <property name="sqlSessionFactoryBeanName" value="ssf_user"/>

  45.        <!--指定com.tianshouzhi.atomikos.mappers.db_user包下的UserMapper接口使用ssf_user獲取底層數據庫連接-->

  46.        <property name="basePackage" value="com.tianshouzhi.atomikos.mappers.db_user"/>

  47.    </bean>

  48.    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  49.        <property name="sqlSessionFactoryBeanName" value="ssf_account"/>

  50.        <!--指定com.tianshouzhi.atomikos.mappers.ds_account包下的AccountMapper接口使用ssf_account獲取底層數據庫連接-->

  51.        <property name="basePackage" value="com.tianshouzhi.atomikos.mappers.ds_account"/>

  52.    </bean>

  53.    <!--================配置atomikos事務管理器========================-->

  54.    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"

  55.          destroy-method="close">

  56.        <property name="forceShutdown" value="false"/>

  57.    </bean>

  58.    <!--============配置spring的JtaTransactionManager,底層委派給atomikos進行處理===============-->

  59.    <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">

  60.        <property name="transactionManager" ref="atomikosTransactionManager"/>

  61.    </bean>

  62.    <!--配置spring聲明式事務管理器-->

  63.    <tx:annotation-driven transaction-manager="jtaTransactionManager"/>

  64.    <bean id="jtaService" class="com.tianshouzhi.atomikos.JTAService"/>

  65. </beans>

測試代碼

  1. package com.tianshouzhi.atomikos;

  2. import org.springframework.context.ApplicationContext;

  3. import org.springframework.context.support.ClassPathXmlApplicationContext;

  4. public class AtomikosSpringMybatisExample {

  5.   public static void main(String[] args) {

  6.      ApplicationContext context = new ClassPathXmlApplicationContext("spring-atomikos.xml");

  7.      JTAService jtaService = context.getBean("jtaService", JTAService.class);

  8.      jtaService.insert();

  9.   }

  10. }

   建議讀者先直接按照上述代碼運行,以確定代碼執行后,db_user庫的user表和db_account的account表中的確各插入了一條記錄,以證明我們的代碼的確是操作了2個庫,屬于分布式事務。

   然后將JTAService中的異常模擬的注釋打開,會發現出現異常后,兩個庫中都沒有新插入的數據庫,說明我們使用的JTA事務管理器的確保證數據的一致性了。

Atomikos配置

   在掌握了Atomikos基本使用之后,我們對Atomikos的配置進行一下簡單的介紹。Atomikos在啟動后,默認會從以下幾個位置讀取配置文件,這里筆者直接貼出atomikos源碼進行說明:

日本韩国三级aⅴ在线观看com.atomikos.icatch.provider.imp.AssemblerImp#initializeProperties方法中定義了配置加載順序邏輯:

  1. @Override

  2.    public ConfigProperties initializeProperties() {

  3.        //讀取classpath下的默認配置transactions-defaults.properties

  4.        Properties defaults = new Properties();

  5.        loadPropertiesFromClasspath(defaults, DEFAULT_PROPERTIES_FILE_NAME);

  6.        //讀取classpath下,transactions.properties配置,覆蓋transactions-defaults.properties中相同key的值

  7.        Properties transactionsProperties = new Properties(defaults);

  8.        loadPropertiesFromClasspath(transactionsProperties, TRANSACTIONS_PROPERTIES_FILE_NAME);

  9.        //讀取classpath下,jta.properties,覆蓋transactions-defaults.properties、transactions.properties中相同key的值

  10.        Properties jtaProperties = new Properties(transactionsProperties);

  11.        loadPropertiesFromClasspath(jtaProperties, JTA_PROPERTIES_FILE_NAME);

  12.        

  13.        //讀取通過java -Dcom.atomikos.icatch.file方式指定的自定義配置文件路徑,覆蓋之前的同名配置

  14.        Properties customProperties = new Properties(jtaProperties);

  15.        loadPropertiesFromCustomFilePath(customProperties);

  16.        //最終構造一個ConfigProperties對象,來表示實際要使用的配置

  17.        Properties finalProperties = new Properties(customProperties);

  18.        return new ConfigProperties(finalProperties);

  19.    }

日本韩国三级aⅴ在线观看配置文件優先級:transactions-defaults.properties<transactions.properties<jta.properties<自定義配置文件路徑,后面的配置會覆蓋之前同名key的配置。

其中transactions-defaults.properties是atomikos自帶的默認配置,位于transactions-xxx.jar中.

5B4E3D0B-5CE5-4177-9CAD-C351ED3E897A.png

日本韩国三级aⅴ在线观看注意不同版本的默認配置可能不同。特別是3.x版本和4.x版本的差異比較明顯。  

以下是4.0.6中 transactions-default.properties中配置內容,筆者對這些配置進行了歸類,如下:

  1. ===============================================================

  2. ============          事務管理器(TM)配置參數       ==============

  3. ===============================================================

  4. #指定是否啟動磁盤日志,默認為true。在生產環境下一定要保證為true,否則數據的完整性無法保證

  5. com.atomikos.icatch.enable_logging=true

  6. #JTA/XA資源是否應該自動注冊

  7. com.atomikos.icatch.automatic_resource_registration=true

  8. #JTA事務的默認超時時間,默認為10000ms

  9. com.atomikos.icatch.default_jta_timeout=10000

  10. #事務的最大超時時間,默認為300000ms。這表示事務超時時間由 UserTransaction.setTransactionTimeout()較大者決定。4.x版本之后,指定為0的話則表示不設置超時時間

  11. com.atomikos.icatch.max_timeout=300000

  12. #指定在兩階段提交時,是否使用不同的線程(意味著并行)。3.7版本之后默認為false,更早的版本默認為true。如果為false,則提交將按照事務中訪問資源的順序進行。

  13. com.atomikos.icatch.threaded_2pc=false

  14. #指定最多可以同時運行的事務數量,默認值為50,負數表示沒有數量限制。在調用 UserTransaction.begin()方法時,可能會拋出一個”Max number of active transactions reached”異常信息,表示超出最大事務數限制

  15. com.atomikos.icatch.max_actives=50

  16. #是否支持subtransaction,默認為true

  17. com.atomikos.icatch.allow_subtransactions=true

  18. #指定在可能的情況下,否應該join 子事務(subtransactions),默認值為true。如果設置為false,對于有關聯的不同subtransactions,不會調用XAResource.start(TM_JOIN)

  19. com.atomikos.icatch.serial_jta_transactions=true

  20. #指定JVM關閉時是否強制(force)關閉事務管理器,默認為false

  21. com.atomikos.icatch.force_shutdown_on_vm_exit=false

  22. #在正常關閉(no-force)的情況下,應該等待事務執行完成的時間,默認為Long.MAX_VALUE

  23. com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807

  24. ===============================================================

  25. =========        事務日志(Transaction logs)記錄配置       =======

  26. ===============================================================

  27. #事務日志目錄,默認為./。

  28. com.atomikos.icatch.log_base_dir=./

  29. #事務日志文件前綴,默認為tmlog。事務日志存儲在文件中,文件名包含一個數字后綴,日志文件以.log為擴展名,如tmlog1.log。遇到checkpoint時,新的事務日志文件會被創建,數字增加。

  30. com.atomikos.icatch.log_base_name=tmlog

  31. #指定兩次checkpoint的時間間隔,默認為500

  32. com.atomikos.icatch.checkpoint_interval=500

  33. ===============================================================

  34. =========          事務日志恢復(Recovery)配置       =============

  35. ===============================================================

  36. #指定在多長時間后可以清空無法恢復的事務日志(orphaned),默認86400000ms

  37. com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000

  38. #指定兩次恢復掃描之間的延遲時間。默認值為與com.atomikos.icatch.default_jta_timeout相同

  39. com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}

  40. #提交失敗時,再拋出一個異常之前,最多可以重試幾次,默認值為5

  41. com.atomikos.icatch.oltp_max_retries=5

  42. #提交失敗時,每次重試的時間間隔,默認10000ms

  43. com.atomikos.icatch.oltp_retry_interval=10000

  44. ===============================================================

  45. =========          其他       =============================== ==

  46. ===============================================================

  47. java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory

  48. com.atomikos.icatch.client_demarcation=false

  49. java.naming.provider.url=rmi://localhost:1099

  50. com.atomikos.icatch.rmi_export_class=none

  51. com.atomikos.icatch.trust_client_tm=false

當我們想對默認的配置進行修改時,可以在classpath下新建一個jta.properties,覆蓋同名的配置項即可。

日本韩国三级aⅴ在线观看關于不同版本配置的差異,請參考官方文檔:http://www.atomikos.com/Documentation/JtaProperties

打印日志

4.x版本之后,優先嘗試使用slf4j,如果沒有則嘗試使用log4j,如果二者都沒有,則使用JUL。

參見:http://www.atomikos.com/Documentation/ConfiguringTheLogs

注意這里是說的是如何配置打印工作日志(work log),而前面說的是事務日志(transactions log),二者不是不同的。


文章分類: 分布式事物
分享到:
支付寶贊助-Java幫幫社區
微信贊助-Java幫幫社區
Java幫幫公眾號生態

Java幫幫公眾號生態

總有一款適合你

Java幫幫-微信公眾號

Java幫幫-微信公眾號

將分享做到極致

Python幫幫-公眾號

Python幫幫-公眾號

人工智能,爬蟲,學習教程

大數據驛站-微信公眾號

大數據驛站-微信公眾號

一起在數據中成長

九點編程-公眾號

九點編程-公眾號

深夜九點學編程

程序員生活志-公眾號

程序員生活志-公眾號

互聯網,職場,程序員那些事兒

Java幫幫學習群生態

Java幫幫學習群生態

總有一款能幫到你

Java學習群

Java學習群

與大牛一起交流

大數據學習群

大數據學習群

在數據中成長

九點編程學習群

九點編程學習群

深夜九點學編程

python學習群

python學習群

人工智能,爬蟲

測試學習群

測試學習群

感受測試的魅力

Java幫幫生態承諾

Java幫幫生態承諾

一直堅守,不負重望

初心
勤儉
誠信
正義
分享
合作品牌 非盈利生態-優質內容分享傳播者
關于我們
友鏈申請
友鏈交換:加幫主QQ2524138991 留言即可 24小時內答復  
全站內容非商業用途,內容來源于網友,并遵循 許可,如有異議請聯系客服。
會員登錄
獲取驗證碼
登錄
登錄
我的資料
留言
回到頂部