当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 荐 [Node.js] 基于NodeJS+Express+mongoDB+Bootstrap的博客系统实战

荐 [Node.js] 基于NodeJS+Express+mongoDB+Bootstrap的博客系统实战

2020年07月17日  | 移动技术网IT编程  | 我要评论

MyBlog实战

项目要求

  • a. 前台和后台的页面布局
    • 前台要求有首页、列表页、详情页面、登录、注册
    • 后台要求有登录页面、列表、添加修改页面
    • 页面要求简洁、美观、大方
  • b. 后台功能要求
    • 前台注册用户在后台的分页展示
    • 后台可以对分类进行管理
    • 后台可以对文章进行管理
    • 后台可以针对文章的评论进行展示
    • 后台需要登录才能进入后台管理系统
  • c. 前台功能要求
    • 首页按照分类展示对应的最新几条文章
    • 列表页可以根据不同的分类进行文章列表的切换
    • 详情页在登录的前提下,可以对文章进行评论(未实现,jQuery可能出问题了)
    • 前台用户可以正常的登录和注册

 
 

项目代码下载

MyBlog

 
 

实现效果展示

  • 总体效果展示在这里插入图片描述
  • 前台注册在这里插入图片描述
  • 前台登录在这里插入图片描述
  • 前台阅读文章
    在这里插入图片描述
  • 前台文章分类在这里插入图片描述
  • 后台版块首页
    在这里插入图片描述
  • 后台版块添加在这里插入图片描述
  • 后台版块修改在这里插入图片描述
  • 后台版块删除在这里插入图片描述
  • 后台内容首页在这里插入图片描述
  • 后台内容添加在这里插入图片描述
  • 后台内容修改在这里插入图片描述
  • 后台内容删除在这里插入图片描述
  • 退出系统在这里插入图片描述

准备工作

博客系统解析图

在这里插入图片描述
 

项目目录结构

在这里插入图片描述

各个文件的作用

在这里插入图片描述

js部分

  • 导入模块
var express = require('express');
//加载模板处理模块
var swig = require('swig');
var path = require('path');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');//中间件/可从request中获取body数据
var Cookies = require('cookies');
var User = require('./models/User');
  • 设置静态文件托管
app.use('/public', express.static(__dirname + '/public'));
  • 路由控制
app.use('/', require('./routers/main'));
app.use('/admin', require('./routers/admin'));
app.use('/api', require('./routers/api'));
  • 监听Http请求
mongoose.connect('mongodb://localhost:27017/iBlog', function (err) {
    if (err) {
        console.log('数据库连接失败');
        return;
    }
    else {
        console.log('数据库连接成功');
        app.listen(8081, 'localhost');
        console.log('Server is running at http://localhost:8081');
    }
})

 
 

前台流程

用户注册前端页面

判断用户名是否为空
判断用户名是否被注册过
判断密码不能为空
在输入两次密码是要保持一致

routerApi.post('/user/register', function (req, res) {
    var username = req.body.username;
    var password = req.body.password;
    var repassword = req.body.repassword;

    //用户名判空
    if (username === '') {
        responseData.code = '1';
        responseData.message = '用户名不能为空';
        res.json(responseData);
        return;
    }
    //密码检测,不能为空
    if (password === '' || repassword === '') {
        responseData.code = '2';
        responseData.message = '密码不能为空';
        res.json(responseData);
        return;
    }
    //两次密码需一样
    if (password !== repassword) {
        responseData.code = '3';
        responseData.message = '两次密码不一致';
        res.json(responseData);
        return;
    }
    //检测用户名是否已经被注册,如果数据库存在同名数据表示用户名已经被注册
    User.findOne({
        username: username
    }).then(function (userInfo) {
        if (userInfo) {
            //表示数据库中有该记录
            responseData.code = '4';
            responseData.message = '用户名已被注册';
            res.json(responseData);
            return;
        }
        //保存用户注册的信息到数据库中
        else {
            // 无上述情况//则可注册保存用户注册信息到数据库中//返回注册成功
            var userRegisterData = new User({
                username: username,
                password: password,
                isSuperAdmin: false,
                isAdmin: true
            });
            userRegisterData.save();//
            return;
        }
    }).then(function (newUserInfo) {
        console.log(newUserInfo);//
        responseData.code = '0';
        responseData.message = '注册成功';
        res.json(responseData);
        return;
    });

});

 

用户登录

判断用户名或密码是否被注册过
判断用户名或密码是否正确

routerApi.post('/user/login', function (req, res) {
    var uName = req.body.username;
    var pWord = req.body.password;
    //空值等检测放在前端处理

    //后台数据验证处理
    User.findOne({
        username: uName,
        password: pWord
    }).then(function (userInfo) {
        // console.log(userInfo);
        if (!userInfo) {
            responseData.code = '1';
            responseData.message = '用户名或密码错误';
            res.json(responseData);
            return;
        }
        // else {
        //此处登录验证成功,进入用户首页有两种方式:
        //1、前端根据返回的成功数据进行Url重定向实现//这样相当于二次请求服务器 TODO 思考
        //2、后台 用redirect 进行路由重定向
        // res.redirect('/');
        responseData.code = '0';
        responseData.message = '成功';
        //添加返回用户cookie数据
        responseData.userInfo = {
            _id: userInfo._id,
            username: userInfo.username
        };
        req.cookies.set('userInfo', JSON.stringify({
            _id: userInfo._id,
            username: userInfo.username
        }));
        res.json(responseData);
        // console.log('这里打印 登录成功服务端返回给客户端的 返回信息 ' + responseData);
        return;
        // }
    });
});
function loginFunc() {
    var loginBox = $("#loginBox");
    var userInfoBox = $("#userInfoBox");
    var username = loginBox.find('input[name="username"]').val();
    var password = loginBox.find('input[name="password"]').val();
    //取表单填写数据//
    if (username === '' || password === '') {
        alert('你的信息未填写完整...')
    }
    else {
        //采用 jQuery AJax方式上传
        $.ajax({
            type: 'post',
            url: 'api/user/login',
            data: {
                username: username,
                password: password
            },
            dataType: 'json',
            success: function (resData) {
                if (resData.code === '0') {
                    //登录成功
                    // alert(resData.message);
                    window.location.reload();
                }
                else {
                    alert(resData.message);
                }
            },
            error: function () {
                alert('Error');
            }

        });
    }

}

 

使用cookie保存用户

获取当前登录用户的类型//是否是管理员
只有超级管理员可以进行//用户管理//普通用户//只能进行模块//内容//留言等管理

app.use(function (req, res, next) {
    req.cookies = new Cookies(req, res);
    // console.log('这里打印服务端返回客户端的cookies  ' + req.cookies.get('userInfo'));
    //解析用户登录的cookies信息
    req.userInfo = {};
    if (req.cookies.get('userInfo')) {
        try {
            req.userInfo = JSON.parse(req.cookies.get('userInfo'));
            //获取当前登录用户的类型//是否是管理员
            //只有超级管理员可以进行//用户管理//普通用户//只能进行模块//内容//留言等管理
            User.findById(req.userInfo._id).then(function (userInfo) {
                req.userInfo.isAdmin = Boolean(userInfo.isAdmin);
                req.userInfo.isSuperAdmin = Boolean(userInfo.isSuperAdmin);
                next();
            });
        }
        catch (e) {
            // console.log('Cookies have some Error');
            // next();
        }
    }
    else {
        // console.log('不存在用户cookie 数据!');
        next();
    }
});

 

文章加载获取指定文章的所有评论

查询当前这篇内容的信息

routerApi.get('/comment', function (req, res) {
    var contentID = req.query.contentID || '';
    //查询当前这篇内容的信息
    Content.findOne({
        _id: contentID
    }).then(function (content) {
        responseData.data = content.comments.reverse();
        res.json(responseData);
    });
});

 

留言评论提交

查询当前这篇内容的信息

routerApi.post('/comment/post', function (req, res) {
    //内容的ID
    var contentID = req.body.contentID || '';
    //定义评论 数组中字段
    var postData = {
        username: req.userInfo.username,
        postTime: new Date(),
        content: req.body.content
    }
    //查询当前这篇内容的信息
    Content.findOne({
        _id: contentID
    }).then(function (content) {
        content.comments.push(postData);
        return content.save();
    }).then(function (newContent) {
        responseData.message = "评论成功";
        responseData.data = newContent;
        res.json(responseData);
    });
});

 

渲染当前内容所有的评论内容

判断有没有上一页/下一页
判断有没有评论
在没有评论的时候要保证上一页/下一页要隐藏

function renderComments() {
    var length = 0;
    if (comments !== null) length = comments.length;
    //用于处理评论分页
    pages = Math.ceil(length / limit);
    $lis = $(".pager li");
    $lis.eq(1).html(page + '/' + pages);
    var start = (page - 1) * limit;
    var end = Math.min(start + limit, length);
    if (page < 1) {
        page = 1;
        $lis.eq(0).html('<span>没有上一页了</span>');
    }
    else {
        $lis.eq(0).html('<a href="javascript:;">上一页</a>')
    }
    if (page > pages) {
        page = pages;
        $lis.eq(2).html('<span>没有下一页了</span>');
    }
    else {
        $lis.eq(2).html('<a href="javascript:;">下一页</a>')
    }

    //渲染评论列表
    $("#messageCount").html(length);
    var htmlStr = '';
    if (comments.length === 0) {
        htmlStr += '<div class="messageBox"><p>暂时还没有评论!</p></div>';
        $(".pager").hide();
    }
    else {
        for (var i = start; i < end; i++) {
            var username = (comments[i].username === undefined) ? '游客' : comments[i].username;
            htmlStr += '<div class="messageBox">'
            htmlStr += '<p class="messageLine clear"><span class="floatLeft">' + username + '</span>'
            htmlStr += '<span class="floatRight">' + StringToDate(comments[i].postTime).format('yyyy-MM-dd hh:mm') + '</span> </p>'
            htmlStr += '<p>' + comments[i].content + '</p>'
            htmlStr += '</div>';
        }
    }
    $(".messageList").html(htmlStr);
}

 

绑定上一页/下一页

$('.pager').delegate('a', 'click', function () {
    // alert('点击了 上/下 一页');
    if ($(this).parent().hasClass("previous")) {
        //上一页
        page--;
    } else {
        //下一页
        page++;
    }
    renderComments(comments);
});

 

每次页面重载时获取一下该文章的所有评论

$.ajax({
    type: 'get',
    url: '/api/comment',
    data: {
        contentID: $('#contentID').val()
    },
    success: function (resData) {
        // console.log(resData);
        comments = resData.data;
        renderComments();
    },
    // error: function (err) {
    //     alert(err);
    // }
});

 

程序主入口

router.get('/', function (req, res, next) {
    // console.log('渲染首页模板的用户数据 ' + JSON.stringify(req.userInfo));

    var reqPage = Number((req.query.page) === undefined ? 0 : req.query.page);
    data.category = req.query.category || '';
    data.count = 0;
    //分页
    data.page = reqPage <= 0 ? 1 : reqPage;
    data.limit = 3;
    data.pages = 0;
    //查询筛选条件
    var whereStr = {};
    if (data.category) {
        whereStr.category = data.category;
    }
    //如果用户未登录//游客 则只显示 首页--即无自己定制版块


    //读取某用户所有分类信息
    Category.find().where(whereStr).then(function (count) {

        data.count = count;
        //总页数
        data.pages = Math.ceil(data.count / data.limit);
        //取值不能超过pages
        data.page = Math.min(data.page, data.pages);
        //取值不能小于 1
        data.page = Math.max(data.page, 1);

        var skip = (data.page - 1) * data.limit;
        return Content.where(whereStr).find().limit(data.limit).skip(skip).populate(['category', 'user']).sort({
            addTime: -1
        });

    }).then(function (contents) {
        data.contents = contents;
        // console.log(data);
        res.render('main/mainIndex', data);
    });

});

 

内容详情

在点击阅读全文时,保证获取到当前文章的所有数据

router.get('/view', function (req, res, next) {
    var contentID = req.query.contentID || '';
    Content.findOne({
        _id: contentID
    }).then(function (content) {
        data.content = content;
        // console.log('点击查阅具体内容详情数据 ' + data);
        console.dir(data);

        //用户点击 阅读文章详情,阅读数进行统计写入数据
        content.views++;
        content.save();
        res.render('main/viewDetail', data);
    });
});

时间的处理

利用getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getMilliseconds()获取当前时间的信息
利用字符串转成日期类型

//时间格式化函数
Date.prototype.format = function (format) {
    var o = {
        "M+": this.getMonth() + 1, //month
        "d+": this.getDate(), //day
        "h+": this.getHours(), //hour
        "m+": this.getMinutes(), //minute
        "s+": this.getSeconds(), //second
        "q+": Math.floor((this.getMonth() + 3) / 3), //quarter
        "S": this.getMilliseconds() //millisecond
    }
    if (/(y+)/.test(format)) {
        format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(format)) {
            format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
        }
    }
    return format;
}


//+---------------------------------------------------
//| 字符串转成日期类型
//| 格式 MM/dd/YYYY MM-dd-YYYY YYYY/MM/dd YYYY-MM-dd
//+---------------------------------------------------
function StringToDate(DateStr) {

    var converted = Date.parse(DateStr);
    var myDate = new Date(converted);
    if (isNaN(myDate)) {
        var arys = DateStr.split('-');
        myDate = new Date(arys[0], --arys[1], arys[2]);
    }
    return myDate;
}

 
 

后台流程

管理首页

routerAdmin.get('/', function (req, res, next) {
    // res.send('后台管理首页');
    res.render('admin/adminIndex', {
        userInfo: req.userInfo
    });
});

 

用户管理

routerAdmin.get('/user', function (req, res, next) {
    // 从数据库中读取所有用户数据
    /*
     *   sort() 可对字段指定排序 传值 -1降序 1 升序
     *   对数据进行分页
     *   limit(number) 限制从数据库中取出条数
     *   skip(number) 忽略数据的条数
     *
     *   eg:每页显示2条
     *   第一页:1-2 skip 0  -> 当前页 -1 * limit
     *   第二页:3-4 skip 2 ->
     *   ...
     *
     * */
    // var page = 1;
    // console.log(req.query.page);
    var reqPage = Number((req.query.page) === undefined ? 0 : req.query.page);
    // console.log(reqPage);
    var page = reqPage <= 0 ? 1 : reqPage;
    var limit = 2;
    var pages = 0;
    var skip = (page - 1) * limit;
    //
    User.count().then(function (count) {
        // console.log(count);
        //总页数
        pages = Math.ceil(count / limit);
        //
        User.find().sort({_id: -1}).limit(limit).skip(skip).then(function (users) {
            // console.log(users);
            res.render('admin/user_index', {
                userInfo: req.userInfo,
                users: users,
                count: count,
                limit: limit,
                pages: pages,
                page: page
            });
        });
    });

});

 

分类首页

routerAdmin.get('/category', function (req, res, next) {
    // 从数据库中读取所有分类数据
    var reqPage = Number((req.query.page) === undefined ? 0 : req.query.page);
    var page = reqPage <= 0 ? 1 : reqPage;
    var limit = 2;
    var pages = 0;
    var skip = (page - 1) * limit;
    //
    Category.count().then(function (count) {
        // console.log(count);
        //总页数
        pages = Math.ceil(count / limit);
        //
        Category.find().sort({_id: -1}).limit(limit).skip(skip).then(function (categories) {
            // console.log('分类首页回显数据  ' + categories);
            res.render('admin/category_index', {
                userInfo: req.userInfo,
                categories: categories,
                count: count,
                limit: limit,
                pages: pages,
                page: page
            });
        });
    });


});

 

分类添加页面

routerAdmin.get('/category/add', function (req, res, next) {
    res.render('admin/category_add', {
        userInfo: req.userInfo
    });
});

 

分类添加数据上传

判断名字是否为空
判断分类是否存在

routerAdmin.post('/category/add', function (req, res, next) {
    // console.log(req.body);
    var name = req.body.name || '';
    if (name === '') {
        res.render('admin/error', {
            userInfo: req.userInfo,
            message: '名称不能为空',
            url: ''
        });
    }
    else {
        //名称部位空//验证数据库只能怪是否存在
        Category.findOne({
            name: name
        }).then(function (resData) {
            if (resData) {
                //数据库中已经存在
                res.render('admin/error', {
                    userInfo: req.userInfo,
                    message: '分类已经存在'
                });
            }
            else {
                //不存在则写入数据
                return new Category({
                    name: name
                }).save();
            }
        }).then(function (newCategory) {
            //返回新的分类数据
            res.render('admin/success', {
                userInfo: req.userInfo,
                message: '分类保存成功',
                url: '/admin/category',
                categories: newCategory
            });
        });
    }

});

 

分类编辑

判断分类信息是否存在

routerAdmin.get('/category/edit', function (req, res) {
    //获取要修改的数据//以表单形式展现出来
    var id = req.query.id || '';
    Category.findOne({
        _id: id
    }).then(function (category) {
        if (!category) {
            res.render('admin/error', {
                userInfo: req.userInfo,
                message: '分类信息不存在'
            });
            // Promise.reject(reason)方法返回一个用reason拒绝的Promise
            return Pramise.reject();
        }
        else {
            res.render('admin/category_edit', {
                userInfo: req.userInfo,
                category: category
            });
        }
    });

});

 

分类修改 保存

判断数据库是否有数据
用户没有做任何修改提交时候 判断处理【可放在前端判断】
要修改的分类名称是否在数据库中已存在

routerAdmin.post('/category/edit', function (req, res) {
    //获取要修改的分类信息
    var id = req.query.id || '';
    //获取Post提交过来的 修改的名称
    var newName = req.body.name || '';

    //判断数据库是否已有
    Category.findOne({
        _id: id
    }).then(function (category) {
        if (!category) {
            res.render('admin/error', {
                userInfo: req.userInfo,
                message: '分类信息不存在'
            });
            return Pramise.reject();
        }
        else {
            // 当用户没有做任何修改提交时候 判断处理【可放在前端判断】
            if (newName === category.name) {
                res.render('admin/error', {
                    userInfo: req.userInfo,
                    message: '修改成功',
                    url: '/admin/category'
                });
            }
            else {
                //要修改的分类名称是否在数据库中已存在
                return Category.findOne({
                    _id: {$ne: id},
                    name: newName
                });
            }

        }
    }).then(function (sameCategory) {
        if (sameCategory) {
            res.render('admin/error', {
                userInfo: req.userInfo,
                message: '数据库中已经存在同名分类',
            });
            return Pramise.reject();
        }
        else {
            return Category.update({
                    //条件-当前ID
                    _id: id
                }, {
                    //修改的内容- 更新的名称
                    name: newName
                }
            );
        }
    }).then(function () {
        res.render('admin/success', {
            userInfo: req.userInfo,
            message: '修改成功',
            url: '/admin/category'
        });
    });


});

 

分类删除

获取要删除的分类ID

routerAdmin.get('/category/delete', function (req, res) {
    //获取要删除的分类ID
    var id = req.query.id || '';
    Category.remove({
        _id: id
    }).then(function () {
        res.render('admin/success', {
            userInfo: req.userInfo,
            message: '删除成功',
            url: '/admin/category'
        })
    });
});

 

内容管理首页

routerAdmin.get('/content', function (req, res, next) {
    // res.render('admin/content_index',{});
    // 从数据库中读取所有分类数据
    var reqPage = Number((req.query.page) === undefined ? 0 : req.query.page);
    var page = reqPage <= 0 ? 1 : reqPage;
    var limit = 2;
    var pages = 0;
    var skip = (page - 1) * limit;
    //
    Content.count().then(function (count) {
        // console.log(count);
        //总页数
        pages = Math.ceil(count / limit);
        //
        Content.find().sort({addTime: -1}).limit(limit).skip(skip).populate('category').then(function (contents) {
            // console.log('分类首页回显数据' + contents);
            res.render('admin/content_index', {
                userInfo: req.userInfo,
                contents: contents,
                count: count,
                limit: limit,
                pages: pages,
                page: page
            });
        });
    });
});

 

内容添加

routerAdmin.get('/content/add', function (req, res, next) {
    //内容添加//下拉选择分类//从数据库取出分类数据
    Category.find().sort({_id: -1}).then(function (categories) {
        res.render('admin/content_add', {
            userInfo: req.userInfo,
            categories: categories
        });
    });
});

 

内容添加 数据上传

routerAdmin.post('/content/add', function (req, res, next) {
    //
    var postData = req.body;
    // console.log('添加内容传入的数据' + postData.category);
    // console.dir(postData.category);
    //字段检测等可放前端检测
    //前端检测 可对输入框等 进行响应交互等处理
    if (postData.category === '' || postData.title === '' || postData.content === '') {
        res.render('admin/error', {
            userInfo: req.userInfo,
            message: '有未填写的信息'
        });
        return;
    }
    else {
        //数据写入到数据库
        var newContent = new Content({
            category: postData.category,
            user: req.userInfo._id.toString(),
            title: postData.title,
            description: postData.description,
            content: postData.content
        });
        // console.log(newContent);
        newContent.save().then(function (rs) {
            res.render('admin/success', {
                userInfo: req.userInfo,
                message: '内容数据保存成功',
                url: '/admin/content'
            });
        });
    }
});

 

修改内容

判断内容信息是否存在

routerAdmin.get('/content/edit', function (req, res, next) {
    //
    var id = req.query.id || '';
    var resCategories = {};
    Category.find().sort({_id: -1}).then(function (categories) {
        resCategories = categories;
        return Content.findOne({
            _id: id
        }).populate('category').then(function (content) {
            if (!content) {
                res.render('admin/error', {
                    userInfo: req.userInfo,
                    message: '内容信息不存在'
                });
                // Promise.reject(reason)方法返回一个用reason拒绝的Promise
                return Pramise.reject();
            }
            else {
                res.render('admin/content_edit', {
                    userInfo: req.userInfo,
                    categories: resCategories,
                    content: content
                });
            }
        });
    });

});

 

保存修改内容

routerAdmin.post('/content/edit', function (req, res, next) {
    //
    var id = req.query.id || '';
    var postData = req.body;
    // console.log('添加内容传入的数据' + postData.category);
    //字段检测等可放前端检测
    //前端检测 可对输入框等 进行响应交互等处理
    if (postData.category === '' || postData.title === '' || postData.content === '') {
        res.render('admin/error', {
            userInfo: req.userInfo,
            message: '有未填写的信息'
        });
        return;
    }
    else {
        //保存数据到数据库
        Content.update({
            //条件
            _id: id
        }, {
            //更新的数据字段
            category: postData.category,
            title: postData.title,
            description: postData.description,
            content: postData.content
        }).then(function () {
            res.render('admin/success', {
                userInfo: req.userInfo,
                message: '内容数据修改成功',
                //保存成功可跳转到指定Url页面 eg:内容展示详情页面
                // url: '/admin/content/edit?id=' + id
                url: '/admin/content'
            });
        });
    }

});

 

内容删除

routerAdmin.get('/content/delete', function (req, res, next) {
    var id = req.query.id || '';
    Content.remove({
        //删除的条件
        _id: id
    }).then(function () {
        res.render('admin/success', {
            userInfo: req.userInfo,
            message: '删除成功',
            url: '/admin/content'
        });
    });
});

本文地址:https://blog.csdn.net/DeathDomain/article/details/107377881

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网