后端小白的我,是如何成功搭建 express+mongodb 的简洁博客网站后端的

54次阅读

共计 22028 个字符,预计需要花费 56 分钟才能阅读完成。

前言
blog-node 是采用了主流的前后端分离思想的,主里只讲 后端。
blog-node 项目是 node + express + mongodb 的进行开发的,项目已经开源,项目地址在 github 上。
效果请看 http:// 乐趣区.cn/main.html
1. 后端
1.1 已经实现功能

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

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 乐趣区 <https://github.com/ 乐趣区 >
*/

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 乐趣区 <https://github.com/ 乐趣区 >
*/

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 乐趣区 <https://github.com/ 乐趣区 >
*/

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 乐趣区 <https://github.com/ 乐趣区 >
*/

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/ 乐趣区 /blog-react 管理后台:https://github.com/ 乐趣区 /blog-react-admin
后端:https://github.com/ 乐趣区 /blog-node
blog:https://github.com/ 乐趣区 /blog

本博客系统的系列文章:

react + node + express + ant + mongodb 的简洁兼时尚的博客网站
react + Ant Design + 支持 markdown 的 blog-react 项目文档说明
基于 node + express + mongodb 的 blog-node 项目文档说明
服务器小白的我, 是如何将 node+mongodb 项目部署在服务器上并进行性能优化的

9. 最后
小汪也是第一次搭建 node 后端项目,也参考了其他项目。
参考项目:1. nodepress2. React-Express-Blog-Demo
对 全栈开发 有兴趣的朋友,可以扫下方二维码,关注我的公众号,我会不定期更新有价值的内容。
微信公众号:乐趣区分享 前端、后端开发 等相关的技术文章,热点资源,全栈程序员的成长之路。
关注公众号并回复 福利 便免费送你视频资源,绝对干货。
福利详情请点击:免费资源分享 –Python、Java、Linux、Go、node、vue、react、javaScript

正文完
 0