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) {//内容的IDvar 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);//取值不能超过pagesdata.page = Math.min(data.page, data.pages);//取值不能小于 1data.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拒绝的Promisereturn 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) {//获取要删除的分类IDvar 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拒绝的Promisereturn 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=' + idurl: '/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'});});
});

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

  1. NodeJS+Express+MongoDB - 张果 - 博客园

    目录 一.MongoDB 1.1.安装MongoDB 1.1.1.配置运行环境 1.1.2.运行MongoDB 1.2.数据库操作 1.2.1.创建数据库与查看数据库 1.2.2.删除数据库 1.2. ...

  2. 基于 Vue 和 SpringBoot 实现的博客系统(附源码)

    今天给大家分享一个基于 Vue 和 SpringBoot 实现的博客系统! 源码在文章结尾处,大家自行下载即可,我设置的免积分下载! 一.主要功能 1.前端 后台管理系统采用Vue开发. 文章模块,支 ...

  3. 基于ssm+mysql的javaee微博博客系统,Java实现类似新浪微博网站、朋友圈分享系统

    基于SSM+mysql的javaee微博博客系统,Java实现类似新浪微博网站.朋友圈分享系统 主要技术 SpringBoot\SSM(两个版本都有) HTML.jQuery.HTML.CSS.JS ...

  4. 基于SpringBoot和Vue的个人博客系统

    基于SpringBoot和Vue的个人博客系统 前言 ​ 本期项目分享一个漫威主题的炫酷博客系统,基于SpringBoot和Vue开发的前端分离项目.博客系统分为博客前台和博客后台两部分,游客可以访问 ...

  5. nodejs实战《一起学 Node.js》 使用 Express + MongoDB 搭建多人博客

    GitHub: https://github.com/nswbmw/N-blog N-blog 使用 Express + MongoDB 搭建多人博客 开发环境 Node.js: 6.9.1 Mong ...

  6. react+express+mongodb搭建个人博客

    这是本人用React+Express+mongodb搭建的一个简易博客系统,包括前端展示和后台管理界面.查看源码欢迎访问我的github 以下是参考我的源码后的操作 技术架构 前端 基础:HTML+C ...

  7. java基于ssm的个人博客系统_一个基于 Spring Boot 的开源免费博客系统

    概况 mblog 开源免费的博客系统, Java 语言开发, 支持 mysql/h2 数据库, 采用 spring-boot.jpa.shiro.bootstrap 等流行框架开发.支持多用户, 支持 ...

  8. 基于SpringBoot+SSM+MySQL的个人博客系统

    资源下载地址:https://download.csdn.net/download/sheziqiong/85769040 资源下载地址:https://download.csdn.net/downl ...

  9. node.js基于微信小程序的校园失物招领系统毕业设计源码072343

    微信小程序的校园失物招领系统 摘  要 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,微信小程序的校 ...

最新文章

  1. html的body内标签之input系列1
  2. 抽象类的基本概念------abstract
  3. TabHost的使用(二):实现TabHost.TabContentFactory接口
  4. 聊聊 MySql 索引那些事儿
  5. Distributed Systems笔记-Web Service Design Patterns
  6. 计算机三级网络技术知识点大纲,全国计算机等级考试三级网络技术考试大纲(2019年版)...
  7. 双击U盘时既能运行程序又能打开U盘
  8. 2019 VOD编码工具指南
  9. echart 折线从左到右动画效果_echarts之自动切换折线图
  10. Lintcode: O(1) Check Power of 2
  11. 小甲鱼c语言课后作业_知识,就是力量——山财“学习小课堂”助你蓄力
  12. UVa 11998 破碎的键盘(数组实现链表)
  13. Java程序员的级别定义: 对号入座, 你在哪个阶段心里要有点数
  14. JAVA零碎要点008---tomcat启动的时候报错了严重: End event threw exception java.lang.reflect.InvocationTargetExcepti
  15. 阿里云天池 学习赛汇总(教学赛,零基础入门,长期赛)
  16. easyui添加删除表格任意行(2)
  17. tongweb设置gzip
  18. 使用片段嵌入进行文档搜索
  19. 人工智能导论(数据挖掘)
  20. codeforces 1553B Reverse String

热门文章

  1. Log4j、Log4j 2、JUL、JCL 、SFL4J 、Logback 与 Lombok 的使用
  2. RECT JS 由浅入深
  3. linux静态网络ip dns怎么设置,Linux下如何配置静态IP设置DNS和主机名?
  4. Android和iPhone应用程序界面布局示例
  5. HTML5+CSS3---弹性盒子模型
  6. 6-3 二叉树的重建 uva536
  7. linux md5sum命令
  8. jsp 中的时间格式化
  9. NHibernate之映射文件配置说明
  10. cocos2d-x 3.2 移植到android