最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成Flink-CDC實現(xiàn)對數(shù)據(jù)庫數(shù)據(jù)的監(jiān)聽問題

 更新時間:2024年07月26日 11:53:09   作者:手握抽象  
Flink CDC(Flink Change Data Capture)是一種基于數(shù)據(jù)庫日志的CDC技術(shù),它實現(xiàn)了一個全增量一體化的數(shù)據(jù)集成框架,這篇文章主要介紹了SpringBoot集成Flink-CDC,實現(xiàn)對數(shù)據(jù)庫數(shù)據(jù)的監(jiān)聽,需要的朋友可以參考下

一、什么是 CDC ?

  CDC 是 Change Data Capture(變更數(shù)據(jù)獲?。?nbsp;的簡稱。 核心思想是,監(jiān)測并捕獲數(shù)據(jù)庫的變動(包括數(shù)據(jù)或數(shù)據(jù)表的插入、 更新以及刪除等),將這些變更按發(fā)生的順序完整記錄下來,寫入到消息中間件中以供其他服務(wù)進行訂閱及消費。

二、Flink-CDC 是什么?

CDC Connectors for Apache Flink是一組用于Apache Flink 的源連接器,使用變更數(shù)據(jù)捕獲 (CDC) 從不同數(shù)據(jù)庫獲取變更。用于 Apache Flink 的 CDC 連接器將 Debezium 集成為捕獲數(shù)據(jù)更改的引擎。所以它可以充分發(fā)揮 Debezium 的能力。

大概意思就是,F(xiàn)link 社區(qū)開發(fā)了 flink-cdc-connectors 組件,這是一個可以直接從 MySQL、 PostgreSQL等數(shù)據(jù)庫直接讀取全量數(shù)據(jù)和增量變更數(shù)據(jù)的 source 組件。

Flink-CDC 開源地址: Apache/Flink-CDC

Flink-CDC 中文文檔:Apache Flink CDC | Apache Flink CDC

三、SpringBoot 整合 Flink-CDC

3.1、如何集成到SpringBoot中?

Flink 作業(yè)通常獨立于一般的服務(wù)之外,專門編寫代碼,用 Flink 命令行工具來運行和停止。將Flink 作業(yè)集成到 Spring Boot 應(yīng)用中并不常見,而且一般也不建議這樣做,因為Flink作業(yè)一般運行在大數(shù)據(jù)環(huán)境中。

然而,在特殊需求下,我們可以做一些改變使 Flink 應(yīng)用適應(yīng) Spring Boot 環(huán)境,比如在你的場景中使用 Flink CDC 進行 數(shù)據(jù)變更捕獲。將 Flink 作業(yè)以本地項目的方式啟動,集成在 Spring Boot應(yīng)用中,可以使用到 Spring 的便利性。

  • CommandLineRunner
  • ApplicationRunner

3.2、集成舉例

1、CommandLineRunner

@SpringBootApplication
public class MyApp {
  public static void main(String[] args) {
    SpringApplication.run(MyApp.class, args);
  }
  @Bean
  public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
    return args -> {
      StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
      DebeziumSourceFunction<String> sourceFunction = MySqlSource.<String>builder()
              .hostname("localhost")
              .port(3306)
              .username("flinkuser")
              .password("flinkpw")
              .databaseList("mydb") // monitor all tables under "mydb" database
              .tableList("mydb.table1", "mydb.table2") // monitor only "table1" and "table2" under "mydb" database
              .deserializer(new StringDebeziumDeserializationSchema()) // converts SourceRecord to String
              .build();
      DataStreamSource<String> mysqlSource = env.addSource(sourceFunction);
      // formulate processing logic here, e.g., printing to standard output
      mysqlSource.print();
      // execute the Flink job within the Spring Boot application
      env.execute("Flink CDC");
    };
  }
}

2、ApplicationRunner

@SpringBootApplication
public class FlinkCDCApplication implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(FlinkCDCApplication.class, args);
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        // Configure your Flink job here
        DebeziumSourceFunction<String> sourceFunction = MySqlSource.<String>builder()
                .hostname("localhost")
                .port(3306)
                .username("flinkuser")
                .password("flinkpw")
                .databaseList("mydb")
                // set other source options ...
                .deserializer(new StringDebeziumDeserializationSchema()) // Converts SourceRecord to String
                .build();
        DataStream<String> cdcStream = env.addSource(sourceFunction);
        // Implement your processing logic here
        // For example:
        cdcStream.print();
        // Start the Flink job within the Spring Boot application
        env.execute("Flink CDC with Spring Boot");
    }
}

這次用例采用 ApplicationRunner,不過要改變一下,讓 Flink CDC 作為 Bean 來實現(xiàn)。

四、功能實現(xiàn)

4.1、功能邏輯

總體來講,不太想把 Flink CDC單獨拉出來,更想讓它依托于一個服務(wù)上,徹底當成一個組件。

其中在生產(chǎn)者中,我們將要進行實現(xiàn):

4.2、所需環(huán)境

  • MySQL 5.7 +:確保源數(shù)據(jù)庫已經(jīng)開啟  Binlog 日志功能,并且設(shè)置 Row 格式
  • Spring Boot 2.7.6:還是不要輕易使用 3.0 以上為好,有好多jar沒有適配
  • RabbitMQ:適配即可
  • Flink CDC:特別注意版本

4.3、Flink CDC POM依賴

<flink.version>1.13.6</flink.version>
<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-clients_2.12</artifactId>
   <version>${flink.version}</version>
</dependency>
   <dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-java</artifactId>
   <version>${flink.version}</version>
</dependency>
<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-streaming-java_2.12</artifactId>
   <version>${flink.version}</version>
</dependency>
<!--mysql -cdc-->
<dependency>
   <groupId>com.ververica</groupId>
   <artifactId>flink-connector-mysql-cdc</artifactId>
   <version>2.0.0</version>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.10</version>
</dependency>
<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.8.5</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>2.0.42</version>
</dependency>

上面是一些Flink CDC必須的依賴,當然如果需要實現(xiàn)其他數(shù)據(jù)庫,可以替換其他數(shù)據(jù)庫的CDC jar。怎么安排jar包的位置和其余需要的jar,這個可自行調(diào)整。

4.4、代碼展示

核心類

  • MysqlEventListener:配置類
  • MysqlDeserialization:MySQL消息讀取自定義序列化
  • DataChangeInfo:封裝的變更對象
  • DataChangeSink:繼承一個Flink提供的抽象類,用于定義數(shù)據(jù)的輸出或“下沉”邏輯,sink 是Flink處理流的最后階段,通常用于將數(shù)據(jù)寫入外部系統(tǒng),如數(shù)據(jù)庫、文件系統(tǒng)、消息隊列等

(1)通過 ApplicationRunner 接入 SpringBoot

@Component
public class MysqlEventListener implements ApplicationRunner {
    private final DataChangeSink dataChangeSink;
    public MysqlEventListener(DataChangeSink dataChangeSink) {
        this.dataChangeSink = dataChangeSink;
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DebeziumSourceFunction<DataChangeInfo> dataChangeInfoMySqlSource = buildDataChangeSourceRemote();
        DataStream<DataChangeInfo> streamSource = env
                .addSource(dataChangeInfoMySqlSource, "mysql-source")
                .setParallelism(1);
        streamSource.addSink(dataChangeSink);
        env.execute("mysql-stream-cdc");
    }
    private DebeziumSourceFunction<DataChangeInfo> buildDataChangeSourceLocal() {
        return MySqlSource.<DataChangeInfo>builder()
                .hostname("127.0.0.1")
                .port(3306)
                .username("root")
                .password("0507")
                .databaseList("flink-cdc-producer")
                .tableList("flink-cdc-producer.producer_content", "flink-cdc-producer.name_content")
                /*
                 * initial初始化快照,即全量導(dǎo)入后增量導(dǎo)入(檢測更新數(shù)據(jù)寫入)
                 * latest:只進行增量導(dǎo)入(不讀取歷史變化)
                 * timestamp:指定時間戳進行數(shù)據(jù)導(dǎo)入(大于等于指定時間錯讀取數(shù)據(jù))
                 */
                .startupOptions(StartupOptions.latest())
                .deserializer(new MysqlDeserialization())
                .serverTimeZone("GMT+8")
                .build();
    }
}

(2)自定義 MySQL 消息讀取序列化

public class MysqlDeserialization implements DebeziumDeserializationSchema<DataChangeInfo> {
    public static final String TS_MS = "ts_ms";
    public static final String BIN_FILE = "file";
    public static final String POS = "pos";
    public static final String CREATE = "CREATE";
    public static final String BEFORE = "before";
    public static final String AFTER = "after";
    public static final String SOURCE = "source";
    public static final String UPDATE = "UPDATE";
    /**
     * 反序列化數(shù)據(jù),轉(zhuǎn)為變更JSON對象
     */
    @Override
    public void deserialize(SourceRecord sourceRecord, Collector<DataChangeInfo> collector) {
        String topic = sourceRecord.topic();
        String[] fields = topic.split("\\.");
        String database = fields[1];
        String tableName = fields[2];
        Struct struct = (Struct) sourceRecord.value();
        final Struct source = struct.getStruct(SOURCE);
        DataChangeInfo dataChangeInfo = new DataChangeInfo();
        dataChangeInfo.setBeforeData(getJsonObject(struct, BEFORE).toJSONString());
        dataChangeInfo.setAfterData(getJsonObject(struct, AFTER).toJSONString());
        //5.獲取操作類型  CREATE UPDATE DELETE
        Envelope.Operation operation = Envelope.operationFor(sourceRecord);
//        String type = operation.toString().toUpperCase();
//        int eventType = type.equals(CREATE) ? 1 : UPDATE.equals(type) ? 2 : 3;
        dataChangeInfo.setEventType(operation.name());
        dataChangeInfo.setFileName(Optional.ofNullable(source.get(BIN_FILE)).map(Object::toString).orElse(""));
        dataChangeInfo.setFilePos(Optional.ofNullable(source.get(POS)).map(x -> Integer.parseInt(x.toString())).orElse(0));
        dataChangeInfo.setDatabase(database);
        dataChangeInfo.setTableName(tableName);
        dataChangeInfo.setChangeTime(Optional.ofNullable(struct.get(TS_MS)).map(x -> Long.parseLong(x.toString())).orElseGet(System::currentTimeMillis));
        //7.輸出數(shù)據(jù)
        collector.collect(dataChangeInfo);
    }
    private Struct getStruct(Struct value, String fieldElement) {
        return value.getStruct(fieldElement);
    }
    /**
     * 從元數(shù)據(jù)獲取出變更之前或之后的數(shù)據(jù)
     */
    private JSONObject getJsonObject(Struct value, String fieldElement) {
        Struct element = value.getStruct(fieldElement);
        JSONObject jsonObject = new JSONObject();
        if (element != null) {
            Schema afterSchema = element.schema();
            List<Field> fieldList = afterSchema.fields();
            for (Field field : fieldList) {
                Object afterValue = element.get(field);
                jsonObject.put(field.name(), afterValue);
            }
        }
        return jsonObject;
    }
    @Override
    public TypeInformation<DataChangeInfo> getProducedType() {
        return TypeInformation.of(DataChangeInfo.class);
    }
}

(3)封裝的變更對象

@Data
public class DataChangeInfo implements Serializable {
    /**
     * 變更前數(shù)據(jù)
     */
    private String beforeData;
    /**
     * 變更后數(shù)據(jù)
     */
    private String afterData;
    /**
     * 變更類型 1新增 2修改 3刪除
     */
    private String eventType;
    /**
     * binlog文件名
     */
    private String fileName;
    /**
     * binlog當前讀取點位
     */
    private Integer filePos;
    /**
     * 數(shù)據(jù)庫名
     */
    private String database;
    /**
     * 表名
     */
    private String tableName;
    /**
     * 變更時間
     */
    private Long changeTime;
}

這里的 beforeData 、afterData直接存儲 Struct 不好嗎,還得費勁去來回轉(zhuǎn)?

我曾嘗試過使用 Struct 存放在對象中,但是無法進行序列化。具體原因可以網(wǎng)上搜索,或者自己嘗試一下。

(4)定義 Flink 的 Sink

@Component
@Slf4j
public class DataChangeSink extends RichSinkFunction<DataChangeInfo> {
    transient RabbitTemplate rabbitTemplate;
    transient ConfirmService confirmService;
    transient TableDataConvertService tableDataConvertService;
    @Override
    public void invoke(DataChangeInfo value, Context context) {
        log.info("收到變更原始數(shù)據(jù):{}", value);
        //轉(zhuǎn)換后發(fā)送到對應(yīng)的MQ
        if (MIGRATION_TABLE_CACHE.containsKey(value.getTableName())) {
            String routingKey = MIGRATION_TABLE_CACHE.get(value.getTableName());
            //可根據(jù)需要自行進行confirmService的設(shè)計
            rabbitTemplate.setReturnsCallback(confirmService);
            rabbitTemplate.setConfirmCallback(confirmService);
            rabbitTemplate.convertAndSend(EXCHANGE_NAME, routingKey, tableDataConvertService.convertSqlByDataChangeInfo(value));
        }
    }
    /**
     * 在啟動SpringBoot項目是加載了Spring容器,其他地方可以使用@Autowired獲取Spring容器中的類;但是Flink啟動的項目中,
     * 默認啟動了多線程執(zhí)行相關(guān)代碼,導(dǎo)致在其他線程無法獲取Spring容器,只有在Spring所在的線程才能使用@Autowired,
     * 故在Flink自定義的Sink的open()方法中初始化Spring容器
     */
    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        this.rabbitTemplate = ApplicationContextUtil.getBean(RabbitTemplate.class);
        this.confirmService = ApplicationContextUtil.getBean(ConfirmService.class);
        this.tableDataConvertService = ApplicationContextUtil.getBean(TableDataConvertService.class);
    }
}

(5)數(shù)據(jù)轉(zhuǎn)換類接口和實現(xiàn)類

public interface TableDataConvertService {
    String convertSqlByDataChangeInfo(DataChangeInfo dataChangeInfo);
}
@Service
public class TableDataConvertServiceImpl implements TableDataConvertService {
    @Autowired
    Map<String, SqlGeneratorService> sqlGeneratorServiceMap;
    @Override
    public String convertSqlByDataChangeInfo(DataChangeInfo dataChangeInfo) {
        SqlGeneratorService sqlGeneratorService = sqlGeneratorServiceMap.get(dataChangeInfo.getEventType());
        return sqlGeneratorService.generatorSql(dataChangeInfo);
    }
}

因為在 dataChangeInfo 中我們有封裝對象的類型(CREATE、DELETEUPDATE),所以我希望通過不同類來進行不同的工作。于是就有了下面的類結(jié)構(gòu):

根據(jù) dataChangeInfo 的類型去生成對應(yīng)的 SqlGeneratorServiceImpl

這是策略模式還是模板方法?

策略模式(Strategy Pattern)允許在運行時選擇算法的行為。在策略模式中,定義了一系列的算法(策略),并將每一個算法封裝起來,使它們可以相互替換。策略模式允許算法獨立于使用它的客戶端進行變化。

InsertSqlGeneratorServiceImpl、UpdateSqlGeneratorServiceImpl 和 DeleteSqlGeneratorServiceImpl 各自實現(xiàn)了 SqlGeneratorService 接口,這確實表明了一種策略。每一個實現(xiàn)類表示一個特定的SQL生成策略,并且可以相互替換,只要它們遵守同一個接口。

模板方法模式(Template Method Pattern),則側(cè)重于在抽象類中定義算法的框架,讓子類實現(xiàn)算法的某些步驟而不改變算法的結(jié)構(gòu)。AbstractSqlGenerator 作為抽象類的存在是為了被繼承,但如果它不含有模板方法(即沒有定義算法骨架的方法),那它就不符合模板方法模式。

在實際應(yīng)用中,一個設(shè)計可能同時結(jié)合了多個設(shè)計模式,或者在某些情況下,一種設(shè)計模式的實現(xiàn)可能看起來與另一種模式類似。在這種情況下,若 AbstractSqlGenerator 提供了更多的共享代碼或默認實現(xiàn)表現(xiàn)出框架角色,那么它可能更接近模板方法。而如果 AbstractSqlGenerator 僅僅作為一種接口實現(xiàn)方式,且策略之間可以相互替換,那么這確實更符合策略模式。

值得注意的是,在 TableDataConvertServiceImpl 中,我們注入了一個 Map<String, SqlGeneratorService> sqlGeneratorServiceMap,通過它來進行具體實現(xiàn)類的獲取。那么他是個什么東西呢?作用是什么呢?為什么可以通過它來獲取呢?

@Resource、@Autowired 標注作用于 Map 類型時,如果 Map 的 key 為 String 類型,則 Spring 會將容器中所有類型符合 Map 的 value 對應(yīng)的類型的 Bean 增加進來,用 Bean 的 id 或 name 作為 Map 的 key。

那么可以看到下面第六步,在進行DeleteSqlGeneratorServiceImpl裝配的時候進行指定了名字@Service("DELETE"),方便通過dataChangeInfo獲取。

(6)轉(zhuǎn)換類部分代碼

public interface SqlGeneratorService {
    String generatorSql(DataChangeInfo dataChangeInfo);
}
public abstract class AbstractSqlGenerator implements SqlGeneratorService {
    @Override
    public String generatorSql(DataChangeInfo dataChangeInfo) {
        return null;
    }
     public String quoteIdentifier(String identifier) {
        // 對字段名進行轉(zhuǎn)義處理,這里簡化為對其加反引號
        // 實際應(yīng)該處理數(shù)據(jù)庫標識符的特殊字符
        return "`" + identifier + "`";
    }
}
@Service("DELETE")
@Slf4j
public class DeleteSqlGeneratorServiceImpl extends AbstractSqlGenerator {
    @Override
    public String generatorSql(DataChangeInfo dataChangeInfo) {
        String beforeData = dataChangeInfo.getBeforeData();
        Map<String, Object> beforeDataMap = JSONObjectUtils.JsonToMap(beforeData);
        StringBuilder wherePart = new StringBuilder();
        for (String key : beforeDataMap.keySet()) {
            Object beforeValue = beforeDataMap.get(key);
            if ("create_time".equals(key)){
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                beforeValue = dateFormat.format(beforeValue);
            }
            if (wherePart.length() > 0) {
                // 不是第一個更改的字段,增加逗號分隔
                wherePart.append(", ");
            }
            wherePart.append(quoteIdentifier(key)).append(" = ").append(formatValue(beforeValue));
        }
        log.info("wherePart : {}", wherePart);
        return "DELETE FROM " + dataChangeInfo.getTableName() + " WHERE " + wherePart;
    }
}

核心代碼如上所示,具體實現(xiàn)可自行設(shè)計。

五、源碼獲取

Github:incremental-sync-flink-cdc

到此這篇關(guān)于SpringBoot集成Flink-CDC,實現(xiàn)對數(shù)據(jù)庫數(shù)據(jù)的監(jiān)聽的文章就介紹到這了,更多相關(guān)SpringBoot集成Flink-CDC監(jiān)聽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 使用POI合并兩個word文檔

    java 使用POI合并兩個word文檔

    這篇文章主要介紹了java 使用POI合并兩個word文檔的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • spring中@Autowired自動注入依賴項的使用

    spring中@Autowired自動注入依賴項的使用

    當使用@Autowired注解時,它可以自動注入依賴項,例如其他類的實例,本文就來詳細的介紹一下,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • Java 仿天貓服裝商城系統(tǒng)的實現(xiàn)流程

    Java 仿天貓服裝商城系統(tǒng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學(xué)書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個仿天貓服裝商城系統(tǒng),大家可以在過程中查缺補漏,提升水平
    2021-11-11
  • 教你開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細過程

    教你開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細過程

    這篇文章主要介紹了開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細過程,集成包括引入依賴配置文件及訪問驗證的相關(guān)知識,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • Java 基于TCP Socket 實現(xiàn)文件上傳

    Java 基于TCP Socket 實現(xiàn)文件上傳

    這篇文章主要介紹了Java 基于TCP Socket 實現(xiàn)文件上傳的示例代碼,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-12-12
  • Java基于UDP協(xié)議實現(xiàn)簡單的聊天室程序

    Java基于UDP協(xié)議實現(xiàn)簡單的聊天室程序

    這篇文章主要為大家詳細介紹了Java基于UDP協(xié)議實現(xiàn)簡單的聊天室程序的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-07-07
  • Java 單例模式的實現(xiàn)資料整理

    Java 單例模式的實現(xiàn)資料整理

    這篇文章主要介紹了Java 單例模式的實現(xiàn)的相關(guān)資料,并附簡單實例代碼,需要的朋友可以參考下
    2016-10-10
  • Java實現(xiàn)多級表頭和復(fù)雜表頭的導(dǎo)出功能

    Java實現(xiàn)多級表頭和復(fù)雜表頭的導(dǎo)出功能

    這篇文章主要為大家詳細介紹了Java實現(xiàn)多級表頭和復(fù)雜表頭的導(dǎo)出功能的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03
  • java開發(fā)RocketMQ消息中間件原理基礎(chǔ)詳解

    java開發(fā)RocketMQ消息中間件原理基礎(chǔ)詳解

    最近 RocketMQ 剛剛上生產(chǎn)環(huán)境,閑暇之時在這里做一些分享,主要目的是讓初學(xué)者能快速上手RocketMQ,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • springboot application無法使用$獲取pom變量的問題及解決

    springboot application無法使用$獲取pom變量的問題及解決

    這篇文章主要介紹了springboot application無法使用$獲取pom變量的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02

最新評論

深水埗区| 锡林浩特市| 宁强县| 黑水县| 苍山县| 琼结县| 西青区| 荃湾区| 鄂托克前旗| 美姑县| 弥渡县| 渭源县| 长兴县| 长沙市| 安仁县| 聂拉木县| 深州市| 吉林省| 东阳市| 永嘉县| 汨罗市| 张家界市| 德钦县| 柞水县| 新邵县| 启东市| 密山市| 乌审旗| 时尚| 子长县| 营山县| 兖州市| 惠安县| 阳谷县| 乐亭县| 山西省| 共和县| 陇西县| 长兴县| 永仁县| 如皋市|