项目实战:博客项目

一、项目概括

1 功能描述

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

2 具体设计信息

3 技术栈选择

mongodb + express框架 + node.js

4 项目文件结构

二、案例初始化

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 初始化用户
1.4 mongodb下的数据库结构

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依赖的其他环境 python 3.8.3 node-gyp npm install -g node-gyp windows-build-tools npm install – global – production windows-build-tools
2.3 cookie与session
cookie: 浏览器在电脑中硬盘中开辟的一块空间,主要供服务器端存储数据。 cookie中的数据是以域名的形式进行区分的。 cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。 cookie中的数据会随着请求被自动发送到服务器端。
session: 实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid作为唯一标识。

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文件中。

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. 登录验证 ①未填写密码: ②未填写邮箱: ③邮箱地址或密码错误
  2. 显示用户信息
  3. 新增用户
  4. 删除用户:将 哈利波特 删除
  5. 数据分页
  6. 退出系统
经验分享 程序员 微信小程序 职场和发展