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,
    };
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容