Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于 node express mongodb 的 blog-node 项目文档说明 #17

Open
9 of 12 tasks
biaochenxuying opened this issue Nov 25, 2018 · 0 comments
Open
9 of 12 tasks
Assignees
Labels
MongoDB MongoDB node.js node.js

Comments

@biaochenxuying
Copy link
Owner

biaochenxuying commented Nov 25, 2018

项目结构图

前言

blog-node 是采用了主流的前后端分离思想的,主里只讲 后端。

blog-node 项目是 node + express + mongodb 的进行开发的,项目已经开源,项目地址在 github 上。

效果请看 http://biaochenxuying.cn/main.html

1. 后端

1.1 已经实现功能

  • 登录
  • 文章管理
  • 标签管理
  • 评论
  • 留言管理
  • 用户管理
  • 友情链接管理
  • 时间轴管理
  • 身份验证

1.2 待实现功能

  • 点赞、留言和评论 的通知管理
  • 个人中心(用来设置博主的各种信息)
  • 工作台( 接入百度统计接口,查看网站浏览量和用户访问等数据 )

2. 技术

  • node
  • cookie-parser : "~1.4.3"
  • crypto : "^1.0.1"
  • express: "~4.16.0"
  • express-session : "^1.15.6",
  • http-errors : "~1.6.2",
  • mongodb : "^3.1.8",
  • mongoose : "^5.3.7",
  • mongoose-auto-increment : "^5.0.1",
  • yargs : "^12.0.2"

3. 主文件 app.js

// modules
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const session = require('express-session');

// import 等语法要用到 babel 支持
require('babel-register');

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser('blog_node_cookie'));
app.use(
	session({
		secret: 'blog_node_cookie',
		name: 'session_id', //# 在浏览器中生成cookie的名称key,默认是connect.sid
		resave: true,
		saveUninitialized: true,
		cookie: { maxAge: 60 * 1000 * 30, httpOnly: true }, //过期时间
	}),
);

const mongodb = require('./core/mongodb');

// data server
mongodb.connect();

//将路由文件引入
const route = require('./routes/index');

//初始化所有路由
route(app);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
	next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
	// set locals, only providing error in development
	res.locals.message = err.message;
	res.locals.error = req.app.get('env') === 'development' ? err : {};

	// render the error page
	res.status(err.status || 500);
	res.render('error');
});

module.exports = app;

4. 数据库 core/mongodb.js

/**
 * Mongoose module.
 * @file 数据库模块
 * @module core/mongoose
 * @author  biaochenxuying <https://github.com/biaochenxuying>
 */

const consola = require('consola')
const CONFIG = require('../app.config.js')
const mongoose = require('mongoose')
const autoIncrement = require('mongoose-auto-increment')

// remove DeprecationWarning
mongoose.set('useFindAndModify', false)


// mongoose Promise
mongoose.Promise = global.Promise

// mongoose
exports.mongoose = mongoose

// connect
exports.connect = () => {

	// 连接数据库
	mongoose.connect(CONFIG.MONGODB.uri, {
		useCreateIndex: true,
		useNewUrlParser: true,
		promiseLibrary: global.Promise
	})

	// 连接错误
	mongoose.connection.on('error', error => {
		consola.warn('数据库连接失败!', error)
	})

	// 连接成功
	mongoose.connection.once('open', () => {
		consola.ready('数据库连接成功!')
	})

	// 自增 ID 初始化
	autoIncrement.initialize(mongoose.connection)
	
	// 返回实例
	return mongoose
}

5. 数据模型 Model

这里只介绍 用户、文章和评论 的模型。

5.1 用户

用户的字段都有设置类型 type,大多都设置了默认值 default ,邮箱设置了验证规则 validate,密码保存用了 crypto 来加密。

用了中间件自增 ID 插件 mongoose-auto-increment。

/**
 * User model module.
 * @file 权限和用户数据模型
 * @module model/user
 * @author biaochenxuying <https://github.com/biaochenxuying>
 */

const crypto = require('crypto');
const { argv } = require('yargs');
const { mongoose } = require('../core/mongodb.js');
const autoIncrement = require('mongoose-auto-increment');

const adminSchema = new mongoose.Schema({
	// 名字
	name: { type: String, required: true, default: '' },

	// 用户类型 0:博主 1:其他用户
	type: { type: Number, default: 1 },

	// 手机
	phone: { type: String, default: '' },

	//封面
	img_url: { type: String, default: '' },

	// 邮箱
	email: { type: String, required: true, validate: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/ },

	// 个人介绍
	introduce: { type: String, default: '' },

	// 头像
	avatar: { type: String, default: 'user' },

	// 密码
	password: {
		type: String,
		required: true,
		default: crypto
			.createHash('md5')
			.update(argv.auth_default_password || 'root')
			.digest('hex'),
	},

	// 创建日期
	create_time: { type: Date, default: Date.now },

	// 最后修改日期
	update_time: { type: Date, default: Date.now },
});

// 自增 ID 插件配置
adminSchema.plugin(autoIncrement.plugin, {
	model: 'User',
	field: 'id',
	startAt: 1,
	incrementBy: 1,
});

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

5.2 文章

文章是分类型的:文章类型 => 1: 普通文章,2: 简历,3: 管理员介绍
而且简历和管理员介绍的文章只能是各自一篇(因为前台展示那里有个导航 关于我 ,就是请求管理员介绍这篇文章的,简历也是打算这样子用的),普通文章可以是无数篇。

点赞的用户 like_users 那里应该只保存用户 id 的,这个后面修改一下。

/**
 * Article model module.
 * @file 文章数据模型
 * @module model/article
 * @author biaochenxuying <https://github.com/biaochenxuying>
 */

const { mongoose } = require('../core/mongodb.js');
const autoIncrement = require('mongoose-auto-increment');

// 文章模型
const articleSchema = new mongoose.Schema({
	// 文章标题
	title: { type: String, required: true, validate: /\S+/ },

	// 文章关键字(SEO)
	keyword: [{ type: String, default: '' }],

	// 作者
	author: { type: String, required: true, validate: /\S+/ },

	// 文章描述
	desc: { type: String, default: '' },

	// 文章内容
	content: { type: String, required: true, validate: /\S+/ },

	// 字数
	numbers: { type: String, default: 0 },

	// 封面图
	img_url: { type: String, default: 'https://upload-images.jianshu.io/upload_images/12890819-80fa7517ab3f2783.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240' },

	// 文章类型 => 1: 普通文章,2: 简历,3: 管理员介绍
	type: { type: Number, default: 1 },

	// 文章发布状态 => 0 草稿,1 已发布
	state: { type: Number, default: 1 },

	// 文章转载状态 => 0 原创,1 转载,2 混合
	origin: { type: Number, default: 0 },

	// 文章标签
	tags: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tag', required: true }],

	comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment', required: true }],

	// 文章分类
	category: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category', required: true }],

	// 点赞的用户
	like_users: [
		{
			// 用户id
			id: { type: mongoose.Schema.Types.ObjectId },

			// 名字
			name: { type: String, required: true, default: '' },

			// 用户类型 0:博主 1:其他用户
			type: { type: Number, default: 1 },

			// 个人介绍
			introduce: { type: String, default: '' },

			// 头像
			avatar: { type: String, default: 'user' },

			// 创建日期
			create_time: { type: Date, default: Date.now },
		},
	],

	// 其他元信息
	meta: {
		views: { type: Number, default: 0 },
		likes: { type: Number, default: 0 },
		comments: { type: Number, default: 0 },
	},

	// 创建日期
	create_time: { type: Date, default: Date.now },

	// 最后修改日期
	update_time: { type: Date, default: Date.now },
});

// 自增 ID 插件配置
articleSchema.plugin(autoIncrement.plugin, {
	model: 'Article',
	field: 'id',
	startAt: 1,
	incrementBy: 1,
});

// 文章模型
module.exports = mongoose.model('Article', articleSchema);

5.3 评论

评论功能是实现了简单的三级评论的,第三者的评论(就是别人对一级评论进行再评论)放在 other_comments 里面。

/**
 * Comment model module.
 * @file 评论数据模型
 * @module model/comment
 * @author biaochenxuying <https://github.com/biaochenxuying>
 */

const { mongoose } = require('../core/mongodb.js');
const autoIncrement = require('mongoose-auto-increment');

// 评论模型
const commentSchema = new mongoose.Schema({
	// 评论所在的文章 id
	article_id: { type: mongoose.Schema.Types.ObjectId, required: true },

	// content
	content: { type: String, required: true, validate: /\S+/ },

	// 是否置顶
	is_top: { type: Boolean, default: false },

	// 被赞数
	likes: { type: Number, default: 0 },

	user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },

	// 父评论的用户信息
	user: {
		// 用户id
		user_id: { type: mongoose.Schema.Types.ObjectId },

		// 名字
		name: { type: String, required: true, default: '' },

		// 用户类型 0:博主 1:其他用户
		type: { type: Number, default: 1 },

		// 头像
		avatar: { type: String, default: 'user' },
	},

	// 第三者评论
	other_comments: [
		{
			user: {
				id: { type: mongoose.Schema.Types.ObjectId },

				// 名字
				name: { type: String, required: true, default: '' },

				// 用户类型 0:博主 1:其他用户
				type: { type: Number, default: 1 },
			},

			// content
			content: { type: String, required: true, validate: /\S+/ },

			// 状态 => 0 待审核 / 1 通过正常 / -1 已删除 / -2 垃圾评论
			state: { type: Number, default: 1 },

			// 创建日期
			create_time: { type: Date, default: Date.now },
		},
	],

	// 状态 => 0 待审核 / 1 通过正常 / -1 已删除 / -2 垃圾评论
	state: { type: Number, default: 1 },

	// 创建日期
	create_time: { type: Date, default: Date.now },

	// 最后修改日期
	update_time: { type: Date, default: Date.now },
});

// 自增 ID 插件配置
commentSchema.plugin(autoIncrement.plugin, {
	model: 'Comment',
	field: 'id',
	startAt: 1,
	incrementBy: 1,
});

// 标签模型
module.exports = mongoose.model('Comment', commentSchema);

其他模块的具体需求,都是些常用的逻辑可以实现的,也很简单,这里就不展开讲了。

6. 路由接口 routes

6.1 主文件

/*
*所有的路由接口
*/
const user = require('./user');
const article = require('./article');
const comment = require('./comment');
const message = require('./message');
const tag = require('./tag');
const link = require('./link');
const category = require('./category');
const timeAxis = require('./timeAxis');

module.exports = app => {
	app.post('/login', user.login);
	app.post('/logout', user.logout);
	app.post('/loginAdmin', user.loginAdmin);
	app.post('/register', user.register);
	app.post('/delUser', user.delUser);
	app.get('/currentUser', user.currentUser);
	app.get('/getUserList', user.getUserList);

	app.post('/addComment', comment.addComment);
	app.post('/addThirdComment', comment.addThirdComment);
	app.post('/changeComment', comment.changeComment);
	app.post('/changeThirdComment', comment.changeThirdComment);
	app.get('/getCommentList', comment.getCommentList);

	app.post('/addArticle', article.addArticle);
	app.post('/updateArticle', article.updateArticle);
	app.post('/delArticle', article.delArticle);
	app.get('/getArticleList', article.getArticleList);
	app.get('/getArticleListAdmin', article.getArticleListAdmin);
	app.post('/getArticleDetail', article.getArticleDetail);
	app.post('/likeArticle', article.likeArticle);

	app.post('/addTag', tag.addTag);
	app.post('/delTag', tag.delTag);
	app.get('/getTagList', tag.getTagList);

	app.post('/addMessage', message.addMessage);
	app.post('/addReplyMessage', message.addReplyMessage);
	app.post('/delMessage', message.delMessage);
	app.post('/getMessageDetail', message.getMessageDetail);
	app.get('/getMessageList', message.getMessageList);

	app.post('/addLink', link.addLink);
	app.post('/updateLink', link.updateLink);
	app.post('/delLink', link.delLink);
	app.get('/getLinkList', link.getLinkList);

	app.post('/addCategory', category.addCategory);
	app.post('/delCategory', category.delCategory);
	app.get('/getCategoryList', category.getCategoryList);

	app.post('/addTimeAxis', timeAxis.addTimeAxis);
	app.post('/updateTimeAxis', timeAxis.updateTimeAxis);
	app.post('/delTimeAxis', timeAxis.delTimeAxis);
	app.get('/getTimeAxisList', timeAxis.getTimeAxisList);
	app.post('/getTimeAxisDetail', timeAxis.getTimeAxisDetail);
};

6.2 文章

各模块的列表都是用了分页的形式的。

import Article from '../models/article';
import User from '../models/user';
import { responseClient, timestampToTime } from '../util/util';

exports.addArticle = (req, res) => {
	// if (!req.session.userInfo) {
	// 	responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
	// 	return;
	// }
	const { title, author, keyword, content, desc, img_url, tags, category, state, type, origin } = req.body;
	let tempArticle = null
	if(img_url){
		tempArticle = new Article({
			title,
			author,
			keyword: keyword ? keyword.split(',') : [],
			content,
			numbers: content.length,
			desc,
			img_url,
			tags: tags ? tags.split(',') : [],
			category: category ? category.split(',') : [],
			state,
			type,
			origin,
		});
	}else{
		tempArticle = new Article({
			title,
			author,
			keyword: keyword ? keyword.split(',') : [],
			content,
			numbers: content.length,
			desc,
			tags: tags ? tags.split(',') : [],
			category: category ? category.split(',') : [],
			state,
			type,
			origin,
		});
	}
	
	tempArticle
		.save()
		.then(data => {
			responseClient(res, 200, 0, '保存成功', data);
		})
		.catch(err => {
			console.log(err);
			responseClient(res);
		});
};

exports.updateArticle = (req, res) => {
	// if (!req.session.userInfo) {
	// 	responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
	// 	return;
	// }
	const { title, author, keyword, content, desc, img_url, tags, category, state, type, origin, id } = req.body;
	Article.update(
		{ _id: id },
		{
			title,
			author,
			keyword: keyword ? keyword.split(','): [],
			content,
			desc,
			img_url,
			tags: tags ? tags.split(',') : [],
			category:category ? category.split(',') : [],
			state,
			type,
			origin,
		},
	)
		.then(result => {
			responseClient(res, 200, 0, '操作成功', result);
		})
		.catch(err => {
			console.error(err);
			responseClient(res);
		});
};

exports.delArticle = (req, res) => {
	let { id } = req.body;
	Article.deleteMany({ _id: id })
		.then(result => {
			if (result.n === 1) {
				responseClient(res, 200, 0, '删除成功!');
			} else {
				responseClient(res, 200, 1, '文章不存在');
			}
		})
		.catch(err => {
			console.error('err :', err);
			responseClient(res);
		});
};

// 前台文章列表
exports.getArticleList = (req, res) => {
	let keyword = req.query.keyword || null;
	let state = req.query.state || '';
	let likes = req.query.likes || '';
	let tag_id = req.query.tag_id || '';
	let category_id = req.query.category_id || '';
	let pageNum = parseInt(req.query.pageNum) || 1;
	let pageSize = parseInt(req.query.pageSize) || 10;
	let conditions = {};
	if (!state) {
		if (keyword) {
			const reg = new RegExp(keyword, 'i'); //不区分大小写
			conditions = {
				$or: [{ title: { $regex: reg } }, { desc: { $regex: reg } }],
			};
		}
	} else if (state) {
		state = parseInt(state);
		if (keyword) {
			const reg = new RegExp(keyword, 'i');
			conditions = {
				$and: [
					{ $or: [{ state: state }] },
					{ $or: [{ title: { $regex: reg } }, { desc: { $regex: reg } }, { keyword: { $regex: reg } }] },
				],
			};
		} else {
			conditions = { state };
		}
	}

	let skip = pageNum - 1 < 0 ? 0 : (pageNum - 1) * pageSize;
	let responseData = {
		count: 0,
		list: [],
	};
	Article.countDocuments(conditions, (err, count) => {
		if (err) {
			console.log('Error:' + err);
		} else {
			responseData.count = count;
			// 待返回的字段
			let fields = {
				title: 1,
				author: 1,
				keyword: 1,
				content: 1,
				desc: 1,
				img_url: 1,
				tags: 1,
				category: 1,
				state: 1,
				type: 1,
				origin: 1,
				comments: 1,
				like_User_id: 1,
				meta: 1,
				create_time: 1,
				update_time: 1,
			};
			let options = {
				skip: skip,
				limit: pageSize,
				sort: { create_time: -1 },
			};
			Article.find(conditions, fields, options, (error, result) => {
				if (err) {
					console.error('Error:' + error);
					// throw error;
				} else {
					let newList = [];
					if (likes) {
						// 根据热度 likes 返回数据
						result.sort((a, b) => {
							return b.meta.likes - a.meta.likes;
						});
						responseData.list = result;
					} else if (category_id) {
						// 根据 分类 id 返回数据
						result.forEach(item => {
							if (item.category.indexOf(category_id) > -1) {
								newList.push(item);
							}
						});
						let len = newList.length;
						responseData.count = len;
						responseData.list = newList;
					} else if (tag_id) {
						// 根据标签 id 返回数据
						result.forEach(item => {
							if (item.tags.indexOf(tag_id) > -1) {
								newList.push(item);
							}
						});
						let len = newList.length;
						responseData.count = len;
						responseData.list = newList;
					} else {
						responseData.list = result;
					}
					responseClient(res, 200, 0, '操作成功!', responseData);
				}
			});
		}
	});
};

// 后台文章列表
exports.getArticleListAdmin = (req, res) => {
	let keyword = req.query.keyword || null;
	let state = req.query.state || '';
	let likes = req.query.likes || '';
	let pageNum = parseInt(req.query.pageNum) || 1;
	let pageSize = parseInt(req.query.pageSize) || 10;
	let conditions = {};
	if (!state) {
		if (keyword) {
			const reg = new RegExp(keyword, 'i'); //不区分大小写
			conditions = {
				$or: [{ title: { $regex: reg } }, { desc: { $regex: reg } }],
			};
		}
	} else if (state) {
		state = parseInt(state);
		if (keyword) {
			const reg = new RegExp(keyword, 'i');
			conditions = {
				$and: [
					{ $or: [{ state: state }] },
					{ $or: [{ title: { $regex: reg } }, { desc: { $regex: reg } }, { keyword: { $regex: reg } }] },
				],
			};
		} else {
			conditions = { state };
		}
	}

	let skip = pageNum - 1 < 0 ? 0 : (pageNum - 1) * pageSize;
	let responseData = {
		count: 0,
		list: [],
	};
	Article.countDocuments(conditions, (err, count) => {
		if (err) {
			console.log('Error:' + err);
		} else {
			responseData.count = count;
			// 待返回的字段
			let fields = {
				title: 1,
				author: 1,
				keyword: 1,
				content: 1,
				desc: 1,
				img_url: 1,
				tags: 1,
				category: 1,
				state: 1,
				type: 1,
				origin: 1,
				comments: 1,
				like_User_id: 1,
				meta: 1,
				create_time: 1,
				update_time: 1,
			};
			let options = {
				skip: skip,
				limit: pageSize,
				sort: { create_time: -1 },
			};
			Article.find(conditions, fields, options, (error, result) => {
				if (err) {
					console.error('Error:' + error);
					// throw error;
				} else {
					if (likes) {
						result.sort((a, b) => {
							return b.meta.likes - a.meta.likes;
						});
					}
					responseData.list = result;
					responseClient(res, 200, 0, '操作成功!', responseData);
				}
			})
				.populate([
					{ path: 'tags', },
					{ path: 'comments',  },
					{ path: 'category',  },
				])
				.exec((err, doc) => {});
		}
	});
};

// 文章点赞
exports.likeArticle = (req, res) => {
	if (!req.session.userInfo) {
		responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
		return;
	}
	let { id, user_id } = req.body;
	Article.findOne({ _id: id })
		.then(data => {
			let fields = {};
			data.meta.likes = data.meta.likes + 1;
			fields.meta = data.meta;
			let like_users_arr = data.like_users.length ? data.like_users : [];
			User.findOne({ _id: user_id })
				.then(user => {
					let new_like_user = {};
					new_like_user.id = user._id;
					new_like_user.name = user.name;
					new_like_user.avatar = user.avatar;
					new_like_user.create_time = user.create_time;
					new_like_user.type = user.type;
					new_like_user.introduce = user.introduce;
					like_users_arr.push(new_like_user);
					fields.like_users = like_users_arr;
					Article.update({ _id: id }, fields)
						.then(result => {
							responseClient(res, 200, 0, '操作成功!', result);
						})
						.catch(err => {
							console.error('err :', err);
							throw err;
						});
				})
				.catch(err => {
					responseClient(res);
					console.error('err 1:', err);
				});
		})
		.catch(err => {
			responseClient(res);
			console.error('err 2:', err);
		});
};

// 文章详情
exports.getArticleDetailByType = (req, res) => {
	let { type } = req.body;
	if (!type) {
		responseClient(res, 200, 1, '文章不存在 !');
		return;
	}
	Article.findOne({ type: type }, (Error, data) => {
		if (Error) {
			console.error('Error:' + Error);
			// throw error;
		} else {
			data.meta.views = data.meta.views + 1;
			Article.updateOne({ type: type }, { meta: data.meta })
				.then(result => {
					responseClient(res, 200, 0, '操作成功 !', data);
				})
				.catch(err => {
					console.error('err :', err);
					throw err;
				});
		}
	})
		.populate([
			{ path: 'tags', select: '-_id' },
			{ path: 'category', select: '-_id' },
			{ path: 'comments', select: '-_id' },
		])
		.exec((err, doc) => {
			// console.log("doc:");          // aikin
			// console.log("doc.tags:",doc.tags);          // aikin
			// console.log("doc.category:",doc.category);           // undefined
		});
};

// 文章详情
exports.getArticleDetail = (req, res) => {
	let { id } = req.body;
	let type = Number(req.body.type) || 1; //文章类型 => 1: 普通文章,2: 简历,3: 管理员介绍
	console.log('type:', type);
	if (type === 1) {
		if (!id) {
			responseClient(res, 200, 1, '文章不存在 !');
			return;
		}
		Article.findOne({ _id: id }, (Error, data) => {
			if (Error) {
				console.error('Error:' + Error);
				// throw error;
			} else {
				data.meta.views = data.meta.views + 1;
				Article.updateOne({ _id: id }, { meta: data.meta })
					.then(result => {
						responseClient(res, 200, 0, '操作成功 !', data);
					})
					.catch(err => {
						console.error('err :', err);
						throw err;
					});
			}
		})
			.populate([
				{ path: 'tags',  },
				{ path: 'category',  },
				{ path: 'comments',  },
			])
			.exec((err, doc) => {
				// console.log("doc:");          // aikin
				// console.log("doc.tags:",doc.tags);          // aikin
				// console.log("doc.category:",doc.category);           // undefined
			});
	} else {
		Article.findOne({ type: type }, (Error, data) => {
			if (Error) {
				console.log('Error:' + Error);
				// throw error;
			} else {
				if (data) {
					data.meta.views = data.meta.views + 1;
					Article.updateOne({ type: type }, { meta: data.meta })
						.then(result => {
							responseClient(res, 200, 0, '操作成功 !', data);
						})
						.catch(err => {
							console.error('err :', err);
							throw err;
						});
				} else {
					responseClient(res, 200, 1, '文章不存在 !');
					return;
				}
			}
		})
			.populate([
				{ path: 'tags',  },
				{ path: 'category',  },
				{ path: 'comments',  },
			])
			.exec((err, doc) => {});
	}
};

6.3 评论

评论是有状态的:状态 => 0 待审核 / 1 通过正常 / -1 已删除 / -2 垃圾评论。
管理一级和三级评论是设置前台能不能展示的,默认是展示,如果管理员看了,是条垃圾评论就 设置为 -1 或者 -2 ,进行隐藏,前台就不会展现了。

import { responseClient } from '../util/util';
import Comment from '../models/comment';
import User from '../models/user';
import Article from '../models/article';

//获取全部评论
exports.getCommentList = (req, res) => {
	let keyword = req.query.keyword || null;
	let comment_id = req.query.comment_id || null;
	let pageNum = parseInt(req.query.pageNum) || 1;
	let pageSize = parseInt(req.query.pageSize) || 10;
	let conditions = {};
	if (comment_id) {
		if (keyword) {
			const reg = new RegExp(keyword, 'i'); //不区分大小写
			conditions = {
				_id: comment_id,
				content: { $regex: reg },
			};
		} else {
			conditions = {
				_id: comment_id,
			};
		}
	} else {
		if (keyword) {
			const reg = new RegExp(keyword, 'i'); //不区分大小写
			conditions = {
				content: { $regex: reg },
			};
		}
	}

	let skip = pageNum - 1 < 0 ? 0 : (pageNum - 1) * pageSize;
	let responseData = {
		count: 0,
		list: [],
	};
	Comment.countDocuments(conditions, (err, count) => {
		if (err) {
			console.error('Error:' + err);
		} else {
			responseData.count = count;
			// 待返回的字段
			let fields = {
				article_id: 1,
				content: 1,
				is_top: 1,
				likes: 1,
				user_id: 1,
				user: 1,
				other_comments: 1,
				state: 1,
				create_time: 1,
				update_time: 1,
			};
			let options = {
				skip: skip,
				limit: pageSize,
				sort: { create_time: -1 },
			};
			Comment.find(conditions, fields, options, (error, result) => {
				if (err) {
					console.error('Error:' + error);
					// throw error;
				} else {
					responseData.list = result;
					responseClient(res, 200, 0, '操作成功!', responseData);
				}
			});
		}
	});
};

// 添加一级评论
exports.addComment = (req, res) => {
	if (!req.session.userInfo) {
		responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
		return;
	}
	let { article_id, user_id, content } = req.body;
	User.findById({
		_id: user_id,
	})
		.then(result => {
			// console.log('result :', result);
			if (result) {
				let userInfo = {
					user_id: result._id,
					name: result.name,
					type: result.type,
					avatar: result.avatar,
				};
				let comment = new Comment({
					article_id: article_id,
					content: content,
					user_id: user_id,
					user: userInfo,
				});
				comment
					.save()
					.then(commentResult => {
						Article.findOne({ _id: article_id }, (errors, data) => {
							if (errors) {
								console.error('Error:' + errors);
								// throw errors;
							} else {
								data.comments.push(commentResult._id);
								data.meta.comments = data.meta.comments + 1;
								Article.updateOne({ _id: article_id }, { comments: data.comments, meta: data.meta })
									.then(result => {
										responseClient(res, 200, 0, '操作成功 !', commentResult);
									})
									.catch(err => {
										console.error('err :', err);
										throw err;
									});
							}
						});
					})
					.catch(err2 => {
						console.error('err :', err2);
						throw err2;
					});
			} else {
				responseClient(res, 200, 1, '用户不存在');
			}
		})
		.catch(error => {
			console.error('error :', error);
			responseClient(res);
		});
};

// 添加第三者评论
exports.addThirdComment = (req, res) => {
	if (!req.session.userInfo) {
		responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
		return;
	}
	let { article_id, comment_id, user_id, content } = req.body;

	Comment.findById({
		_id: comment_id,
	})
		.then(commentResult => {
			User.findById({
				_id: user_id,
			})
				.then(userResult => {
					if (userResult) {
						let userInfo = {
							user_id: userResult._id,
							name: userResult.name,
							type: userResult.type,
							avatar: userResult.avatar,
						};
						let item = {
							user: userInfo,
							content: content,
						};
						commentResult.other_comments.push(item);
						Comment.updateOne(
							{ _id: comment_id },
							{
								other_comments: commentResult,
							},
						)
							.then(result => {
								responseClient(res, 200, 0, '操作成功', result);
								Article.findOne({ _id: article_id }, (errors, data) => {
									if (errors) {
										console.error('Error:' + errors);
										// throw errors;
									} else {
										data.meta.comments = data.meta.comments + 1;
										Article.updateOne({ _id: article_id }, { meta: data.meta })
											.then(result => {
												// console.log('result :', result);
												responseClient(res, 200, 0, '操作成功 !', result);
											})
											.catch(err => {
												console.log('err :', err);
												throw err;
											});
									}
								});
							})
							.catch(err1 => {
								console.error('err1:', err1);
								responseClient(res);
							});
					} else {
						responseClient(res, 200, 1, '用户不存在');
					}
				})
				.catch(error => {
					console.error('error :', error);
					responseClient(res);
				});
		})
		.catch(error2 => {
			console.error('error2 :', error2);
			responseClient(res);
		});
};

// 管理一级评论
exports.changeComment = (req, res) => {
	if (!req.session.userInfo) {
		responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
		return;
	}
	let { id, state } = req.body;
	Comment.updateOne(
		{ _id: id },
		{
			state: Number(state),
		},
	)
		.then(result => {
			responseClient(res, 200, 0, '操作成功', result);
		})
		.catch(err => {
			console.error('err:', err);
			responseClient(res);
		});
};

// 管理第三者评论
exports.changeThirdComment = (req, res) => {
	if (!req.session.userInfo) {
		responseClient(res, 200, 1, '您还没登录,或者登录信息已过期,请重新登录!');
		return;
	}
	let { comment_id, state, index } = req.body;
	Comment.findById({
		_id: comment_id,
	})
		.then(commentResult => {
			let i = index ? Number(index) : 0;
			if (commentResult.other_comments.length) {
				commentResult.other_comments[i].state = Number(state);
				Comment.updateOne(
					{ _id: comment_id },
					{
						other_comments: commentResult,
					},
				)
					.then(result => {
						responseClient(res, 200, 0, '操作成功', result);
					})
					.catch(err1 => {
						console.error('err1:', err1);
						responseClient(res);
					});
			} else {
				responseClient(res, 200, 1, '第三方评论不存在!', result);
			}
		})
		.catch(error2 => {
			console.log('error2 :', error2);
			responseClient(res);
		});
};

其他模块的具体需求,都是些常用的逻辑可以实现的,也很简单,这里就不展开讲了。

7. Build Setup ( 构建安装 )

# install dependencies
npm install 

# serve with hot reload at localhost: 3000
npm start 

# build for production with minification
请使用 pm2 ,可以永久运行在服务器上,且不会一报错 node 程序就挂了。

8. 项目地址

如果觉得该项目不错或者对你有所帮助,欢迎到 github 上给个 star,谢谢。

项目地址:

前台展示: https://github.com/biaochenxuying/blog-react

管理后台:https://github.com/biaochenxuying/blog-react-admin

后端:https://github.com/biaochenxuying/blog-node

blog:https://github.com/biaochenxuying/blog

本博客系统的系列文章:

9. 最后

小汪也是第一次搭建 node 后端项目,也参考了其他项目。

参考项目:
1. nodepress
2. React-Express-Blog-Demo

@biaochenxuying biaochenxuying added node.js node.js MongoDB MongoDB labels Nov 25, 2018
@biaochenxuying biaochenxuying self-assigned this Nov 25, 2018
@biaochenxuying biaochenxuying added this to node.js 相关 in Node.js Nov 25, 2018
@biaochenxuying biaochenxuying added this to MongoDB 相关 in MongoDB Nov 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MongoDB MongoDB node.js node.js
Projects
MongoDB
MongoDB 相关
Node.js
node.js 相关
Development

No branches or pull requests

1 participant