使用React、Node.js、MongoDB、Socket.IO開發一個角色投票應用的學習過程(三)

這幾篇都是我原來首發在 segmentfault 上的地址:http://www.javashuo.com/article/p-bwegrihr-z.html 忽然想起來我這個博客冷落了好多年了,也該更新一下,呵呵

前篇

使用React、Node.js、MongoDB、Socket.IO開發一個角色投票應用的學習過程(一)
使用React、Node.js、MongoDB、Socket.IO開發一個角色投票應用的學習過程(二)html

原文第十三步,Express API路由

第一個路由是用來建立角色的mysql

app.post('/api/characters',(req,res,next) => {
  let gender = req.body.gender;
  let characterName = req.body.name;
  let characterIdLookupUrl = 'https://api.eveonline.com/eve/CharacterId.xml.aspx?names=' + characterName;

  const parser = new xml2js.Parser();

  async.waterfall([
    function(callback) {
      request.get(characterIdLookupUrl,(err,request,xml) => {
        if(err) return next(err);
        parser.parseString(xml,(err,parsedXml) => {
          try {
            let characterId = parsedXml.eveapi.result[0].rowset[0].row[0].$.characterID;

            app.models.character.findOne({ characterId: characterId},(err,model) => {
              if(err) return next(err);

              if(model) {
                return res.status(400).send({ message: model.name + ' is alread in the database'});
              }

              callback(err,characterId);
            });
          } catch(e) {
            return res.status(400).send({ message: ' xml Parse Error'});
          }
        });
      });
    },
    function(characterId) {
      let characterInfoUrl = 'https://api.eveonline.com/eve/CharacterInfo.xml.aspx?characterID=' + characterId;
      console.log(characterInfoUrl);
      request.get({ url: characterInfoUrl },(err,request,xml) => {
        if(err) return next(err);
        parser.parseString(xml, (err,parsedXml) => {
          if (err) return res.send(err);
          try{
            let name = parsedXml.eveapi.result[0].characterName[0];
            let race = parsedXml.eveapi.result[0].race[0];
            let bloodline = parsedXml.eveapi.result[0].bloodline[0];
            app.models.character.create({
              characterId: characterId,
              name: name,
              race: race,
              bloodline: bloodline,
              gender: gender
            },(err,model) => {
              if(err) return next(err);
              res.send({ message: characterName + ' has been added successfully!'});
            });
          } catch (e) {
            res.status(404).send({ message: characterName + ' is not a registered citizen of New Eden',error: e.message });
          }
        });
      });
    }
  ]);
});

是否是看起來和原文的基本如出一轍,只不過把var 變成了let 匿名函數變成了ES6的'=>'箭頭函數,雖然我用的是warterline而原文中用的是mongoose可是包括方法名基本都同樣,因此我感受waterline是在API上最接近mongooselinux

順便說一下,我爲何不喜歡mongodb,僅僅是由於有一次我安裝了,只往裏面寫了幾條測試數據,按文本算最多幾kb,但次日重啓機器的時候,系統提示我,個人/home分區空間不足了(雙系統分區分給linux分小了原本就不大),結果一查mongodb 的data文件 有2G多,我不知道什麼緣由,多是配置不對仍是別的什麼緣由,反正,當天我就把它刪除了,git

完成了這個API咱們就能夠往數據庫裏添加東西了,不知道哪些用戶名能夠用?至關簡單,反正我用的全是一名人的名字(英文名),外國人也喜歡搶註名字,嘿嘿嘿es6

add character ui

原文第十三步,Home組件

基本保持和原文同樣,只是用lodash 替換了 underscoregithub

一開始我看到網上介紹lodash是能夠無縫替換underscore,中要修改引用就能夠,可是我用的版本是4.11.2已經有不少方法不同了,還去掉了很多方法(沒有去關注underscore是否是也在最新版本中有一樣的改動)sql

原文中:mongodb

......
import {first, without, findWhere} from 'underscore';
......

var loser = first(without(this.state.characters, findWhere(this.state.characters, { characterId: winner }))).characterId;

......

修改成:數據庫

......
import {first, filter} from 'lodash';
......

let loser = first(filter(this.state.characters,item => item.characterId != winner )).characterId;

findWhere 在最新版本的lodash中已經不存正,我用了filter來實現相同功能。express

第十四步:Express API 路由(2/2)

GET /api/characters

原文的實現方法

/**
 * GET /api/characters
 * Returns 2 random characters of the same gender that have not been voted yet.
 */
app.get('/api/characters', function(req, res, next) {
  var choices = ['Female', 'Male'];
  var randomGender = _.sample(choices);

  Character.find({ random: { $near: [Math.random(), 0] } })
    .where('voted', false)
    .where('gender', randomGender)
    .limit(2)
    .exec(function(err, characters) {
      if (err) return next(err);

      if (characters.length === 2) {
        return res.send(characters);
      }

      var oppositeGender = _.first(_.without(choices, randomGender));

      Character
        .find({ random: { $near: [Math.random(), 0] } })
        .where('voted', false)
        .where('gender', oppositeGender)
        .limit(2)
        .exec(function(err, characters) {
          if (err) return next(err);

          if (characters.length === 2) {
            return res.send(characters);
          }

          Character.update({}, { $set: { voted: false } }, { multi: true }, function(err) {
            if (err) return next(err);
            res.send([]);
          });
        });
    });
});

能夠看到原文中用{ random: { $near: [Math.random(), 0] } }作爲查詢條件從而在數據庫裏取出兩條隨機的記錄返回給頁面進行PK,前文說過random的類型在mysql沒有相似的,因此我把這個字段刪除了。原本mysql,能夠用order by rand() 之類的方法可是,waterlinesort(order by rand())不被支持,因此我是把全部符合條件的記錄取出來,能過lodashsampleSize方法從全部記錄中獲取兩天隨機記錄。

app.get('/api/characters', (req,res,next) => {
  let choice = ['Female', 'Male'];
  let randomGender = _.sample(choice);
  //原文中是經過nearby字段來實現隨機取值,waterline沒有實現mysql order by rand()返回隨機記錄,因此返回全部結果,用lodash來處理
  app.models.character.find()
    .where({'voted': false})
    .exec((err,characters) => {
      if(err) return next(err);
      
      //用lodash來取兩個隨機值
      let randomCharacters = _.sampleSize(_.filter(characters,{'gender': randomGender}),2); 
      if(randomCharacters.length === 2){
      //console.log(randomCharacters);
        return res.send(randomCharacters);
      }

      //換個性別再試試
      let oppsiteGender = _.first(_.without(choice, randomGender));
      let oppsiteCharacters = _.sampleSize(_.filter(characters,{'gender': oppsiteGender}),2); 

      if(oppsiteCharacters === 2) {
        return res.send(oppsiteCharacters);
      }
      //沒有符合條件的character,就更新voted字段,開始新一輪PK
      app.models.character.update({},{'voted': false}).exec((err,characters) => {
        if(err) return next(err);
        return res.send([]);
      });
      


    });

});

在數據量大的狀況下,這個的方法性能上確定會有問題,好在咱們只是學習過程,數據量也不大。將就用一下,能實現相同的功能就能夠了。

GET /api/characters/search

這個API以前還有兩個API,和原文基本同樣,所作的修改只是用了ES6的語法,就不浪費篇幅了,能夠去個人github

這一個也只是一點mongoosewaterline的一點點小區別
原文中mongoose的模糊查找是用正則來作的,mysql好像也能夠,可是warterline中沒有找到相關方法(它的文檔太簡陋了)
因此原文中

app.get('/api/characters/search', function(req, res, next) {
  var characterName = new RegExp(req.query.name, 'i');

  Character.findOne({ name: characterName }, function(err, character) {
    ......

我改爲了

app.get('/api/characters/search', (req,res,next) => {
  app.models.character.findOne({name:{'contains':req.query.name}}, (err,character) => {
    .....

經過contains來查找,其實就是like %sometext%的方法來實現
下面還有兩個方法修改的地方也大同小異,就不仔細講了,看代碼吧

GET /api/stats

這個是原文最後一個路由了,
原文中用了一串的函數來獲取各類統計信息,原做者也講了能夠優化,哪咱們就把它優化一下吧

app.get('/api/stats', (req,res,next) => {
  let asyncTask = [];
  let countColumn = [
        {},
        {race: 'Amarr'},
        {race: 'Caldari'},
        {race: 'Gallente'},
        {race: 'Minmatar'},
        {gender: 'Male'},
        {gender: 'Female'}
      ];
  countColumn.forEach(column => {
    asyncTask.push( callback => {
      app.models.character.count(column,(err,count) => {
        callback(err,count);
      });
    })
  });

  asyncTask.push(callback =>{
    app.models.character.find()
              .sum('wins')
              .then(results => {
                callback(null,results[0].wins);
              });
  } );

  asyncTask.push(callback => {
    app.models.character.find()
              .sort('wins desc')
              .limit(100)
              .select('race')
              .exec((err,characters) => {
                if(err) return next(err);

                let raceCount = _.countBy(characters,character => character.race);
                console.log(raceCount);
                let max = _.max(_.values(raceCount));
                console.log(max);
                let inverted = _.invert(raceCount);
                let topRace = inverted[max];
                let topCount = raceCount[topRace];

                

                callback(err,{race: topRace, count: topCount});
              });
  });

  asyncTask.push(callback => {
    app.models.character.find()
              .sort('wins desc')
              .limit(100)
              .select('bloodline')
              .exec((err,characters) => {
                if(err) return next(err);

                let bloodlineCount = _.countBy(characters,character => character.bloodline);
                let max = _.max(_.values(bloodlineCount));
                let inverted = _.invert(bloodlineCount);
                let topBloodline = inverted[max];
                let topCount = bloodlineCount[topBloodline];

                callback(err,{bloodline: topBloodline, count: topCount});
              });
  });

  async.parallel(asyncTask,(err,results) => {
    if(err) return next(err);
    res.send({
      totalCount: results[0],
          amarrCount: results[1],
          caldariCount: results[2],
          gallenteCount: results[3],
          minmatarCount: results[4],
          maleCount: results[5],
          femaleCount: results[6],
          totalVotes: results[7],
          leadingRace: results[8],
          leadingBloodline:results[9]
    });
  }) 
});

我把要統計數據的字段放入一個數組countColumn經過forEach把push到asyncTask,最後兩個統計方法不同的函數,單獨push,最後用async.parallel方法執行並得到結果。

underscore的max方法能夠從{a:1,b:6,d:2,e:3}返回最大值,可是lodash新版中的不行,只能經過_.max(_.values(bloodlineCount))這樣的方式返回最大值。

相關文章
相關標籤/搜索