Egg-CNode源码学习

1、实体类分析

1.1 用户对象

   const UserSchema = new Schema({
    name: { type: String },
    loginname: { type: String }, //登录名
    pass: { type: String },//密码 
    email: { type: String },//邮箱
    url: { type: String },
    profile_image_url: { type: String },
    location: { type: String },
    signature: { type: String },
    profile: { type: String },//简介
    weibo: { type: String },
    avatar: { type: String },//头像
  
   //github信息
    githubId: { type: String }, 
    githubUsername: { type: String },
    githubAccessToken: { type: String },
    is_block: { type: Boolean, default: false },

    score: { type: Number, default: 0 },//分数
    topic_count: { type: Number, default: 0 },//话题数量
    reply_count: { type: Number, default: 0 },//回复数量
    follower_count: { type: Number, default: 0 },
    following_count: { type: Number, default: 0 },
    collect_tag_count: { type: Number, default: 0 },
    collect_topic_count: { type: Number, default: 0 },
    create_at: { type: Date, default: Date.now },
    update_at: { type: Date, default: Date.now },
    is_star: { type: Boolean },
    level: { type: String },
    active: { type: Boolean, default: false },//是否激活

    receive_reply_mail: { type: Boolean, default: false },
    receive_at_mail: { type: Boolean, default: false },
    from_wp: { type: Boolean },

    retrieve_time: { type: Number },
    retrieve_key: { type: String },

    accessToken: { type: String },
  });

1.2 话题对象

const TopicSchema = new Schema({
    title: { type: String },
    content: { type: String },
    author_id: { type: ObjectId },
    top: { type: Boolean, default: false }, // 置顶帖
    good: { type: Boolean, default: false }, // 精华帖
    lock: { type: Boolean, default: false }, // 被锁定主题
    reply_count: { type: Number, default: 0 },//回复的数量
    visit_count: { type: Number, default: 0 },//查看的数量
    collect_count: { type: Number, default: 0 },//收藏的数量
    create_at: { type: Date, default: Date.now },
    update_at: { type: Date, default: Date.now },
    last_reply: { type: ObjectId },//最新的回复ID
    last_reply_at: { type: Date, default: Date.now },//最新回复时间
    content_is_html: { type: Boolean },
    tab: { type: String },//话题分类
    deleted: { type: Boolean, default: false },//是否删除
  });

1.3 话题收藏对象

const TopicCollectSchema = new Schema({
    user_id: { type: ObjectId },  //用户ID
    topic_id: { type: ObjectId },  //话题ID
    create_at: { type: Date, default: Date.now },
  });

1.4 回复对象

 const ReplySchema = new Schema({
    content: { type: String }, //回复内容
    topic_id: { type: ObjectId },//话题id
    author_id: { type: ObjectId },//作者ID
    reply_id: { type: ObjectId },//回复的内容ID???
    create_at: { type: Date, default: Date.now },
    update_at: { type: Date, default: Date.now },
    content_is_html: { type: Boolean },
    ups: [ Schema.Types.ObjectId ],//赞的数量
    deleted: { type: Boolean, default: false },
  },

1.5 消息对象

 const MessageSchema = new Schema({
    type: { type: String },  //消息类型是回复文章还是at人。
    master_id: { type: ObjectId }, //被回复消息人的id
    author_id: { type: ObjectId },//回复消息人id
    topic_id: { type: ObjectId },//主题id
    reply_id: { type: ObjectId },//回复内容id
    has_read: { type: Boolean, default: false },//消息是否已经读了
    create_at: { type: Date, default: Date.now },
  });

2、项目结构和模块分析

2.1 项目结构

2.1.1 、项目结构按照egg项目格式。
2.1.2、egg-cnode中将api路由和web页面路由进行分离,同时controller中的web都是为web服务,api'都是为客户端api服务。
2.1.3、通过中间件权限验证,在路由中配置中间件。
2.1.4、通过中间件对用户发表话题信息进行限制,每天限制发表10条。(通过缓存进行控制)
2.1.5、错误页面配置。
2.1.6、通过中间件分页pagination配置问题。可以对最大页数进行限制。
2.1.7、断点调试可以使用vscode

2.2 项目模块

2.2.1 egg-mongoose模块

3、部分代码思路

3.1 CNode首页信息获取

    //1、分页查询topics
    const topics = await this.service.topic.getTopicsByQuery(query, options);
    //2、取排行榜上的用户,倒序根据积分查询,先从缓存中查询,缓存中没有再在数据库中查询
    let tops = await this.service.cache.get('tops');
    if (!tops) {
      tops = await this.service.user.getUsersByQuery(
        { is_block: false },
        { limit: 10, sort: '-score' }
      );
      await this.service.cache.setex('tops', tops, 60);
    }
    //3、取0回复的主题
    let no_reply_topics = await this.service.cache.get('no_reply_topics');  
   //4、获取分页数据,取分页数据,分页数据进行缓存,根据分页的tab进行缓存数据
    const pagesCacheKey = JSON.stringify(query) + 'pages';
    let pages = await this.service.cache.get(pagesCacheKey);
    if (!pages) {
      const all_topics_count = await this.service.topic.getCountByQuery(query);
      pages = Math.ceil(all_topics_count / limit);
      await this.service.cache.setex(pagesCacheKey, pages, 60 * 1);
    }

3.2 CNode话题详情获取

1、//获取topic的完整信息
    const [ topic, author, replies ] = await service.topic.getFullTopic(topic_id);
1.1  获取话题详情:const topic = await this.ctx.model.Topic.findOne(query);
1.2  替换数据库中name,让用户可以超链接点击: topic.linkedContent = this.service.at.linkUsers(topic.content);
1.3 获取话题详细作者信息:const author = await this.service.user.getUserById(topic.author_id);
1.4、获取回复详情:const replies = await this.service.reply.getRepliesByTopicId(topic._id);
1.4.1、根据id获取话题回复列表:可以将replies打印出来看看。
async getRepliesByTopicId(id) {
    const query = { topic_id: id, deleted: false };
   //先获取所有回复信息
    let replies = await this.ctx.model.Reply.find(query, '', {
      sort: 'create_at',
    }).exec();
   //通过promise获取所有回复的作者信息
    return Promise.all(
      replies.map(async item => {
        const author = await this.service.user.getUserById(item.author_id);
        item.author = author || { _id: '' };
        item.content = await this.service.at.linkUsers(item.content);
        return item;
      })
    );
  }
2、 // 增加 visit_count
    topic.visit_count += 1;
    // 写入 DB
    await service.topic.incrementVisitCount(topic_id);

3.2 topic回复

1
    const topicId = ctx.params.topic_id;
    const content = (ctx.request.body.content || '').trim();
   //是回复topic还是回复topic下面的用户,如果为null就是回复topic
    const replyId = ctx.request.body.reply_id || null;

    const { topic, author } = await ctx.service.topic.getTopicById(topicId);
1.1 //根据topic id获取topic详细信息
 const topic = await this.ctx.model.Topic.findOne({ _id: id }).exec();
1.2  //根据topic中的作者id获取作者详细信息
const author = await this.service.user.getUserById(topic.author_id);
1.3 //更新最新回复,在获取topic详细信息中会用到,这里回复不需要使用
    let last_reply = null;
    if (topic.last_reply) {
      last_reply = await this.service.reply.getReplyById(topic.last_reply);
    }
2  //保存回复详细信息
    const reply = await ctx.service.reply.newAndSave(content, topicId, ctx.request.user.id, replyId);
3、//更新最后回复
    await ctx.service.topic.updateLastReply(topicId, reply._id);
    // 发送 at 消息,并防止重复 at 作者
    const newContent = content.replace('@' + author.loginname + ' ', '');
4、//通过分析详细回复内容,向用户发送消息,这里只对不是作者的用户是at操作(这里一定要注意理解,主要是回复内容有reply类型和at类型)
    await ctx.service.at.sendMessageToMentionUsers(newContent, topicId, ctx.request.user.id, reply._id);
4.1 //分析消息内容,批量对用户at
let users = await this.service.user.getUsersByNames(this.fetchUsers(text));
    users = users.filter(user => {
      return !user._id.equals(authorId);
    });
    return Promise.all(
      users.map(user => {
        return this.service.message.sendAtMessage(
          user._id,
          authorId,
          topicId,
          reply_id
        );
      })
    );

5、//用户发表了回复,可以进行增加积分操作
    const user = await ctx.service.user.getUserById(ctx.request.user.id);
    user.score += 5;
    user.reply_count += 1;
    await user.save();
6、//这里只是简单的回复内容,如果不是作者就需要发送reply消息
    if (topic.author_id.toString() !== ctx.request.user.id.toString()) {
      await ctx.service.message.sendReplyMessage(topic.author_id, ctx.request.user.id, topic._id, reply._id);
    }

    ctx.body = {
      success: true,
      reply_id: reply._id,
    };
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容