前言
blog-node 是采用了主流的前后端分离思想的,主里只讲 后端。
blog-node 项目是 node + express + mongodb 的进行开发的,项目已经开源,项目地址在 github 上。
效果请看 http://biaochenxuying.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 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