後端小白的我,是如何成功搭建 express+mongodb 的簡潔博客網站後端的

項目結構圖

前言

blog-node 是採用了主流的先後端分離思想的,主裏只講 後端。html

blog-node 項目是 node + express + mongodb 的進行開發的,項目已經開源,項目地址在 github 上。node

效果請看 biaochenxuying.cn/main.htmlreact

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

這裏只介紹 用戶、文章和評論 的模型。git

5.1 用戶

用戶的字段都有設置類型 type,大多都設置了默認值 default ,郵箱設置了驗證規則 validate,密碼保存用了 crypto 來加密。github

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

/**
 * 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 的,這個後面修改一下。express

/**
 * 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 裏面。npm

/**
 * 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);

複製代碼

其餘模塊的具體需求,都是些經常使用的邏輯能夠實現的,也很簡單,這裏就不展開講了。json

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 後端項目,也參考了其餘項目。

參考項目:

相關文章
相關標籤/搜索