项目实战:博客项目

博客项目

    • 一、项目概括
        • 1 功能描述
        • 2 具体设计信息
        • 3 技术栈选择
        • 4 项目文件结构
    • 二、案例初始化
        • 1. 建立项目所需文件夹
        • 2. 初始化项目描述文件
        • 3. 下载项目所需第三方模块
        • 4. 创建网站服务器
        • 5. 构建模块化路由
        • 6. 构建博客管理页面模板
    • 三、登录功能
        • 1. 创建用户集合,初始化用户
            • 1.1 连接数据库
            • 1.2 创建用户集合
            • 1.3 初始化用户
            • 1.4 mongodb下的数据库结构
        • 2. 登录验证
            • 2.1 验证步骤
            • 2.2 密码加密bcrypt
            • 2.3 cookie与session
        • 3. 退出系统
    • 四、 用户管理功能
        • 1. 新增用户
        • 2. 修改用户
        • 3. 删除用户
        • 4. 数据分页
    • 五、部分测试结果展示

一、项目概括

1 功能描述

   设计一个博客文章管理系统,使之具有管理员登录验证、密码加密、显示、新增、修改、删除、查询用户和文章、数据分页、退出登录等功能。

2 具体设计信息

  • 用户信息包括:用户编号、用户名、邮箱、密码、角色、状态。
  • 文章信息包括:ID、标题、发布时间、作者。

3 技术栈选择

   mongodb + express框架 + node.js

4 项目文件结构

项目实战:博客项目_第1张图片

二、案例初始化

1. 建立项目所需文件夹

  • public 静态资源
  • model 数据库操作
  • route 路由
  • views 模板

2. 初始化项目描述文件

  • 使用 npm init -y 生成 package.json 文件

3. 下载项目所需第三方模块

npm install express mongoose art-template express-art-template

4. 创建网站服务器

  • app.js 项目入口文件\项目主文件
// 引入 express 框架
const express = require('express');

// 创建网站服务器
const app = express();

// 监听80端口
app.listen(80);
console.log('服务器已启动');

5. 构建模块化路由

  • route/home.js 创建博客展示页面路由
// 引入 express 框架
const express = require('express');

//创建博客展示页面路由对象
const home = express.Router();


home.get('/home', (req, res) => {
   res.send('欢迎来到博客首页');
});

//将路由对象作为模块成员进行导出
module.exports = home;
  • route/admin.js 创建博客管理页面路由
// 引入 express 框架
const express = require('express');

//创建博客展示页面路由对象
const admin = express.Router();

//创建登录路由
admin.get('/login', require('./admin/loginPage'));

//实现登录功能
admin.post('/login', require('./admin/login'));

//创建用户列表路由
admin.get('/user', require('./admin/userPage'));

//实现退出功能
admin.get('/logout', require('./admin/logout'));

//创建用户编辑页面路由
admin.get('/user-edit', require('./admin/user-edit'));

//创建实现用户添加功能路由
admin.post('/user-edit', require('./admin/user-edit-fn'));

//实现修改用户信息功能
admin.post('/user-modify', require('./admin/user-modify'));

//实现删除用户功能路由
admin.get('/delete', require('./admin/user-delete'));

//将路由对象作为模块成员进行导出
module.exports = admin;
  • 将home、admin路由引入到app.js中
//引入路由模块
const home = require('./route/home');
const admin = require('./route/admin');

//将路由和请求路径进行匹配
app.use('/home', home);
app.use('/admin', admin);

6. 构建博客管理页面模板

需要注意:

  1. 静态资源的外链文件,是浏览器解析的,所以相对路径是相对于浏览器的请求地址。
<link href="/admin/css/boot-crm.css" rel="stylesheet" type="text/css" />
  1. 模板的路径是由模板引擎来解析的,所以写相对路径是没有问题的。
{{include './common/header'}}

三、登录功能

1. 创建用户集合,初始化用户

1.1 连接数据库

model/connect.js,代码如下:

//连接数据库

// 引入系统模块mongoose
const mongoose = require('mongoose');
// 数据库连接   27017是mongodb数据库的默认端口
mongoose.connect('mongodb://localhost/blog', { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => console.log('数据库连接成功')) //连接成功
    .catch((err) => console.log(err, '数据库连接失败')); //连接失败
1.2 创建用户集合

model/user.js,代码如下:

const mongoose = require('mongoose');
//创建用户集合规则
const userSchema = new mongoose.Schema({
    username: {
        type: String,
        //true表示title 属性必传;后面的是错误提示内容
        required: [true, '请传入用户名'],
        //最小长度是2,最大长度是5
        minlength: [2, '姓名长度不能小于2'],
        maxlength: [20, '姓名长度最大不能超过20']
    },
    email: {
        type: String,
        //保证邮箱地址在插入数据库时不重复
        unique: true,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    role: {
        type: String,
        required: true
    },
    //0 启用状态
    //1 禁用状态
    state: {
        type: Number,
        default: 0
    },
});
//使用规则,创建集合,返回集合构造函数(参数一:集合名称;参数二:集合规则)
const User = mongoose.model('User', userSchema);

//将用户集合作为模块成员进行导出
module.exports = { User };
1.3 初始化用户
User.create({
    username: 'wxy',
    email: '[email protected]',
    password: '123456',
    role: 'admin',
    state: 0 //启用状态
}).then(() => {
    console.log('用户创建成功');
}).catch(() => {
    console.log('用户创建失败');
}) 
1.4 mongodb下的数据库结构

在这里插入图片描述
项目实战:博客项目_第2张图片

2. 登录验证

2.1 验证步骤
  1. 为登录表单项设置请求地址、请求方式以及表单项name属性。
<form action="/admin/login" method="post" id="loginForm">
         <div class="form-group">
              <label for="exampleInputEmail1">邮箱:</label>
              <input type="text" class="form-control" id="usercode" placeholder="请输入邮箱地址" name="email">
          </div>
          <div class="form-group">
              <label for="exampleInputPassword1">密码:</label>
              <input type="password" class="form-control" id="userpassword" placeholder="请输入密码" name="password">
          </div>
          <div class="form-group">
              <div class="checkbox">
                  <label>
                  <input type="checkbox">记住密码
                  </label>
              </div>
          </div>
          <div class="form-group">
       <button type="submit" class="btn btn-primary" id="btn">立即登录</button>
</form>
  1. 当用户点击登录按钮时,客户端验证用户是否填写了登录表单,如果邮箱或密码没有输入,阻止表单提交。
<script type="text/javascript">
      //为表单添加提交事件
      $('#loginForm').on('submit', function() {
          //获取到表单中用户输入的内容
          var result = serializeToJson($(this));
          //如果用户没有输入邮件地址的话
          if(result.email.trim().length == 0){
                alert('请输入邮件地址');
                //阻止程序向下执行
                return false;
          }
          //如果用户没有输入密码
          if(result.password.trim().length == 0){
                alert('请输入密码');
                //阻止程序向下执行
                return false;
          }
      })
</script>
  1. 服务器端接收请求参数,验证用户是否填写了登录表单,如果邮箱或密码没有输入,为客户端做出相应,阻止程序向下执行。
//接收请求参数
//解构 出 email password
const { email, password } = req.body;
//如果用户没有输入邮箱地址
if (email.trim().length == 0 || password.trim().length == 0) {
    return res.status(400).render('admin/error', { msg: '邮件地址或者密码错误' });
  1. 根据邮箱地址查询用户信息。如果用户不存在,为客户端做出响应,阻止程序向下执行;如果用户存在,将密码进行比对,比对成功->登陆成功;比对失败->登陆失败。
let user = await User.findOne({ email });
    //查询到了用户
    if (user) {
        // 将客户端传递过来的密码和用户信息中的密码进行比对
        if (password == user.password) {
            //登陆成功
            //将用户名存储在请求对象中
            req.session.username = user.username;

            req.app.locals.userInfo = user;
            //重定向到用户列表页面
            res.redirect('/admin/user');
        } else {
            res.status(400).render('admin/login', { msg: '邮箱地址或密码错误' });
        }
    } else {
        //没有查询到用户
        res.status(400).render('admin/login', { msg: '邮箱地址或密码错误' });
    }
2.2 密码加密bcrypt

bcrypt依赖的其他环境

  1. python 3.8.3
  2. node-gyp
    npm install -g node-gyp
  3. windows-build-tools
    npm install – global – production windows-build-tools
2.3 cookie与session

cookie: 浏览器在电脑中硬盘中开辟的一块空间,主要供服务器端存储数据。

  • cookie中的数据是以域名的形式进行区分的。
  • cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
  • cookie中的数据会随着请求被自动发送到服务器端。
    项目实战:博客项目_第3张图片

session: 实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid作为唯一标识。
项目实战:博客项目_第4张图片

3. 退出系统

① 建立退出功能的路由。

//实现退出功能
admin.get('/logout', require('./admin/logout'));

② 实现退出功能

module.exports = (req, res) => {
    //删除session
    req.session.destroy(function() {
        //删除cookie
        res.clearCookie('connext.sid');
        //重定向到用户登录页面
        res.redirect('admin/login');
    })
}

四、 用户管理功能

1. 新增用户

① 为用户列表页面的新增用户按钮添加链接。

<a href="/admin/user-edit" class="btn btn-primary" >新建用户</a>

② 添加一个链接对应的路由,在路由处理函数中渲染新增用户模板。

//创建用户编辑页面路由
admin.get('/user-edit', require('./admin/user-edit'));
module.exports = async(req, res) => {
    //添加操作
    res.render('admin/user-edit', {
        message: message,
        link: '/admin/user-edit',
        button: '添加'
    });
};

③为新增用户表单制定请求地址、请求方式、为表单项添加name属性。

<form class = "from-container" action="/admin/user-edit" method="post">
        <div class="form-group">
            <label for="">用户名</label>
            <input type="text" class="form-control" placeholder="请填写用户名" name="username">
        </div>
        <div class="form-group">
            <label for="">邮箱</label>
            <input type="text" class="form-control" placeholder="请填写邮箱地址" name="email">
        </div>
        <div class="form-group">
            <label for="">密码</label>
            <input type="password" class="form-control" placeholder="请输入密码" name="password">
        </div> 
        <div class="form-group">
            <label for="">角色</label>
            <select name="role" id="normal" class="form-control">
                    <option value="normal">普通用户</option>
                    <option value="admin">超级管理员</option>
             </select>
        </div>
        <div class="form-group">
            <label for="">状态</label>
            <select name="state" id="normal" class="form-control">
                    <option value="0">启用</option>
                    <option value="1">禁用</option>
            </select>
        </div>
        <button type="submit" class="btn btn-primary">添加用户</button>
</form>

④ 增加实现添加用户的功能路由。

//创建实现用户添加功能路由
admin.post('/user-edit', require('./admin/user-edit-fn'));

⑤接收到客户端传递过来的请求参数。

res.send(req.body);

⑥ 对请求参数的格式进行验证。
使用 npm install joi 命令下载第三方模块Joi。

//引入joi模块
const Joi = require('joi');

module.exports = async(req, res) => {
    //定义对象的验证规则
    const schema = {
        //必须按字段 要加 required 方法
        //.error 自定义错误信息
        username: Joi.string().min(2).max(12).required().error(new Error('用户名不符合验证规则')),
        email: Joi.string().email().required().error(new Error('邮箱格式不符合验证规则')),
        password: Joi.string().regex(/^[a-zA-z0-9]{3,30}$/).required().error(new Error('密码格式不符合验证规则')),
        //客户端必须 传入 noemal 或者 admin,传入其他的均是错的
        role: Joi.string().valid('normal', 'admin').required().error(new Error('角色不符合验证规则')),
        state: Joi.number().valid(0, 1).required().error(new Error('状态值不符合验证规则')),
    };
    try {
        //实施验证
        await Joi.validate(req.body, schema);
    } catch (error) {
        //验证没有通过
        //重定向回用户添加页面
        res.redirect(`/admin/user-edit?message=${error.message}`);
        return;
    }
    console.log('验证通过');
    res.send(req.body);
};

⑦ 验证当前要注册的邮箱地址是否已经注册过。

//根据邮箱地址查询用户是否存在
let user = await User.findOne({ email: req.body.email });
//如果用户已经存在 邮箱地址已经被别人占用
if (user) {
    //重定向回用户添加页面
    return res.redirect(`/admin/user-edit?message=邮箱地址已经被占用`);
}

⑧ 对密码进行加密处理。
⑨ 优化请求处理代码和错误处理代码,将其放到单独的js文件中。
项目实战:博客项目_第5张图片

2. 修改用户

① 将要修改的用户ID传递到服务器。

 <a href="/admin/user-edit?id={{@$value._id}}" class="btn btn-success btn-xs">修改</a>

② 建立用户信息修改功能对应的路由。

//实现修改用户信息功能
admin.post('/user-modify', require('./admin/user-modify'));

③ 接收客户端表单传递过来的请求参数;根据客户端id查询用户信息,并将客户端传递过来的密码和数据库中的密码进行比对;如果比对失败,对客户端做出相应;如果比对成功,将用户信息更新到数据库中。

//导入用户集合的构造函数
const { User } = require('../../model/user');
module.exports = async(req, res) => {
    //接收客户端传递过来的请求参数
    const { username, email, role, state } = req.body;
    //即将要修改的用户id
    const id = req.query.id;
    //let user = await User.findOne({ _id: id });
    //将用户信息更新到数据库中
    await User.updateOne({ _id: id }, {
        username: username,
        email: email,
        role: role,
        state: state
    });
    res.redirect('/admin/user');
};

3. 删除用户

① 在确认删除框中添加隐藏域用以存储要删除用户的ID值。

<!-- 删除确认弹出框 开始-->
  <div class="modal fade confire-modal">
      <div class="modal-dialog modal-lg">
          <form action="" class="modal-content">
              <div class="modal-header">
                  <button type="button" class="close" data-dismiss="modal">
                      <span>&times;</span>
                   </button>
                  <h4 class="modal-title">请确认</h4>
              </div>
              <div class="modal-body">
                  <p>您确定要删除这个用户吗?</p>
                  <input type="hidden" name="id">
              </div>
              <div class="modal-footer">
                  <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                  <input type="submit" class="btn btn-primary">
              </div>
          </form>
      </div>
  </div>
<!-- 删除确认弹出框 结束-->

② 为删除按钮添加自定义属性用以存储要删除用户的ID值。

<a href="#" class="btn btn-danger btn-xs delete" data-id="{{@$value._id}}" data-toggle="modal" data-target=".confire-modal">删除</a>

③ 为删除按钮添加点击事件,在点击事件处理函数中获取自定义属性中的ID值并存储在表单的隐藏域中。

{{block 'script'}}
<!-- 编写js代码 -->
<script type="text/javascript">
    //删除用户
    $('.delete').on('click', function() {
        //获取用户id
        var id = $(this).attr('data-id');
        alert(id);
        //将要删除的用户id存储在隐藏域中
        $('#deleteUserId').val(id);
    })
</script>
{{/block}} {{/block}}

④ 为删除表单添加提交地址以及提交方式。

 <form action="/admin/delete" method="get" class="modal-content">

⑤ 在服务器端建立删除功能路由,接收客户端传递过来的id参数,根据id删除用户。

//导入用户集合的构造函数
const { User } = require('../../model/user');
module.exports = async(req, res) => {
    //获取要删除的用户id
    const id = req.query.id;
    //将用户信息从数据库中删除
    await User.findByIdAndDelete({ _id: id });
    res.redirect('/admin/user');
};

4. 数据分页

① 创建数据分页路由(相关代码放在展示页面路由中)。

//导入用户集合的构造函数
const { User } = require('../../model/user');
module.exports = async(req, res) => {
    //接收客户端传递过来的当前页参数
    let page = req.query.page || 1;
    //每一页显示的数据条数
    let pagesize = 10;
    //查询用户数据的总数
    let count = await User.countDocuments({});
    //总页数
    let total = Math.ceil(count / pagesize);
    //页码对应的数据查询开始位置
    let start = (page - 1) * pagesize;
    //将用户信息从数据库中查询出来
    let users = await User.find({}).limit(pagesize).skip(start);

    //渲染用户列表模板
    res.render('admin/user', {
        users: users,
        page: page,
        total: total
    });
};

② user.art 模板中对应的分页代码:

<!--分页开始-->
 <nav aria-label="Page navigation">
     <ul class="pagination">
         <!-- 上一页 -->
         <li style="display:<%= page-0-1 < 1 ? 'none' : 'inline' %>">
             <a href="/admin/user?page=<%=page-1 %>" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
             </a>
         </li>

         <% for (var i = 1;i <= total;i++){ %>
         <li><a href="/admin/user?page=<%=i %>">{{i}}</a></li>
         <% } %>
         <!-- 下一页 -->
         <li style="display:<%= page-0+1 > total ? 'none' : 'inline' %>">
             <a href="/admin/user?page=<%=page-0+1 %>" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
             </a>
         </li>
     </ul>
 </nav>
 <!--分页结束-->

五、部分测试结果展示

  1. 登录验证
    ①未填写密码:
    项目实战:博客项目_第6张图片
    ②未填写邮箱:
    项目实战:博客项目_第7张图片
    ③邮箱地址或密码错误
    在这里插入图片描述

  2. 显示用户信息
    项目实战:博客项目_第8张图片

  3. 新增用户
    项目实战:博客项目_第9张图片
    项目实战:博客项目_第10张图片
    项目实战:博客项目_第11张图片

  4. 修改用户
    将用户名 哈利波特->哈利波特123;邮箱[email protected]>[email protected];超级管理员->普通用户。项目实战:博客项目_第12张图片在这里插入图片描述

  5. 删除用户:将 哈利波特 删除
    项目实战:博客项目_第13张图片
    在这里插入图片描述

  6. 数据分页
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  7. 退出系统
    在这里插入图片描述

你可能感兴趣的:(项目实战,mongodb,node.js,html,bootstrap)