第6章 我的瓶子 - nswbmw/N-drifter GitHub Wiki

我们在玩漂流瓶的时候,捡到过的漂流瓶可以到 "我的瓶子" 里查看,下面我们来实现这个功能。

我们设定:捡到的漂流瓶可以长久保存,除非自己删除。我们使用 MongoDB 来实现数据持久化存储。

打开 package.json ,添加对 mongoose 的依赖:

{
  "name": "drifter",
  "version": "0.0.1",
  "dependencies": {
    "express": "^3",
    "redis": "*",
    "node-uuid": "*",
    "generic-pool": "*",
    "mongoose": "*"
  },
  "devDependencies": {
    "request": "*"
  }
}

npm install 安装 mongoose 包。

打开 app.js ,在上面添加一行代码:

var mongodb = require('./models/mongodb.js');

app.get('/') 修改为:

app.get('/', function (req, res) {
  if (!req.query.user) {
    return res.json({code: 0, msg: "信息不完整"});
  }
  if (req.query.type && (["male", "female"].indexOf(req.query.type) === -1)) {
    return res.json({code: 0, msg: "类型错误"});
  }
  redis.pick(req.query, function (result) {
    if (result.code === 1) {
      mongodb.save(req.query.user, result.msg, function (err) {
        if (err) {
          return res.json({code: 0, msg: "获取漂流瓶失败,请重试"});
        }
        return res.json(result);
      });
    }
    res.json(result);
  });
});

以上代码的意思是:我们在捡到一个瓶子后,同时将该瓶子放到 "我的瓶子" 里(即存到 MongoDB 中)。我们将存入 MongoDB 的文档设置为以下形式:

{
  bottle: ["$picker"],
  message: [
    ["$owner", "$time", "$content"]
  ]
}

这样设置是为了方便实现后面的漂流瓶聊天功能,下一小节将会介绍。其中,bottle 数组第一项存储了捡瓶子的用户。message 数组存储了漂流瓶的原始信息(数组),数组第一项为漂流瓶的主人,第二项为扔瓶子的时间,第三项为漂流瓶的内容。

对应在 models 文件夹下新建 mongodb.js ,添加如下代码:

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/drifter', {server: {poolSize: 10}});

// 定义漂流瓶模型,并设置数据存储到 bottles 集合
var bottleModel = mongoose.model('Bottle', new mongoose.Schema({
  bottle: Array,
  message: Array
}, {
  collection: 'bottles' 
}));

// 将用户捡到漂流瓶改变格式保存
exports.save = function(picker, _bottle, callback) {
  var bottle = {bottle: [], message: []};
  bottle.bottle.push(picker);
  bottle.message.push([_bottle.owner, _bottle.time, _bottle.content]);
  bottle = new bottleModel(bottle);
  bottle.save(function (err) {
    callback(err);
  });
};

这里我们将漂流瓶的信息存到了 MongoDB 的 drifter 数据库的 bottles 集合里了,并设置连接池大小为 10。关于如何配置和启动 MongoDB 前面有过介绍,这里不再赘述。

现在,当一个用户捡到一个瓶子的时候,该漂流瓶信息会自动存储到 MongoDB 中持久化保存。接下来我们实现当用户点击 "我的瓶子" 时获取全部瓶子的功能。

打开 app.js ,在 app.listen(3000); 前添加如下代码:

// 获取一个用户所有的漂流瓶
// GET /user/nswbmw
app.get('/user/:user', function (req, res) {
  mongodb.getAll(req.params.user, function (result) {
    res.json(result);
  });
});

以上代码的意思是:当用户点击 "我的瓶子" 时,发送请求到服务器并返回该用户捡到的所有漂流瓶。

注意:我们并没有添加对用户的认证,即看起来任何人都可以获取任一用户的漂流瓶列表,这个工作可以在客户端完成。

打开 mongodb.js ,在最后添加如下代码:

// 获取用户捡到的所有漂流瓶
exports.getAll = function(user, callback) {
  bottleModel.find({"bottle": user}, function (err, bottles) {
    if (err) {
      return callback({code: 0, msg: "获取漂流瓶列表失败..."});
    }
    callback({code: 1, msg: bottles});
  });
};

以上代码的意思是:查找数据库中所有文档,返回满足 bottle 数组中有一项为 user 的所有文档。

试想一种情况:用户 A 扔的漂流瓶被用户 B 捡到了,此时用户 B 的漂流瓶列表中应该有该漂流瓶,而用户 A 的漂流瓶列表中应该没有该漂流瓶(即使是自己扔的),除非用户 B 回复了该漂流瓶(下一小节会介绍)。这就是为什么我们往 MongoDB 中第一次存储文档时,只在 bottle 中存储 picker 而不存储 owner 。

接下来我们实现当点击其中一个漂流瓶时,获取该漂流瓶内容的功能。

打开 app.js ,在 app.listen(3000); 前添加如下代码:

// 获取特定 id 的漂流瓶
// GET /bottle/529a8b5b39242c82417b43c3
app.get('/bottle/:_id', function (req, res) {
  mongodb.getOne(req.params._id, function (result) {
    res.json(result);
  });
});

以上代码的意思是:根据查询的漂流瓶 id (即文档的 _id 键值,因为我们在获取漂流瓶列表的时候返回的文档包含 _id 键)查找并返回该漂流瓶信息。

打开 mongodb.js ,在最后添加如下代码:

// 获取特定 id 的漂流瓶
exports.getOne = function(_id, callback) {
  // 通过 id 获取特定的漂流瓶
  bottleModel.findById(_id, function (err, bottle) {
    if (err) {
      return callback({code: 0, msg: "读取漂流瓶失败..."});
    }
    // 成功时返回找到的漂流瓶
    callback({code: 1, msg: bottle});
  });
};

这里我们使用 mongoose 中的 findById 方法查询特定 id 的漂流瓶,findById 的第一个参数可以是字符串或 ObjectId 对象。

至此,我们完成了 "我的瓶子" 的功能。