成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

Node.js + React Native 畢設(shè):農(nóng)業(yè)物聯(lián)網(wǎng)監(jiān)測(cè)系統(tǒng)的開(kāi)發(fā)手記

renweihub / 861人閱讀

摘要:所幸,鄙人所在的硬件專業(yè),指導(dǎo)老師并不懂軟件,他只是想要一個(gè)農(nóng)業(yè)物聯(lián)網(wǎng)的監(jiān)測(cè)系統(tǒng),能提供給我的就是一個(gè)數(shù)據(jù)庫(kù),帶著一個(gè)物聯(lián)網(wǎng)系統(tǒng)運(yùn)行一年所保存的傳感器數(shù)據(jù)。該物聯(lián)網(wǎng)監(jiān)測(cè)系統(tǒng)整體上可分為三層數(shù)據(jù)庫(kù)層,服務(wù)器層和客戶端層。的執(zhí)行是同步的。

畢設(shè)大概是大學(xué)四年里最坑爹之一的事情了,畢竟一旦選題不好,就很容易浪費(fèi)一年的時(shí)間做一個(gè)并沒(méi)有什么卵用,又不能學(xué)到什么東西的雞肋項(xiàng)目。所幸,鄙人所在的硬件專業(yè),指導(dǎo)老師并不懂軟件,他只是想要一個(gè)農(nóng)業(yè)物聯(lián)網(wǎng)的監(jiān)測(cè)系統(tǒng),能提供給我的就是一個(gè)Oracle 11d數(shù)據(jù)庫(kù),帶著一個(gè)物聯(lián)網(wǎng)系統(tǒng)運(yùn)行一年所保存的傳感器數(shù)據(jù)...That"s all。然后,因?yàn)樗欢浖?,所以他顯然以結(jié)果為導(dǎo)向,只要我交出一個(gè)移動(dòng)客戶端和一個(gè)服務(wù)端,并不會(huì)關(guān)心我在其中用了多少坑爹的新技術(shù)。

那還說(shuō)什么?上!我以強(qiáng)烈的惡搞精神,決定采用業(yè)界最新最坑爹最有可能爛尾的技術(shù),組成一個(gè) Geek 大雜燴,幻想未來(lái)那個(gè)接手我工作的師兄的一臉懵逼,我露出了邪惡的笑容,一切只為了滿足自己的上新欲。

全部代碼在 GPL 許可證下開(kāi)源:

服務(wù)端代碼:https://github.com/CauT/the-wall

客戶端代碼:https://github.com/CauT/Night...

由于數(shù)據(jù)庫(kù)是學(xué)校實(shí)驗(yàn)室所有,所以不能放出數(shù)據(jù)以供運(yùn)行,萬(wàn)分抱歉~。理論上應(yīng)該有一份文檔,但事實(shí)上太懶,不知道什么時(shí)候會(huì)填坑~。

總體架構(gòu)

OK,上圖說(shuō)明技術(shù)框架。


?
該物聯(lián)網(wǎng)監(jiān)測(cè)系統(tǒng)整體上可分為三層:數(shù)據(jù)庫(kù)層,服務(wù)器層和客戶端層。

數(shù)據(jù)庫(kù)和代碼層

數(shù)據(jù)庫(kù)層除了原有的Oracle 11d數(shù)據(jù)庫(kù)以外,還額外增加了一個(gè)Redis數(shù)據(jù)庫(kù)。之所以增加第二個(gè)數(shù)據(jù)庫(kù),原因?yàn)椋?/p>

Node.js 的 Oracle 官方依賴 node-oracledb 沒(méi)有ORM,也就是說(shuō),所有的對(duì)數(shù)據(jù)庫(kù)的操作,都是直接執(zhí)行SQL語(yǔ)句,簡(jiǎn)單粗暴,我擔(dān)心自己孱弱的數(shù)據(jù)庫(kù)功底(本行是 Android 開(kāi)發(fā))會(huì)引發(fā)鎖表問(wèn)題,所以通過(guò)限制只讀來(lái)避開(kāi)這個(gè)問(wèn)題。

由于該系統(tǒng)服務(wù)于農(nóng)業(yè)企業(yè)的內(nèi)部管理人員,因此其賬號(hào)數(shù)量和總體數(shù)據(jù)量必然有限,因此使用 redis 這種內(nèi)存型數(shù)據(jù)庫(kù),可以不必考慮非關(guān)系型數(shù)據(jù)庫(kù)在容量占用上的劣勢(shì)。讀取速度反而較傳統(tǒng)的 SQL 數(shù)據(jù)庫(kù)有一定的優(yōu)勢(shì)。

使用非關(guān)系型數(shù)據(jù)庫(kù)比關(guān)系型數(shù)據(jù)庫(kù)好玩多了(霧

之所以寫(xiě)了右邊的Git部分,是因?yàn)樵敬蛩憷胐ocker技術(shù)搞一個(gè)持續(xù)集成和部署的程序,實(shí)現(xiàn)提交代碼=>自動(dòng)測(cè)試=>更新服務(wù)器部署更新=>客戶端自動(dòng)更新 這樣一整套持續(xù)交付的流程,然而最后并沒(méi)有時(shí)間寫(xiě)。

服務(wù)器層

服務(wù)器層,采用 Node.js 的 Express 框架作為客戶端的 API 后臺(tái)。因?yàn)?Node.js 的單線程異步并發(fā)結(jié)構(gòu)使之可以輕松實(shí)現(xiàn)較高的 QPS,所以非常適合 API 后端這一特點(diǎn)。其框架設(shè)計(jì)和主要功能如下圖所示:

像網(wǎng)關(guān)層:鑒權(quán)模塊這么裝逼的說(shuō)法,本質(zhì)也就是app.use(jwt({secret: config.jwt_secret}).unless({path: ["/signin"]}));一行而已。因?yàn)槭侵苯訌漠厴I(yè)論文里拿下來(lái)的圖,畢業(yè)論文都這尿性你們懂的,所以一些故弄玄虛敬請(qǐng)諒解。

客戶端層

?客戶端層絕大部分是 React Native 代碼,但是監(jiān)控?cái)?shù)據(jù)的圖表生成這一塊功能(如下圖),由于 React Native 目前沒(méi)有開(kāi)源的成熟實(shí)現(xiàn);試圖通過(guò) Native 代碼來(lái)畫(huà)圖表,需要實(shí)現(xiàn)一個(gè) Native 和 React Native 互相嵌套的架構(gòu),又面臨一些可能的困難;故而最終選擇了內(nèi)嵌一個(gè) html 頁(yè)面,前端代碼采用百度的 Echarts 框架來(lái)繪制圖表。最終的結(jié)構(gòu)就是大部分 React Native + 少部分 Html5 的客戶端結(jié)構(gòu)。

另外就是采用了 Redux 來(lái)統(tǒng)一應(yīng)用的事件分發(fā)和 UI 數(shù)據(jù)管理了。可以說(shuō),React Native 若能留名青史,Redux 必定是不可或缺的一大原因。這一點(diǎn)我們后文再述。

細(xì)節(jié)詳述 服務(wù)端層

服務(wù)端接口表:

?

服務(wù)端程序的編寫(xiě)過(guò)程中,往往涉及到了大量的異步操作,如數(shù)據(jù)庫(kù)讀取,網(wǎng)絡(luò)請(qǐng)求,JSON解析等等。而這些異步操作,又往往會(huì)因?yàn)榫唧w的業(yè)務(wù)場(chǎng)景的要求,而需要保持一定的執(zhí)行順序。此外,還需要保證代碼的可讀性,顯然此時(shí)一味嵌套回調(diào)函數(shù),只會(huì)使我們陷入代碼幾乎不可讀的回調(diào)地獄(Callback Hell)中。最后,由于JavaScript單線程的執(zhí)行環(huán)境的特性,我們還需要避免指定不必要的執(zhí)行順序,以免降低了程序的運(yùn)行性能。因此,我在項(xiàng)目中使用Promise模式來(lái)處理多異步的邏輯過(guò)程。如下代碼所示:

function renderGraph(req, res, filtereds) {
  var x = [];    
  var ys = [];
  var titles = [];

  filtereds[0].forEach(function(row) {
    x.push(getLocalTime(row.RECTIME));
  });

  filtereds.forEach(function(filtered){
    if (filtered[0] == undefined)
      // even if at least one of multi query was succeed
      // fast-fail is essential for secure
      throw new Error("數(shù)據(jù)庫(kù)返回結(jié)果為空");
    var y = [];
    filtered.forEach(function(row) {
      y.push(row.ANALOGYVALUE);
    });
    ys.push(y);
    titles.push(filtered[0].DEVICENAME + ": " + filtered[0].DEVICECODE);
  });

  res.render("graph", {
    titles: titles,
    dataX: x,
    dataY: ys,
    height: req.query.height == undefined ? 200 : req.query.height,
    width: req.query.width == undefined ? 300 : req.query.width,
  });
}

function resFilter(resolve, reject, connection, resultSet, numRows, filtered) {
  resultSet.getRows(
    numRows,
    function (err, rows)
    {
      if (err) {
        console.log(err.message);
        reject(err);
      } else if (rows.length == 0) {
        resolve(filtered);
        process.nextTick(function() {
          oracle.releaseConnection(connection);
        });
      } else if (rows.length > 0) {
        filtered.push(rows[0]);
        resFilter(resolve, reject, connection, resultSet, numRows, filtered);
      }
    }
  );
}

function createQuerySingleDeviceDataPromise(req, res, device_id, start_time, end_time) {
  return oracle.getConnection()
  .then(function(connection) {
    return oracle.execute(
      "SELECT
        DEVICE.DEVICEID,
        DEVICECODE,
        DEVICENAME,
        UNIT,
        ANALOGYVALUE,
        DEVICEHISTROY.RECTIME
      FROM
        DEVICE INNER JOIN DEVICEHISTROY
      ON
        DEVICE.DEVICEID = DEVICEHISTROY.DEVICEID
      WHERE
        DEVICE.DEVICEID = :device_id
        AND DEVICEHISTROY.RECTIME
        BETWEEN :start_time AND :end_time
      ORDER
        BY RECTIME",
      [
        device_id,
        start_time,
        end_time
      ],
      {
        outFormat: oracle.OBJECT,
        resultSet: true
      },
      connection
    )
    .then(function(results) {
      var filtered = [];
      var filterGap = Math.floor(
        (end_time - start_time) / (120 * 100)
      );
      return new Promise(function(resolve, reject) {
        resFilter(resolve, reject,
          connection, results.resultSet, filterGap, filtered);
      });
    })
    .catch(function(err) {
      res.status(500).json({
        status: "error",
        message: err.message
      });
      process.nextTick(function() {
        oracle.releaseConnection(connection);
      });
    });
  });
}

function secureCheck(req, res) {
  let qry = req.query;

  if (
    qry.device_ids == undefined
    || qry.start_time == undefined
    || qry.end_time == undefined
  ) {
    throw new Error("device_ids或start_time或end_time參數(shù)為undefined");
  }

  if (req.query.end_time < req.query.start_time) {
    throw new Error("終止時(shí)間小于起始時(shí)間");
  }
};

router.get("/", function(req, res, next) {
  try {
    var device_ids;
    var queryPromises = [];

    secureCheck(req, res);

    device_ids = req.query.device_ids.toString().split(";");

    for(let i=0; i

這是生成指定N個(gè)傳感器在一段時(shí)間內(nèi)的折線圖的邏輯。顯然,剖析業(yè)務(wù)可知,我們需要在數(shù)據(jù)庫(kù)中查詢N次傳感器,獲得N個(gè)值對(duì)象數(shù)組,然后才能去用N組數(shù)據(jù)渲染出圖表的HTML頁(yè)面。 可以看到,外部核心的Promise控制的流程只集中于下面的幾行之中:Promise.all(queryPromises()).then(renderGraph()).catch()。即,只有獲取完N個(gè)傳感器的數(shù)值之后,才會(huì)去渲染圖表的HTML頁(yè)面,但是這N個(gè)傳感器的獲取過(guò)程卻又是并發(fā)進(jìn)行的,由Promise.all()來(lái)實(shí)現(xiàn)的,合理地利用了有限的機(jī)器性能資源。

然而,推入queryPromises數(shù)組中的每個(gè)Promise對(duì)象,又構(gòu)成了自己的一條Promise邏輯鏈,只有這些子Promise邏輯鏈被處理完了,才可以說(shuō)整個(gè)all()函數(shù)都被執(zhí)行完了。子Promise邏輯鏈大致地可以總結(jié)為以下形式:

function() {    
    return new Promise().then().catch();
}

其中的難點(diǎn)在于:

合理地切分整套業(yè)務(wù)邏輯到不同的then()函數(shù)中,且一個(gè)then()中只能有一個(gè)異步過(guò)程。

函數(shù)體內(nèi)的異步過(guò)程所產(chǎn)生的新的Promise邏輯鏈必須被通過(guò)return的方式掛載到父函數(shù)的Promise邏輯鏈中,否則即可能形成一個(gè)有先有后的控制流程。

catch()函數(shù)必須要做好捕捉和輸出錯(cuò)誤的處理,否則代碼編寫(xiě)過(guò)程中的錯(cuò)誤即不可能被發(fā)現(xiàn),異步編程的整個(gè)過(guò)程也就無(wú)從繼續(xù)下去了。

值得一提的是Promise模式的引入。Node.js 自身不帶有Promise,可以引入標(biāo)準(zhǔn)的ECMAScript的Promise實(shí)現(xiàn),然而其功能較為簡(jiǎn)陋,對(duì)于各種API的實(shí)現(xiàn)過(guò)于匱乏,因此最后選擇了bluebird庫(kù)來(lái)引入Promise模式的語(yǔ)言支持。

由此我們可以看到,沒(méi)有無(wú)緣無(wú)故的高性能。Node.js 的高并發(fā)的優(yōu)良表現(xiàn),是用異步編程的高復(fù)雜度換來(lái)的。當(dāng)然,你也可以選擇不要編程復(fù)雜度,即不采用 Promise,Asnyc 等等異步編程模式,任由代碼淪入回調(diào)地獄之中,那么這時(shí)候的代價(jià)就是維護(hù)復(fù)雜度了。其中取舍,見(jiàn)仁見(jiàn)智。

客戶端層

客戶端主要功能如下表所示:

?

接下來(lái)簡(jiǎn)單介紹下幾個(gè)主要頁(yè)面??梢园l(fā)現(xiàn) iOS 明顯比 Android 要來(lái)的漂亮,因?yàn)橹粚?duì) iOS 做了視覺(jué)上的細(xì)調(diào),直接遷移到 Android 上,就會(huì)由于屏幕顯示的色差問(wèn)題,顯得非常粗糙。所以,對(duì)于跨平臺(tái)的 React Native App 來(lái)說(shuō),做兩套色值配置文件,以供兩個(gè)平臺(tái)使用,還是很有必要的。

?

上圖即是土壤墑情底欄的當(dāng)前數(shù)據(jù)頁(yè)面,分別在Android和iOS上的顯示效果,默認(rèn)展示所有當(dāng)前的傳感器的數(shù)值,可以通過(guò)選擇傳感器種類或監(jiān)測(cè)站編號(hào)進(jìn)行篩選,兩個(gè)條件可以分別設(shè)置,選定后再點(diǎn)擊查找,即向服務(wù)器發(fā)起請(qǐng)求,得到數(shù)據(jù)后刷新頁(yè)面。由于React Native 的組件化設(shè)計(jì),刷新將只刷新下側(cè)的DashBoard部分,且,若有上次已經(jīng)渲染過(guò)的MonitorView,則會(huì)復(fù)用他們,不再重復(fù)渲染,從而實(shí)現(xiàn)了降低CPU占用的性能優(yōu)化。MonitorView,即每一個(gè)傳感器的展示小方塊,自上至下依次展示了傳感器種類,傳感器編號(hào),當(dāng)前的傳感器數(shù)值以及該傳感器顯示數(shù)值的單位。MonitorView和Dashboard均被抽象為一個(gè)一般化,可復(fù)用的組件,使之能夠被利用在氣象信息、病蟲(chóng)害監(jiān)測(cè)之中,提升了開(kāi)發(fā)效率,降低了代碼的重復(fù)率。

?

上圖是土壤墑情界面的歷史數(shù)據(jù)界面,分別在Android和iOS上的展示效果,默認(rèn)不會(huì)顯示數(shù)據(jù),直到輸入了傳感器種類和監(jiān)測(cè)站編號(hào),選擇了年月日時(shí)間后,再點(diǎn)擊查找,才會(huì)得到結(jié)果并顯示出來(lái)。該界面并非如同當(dāng)前數(shù)據(jù)界面一樣,Android和iOS代碼完全共用。原因在于選擇月日和選擇時(shí)間的控件,Android和iOS系統(tǒng)有各自的控件,它們也被封裝為React Native中不同的控件,因此,兩條綠色的選擇時(shí)間的按鈕,被封裝為HistoricalDateSelectPad,分別放在componentIOS和componentAndroid文件夾中。界面下側(cè)的數(shù)據(jù)監(jiān)測(cè)板,即代碼中的Dashboard,是復(fù)用當(dāng)前數(shù)據(jù)中的Dashboard。

?

上圖是土壤墑情界面的圖表生成界面,分別在Android和iOS上的展示效果。時(shí)間選擇界面,查找按鈕,選擇框,均可復(fù)用前兩個(gè)界面的代碼,因此無(wú)需多提。值得說(shuō)的是,生成的折線圖,事實(shí)上是通過(guò)內(nèi)嵌的WebView來(lái)顯示一個(gè)網(wǎng)頁(yè)的。圖表網(wǎng)頁(yè)的生成,則依靠的百度Echarts 第三方庫(kù),然后服務(wù)端提供了一個(gè)預(yù)先寫(xiě)好的前端模板,Express框架填入需要的數(shù)據(jù),最后下發(fā)到移動(dòng)客戶端上,渲染生成圖表。圖表支持了多曲線的刪減,手指選取查看具體數(shù)據(jù)點(diǎn),放大縮小等功能。

?

上圖則是實(shí)際項(xiàng)目應(yīng)用中的Redux相關(guān)文件的結(jié)構(gòu)。stores中存放全局?jǐn)?shù)據(jù)store相關(guān)的實(shí)現(xiàn)。

actions中則存放根據(jù)模塊切割開(kāi)的各類action生成函數(shù)集合。在 Redux 中,改變 State 只能通過(guò) action。并且,每一個(gè) action 都必須是 Javascript Plain Object。事實(shí)上,創(chuàng)建 action 對(duì)象很少用這種每次直接聲明對(duì)象的方式,更多地是通過(guò)一個(gè)創(chuàng)建函數(shù)。這個(gè)函數(shù)被稱為Action Creator。

reducers中存放許多reducer的實(shí)現(xiàn),其中RootReducer是根文件,它負(fù)責(zé)把其他reducer拼接為一整個(gè)reducer,而reducer就是根據(jù) action 的語(yǔ)義來(lái)完成 State 變更的函數(shù)。Reducer 的執(zhí)行是同步的。在給定 initState 以及一系列的 actions,無(wú)論在什么時(shí)間,重復(fù)執(zhí)行多少次 Reducer,都應(yīng)該得到相同的 newState。

性能測(cè)試 服務(wù)端

測(cè)試工具:OS X Activity Monitor(http_load)

?

客戶端
iOS

測(cè)試工具:Xcode 7.3

?

Android

測(cè)試工具:Android Studio 1.2.0

?

代碼量相關(guān)

?

簡(jiǎn)單總結(jié)

React Native 盡管在開(kāi)發(fā)上具有這樣那樣的坑,但是因其天生的跨平臺(tái),和優(yōu)于 Html5的移動(dòng)性能表現(xiàn),使得他在寫(xiě)一些不太復(fù)雜的 App 的時(shí)候,開(kāi)發(fā)速度非常快,自帶兩倍 buff。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/80619.html

相關(guān)文章

  • 幾種典型智慧農(nóng)業(yè)聯(lián)網(wǎng)解決方案

    摘要:本文分享幾種典型具有實(shí)際應(yīng)用過(guò)的智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案,供大家參考。智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案由三部分組成智慧農(nóng)業(yè)物聯(lián)網(wǎng)平臺(tái)智慧農(nóng)業(yè)物聯(lián)網(wǎng)網(wǎng)關(guān)無(wú)線節(jié)點(diǎn)。 智慧農(nóng)業(yè)是現(xiàn)代農(nóng)業(yè)發(fā)展的必然趨勢(shì),也是科學(xué)技術(shù)發(fā)展的必然結(jié)果。本文分享幾種典型具有實(shí)際應(yīng)用過(guò)的智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案,供大家參考。 結(jié)合多年與...

    animabear 評(píng)論0 收藏0
  • 河北信息服務(wù)業(yè)“十三五”規(guī)劃重點(diǎn)推云計(jì)算

    摘要:建立面向中小企業(yè)的信息化服務(wù)云計(jì)算平臺(tái),為中小企業(yè)提供生產(chǎn)管理財(cái)務(wù)管理營(yíng)銷管理人力資源管理等云計(jì)算服務(wù)?!  逗颖笔⌒畔⒎?wù)業(yè)十三五發(fā)展規(guī)劃》明確,十三五期間,河北省將重點(diǎn)推進(jìn)云計(jì)算服務(wù)能力促進(jìn)工程、云計(jì)算創(chuàng)新能力提升工程、云計(jì)算服務(wù)應(yīng)用示范工程、電子政務(wù)集約化建設(shè)工程、數(shù)據(jù)資源開(kāi)發(fā)共享工程、云計(jì)算產(chǎn)業(yè)鏈發(fā)展培育工程、云計(jì)算基礎(chǔ)設(shè)施建設(shè)工程、云計(jì)算安全保障建設(shè)工程等八大工程,統(tǒng)籌規(guī)劃建設(shè)云上...

    gnehc 評(píng)論0 收藏0
  • 最流行編程語(yǔ)言 JavaScript 能做什么?

    摘要:首先很遺憾的一點(diǎn)是,雖然是最好的語(yǔ)言,但是它不是最流行的語(yǔ)言。屬于配置比較高的硬件,而低配的呢三星設(shè)計(jì)了引擎,它能夠運(yùn)行在小于內(nèi)存上,且全部代碼能夠存儲(chǔ)在不足的只讀存儲(chǔ)上。你覺(jué)得還能做什么 首先很遺憾的一點(diǎn)是,PHP雖然是最好的語(yǔ)言,但是它不是最流行的語(yǔ)言。showImg(https://segmentfault.com/img/bVvqTs);同時(shí)對(duì)不起的還有剛剛在4月TIOBE編程...

    褰辯話 評(píng)論0 收藏0
  • 最流行編程語(yǔ)言 JavaScript 能做什么?

    摘要:首先很遺憾的一點(diǎn)是,雖然是最好的語(yǔ)言,但是它不是最流行的語(yǔ)言。屬于配置比較高的硬件,而低配的呢三星設(shè)計(jì)了引擎,它能夠運(yùn)行在小于內(nèi)存上,且全部代碼能夠存儲(chǔ)在不足的只讀存儲(chǔ)上。你覺(jué)得還能做什么 首先很遺憾的一點(diǎn)是,PHP雖然是最好的語(yǔ)言,但是它不是最流行的語(yǔ)言。showImg(https://segmentfault.com/img/bVvqTs);同時(shí)對(duì)不起的還有剛剛在4月TIOBE編程...

    Alan 評(píng)論0 收藏0
  • 翻譯 | 擺脫瀏覽器限制JavaScript

    摘要:在考慮宇航員的生命安全時(shí),輕微的打嗝或者服務(wù)中斷都會(huì)釀成生死事故。也許最大的挑戰(zhàn)來(lái)自谷歌主導(dǎo)的簡(jiǎn)稱。在最近的開(kāi)發(fā)者峰會(huì),以及今年的會(huì)議上,谷歌都為安排了大量討論。由微軟提供,是廣受歡迎的編輯器,到月份已經(jīng)獲得了超過(guò)五百萬(wàn)用戶。 譯者:安冬 (滬江Web前端開(kāi)發(fā)工程師)本文原創(chuàng)翻譯,轉(zhuǎn)載請(qǐng)注明作者及出處。原文地址:http://developer.telerik.com/... 技術(shù)世界...

    xfee 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

renweihub

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<