使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程三

前篇

使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(一)
使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(二)

原文第十三步,Express API路由

一个路由是用来创建角色的

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,parsedXml) => {
          try {
            let characterId = parsedXml.eveapi.result[0].rowset[0].row[0].$.characterID;

            app.models.character.findOne({ characterId: characterId},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 },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
            },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上最接近mongoose

顺便说一下,我为什么不喜欢mongodb,仅仅是因为有一次我安装了,只往里面写了几条测试数据,按文本算最多几kb,但第二天重启机器的时候,系统提示我,我的/home分区空间不足了(双系统分区分给linux分小了本来就不大),结果一查mongodb 的data文件 有2G多,我不知道什么原因,可能是配置不对还是别的什么原因,反正,当天我就把它删除了,

完成了这个API我们就可以往数据库添加东西了,不知道哪些用户名可以用?相当简单,反正我用的全是一名人的名字(英文名),外国人也喜欢抢注名字,嘿嘿嘿

原文第十三步,Home组件

基本保持和原文一样,只是用lodash 替换了 underscore

一开始我看到网上介绍lodash是可以无缝替换underscore,中要修改引用就可以,但是我用的版本是4.11.2已经有很多方法不一样了,还去掉了不少方法(没有去关注underscore是不是也在最新版本中有同样的改动)

原文中:

......
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 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,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',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',next) {
  var characterName = new RegExp(req.query.name,'i');

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

我改成了

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

通过contains来查找,其实就是like %sometext%方法来实现
下面还有两个方法修改的地方也大同小异,就不仔细讲了,看代码

GET /api/stats

这个是原文最后一个路由了,
原文中用了一串的函数获取各种统计信息,原作者也讲了可以优化,哪我们就把它优化一下吧

app.get('/api/stats',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,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,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))这样的方式返回最大值。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom