後端小白的我,是如何成功搭建 express+mongodb 的簡潔部落格網站後端的
前言
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
// 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,謝謝。
專案地址:
本部落格系統的系列文章:
9. 最後
小汪也是第一次搭建 node 後端專案,也參考了其他專案。
對 全棧開發 有興趣的朋友,可以掃下方二維碼,關注我的公眾號,我會不定期更新有價值的內容。
微信公眾號:BiaoChenXuYing
分享 前端、後端開發 等相關的技術文章,熱點資源,全棧程式設計師的成長之路。
關注公眾號並回復 福利 便免費送你視訊資源,絕對乾貨。
相關推薦
小白也能動手搭建屬於自己的部落格網站
目前,我們在大多數的網站搭建教程中都是基於linux系統,可是,對於有些對linux系統完全沒有一點了解的朋友來說,這並不是一個好的訊息,今天,我為大家寫一篇基於騰訊雲windows server 2008伺服器的網站搭建教程,並且以小白式的教程展示給大家,如果你是一名大學
後端小白的我,是如何成功搭建 express+mongodb 的簡潔部落格網站後端的
前言 blog-node 是採用了主流的前後端分離思想的,主裡只講 後端。 blog-node 專案是 node + express + mongodb 的進行開發的,專案已經開源,專案地址在 github 上。 1. 後端 1.1 已經實現功能 登入
搭建一個自己的部落格網站——前端(一
前臺頁面搭建 前臺頁面 前臺頁面 前臺的頁面,主要使用bootstrap和jq。在靜態頁面開發中使用了gulp。gulp功能多多嘛,雖然我用的sublime,很多功能不用gulp也能做到,不過還是有很多東西用
Hexo+GitHub+Netlify一站式搭建屬於自己的部落格網站
喜歡的話請關注我的個人部落格我在馬路邊,此文為博主原創,轉載請標明出處。 “吾生也有涯,而知也無涯。” 都說每個做技術的人都應該有一個屬於自己的部落格網站,但是總是因為種種事情半途而棄,藉著剛剛搭建完部落格的熱情寫一下是如何搭建此部落格的,其實現在搭建部落格很簡單,這套部落格就是採用
搭建 Github Pages 個人部落格網站
目錄 引言 關於部落格 寫部落格對於程式猿來說,應該是個優秀的習慣,個人也覺得蠻高大上的 ^_^。網上的部落格論壇網站也多種多樣,個人覺得在長久以來的不斷競爭淘汰中,各大網站的功能等可能都相差無幾了,選擇自己稍微偏好的
Vue.js小白入門,搭建開發環境
最近Vue.js的熱度持續上升,甚至有標題說2017再不會Vue.js就out了。而作為一個不排斥前段的後端碼農來說,當然也要跟得上時代。近來準備放下手中的DOM操作,來一次Vue.js從入門到放棄。現將環境搭建過程記錄下來。 環境準備 Node.js
如何快速的搭建一個maven+springmvc的專案,適合小白易懂,進來看看
第2步: 第3步: 第4步: 建立完成之後點選專案右鍵選擇java EE Tools 選擇第二個建立web.xml 第5步: 開啟pom.xml進行新增依賴 <!-- 統一版本號 --> <properties>
有1000瓶水,其中有一瓶有毒,小白鼠只要嘗一點帶毒的水24小時後就會死亡至少要多少隻小白鼠才能在24小時
給1000個瓶分別標上如下標籤(10位長度): 0000000001 (第1瓶) 0000000010 (第2瓶) 0000000011 (第3瓶) ...... 1111101000 (第1000瓶) 從編號最後1位是1的所有的瓶子裡面取出1滴混在一起(比如從
有1000瓶水,其中有一瓶有毒,小白鼠只要嘗一點帶毒的水24小時後就會死亡,至少要多少隻小白鼠才能在24小時時鑑別出那瓶水有毒?
我來解釋一下,並給出一個方案,時間不是問題,24小時內肯定可以找出有毒的那瓶。 給1000個瓶分別標上如下標籤(10位長度): 0000000001 (第1瓶) 0000000010 (第2瓶) 0000000011 (第3瓶) ...... 1111101000 (第1000瓶) 從編號最後1
零基礎,想從書本入門python的小白我推薦這本書!
推薦 graphic image 第3版 tex src 圖形 color .py 今天要分享的也是一本Python的學習書籍——《Python程序設計 第3版》,這本書是一本針對所有層次的Python讀者而作的Python入門書。別的就不多介紹了,希望今天分享的這個可以幫
後端小白的VUE入門筆記, 進階篇
使用 vue-cli( 腳手架) 搭建專案 基於vue-cli 建立一個模板專案 通過 npm root -g 可以檢視vue全域性安裝目錄,進而知道自己有沒有安裝vue-cli 如果沒有安裝的話,使用如下命令全域性安裝 cnpm install -g vue-cli 建立一個基於webpack的新專案,
後端小白的Bootstrap筆記
柵格系統 下面這張圖是Bootstrap對柵格系統有關係數的規定 什麼是柵格體統? 柵格系統是Bootstrap提供的移動優先的網格系統, 各個分界點如上: 576px 720px 992px 1200px 一行最多盛放12列, 從上圖中也能看出一共是5種響應尺寸(分別對應不同的尺寸的螢幕) 其實大白話講
《小白滴滴系列》-線程和進程(小白學習,內容均參考網絡資料)
多個 系統 資料 搶占式 線程 並發 強制 資源 資源分配 1、進程就是操作系統將資源分配成一塊一塊的內存 2、線程就是在進程中運行的多個程序 3、線程是程序運行的最小單位,而進程則是分配資源的最小單位。 4、一個進程可以有多個線程 5、任務調度:采取時間片輪轉搶占式執行,
姜一襲蒹葭宮弟子所著嘅白衫,烏發松松噉垂喺腦後
一件事 baidu 對象 使用 努力 樓梯 第一個 而且 印象 姜一襲蒹葭宮弟子所著嘅白衫,烏發松松噉垂喺腦後,而家努力擒蒹葭宮高達九萬九千九百九十九級嘅階梯。九萬九千樓梯如龍蛇般迤邐而上,逐漸消失喺霧靄重重嘅山脈深處。莊姜汗濕衣衫,擡首睇咗睇雲霧繚繞嘅仙宮,宜得將自己長出
web前端小白案例,愛新鮮抽屜式特效
前端 小白 案例 特效 源碼 知識點:企業布局技巧,如何高效的編寫CSS樣式,常用選擇器,基本標簽,動態布局,盒子模型,jquery類庫調用,JS特效編寫,JS編程思維等。 對前端感興趣或者正在學習web前端的小夥伴可以來前端群:189394454,每天都會有幹貨分享。html代碼:
Maven多模塊,Dubbo分布式服務框架,SpringMVC,前後端分離項目,基礎搭建,搭建過程出
接口實現 url 代碼 blacklist order compiler ply 整合過程 ram 一、Maven多模塊項目的創建 我們需要建立一個多模塊的maven項目,其目錄結構為其中student-api用於暴露接口;student-service用語處理業務
Linux下安裝SVN服務端小白教程
空格 password eat section logs ini sta http .cn 轉載:https://www.cnblogs.com/liuxianan/p/linux_install_svn_server.html 安裝 使用yum安裝非常簡單: yum in
小白福音, 零基礎入門軟體測試 首選必備課程
第1章 課程介紹 本章將從軟體測試的起源與發展、測試行業的現狀及職業生涯規劃等整體做介紹。 1-1 課程介紹 1-2 軟體測試的發展 1-3 軟體測試的發展與職業規劃 1-4 軟體測試之“獨孤九劍”
跑步,對於不少健身小白來說,是減重的必備招數
要知道,跑步簡直就是街知巷聞的招式,無論是否健身的人群,都可以使用到這種方法,堅持晨跑與夜跑的人群,也是不在少數。 那麼,如果有減重需求的朋友,跑步這種方法可以嗎?當你天天跑步,重量都沒有下去時,到底是什麼情況?針對這個問題,小編會結合相關的知識在下面為大家展開討論! 一、跑步是減
小白黑蘋果安裝N卡的方法和安裝驅動後開機黑屏的解決方法
1、安裝驅動前。先對clover的config.plist進行設定。CsrActiveConfig由0x3改成0x0。如下圖所示。 2、SystemParameters的設定,如下圖所示。 3、Graphocs這項,所有的勾勾我全部不設定(我的是麗臺k62