Node.js多人博客系统
一些说明:这个项目,我前后大概用了两星期的时间才写完(其实没写完,还有很多功能可以添加)。作为一个实习上班党,还是挺艰辛的。经常,写着写着,看不懂前面的逻辑了或者忘了写到哪了,可能是比较累(懒)吧,有些地方的逻辑其实有一点乱。不过,大部分功能还是实现了的,作为一个后端开发,界面美化,用户体验什么的,还是emmmmm,有待商榷的。
目录
1、实现功能
登录、注册、发表博客、发表评论、更改密码、退出登录、删除账号、更改头像、搜索、浏览次数、回复量等等....
其中,发表博客,需以MarkDown格式发表。
2、主要技术
Express、art-template、multer、Markdown等等,数据库使用MongoDB。
3、简单介绍
登录和注册等权限认证,均使用session来实现,上传头像则是用“multer”模块实现的。
密码使用MD5进行加密,加密后再存入数据库中。用户发表博客,展现时,将博客内容,解析为MarkDown样式。
其他就是一些简单的CRUD操作了。
4、使用模块
"art-template": "^4.12.2",
"blueimp-md5": "^2.10.0",
"body-parser": "^1.18.2",
"bootstrap": "^3.3.7",
"busboy": "^0.2.14",
"express": "^4.16.2",
"express-art-template": "^1.0.0",
"express-session": "^1.15.6",
"github-markdown-css": "^2.10.0",
"jquery": "^3.3.1",
"markdown": "^0.5.0",
"mongoose": "^4.13.0",
"multer": "^1.3.1"
5、目录架构
6、app.js
var express = require('express')
var path = require('path')
var bodyParser = require('body-parser')
var session = require('express-session')
var router = require('./router')
var app = express()
app.use('/public/', express.static(path.join(__dirname, './public/')))
app.use('/node_modules/', express.static(path.join(__dirname, './node_modules/')))
app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views/')) // 默认就是 ./views 目录
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
// 在 Express 这个框架中,默认不支持 Session 和 Cookie
// 但是我们可以使用第三方中间件:express-session 来解决
// 1. npm install express-session
// 2. 配置 (一定要在 app.use(router) 之前)
// 3. 使用
// 当把这个插件配置好之后,我们就可以通过 req.session 来发访问和设置 Session 成员了
// 添加 Session 数据:req.session.foo = 'bar'
// 访问 Session 数据:req.session.foo
app.use(session({
// 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
// 目的是为了增加安全性,防止客户端恶意伪造
secret: 'litong',
resave: false,
saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
}))
// 把路由挂载到 app 中
app.use(router)
app.listen(8888, function () {
console.log('running...')
})
7、路由
var express = require('express')
var user = require('./controllers/userController');
var index = require('./controllers/indexController');
var topics = require('./controllers/topicController');
var comment = require('./controllers/commentController');
var router = express.Router()
router.use('/', index);
router.use('/', user);
router.use('/', topics);
router.use('/', comment);
router.use(function (req, res) {
res.render("./404.html");
});
module.exports = router
indexController.js
var express = require('express');
var router = express.Router();
var Topic = require('./../models/topic');
router.get('/', function (req, res) {
var select = req.query.select
if (select == '' || select == null) {
Topic.find(function (err, topics) {
if (err) {
return res.status(500).send('Server error');
}
// console.log(topics)
for (var key in topics) {
// topics
}
res.render('index.html', {
topics: topics,
user: req.session.user
})
});
} else {
var pattern = new RegExp(select, "i");
Topic.find({
$or: [
{topic_content: pattern},
{topic_title: pattern},
{topic_author_nick: pattern}
]
}
, function (err, topics) {
if (err) {
return res.status(500).send('Server error');
}
console.log(topics)
for (var key in topics) {
// topics
}
res.render('index.html', {
topics: topics,
user: req.session.user
})
});
}
})
module.exports = router
topicController.js
var express = require('express');
var Topic = require('./../models/topic');
var Comment = require('./../models/comment');
var router = express.Router();
var markdown = require('markdown').markdown;
router.get('/topics/new', function (req, res) {
res.render('./topic/new.html', {
user: req.session.user
})
});
router.post('/topics/new', function (req, res) {
var body = req.body;
if (req.session.user == undefined || req.session.user == null){
res.status(200).json({
err_code: 1,
message: '请先登录'
})
}
body.topic_author_id=req.session.user._id;
body.topic_author_avatar=req.session.user.avatar;
body.topic_author_nick=req.session.user.nickname;
body._id=null;
// console.log(body);
new Topic(body).save(function (err, topic) {
if (err) {
return res.status(500).json({
err_code: 500,
message: 'Internal error.'
})
}
res.status(200).json({
err_code: 0,
message: 'OK'
})
})
// res.render('./topic/new.html', {
// user: req.session.user
// })
});
router.get('/topics', function (req, res) {
Topic.findById(req.query.id.replace(/"/g, ''),function (err, topic) {
if (err) {
return res.status(500).send('Server error.');
}
// console.log(topic);
if (req.session.user == undefined || req.session.user == null){
Comment.find({topic_id:req.query.id.replace(/"/g, '')},function(err,comments){
if (err) {
return res.status(500).send('Server error.');
}
// console.log(comments);
res.render('./topic/show.html', {
topic :topic,
content:markdown.toHTML(topic.topic_content),
comments:comments
})
})
}else {
Comment.find({topic_id:req.query.id.replace(/"/g, '')},function(err,comments){
if (err) {
return res.status(500).send('Server error.');
}
// console.log(comments);
res.render('./topic/show.html', {
user: req.session.user,
topic :topic,
content:markdown.toHTML(topic.topic_content),
comments:comments
})
})
}
topic.traffic++;
Topic.findByIdAndUpdate(req.query.id.replace(/"/g, ''),topic,function (err, data) {
})
})
});
module.exports = router;
commentController.js
var express = require('express');
var Comment = require('./../models/comment');
var Topic = require('./../models/topic');
var router = express.Router();
router.post('/comment/new', function (req, res) {
var body = req.body;
if (req.session.user == undefined || req.session.user == null){
res.status(200).json({
err_code: 1,
message: '请先登录'
})
}
body.comment_author_id=req.session.user._id;
body.comment_author_nick=req.session.user.nickname;
body.topic_id=body.topic_id.replace(/"/g, '');
body._id=null;
// console.log(body);
new Comment(body).save(function (err, comment) {
if (err) {
return res.status(500).json({
err_code: 500,
message: 'Internal error.'
})
}
res.status(200).json({
err_code: 0,
message: 'OK'
})
Topic.findById(body.topic_id,function (err, topic) {
if (!err)
topic.reply++;
Topic.findByIdAndUpdate(body.topic_id,topic,function (err, data) {
})
})
})
});
module.exports = router;
userController.js
var express = require('express');
var User = require('./../models/user');
var md5 = require('blueimp-md5');
var multer = require('multer');
var path = require('path');
//设置保存路径
var upload = multer({ dest: 'public/uploads/' })
var router = express.Router();
router.get('/login', function (req, res) {
res.render('login.html')
})
router.post('/login', function (req, res) {
// 1. 获取表单数据
// 2. 查询数据库用户名密码是否正确
// 3. 发送响应数据
var body = req.body
User.findOne({
email: body.email,
password: md5(md5(body.password))
}, function (err, user) {
if (err) {
return res.status(500).json({
err_code: 500,
message: err.message
})
}
// 如果邮箱和密码匹配,则 user 是查询到的用户对象,否则就是 null
if (!user) {
return res.status(200).json({
err_code: 1,
message: 'Email or password is invalid.'
})
}
// 用户存在,登陆成功,通过 Session 记录登陆状态
req.session.user = user
res.status(200).json({
err_code: 0,
message: 'OK'
})
})
})
router.get('/register', function (req, res) {
res.render('register.html')
})
router.post('/register', function (req, res) {
// 1. 获取表单提交的数据
// req.body
// 2. 操作数据库
// 判断改用户是否存在
// 如果已存在,不允许注册
// 如果不存在,注册新建用户
// 3. 发送响应
var body = req.body;
User.findOne({
$or: [{
email: body.email
},
{
nickname: body.nickname
}
]
}, function (err, data) {
if (err) {
return res.status(500).json({
success: false,
message: '服务端错误'
})
}
// console.log(data)
if (data) {
// 邮箱或者昵称已存在
return res.status(200).json({
err_code: 1,
message: 'Email or nickname aleady exists.'
})
return res.send(`邮箱或者密码已存在,请重试`)
}
// 对密码进行 md5 重复加密
body.password = md5(md5(body.password))
new User(body).save(function (err, user) {
if (err) {
return res.status(500).json({
err_code: 500,
message: 'Internal error.'
})
}
// 注册成功,使用 Session 记录用户的登陆状态
req.session.user = user
// Express 提供了一个响应方法:json
// 该方法接收一个对象作为参数,它会自动帮你把对象转为字符串再发送给浏览器
res.status(200).json({
err_code: 0,
message: 'OK'
})
// 服务端重定向只针对同步请求才有效,异步请求无效
// res.redirect('/')
})
})
})
router.get('/logout', function (req, res) {
// 清除登陆状态
req.session.user = null
// 重定向到登录页
res.redirect('/login')
})
router.get('/settings/profile', function (req, res) {
if (req.session.user == undefined || req.session.user == null){
res.redirect('/login')
}
res.render('./settings/profile.html', {
user: req.session.user
})
});
router.post('/settings/profile', function (req, res) {
// console.log(req.body)
if (req.session.user == undefined || req.session.user == null){
res.redirect('/login')
}
var body = req.body;
var user = req.session.user;
// console.log(body.avatar)
for (var key in body) {
if (body[key]!= null && body[key]!= undefined && body[key]!= ''&&key!='email'){
user[key] = body[key];
}
}
User.findByIdAndUpdate(req.session.user._id,user,function (err, user1) {
if (err) {
return res.status(500)
}
req.session.user = user
res.render('./settings/profile.html', {
user: body
});
})
});
router.get('/settings/admin', function (req, res) {
if (req.session.user == undefined || req.session.user == null){
res.redirect('/login')
}
res.render('./settings/admin.html', {
user: req.session.user
})
});
router.post('/settings/admin', function (req, res) {
if (req.session.user == undefined || req.session.user == null) {
res.status(200).json({
err_code: 1,
message: '请先登录!'
})
}
User.findOne({
email: req.session.user.email,
password: md5(md5(req.body.oldPassword))
}, function (err, user) {
if (err) {
return res.status(500).json({
err_code: 500,
message: err.message
})
}
if (user==null) {
return res.status(200).json({
err_code: 2,
message: '密码错误'
})
}
user.password = md5(md5(req.body.password));
User.findByIdAndUpdate(req.session.user._id,user,function (err, data) {
if (err) {
return res.status(500).json({
err_code: 500,
message: err.message
})
}
res.status(200).json({
err_code: 0,
message: '修改成功!'
})
})
});
});
router.post('/upload', upload.single("image"), function(req, res, next) {
//文件路径
var image=req.file.path;
// console.log(req.file.path);
res.status(200).json({
err_code: 0,
message: '修改成功!',
path:image
})
});
router.post('/settings/admin/delete', function (req, res) {
if (req.session.user == undefined || req.session.user == null){
res.status(200).json({
err_code: 1,
message: '请先登录!',
})
}
var user = req.session.user;
User.remove({
_id:req.session.user._id
},function (err, user) {
if (err) {
return res.status(500)
}
// console.log(user)
req.session.user = null;
res.status(200).json({
err_code: 1,
message: '删除当前账号成功!',
})
})
});
module.exports = router;
//注册的另一种写法
// router.post('/register', async function (req, res) {
// var body = req.body
// try {
// if (await User.findOne({ email: body.email })) {
// return res.status(200).json({
// err_code: 1,
// message: '邮箱已存在'
// })
// }
// if (await User.findOne({ nickname: body.nickname })) {
// return res.status(200).json({
// err_code: 2,
// message: '昵称已存在'
// })
// }
// // 对密码进行 md5 重复加密
// body.password = md5(md5(body.password))
// // 创建用户,执行注册
// await new User(body).save()
// res.status(200).json({
// err_code: 0,
// message: 'OK'
// })
// } catch (err) {
// res.status(500).json({
// err_code: 500,
// message: err.message
// })
// }
// })
8、视图
index.html
{{extend './_layouts/home.html'}}
{{block 'title'}}{{'多人博客 - 首页'}}{{/block}}
{{block 'body'}}
<section class="container">
<ul class="media-list">
{{each topics}}
<li class="media">
<div class="media-left">
<a href="#">
<img width="40" height="40" class="media-object" src="{{ $value.topic_author_avatar }}" alt="{{ $value.topic_author_nick }}">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="/topics?id={{ $value._id }}">{{ $value.topic_title }}</a></h4>
<p> {{$value.reply}} 个回复 • {{$value.traffic}} 次浏览 • {{$value.created_time}}</p>
</div>
</li>
{{/each}}
</ul>
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</section>
{{/block}}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/public/css/login.css">
</head>
<body>
<div class="main">
<div class="header">
<a href="/">
<img float="center" src="/public/img/logo3.png" height="110px" alt="">
</a>
<h1>用户登录</h1>
</div>
<form id="login_form">
<div class="form-group">
<label for="">邮箱</label>
<input type="email" class="form-control" id="" name="email" placeholder="Email" autofocus>
</div>
<div class="form-group">
<label for="">密码</label>
<a class="pull-right" href="">忘记密码?</a>
<input type="password" class="form-control" id="" name="password" placeholder="Password">
</div>
<div class="checkbox">
<label>
<input type="checkbox">记住我
</label>
</div>
<button type="submit" class="btn btn-success btn-block">登录</button>
</form>
<div class="message">
<p>没有账号? <a href="/register">点击创建</a>.</p>
</div>
</div>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
$('#login_form').on('submit', function (e) {
e.preventDefault()
var formData = $(this).serialize()
console.log(formData)
$.ajax({
url: '/login',
type: 'post',
data: formData,
dataType: 'json',
success: function (data) {
var err_code = data.err_code
if (err_code === 0) {
// window.alert('注册成功!')
// 服务端重定向针对异步请求无效
window.location.href = '/'
} else if (err_code === 1) {
window.alert('邮箱或者密码错误')
} else if (err_code === 500) {
window.alert('服务器忙,请稍后重试!')
}
}
})
})
</script>
</body>
</html>
new.html
{{extend '../_layouts/home.html'}}
{{block 'title'}}{{'多人博客 - 首页'}}{{/block}}
{{block 'body'}}
<section class="container">
<div class="row">
<div class="col-md-5">
<form id="topic_form" >
<div class="form-group">
<label for="topicType">选择板块</label>
<select name="topic_type" id="topicType" class="form-control">
<option value="1">分享</option>
<option value="2">问答</option>
<option value="3">招聘</option>
<option value="4">客户端测试</option>
</select>
</div>
<div class="form-group">
<label for="topicTitle">标题</label>
<input type="text" class="form-control" id="topicTitle" name="topic_title" placeholder="请输入标题" maxlength="20">
</div>
<div class="form-group">
<label for="topicContent">内容</label>
<textarea class="form-control" rows="3" id="topicContent" name="topic_content"></textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</section>
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script>
$('#topic_form').on('submit', function (e) {
e.preventDefault()
var formData = $(this).serialize()
$.ajax({
url: '/topics/new',
type: 'post',
data: formData,
dataType: 'json',
success: function (data) {
var err_code = data.err_code
if (err_code === 0) {
// window.alert('注册成功!')
// 服务端重定向针对异步请求无效
window.location.href = '/'
} else if (err_code === 1) {
window.alert('请先登录!')
} else if (err_code === 500) {
window.alert('服务器忙,请稍后重试!')
}
}
})
})
</script>
{{/block}}
9、效果图