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,
};
}