88bifa必发nodejs通过lodash合并去重由unixtime和Date组成的两个数组

1. 问题起源


最近在实现一个API,其中有一部分功能是需要从Mongodb中取出一个由Date对象组成的数组,然后将客户端传过来的unixtime合并到该数组中,并且去重复。

比如,假设从mongodb中取回来的数据中有一个叫做gaming的项,专门用来记录用户进入游戏的开始时间和退出时间。
那么mongoose的schema的定义将大概是这样的:

const DeviceLogSchema = new Schema({
 ...
  gaming: [{ enter: Date, exit: Date, _id: false }],          
 ... 
});

而从mongodb取回来的数据大概就是这个样子的:

{
"gaming": [
      {
        "enter": 2017-04-25T14:32:12.081Z,
        "exit": 2017-04-25T14:48:52.082Z,
      },
      {
        "enter": 2017-04-26T14:32:12.081Z,
        "exit": 2017-04-26T14:48:52.082Z,
      }
    ],
}

也就是说通过mongoose的model取回来的记录中,enter和exit都是Date(对mongodb来说)类型的,而对于js来说,就是一个Object(js将所有简单类型以外的数据类型都处理成Object)。

let deviceLog = await DeviceLog.findOne({});
console.log(typeof deviceLog.enter) // ->Object

而客户端每隔一段时间就会调用api来将新的用户游戏时间回传给服务器,但用的格式是unixtime。

{
    "gaming": [{
      "enter": 1493130733081,
      "exit": 1493131734082,
    },{
      "enter": 1493130735084,
      "exit": 1493131736087,
    }],
}

这里的enter和exit的unixtime时间格式,对于js来说,就是number类型的。

我们通过mongoose来保存的时候,不需要将unixtime进行任何转换,直接保存,
mongoose会将其自动转成Date格式进行保存。也就是说,如果保存前的gaming内容是如下这个样子的:

"gaming": [
      {
        "enter": 2017-04-25T14:32:12.081Z,
        "exit": 2017-04-25T14:48:52.082Z,
      },
      {
        "enter": 2017-04-26T14:32:12.081Z,
        "exit": 2017-04-26T14:48:52.082Z,
      },
      {
        "enter": 1493130733081,
        "exit": 1493131734082,
      },
      {
        "enter": 1493130735084,
        "exit": 1493131736087,
      }
    ],

那么通过mongoose的model保存之后,最终会自动成为类似以下这样的格式:

"gaming": [
      {
        "enter": 2017-04-25T14:32:12.081Z,
        "exit": 2017-04-25T14:48:52.082Z,
      },
      {
        "enter": 2017-04-26T14:32:12.081Z,
        "exit": 2017-04-26T14:48:52.082Z,
      },
      {
        "enter": 2017-04-27T14:22:12.021Z,
        "exit": 2017-04-27T15:32:12.031Z,
      },
      {
        "enter": 2017-04-26T16:22:12.082Z,
        "exit": 2017-04-26T16:52:12.082Z,
      }
    ],

那么这里要解决的问题就是:

  • 如何将客户端传过来的新数组和从mongodb取回来的数组进行合并
  • 合并时如何根据游戏进入的时间enter来去重复

当然,我们可以用原始的方法,做两层遍历,分别便利两个不同的数组,然后将其中一个比对数据的类型转换成另外一个数据对应的类型,然后进行比较其是否相等,相等的话就去掉,不想等的话就将数据追加到数组中。

但,这样效率太低了,应该有更好的更优雅的方法来帮助我们处理这种问题。

前言

2. 实验数据

那么我们就根据上面碰到的问题,来建立两个实验所用的数据。一个是代表从mongodb取回来的数据:

const orgGaming = [
      {
        "enter": new Date("2017-04-25T14:32:12.081Z"),
        "exit": new Date("2017-04-25T14:48:52.082Z"),
      },
      {
        "enter": new Date("2017-04-26T14:32:12.081Z"),
        "exit": new Date("2017-04-26T14:48:52.082Z"),
      }
]

一个是客户端传进来的数据:

const newGaming = [
     {
        "enter": 1493130732081, // 这和orgGamine第一条数据重复
        "exit": 1493131732082, // 这和orgGamine第一条数据重复
      },
      {
        "enter": 1493130735084,
        "exit": 1493131736087,
      }
    ]

新数组中的第一条数据和enter和数据库中的第一条数据的enter,事实上是相同的,所以我们希望合并之后这个重复数据是去掉的。

     
看完了Node.js实战,其中在数据存储部分提到了Redis、Mongodb,我自己也根据书中的介绍写了几个简单的demo,在demo的过程首先遇到的问题就是数据类型和常见的CURD写法。
mongodb的常见操作有两种方式,一个是直接使用API,也就相当于你在SQL
Server客户端中使用T-SQL编写SQL语句来操作数据一样,其次就是在程序中使用mongoose驱动来操作数据,相当于我们在程序里用ADO.NET或EF来操作数据,如果你已经写了几个调用API的demo,那么我建议再回过头来看看mongoose的API,多看API,并且记住常见的几个方法比如where,skip,sort等

3. ES6数组合并新特性


其实,如果不是因为要考虑去重复的问题的话,我们完全可以通过ES6的新特性来完成的。

array1.push(...array2)

这里的’…’操作符叫做扩展运算符,是ES6引入的新特性。目的是将一个数组打散成用逗号分隔的参数序列。

const array = [1, 2];
console.log(...array); // 相当于 console.log(1,2)

所以上面的示例代码的意思就是将array2打散后,将每个元素作为参数push到array1中生成新的数组。所以,如果应用到我们的场景中的话

const orgGaming = [
      {
        "enter": new Date("2017-04-25T14:32:12.081Z"),
        "exit": new Date("2017-04-25T14:48:52.082Z"),
      },
      {
        "enter": new Date("2017-04-26T14:32:12.081Z"),
        "exit": new Date("2017-04-26T14:48:52.082Z"),
      }
]

const newGaming = [
     {
        "enter": 1493130732081,
        "exit": 1493131732082,
      },
      {
        "enter": 1493130735084,
        "exit": 1493131736087,
      }
]

orgGaming.push(...newGaming);
console.log(orgGaming);

最终将会输出没有去重复的结果:

[ 
  { enter: 2017-04-25T14:32:12.081Z,
    exit: 2017-04-25T14:48:52.082Z },
  { enter: 2017-04-26T14:32:12.081Z,
    exit: 2017-04-26T14:48:52.082Z },
  { enter: 1493130732081, 
    exit: 1493131732082 },
  { enter: 1493130735084, 
    exit: 1493131736087 } 
]

当然,ES6的这个数组合并方式还可以这样写:

[...array1,...array2]

同时,ES6还提供了对简单数据类型去重复方式:

[...new Set([...array1 ,...array2])];

但是,这个只能针对简单数据类型进行去重复,比如数字类型和字串类型等。

const array1 = ['techgogogo', 'sina', 'baidu'];
const array2 = ['techgogogo', 'google'];
console.log([... new Set([...array1, ...array2])]);

最后输出:

[ 'techgogogo', 'sina', 'baidu', 'google' ]

但是对于我们这里的对象类型组成的数组,它是做不到的。

最重要的是,它没有提供一个comparator的回调方法来放我们处理应该如何判断,两个数据是否是重复的。

这里,lodash的数组操作,也许是个正确的解决方案(之一)。

    
按照以往我们编写Web方式,我们会关注数据从客户端(Views)如果到Controller层,
然后把数据如何从Controller层传递到Dao层以及这两个层面传递数据时的一些技巧,那么在Node里这种思维也是适用的。

4. lodash合并对象类型数组并去重复

lodash的unionWith方式可以合并两个数组,并且可以让我们提供一个comparator方法来控制该如何比较两个数组中的元素是否是一致的,以此来判断这个数据是否是重复的。

官方文档对unionWith方法的描述请看这里:https://lodash.com/docs/4.17.4\#unionWith

_.unionWith([arrays], [comparator])

理解起来也比较简单,请看代码如下:

const _ = require('lodash');
const orgGaming = [
      {
        "enter": new Date("2017-04-25T14:32:12.081Z"),
        "exit": new Date("2017-04-25T14:48:52.082Z"),
      },
      {
        "enter": new Date("2017-04-26T14:32:12.081Z"),
        "exit": new Date("2017-04-26T14:48:52.082Z"),
      }
]

const newGaming = [
     {
        "enter": 1493130732081,
        "exit": 1493131732082,
      },
      {
        "enter": 1493130735084,
        "exit": 1493131736087,
      }
]

gaming = _.unionWith(orgGaming, newGaming, (value1, value2) => {
    if (typeof value1.enter === 'number' && typeof value2.enter === 'number') {
        return (value1.enter === value2.enter);
    } else if (typeof value1.enter === 'number' && typeof value2.enter === 'object') {
        return (value1.enter === value2.enter.getTime());
    } else if (typeof value1.enter === 'object' && typeof value2.enter === 'number') {
        return (value1.enter.getTime() === value2.enter);
    } else if (typeof value1.enter === 'object' && typeof value2.enter === 'object') {
        return (value1.enter.getTime() === value2.enter.getTime());
    }
});

console.log(gaming);

这里关键的地方就是uionWith,有几个地方需要注意:

  • 参数的顺序,特别是前两个数组参数。如果第一个数组中某个成员判定和第二个数组中的某个成员是重复的,那么第一个数组中的该元素会保留,第二个数组中的对应元素会移除。
  • 第三个参数就是一个回调方法,接受两个参数,其实就是两个需要比对的数组的成员,这里我们通过比对两个成员的enter是否相等来判断该成员是否重复。
  • 判断是否重复的时候,我们需要将日记先转换成unixtime的格式,然后再进行比较。

最终我们可以看到去重复后的输出:

[ { enter: 2017-04-25T14:32:12.081Z,
    exit: 2017-04-25T14:48:52.082Z },
  { enter: 2017-04-26T14:32:12.081Z,
    exit: 2017-04-26T14:48:52.082Z },
  { enter: 1493130735084, 
    exit: 1493131736087 } ]

可以看到,最后输出的列表中只有三个对象,其中一个重复的已经被摒弃掉了。

最后我们通过mongoose将这份数据存储到mongodb时,如前面所述,会自动将unixtime转换成Date进行存储,这样数据就统一起来了。这里mongoose的操作就不赘述了,有兴趣的朋友可以自己实践下。

以上就是本人对两个由对象类型组成的数组进行合并的一些尝试和实践,如果大家有更好更优雅的方式的话,欢迎在评论中给出来。先拜谢了!

本文由天地会珠海分舵编写,转载需授权,喜欢点个赞,吐槽请评论,进一步交流请关注公众号techgogogo或者直接联系本人微信zhubaitian1

什么是MongoDB?

   MongoDB 是一个开源的 NoSQL 数据库,相比 MySQL
那样的关系型数据库,它更为轻巧、灵活,非常适合在数据规模很大、事务性不强的场合下使用。 

Mongoose

Mongoose是封装了MongoDB的操作的一个对象模型库,为nodejs而生。就好像我们嫌原生javascript难写,代码量多,于是用jQuery库一样,因为MongoDB的操作接口复杂,不人性,所以有了Mongoose。这个库完全是可选的。
  
Mongoose的使用非常简单,在App的package.js中的dependence中加入mongoose,然后
npm install 即可。

 Mongodb数据类型

   1、null。{“x”:null}。

    2、Boolean。{“x”:true} 、{“x”:false}。

   3、数据类型。在Mongodb
Shell中默认使用64位浮点型数据,如{“x”:2.32}、{“x”:2},如果要使用整数类型则用{“x”:NumberInt(2)}、{“x”:NumberLong(2)}。

    4、字符串。Mongodb中字符串采用UTF-8编码方式,{“x”:”hello world”}。

    5、日期类型。{“x”:new Date()}。

    6、正则表达式。 Mongodb中可使用和javascript相同的正则表达式
{“x”:/itbilu/i}。

   
7、数据。Mongodb中数组的使用和javascript相同{“x”:[“hello”,”world”]}。

    8、内嵌文档。{“x”:{“y”:”Hello”}}。

   
9、Id和ObjectId()。Mongodb每个文档都会包含一个_id,如果你不指定时Mongodb会自动生成一个ObjectId对象。

    10、代码。{“x”:function aa(){}}。

    11、二进制。

常见CURD

var mongoose=require('mongoose');
var Schema=mongoose.Schema;
//1、连接字符串
mongoose.connect('mongodb://localhost/test');
//2、定义你的数据模型(也就是我们在关系数据库中定义的Table)
var TodoSchema=new Schema({
  title:String,
  finished:{type:Boolean,default:false},
  post_date:{type:Date,default:Date.now}
});
//3、访问todo对象模型
mongoose.model('Todo',TodoSchema);
//添加
exports.add=function(title,callback){
  var newTodo=new Todo();
  newTodo.title=title;
  newTodo.save(function(err){
    if(err){
      console.log(err);
      callback(err);
    }else{
      callback(null);
    }
  });
}
//查找单独的数据
var findTodoById=exports.findTodoById=function(id,callback){
  Todo.findOne({_id:id},function(err,doc){
    //doc也就是根据id得到的记录值
    if(err){
      callback(err,null);
    }
    callback(null,doc);
  })
}
//删除
exports.delete=function(id,callback){
  exports.findTodoById(id,function(err,doc){
    if(err){
      callback(err);
    }else{
      doc.remove();
      callback(null);
    }
  })
}
//编辑标题
exports.editTitle=function(id,title,callback){
  exports.findTodoById(id,function(err,doc){
    if(err){
      callback(err);
    }else{
      doc.post_date=new Date();
      doc.title=title;
      doc.save(function(err){
        if(err){
          callback(err);
        }else{
          callback(null);
        }
      })
    }
  })
}
exports.allTodos=function(callback){
  Todo.find({},callback);
}
//分页查询
exports.TodoPageList=function(pageIndex,pageSize,callback){
  var m=Todo.find({}); //也有方法直接这样写: var m=this;
  var start=(pageIndex-1)*pageSize;
  m.skip(start);
  m.limit(pageSize);
  m.sort({'post_date','asc'}); //排序
  //根据关键字查询后分页
  //m.where('title','XXX');
  //执行分页查询
  m.exec(function(err,rs){
    //分页后的结果
    if(err){
      callback(err);
    }else{
      Todo.find(function(err,result){
        /*
          一般情况下的分页你需要同时返回数据库记录总数和分页后的数据,所以这里使用了Todo.find再查询一次
        */
        callback({rows:rs,total:result.length});
      });
    }
  })
}

以上内容是小编给大家介绍的Mongodb 数据类型及Mongoose常用CURD
,希望大家喜欢。

您可能感兴趣的文章: