因为我们的论坛很简单,所以设计 users、topics 和 comments 三个集合即可满足需求,其中 users 集合用来存储用户信息,topics 集合用来存储话题信息,comments 集合用来存储话题的评论。构建 models 目录结构如下:

7.3.1

我们使用 mongoose 的 Schema 来定义我们的数据模型,对应文件修改如下:

user.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var UserSchema = new Schema({
  name: { type: String, required: true },
  email: { type: String, required: true},
  password: { type: String, required: true },
  gender: { type: String, required: true },
  signature: { type: String },
  created_at: { type: Date, default: Date.now },
  updated_at: { type: Date, default: Date.now }
});

UserSchema.index({name: 1});

module.exports = mongoose.model('User', UserSchema);

topic.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var TopicSchema = new Schema({
  user: {
    name: { type: String, required: true },
    email: { type: String, required: true }
  },
  title: { type: String, required: true },
  content: { type: String, required: true },
  tab: { type: String, required: true },
  pv: { type: Number, default: 0 },
  comment: { type: Number, default: 0 },
  created_at: { type: Date, default: Date.now },
  updated_at: { type: Date, default: Date.now }
});

TopicSchema.index({tab: 1, updated_at: -1});
TopicSchema.index({'user.name': 1, updated_at: -1});

module.exports = mongoose.model('Topic', TopicSchema);

comment.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;

var CommentSchema = new Schema({
  topic_id: { type: ObjectId, required: true },
  user: {
    name: { type: String, required: true },
    email: { type: String, required: true }
  },
  content: { type: String, required: true },
  created_at: { type: Date, default: Date.now },
  updated_at: { type: Date, default: Date.now }
});

CommentSchema.index({topic_id: 1, updated_at: 1});

module.exports = mongoose.model('Comment', CommentSchema);

我们根据需求添加了合理的索引,比如可以通过版块名(tab) 或用户名(user.name)按时间降序查找话题,则 topics 集合添加了对应的索引:

TopicSchema.index({tab: 1, updated_at: -1});
TopicSchema.index({'user.name': 1, updated_at: -1});

最后,通过 index.js 导出以供其他模块调用:

index.js

var mongoose = require('mongoose');
var config = require('config-lite').mongodb;

mongoose.connect(config.url, function (err) {
  if (err) {
    console.error('connect to %s error: ', config.url, err.message);
    process.exit(1);
  }
});

exports.User = require('./user');
exports.Topic = require('./topic');
exports.Comment = require('./comment');

接下来我们根据需求定义对应的模型函数,构建 lib 目录结构如下:

7.3.2

user.js

var User = require('../models').User;

//新建一个用户
exports.addUser = function (data) {
  return User.create(data);
};

//通过id获取用户
exports.getUserById = function (id) {
  return User.findbyId(id).exec();
};

//通过name获取用户
exports.getUserByName = function (name) {
  return User.findOne({name: name}).exec();
};

topic.js

var Topic = require('../models').Topic;
var cache = require('co-cache')({
  expire: 10000
});

//新建一个话题
exports.addTopic = function (data) {
  return Topic.create(data);
};

//通过id获取一个话题,并增加pv 1
exports.getTopicById = function (id) {
  return Topic.findByIdAndUpdate(id, {$inc: {pv: 1}}).exec();
};

//通过标签和页码获取10个话题
exports.getTopicsByTab = cache(function getTopicsByTab(tab, p) {
  var query = {};
  if (tab) { query.tab = tab; }
  return Topic.find(query).skip((p - 1) * 10).sort('-updated_at').limit(10).select('-content').exec();
}, {
  key: function (tab, p) {
    tab = tab || 'all';
    return this.name + ':' + tab + ':' + p;
  }
});

//获取用户所有话题
exports.getTopicsByName = function (name) {
  return Topic.find({'user.name': name}).sort('-updated_at').exec();
};

//通过id增加一个话题的评论数
exports.incCommentById = function (id) {
  return Topic.findByIdAndUpdate(id, {$inc: {comment: 1}}).exec();
};

//获取5条最新未评论的话题
exports.getNoReplyTopics = cache(function getNoReplyTopics() {
  return Topic.find({comment: 0}).sort('-updated_at').limit(5).select('title').exec();
});

//获取不同标签的话题数
exports.getTopicsCount = cache(function getTopicsCount(tab) {
  var query = {};
  if (tab) { query.tab = tab; }
  return Topic.count(query).exec();
}, {
  key: function (tab) {
    tab = tab || 'all';
    return this.name + ':' + tab;
  }
});

comment.js

var Comment = require('../models').Comment;

//添加一条评论
exports.addComment = function (data) {
  return Comment.create(data);
};

//根据话题id获取对应评论
exports.getCommentsByTopicId = function (id) {
  return Comment.find({topic_id: id}).sort('updated_at').exec();
};

最后,通过 core.js 聚合并导出模型函数:

core.js

var Comment = require('./comment');
var Topic = require('./topic');
var User = require('./user');

module.exports = {
  get $User () {
    return User;
  },

  get $Comment () {
    return Comment;
  },

  get $Topic () {
    return Topic;
  }
};

不知读者有没有发现,至今为止我们并没有写一行错误处理的代码,数据库查询或更新抛出的错误最终会被 koa-errorhandler 捕获。