博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
后端小白的我,是如何成功搭建 express+mongodb 的简洁博客网站后端的
阅读量:6949 次
发布时间:2019-06-27

本文共 26761 字,大约阅读时间需要 89 分钟。

前言

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

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

效果请看

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

// modulesconst 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 setupapp.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 servermongodb.connect();//将路由文件引入const route = require('./routes/index');//初始化所有路由route(app);// catch 404 and forward to error handlerapp.use(function(req, res, next) {	next(createError(404));});// error handlerapp.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 
*/const consola = require('consola')const CONFIG = require('../app.config.js')const mongoose = require('mongoose')const autoIncrement = require('mongoose-auto-increment')// remove DeprecationWarningmongoose.set('useFindAndModify', false)// mongoose Promisemongoose.Promise = global.Promise// mongooseexports.mongoose = mongoose// connectexports.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 
*/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 
*/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 
*/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 dependenciesnpm install # serve with hot reload at localhost: 3000npm start # build for production with minification请使用 pm2 ,可以永久运行在服务器上,且不会一报错 node 程序就挂了。复制代码

8. 项目地址

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

项目地址:

本博客系统的系列文章:

9. 最后

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

参考项目:

全栈开发 有兴趣的朋友,可以扫下方二维码,关注我的公众号,我会不定期更新有价值的内容。

微信公众号:BiaoChenXuYing 分享 前端、后端开发 等相关的技术文章,热点资源,全栈程序员的成长之路。

关注公众号并回复 福利 便免费送你视频资源,绝对干货。

福利详情请点击:

你可能感兴趣的文章