Node.js多人博客系统

Node.js多人博客系统

一些说明:这个项目,我前后大概用了两星期的时间才写完(其实没写完,还有很多功能可以添加)。作为一个实习上班党,还是挺艰辛的。经常,写着写着,看不懂前面的逻辑了或者忘了写到哪了,可能是比较累(懒)吧,有些地方的逻辑其实有一点乱。不过,大部分功能还是实现了的,作为一个后端开发,界面美化,用户体验什么的,还是emmmmm,有待商榷的。


目录

Node.js多人博客系统

1、实现功能

2、主要技术

3、简单介绍

4、使用模块

5、目录架构

6、app.js

7、路由

indexController.js

topicController.js

commentController.js

 userController.js

8、视图

index.html

login.html

new.html

9、效果图

10、在线演示

11、源码下载


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">&laquo;</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">&raquo;</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、效果图

 

 

 

10、在线演示

http://193.112.74.72:8888/   

这个网站应该可以撑几个月。。。

11、源码下载

https://download.csdn.net/download/litongzero/10620889

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值