项目:个人博客网站


typora-copy-images-to: media

使用到的技术:node、express、swig、mongoose、模块化设计、中间件(body-parser、cookies)、bootstrap

第三方模块、中间件

  • express
  • bodyParser
  • cookies
  • swig: 模板引擎
  • mongoose:操作mongodb数据库
  • markdown:markdown语法解析生成模块
  • jquery
  • bootstrap

项目初始化

生成package.json文件

npm init -y

安装上面的插件

npm install express body-parser...

配置、使用模板引擎

配置

const swig = require('swig');
...
app.engine('html', swig.renderFile);	// 定义当前应用所使用的模板引擎。 参数1:模板引擎后缀;参数2:用于解析处理模块内容的方法
app.set('views', './views');	// 设置模板文件寸法的目录。 参数1:必须是views;参数2:模板文件存放的目录
app.set('view engine', 'html');	// 注册所使用的模板引擎。 参数1:必须是view engine;参数2:和上面的模板引擎后缀必须一致(html)

// 开发过程中,取消模板缓存,方便调试
swig.setDefaults({cache: false});

使用

app.get('/', function(req, res, next){
  // 读取views目录下的指定文件,解析并返回给客户端
  res.render('index', {});	// 参数1:模板文件(可以忽略后缀,可以忽略views/);参数2:传递给模板使用的数据
});

静态文件的托管

配置

// 当用户访问的url以 /public 开始,直接返回 path.join(__dirname, 'node_modules') 对应的文件
app.use('/node_modules', express.static(path.join(__dirname, 'node_modules')));
app.use('/public', express.static(path.join(__dirname, 'public')));

加载中间件

body-parser

const bodyParser = require('body-parser');
...
// 设置body-parser
app.use(bodyParser.urlencoded({extended: true}));
app.use( bodyParser.json() );

cookies

// app.js
const Cookies = require('cookie');
...
// 设置cookie
app.use(function(req, res, next){
  req.cookies = new Cookies(req, res);
  next();
});
// api.js
req.cookies.set( 'userInfo', JSON.stringify(resData.userInfo) );	// 添加cookies
req.cookies.get('userInfo');	// 获取cookies

模块划分

使用app.use()进行模块划分

app.use('/admin', require('./routers/admin'));
app.use('/api', require('./routers/api'));
app.use('/', require('./routers/main'));

路由

前台

  • main模块

    路径 方法 get参数 post参数 备注
    / get 首页
    /view get 内容页
  • api模块

    路径 方法 get参数 post参数 备注
    / get 首页
    /register 用户注册
    /login 用户登录
    /comment 评论获取
    /comment/post 评论提交

后台

  • admin模块

    路径 方法 get参数 post参数 备注
    / 首页
    用户管理
    /user 用户列表
    分类管理
    /category 分类列表
    /category/add 分类添加
    /category/edit 分类修改
    /category/delete 分类删除
    文章内容管理
    /article 文章列表
    /article/add 文章添加
    /article/edit 文章修改
    /article/delete 文章删除
    评论内容管理
    /comment 评论列表
    /comment/delete 评论删除

数据库连接

启动mongodb数据库服务

将mongodb数据库存放到“D:00web20workw02_blogdb”目录下

mongod --dbpath=D:00web20workw02_blogdb

连接数据库

// app.js
const mongoose = require('mongoose');
...
// 连接数据库
mongoose.connect('mongodb://127.0.0.1:27017/blog', {useNewUrlParser: true, useUnifiedTopology: true}, function(err){
    if( err ){
        console.log('数据库连接失败!');
    }else{
        console.log('数据库连接成功!');
        // 监听http端口
        app.listen(5000);
    }
});

数据库设计

  • 用户表

    • schemas/users.js - 表结构对象

      const mongoose = require('mongoose');
      // 用户表结构对象
      module.exports = new mongoose.Schema({
        username: String,
        password: String
      });
      

    • models/User.js - 模型类

      const mongoose = require('mongoose');
      const userSchema = require('../schemas/users');
      // 创建用户表的模型类
      module.exports = mongoose.model('User', userSchema);
      

  • 分类表

    • schemas/categories.js - 表结构对象

      
      

    • model/Category.js - 模型类

      
      

用户

用户注册

  • 前端

    • 注册页面渲染 - 用户注册、登录面板切换
    • 注册功能实现 - ajax提交到api.js - /api/user/register
  • 后端

    • 注册功能(错误码:1xx)
      • 前端验证
        • 1、用户名不能为空
        • 2、密码不能为空
        • 3、两次密码必须一致
      • 数据库验证
        • 1、用户是否已经存在
      • 注册成功
        • 保存用户注册信息到数据库中
// index.js    
	$userInfo = $('#userInfo');
    $loginBox = $('#loginBox');
    $registerBox = $('#registerBox');

    // 注册切换到登录
    $registerBox.find('.colMint').on('click', function(){
        $registerBox.hide();
        $loginBox.show();
    });

    // 登录切换到注册
    $loginBox.find('.colMint').on('click', function(){
        $loginBox.hide();
        $registerBox.show();
    });;

    // 注册功能实现
    $registerBox.find('button').on('click', function(){
        $.ajax({
            type: 'post',
            url: '/api/user/register',
            data: {
                username: $registerBox.find('[name="username"]').val(),
                password: $registerBox.find('[name="password"]').val(),
                repassword: $registerBox.find('[name="repassword"]').val()
            },
            dataType: 'json',
            success: function(result){
                console.log(result);
            }
        });
    });
// api.js
/* 
用户注册
    前端验证
        1、用户名不能为空
        2、密码不能为空
        3、两次密码必须一致
    数据库验证
        1、用户是否已经存在
            1、用户不存在 - 保存用户注册信息到数据库中
*/
router.post('/user/register', function(req, res, next){
    // 接收前端数据
    var username = req.body.username;
    var password = req.body.password;
    var repassword = req.body.repassword;

    // 用户名不能为空
    if( username == '' ){
        resData.code = 101;
        resData.msg = '用户名不能为空';
        res.json(resData);
        return;
    }

    // 密码不能为空
    if( password == '' ){
        resData.code = 102;
        resData.msg = '密码不能为空';
        res.json(resData);
        return;
    }

    // 两次密码必须一致
    if( password != repassword ){
        resData.code = 103;
        resData.msg = '两次密码必须一致';
        res.json(resData);
        return;
    }

    // 用户是否已经存在
    User.findOne({
        username: username
    }).then(function(userInfo){
        if( userInfo ){
            resData.code = 104;
            resData.msg = '用户已被注册';
            res.json(resData);
            return;
        }
        // 保存用户注册信息到数据库中
        return new User({
            username: username,
            password: password
        }).save();

    }).then(function(newUserInfo){
        console.log(newUserInfo);
        resData.msg = '注册成功';
        res.json(resData);
    });
});

用户登录

  • 前端

    • 登录页面渲染 - 用户注册、登录面板切换
    • 登录功能实现 - ajax提交到api.js - /api/user/login
    • 登录成功 - 显示用户信息标签
  • 后端

    • 登录功能(错误码:2xx)
      • 前端验证

        • 1、用户名不能为空
        • 2、密码不能为空
      • 数据库验证

        • 1、用户名和密码是否正确
        • 2、验证是否是管理员 - 在app.js页面中验证
      • 登录成功

        • 返回登录成功信息到前端
        • 保存登录cookie信息

用户退出

  • 前端
    • 点击“退出” - 返送ajax - /api/user/logout
    • 退出成功 - 重新刷新页面
  • 后端
    • 删除cookie

后台管理

首页

  • 前端
    • 使用bootstrap搭建后台管理首页
  • 后端
    • 验证是否是管理员用户

用户管理

  • 数据库

  • 前端

    • 使用bootstrap搭建用户列表
  • 后端

    • 用户 - 列表
      • 路由 - /user
      • 渲染 - 用户列表
        • 从数据库中获取用户数据
        • 分页

分类管理

  • 数据库

  • 前端

    • 使用bootstrap搭建分类列表
  • 后端

    • 分类 - 列表
      • 路由 - /category
      • 渲染 - 分类列表
        • 从数据库获取分类数据
        • 分页
    • 分类 - 添加
      • 路由 - /category/add
      • 渲染 - 分类添加
      • 处理 - 分类添加
        • 接收前端数据
        • 前端验证
          • 1、分类名是否为空
        • 后端验证
          • 1、分类名是否存在
            • 存在: 跳转错误提示【问题:Promise.reject(),卡在这里了】
            • 不存在:添加分类名到数据库中
    • 分类 - 修改
      • 路由 - /category/edit
      • 渲染 - 分类修改页面
        • 1、 获取分类ID
        • 2、查询 categories 数据库,显示该ID对应的内容
        • 3、 render页面
      • 处理 - 分类修改
        • 前端验证
          • 1、 分类ID不能为空
          • 2、 分类名不能为空
        • 数据库验证
          • 1、 新分类名不能存在
        • 更新 updateOne 新分类名到数据库中
    • 分类 - 删除
      • 路由 - /category/delete
      • 处理 - 分类删除
        • 获取分类ID
        • 操作数据库,执行删除语句- remove()

文章管理

  • 数据库

  • 文章 - 列表

    • 路由 - /article
    • 渲染 - 文章列表页面
      • 查询 articles 数据库
  • 文章 - 添加

    • 路由 - /article/add
    • 渲染 - 文章添加页面
      • 1、 查询categoires数据库,渲染分类
      • 2、 渲染页面
    • 处理 - 添加文章
      • 1、 获取前端传递的数据
      • 2、前端验证
        • cateId是否为空
        • title是否为空
      • 3、 数据库验证
        • cateId是否存在
      • 4、 保存文章到数据库中
  • 文章 - 修改

    • 路由 - /article/edit

    • 渲染 - 文章修改页面

      • 1、获取文章id
      • 2、数据库验证
        • 文章id是否存在
        • 根据id,查询 article数据表
      • 3、查询所有的分类数据
      • 4、渲染查询到的文章信息(附带分类数据、文章数据)
    • 处理 - 添加文章

      • 1、获取前端传递的文章数据

      • 2、前端验证

        • cateId是否为空

        • title是否为空

      • 3、 数据库验证【问题:此处在接收前端post数据的同时也get了url中的id参数数据,导致出现 CastError: Cast to ObjectId failed 错误】

        • article的id是否存在

        • cateId是否存在

      • 4、 更新文章

  • 文章 - 删除

    • 路由 - /article/delete
    • 处理 - 文章删除
      • 1、获取文章id
      • 2、数据库验证
        • 文章id是否存在
      • 3、根据id删除文章

前台

首页

  • 首页 - 渲染
    • 1、渲染导航分类标签
      • 获取分类ID - category
      • 获取数据库中的分类数据
    • 2、渲染文章列表
      • 获取数据库中总行数
      • 计算分页所需要的参数
      • 根据以上参数获取文章数据(分页、关联categories和users表)
      • 统一渲染index.html
    • 3、根据导航分类显示不同分类的文章
      • category是否为空
        • 为空:where为空对象
        • 不为空:where条件根据cateId分类文章

详情页

  • 详情页 - 渲染
    • 1、获取URL参数:articleId
      • 验证是否存在articleId
    • 2、根据articleId查询数据库,获取该文章的所有信息
    • 3、阅读数的自增
    • 4、渲染该文章

评论

  • 数据库
  • 前端
    • 初始化就显示评论
    • 点击提交按钮,提交评论
    • 点击上一页、下一页,实现评论翻页
    • 渲染评论,带分页
    • 格式化时间
  • 后端
    • 评论 - 显示
      • 1、获取文章id
      • 2、查询articles数据表
      • 3、通过json返回前端
    • 评论 - 添加
      • 1、获取文章id
      • 2、生成一条评论对象
      • 3、查询articles数据表
      • 4、将生成的评论push到返回的数据中
      • 5、保存新的article数据到表中,并返回新的article数据
      • 6、通过json返回新的article数据到前端
原文地址:https://www.cnblogs.com/pikachu/p/14710402.html