express+mocha+数据库

本文介绍了如何使用Express、Mocha和MongoDB搭建一个包含用户注册、登录、退出功能以及书籍增删改查的应用。详细步骤包括初始化项目、设置MongoDB数据库、实现用户注册与登录的接口、处理会话以及实现书籍的CRUD操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录


该项目是书籍管理系统:用户注册登录后访问首页,可以看到分页展示的书籍,可对书籍进行添加,修改等操作
数据库内容主要分为两类:用户类和书籍类

1. 初始化项目

express:node内置http模块封装
express -e express_mongo_lagou npm i -D npm i nodemon -g //实现热刷新

package.json文件需要修改

// 原来
"start": "node ./bin/www"
// 修改后
"start": "nodemon ./bin/www"

启动项目

npm start

在这里插入图片描述

2. mongodb初始化

  • 在项目中新开终端启动mongodb的服务器(管理员)
    // 自己本地的MongoDB的目录
     mongod --dbpath D:\data\db --logpath D:\data\log\mongodb2.log  --auth
    
    使用shell或是图形界面robo 3T创建所需的数据库

在这里插入图片描述

  • 通过nodejs的mongdb链接数据库。在model文件夹中封装
// 认证登录
db.createUser({
  "user": "u1",
  "pwd": "u1",
  "roles": [{
    role: "readWrite",
    db: "bookSystem"
  }]
})
  • 首先搭建项目架构:实现数据库连接的封装
    model文件夹下的index.js封装连接数据库
// 数据库连接公共方法

// 1 mongo客户端对象
var MongoClient = require('mongodb').MongoClient;
// 2 url
var url = 'mongodb://u1:u1@localhost:27017/bookSystem';
// 3 数据库名称
const dbName = 'bookSystem';

// 4 封装数据库连接的方法
function connect(callback) {
    MongoClient.connect(url, function (err, client) {
        // 回调函数传参是:错误对象,客户端连接成功的对象
        // 有错误先打印错误
        if (err) {
            console.log("数据库连接错误", err);
        } else {
            // 数据库连接成功的对象
            var db = client.db();
            // 如果callback存在的话
            callback & callback(db);
            // 关闭数据库
            client.close();
        }
    });
}

module.exports = {
    connect: connect,
}

在路由router/index.js中测试

router.get('/', function (req, res, next) {
  model.connect(function (db) {
    db.collection('users').find().toArray(function (err, docs) {
      console.log("用户列表", docs);
      res.render('index', { title: 'Express' });
    })
  })
});

在这里插入图片描述
测试数据库连接成功

3. 实现业务需求:实现user和book数据库的增删改查操作

user的业务:注册、登录和退出登录

分析

  • 注册需要页面register.ejs渲染,在index.js中增加/register路由。注册页面提交数据的路由是/users/register,注册页面可能跳转的路由是/login。提交数据主要是实现数据库users的插入,如果成功,跳转路由/login,如果不成功,刷新回本页面/register
  • 登录需要页面登录页面login.ejs渲染,在index.js中增加/login路由。登录页面提交数据的路由是/users/login,登录页面可能跳转的路由是/register。提交数据主要是实现数据库users的查询,如果成功,跳转路由/(首页),如果不成功,刷新回本页面/login.用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录.跳转到首页后,首页从session中获取登录的用户名,将其传给首页模板index.ejs并渲染在页面。
  • 退出登录在index.ejs中实现,路由是/user/logout,主要删除session的内容,并重定向到登录页面/login

公共头部 views/head.ejs

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie-edge">
<link rel='stylesheet' href='/stylesheets/style.css' />
1.1 注册页面 views/register.ejs
<!DOCTYPE html>
<html lang="en">
<head>
    <%- include head %>
    <title>注册页</title>
</head>
<body>
    <h1>注册</h1>
</body>
</html>
1.1 注册页面渲染注册路由 routers/index
// /register路由使用 注册页渲染
router.get('/register', function (req, res, next) {
  res.render('register', {});
});

测试成功
在这里插入图片描述

1.1 修改注册页面 views/register.ejs
<body>
// 这里使用form-box样式自定义
    <div class="form-box">
        <form action="/users/register" method="post">
            <input type="text" name="username" value="" placeholder="请输入用户名">
            <input type="password" name="password" value="" placeholder="请输入密码">
            <input type="password" name="password2" value="" placeholder="请再次输入密码">
            <input type="submit" value="注册">

        </form>
        <div>已有账号,<a href="/login">立即登录</a></div>
    </div>
</body>
1.1 添加注册提交信息的路由接口 routers/user
// 用户注册路由接口测试
router.post('/register', function (req, res, next) {
  var data = {
    username: req.body.username,
    password: req.body.password,
    password2: req.body.password2,
  }
  res.send(data);
});

测试成功
在这里插入图片描述

1.1 修改注册提交信息的路由接口 routers/user
// 用户注册接口测试
router.post('/register', function (req, res, next) {
  var data = {
    username: req.body.username,
    password: req.body.password,
    password2: req.body.password2,
  }
  /* 验证数据暂时忽略 */
  /* 插入数据 */
    model.connect(function (db) {
    db.collection('users').insertOne(data, function (err, ret) {
      if (err) {
        console.log("注册用户失败");
        return res.redirect('/register')
      }
      else {
        console.log("成功");
        return res.redirect('/login')
      }

    })
  })
});
1.2 添加登录提交信息的路由接口 routers/index
//   /login路由使用 登录页面login.ejs渲染
router.get('/login', function (req, res, next) {
  res.render('login', {});
});
1.2 添加渲染页面 login.ejs
<!DOCTYPE html>
<html lang="en">

<head>
    <%- include head %>
    <title>注册页</title>
</head>

<body>

    <div class="form-box">
        <form action="/users/login" method="post">
            <input type="text" name="username" value="" placeholder="请输入用户名">
            <input type="password" name="password" value="" placeholder="请输入密码">
            <input type="submit" value="登录">

        </form>
        <div>没有账号,<a href="/register">立即注册</a></div>
    </div>
</body>

</html>

在这里插入图片描述

  • [ × ] 、、、還在上傳中注册成功后可跳转到登录頁面的視頻
1.2 添加用户登录接口 routers/users
// 用户登录接口  
router.post('/login', function (req, res, next) {

  var data = {
    username: req.body.username,
    password: req.body.password
  }
  // res.send(data)
  /* 验证数据暂时忽略 */
  /* 数据库查询数据 */
  model.connect(function (db) {
    db.collection('users').find(data, function (err, docs) {
      if (err) {
        // console.log("用户登录失败,请重新登录");
        res.redirect('/login')
      }
      else {
        if (docs.length > 0) {
          console.log("用户登录成功");
          res.redirect('/')
        }
        else {
          res.redirect('/login')
        }

      }

    })
  })
});

/…。。。。。。。。用户登录成功跳转首页的视频还在上传中

1.2 用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录express-session

app.js添加express-session

var session = require('express-session');
// 配置session
app.use(session({
  secret: 'mongodb_express_api',
  resave: false,
  saveUninitialized: true,
  // 指定会话的有效时长,5分钟
  cookie: { maxAge: 1000 * 60 * 5 }
}))
1.2 修改user/login接口
if (docs.length > 0) {
	// console.log("用户登录成功,显示首页");
	// 用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录express-session
	// 如果session中存储用户名,说明5分钟之内登陆过系统,不需要再次登录,需要一个登陆拦截
	req.session.username = data.username
	res.redirect('/')
}
1.2 app.js中进行登录拦截
// 需要登陆拦截,在其他页面中如果session中存储了username,说明之前登陆过,如果没有session只能访问登录或注册页面
app.get('*', function (req, res, next) {
  var username = req.session.username
  var path = req.path
  console.log('session', username)
  if (path != '/login' && path != '/register') {
    if (!username) {
      res.redirect('/login')
    }
  }
  next()
})
1.2 修改首页的头部 views/index.ejs,将头部单独放在一个headerbar.ejs
<%- include('headerbar',{}) %>
1.2 添加头部headerbar.ejs
<div class="bar">
    <!-- 用户名,文章,退出 -->
    <span>用户名</span>
    <a href="/write">写文章</a>
    <a href="/user/logout">退出</a><!-- 回到首页 -->
    <a href="/" class="home"><img src="/images/home.png" alt="首页"></a>
</div>

在这里插入图片描述

1.3 添加退出路由 router/user.js
// 退出登录路由
router.get('/logout', function (req, res, next) {
  // 把seesion的用户信息去掉,改为null
  req.session.username = null;
  res.redirect('/login')
})
1.3 在首页路由中获得session存储的username routers/index.js
router.get('/', function (req, res, next) {
  var username = req.session.username;
});
1.3 修改首页的头部 views/index.ejs,将用户名加入进去
  <%- include('headerbar',{username:username}) %>
1.3 头部拿到username并显示 headerbar.ejs
  <%- include('headerbar',{username:username}) %>

在这里插入图片描述
目前完成的需求:注册,登录,退出登录。

book的业务:增删改查

加粗样式

  • 增:首页点击“写文章”进入write路由,使用write.ejs模板渲染。
  • 查:首页会自动分页显示所有的书籍信息。后端先获取数据库一共有多少书籍,根据每页展示条数,可以得到一共需要展示的页数total_num,发给前端;前段根据total_num循环打印出每页的a标签,也就是页码;前端将每次点击a标签的内容传送给后端,后端获取页码数,判断需要展示的数据列表,返回前端用以展示。
  • 删:删除一篇文章
  • 改:编辑文章。
2.1 重新添加一个article的路由,在app.js中配置,routers中新建article.js文件
var articleRouter = require('./routes/article');
app.use('/article', articleRouter);
2.1 在index.js中增加路由write
//   /write路由使用 写文章页面write.ejs渲染
router.get('/write', function (req, res, next) {
  var username = req.session.username;
  res.render('write', { username: username });
});
2.1 views下新建write.ejs模板
<!DOCTYPE html>
<html>
<head>
    <title>写文章</title>
    <%- include head %>
</head>
<body>
    <%- include('headerbar',{username:username}) %>
   <div class="article">
        <form action="/article/add" method="post">
            <input type="text" name="title" value="" placeholder="请输入文章标题">
            <textarea name="content" id=""></textarea>
            <input type="submit" value="发布">
        </form>
    </div>
</body>
</html>
2.1 textarea使用xhediter编辑
<script src="/public/xheditor/jquery/jquery-1.4.4.min.js" type="text/javascript"></script>
<script type="text/javascript" src="/public/xheditor/xheditor-1.2.2.min.js"></script>
<script type="text/javascript" src="/public/xheditor/xheditor_lang/zh-cn.js"></script>
<script>
    $('#elm1').xheditor({ tools: 'full', skin: 'default' });
</script>

在这里插入图片描述

2.1 在article.js中添加写完文章提交的接口 /article/add
var express = require('express');
var router = express.Router();
var model = require('../model');
const { route } = require('.');
// 用户注册接口
router.post('/add', function (req, res, next) {
    var data = {
        title: req.body.title,
        content: req.body.content,
        // 使用时间戳作为插入书籍的时间
        id: Date.now(),
        username: req.session.username
    }
    // res.send(data)
    /* 验证数据暂时忽略 */
    /* 数据库插入数据 */
    model.connect(function (db) {
        db.collection('articles').insertOne(data, function (err, ret) {
            if (err) {
                console.log("插入书籍失败");
                return res.redirect('/write')
            }
            else {
                console.log("插入书籍成功");
                return res.redirect('/')
            }

        })
    })

});
module.exports = router;

在这里插入图片描述

2.2 在首页index.ejs中显示所有书籍,使用传参循环遍历显示书籍
  <div class="list">
   <% list.map(function(item,index){ %>
    <div class="row">
      <!-- 序号 -->
      <span><%- index+1 %></span>
      <!-- <span>作者</span> -->
      <span><%- item.username %></span>
      <!-- 书名 -->
      <span><%- item.title %></span>
      <!-- 插入时间 -->
      <span><%- item.time %></span>
      <span><a href="">编辑</a><a href="">删除</a></span>
    </div>
   <% }) %>
  </div>
2.2 添加/路由 访问数据库中的数据 ,修改原index.js中的渲染首页

文章事件的格式化显示使用到的插件是moment npm i moment -g

/* GET home page. */
/* 数据库访问测试 */
/* router.get('/', function (req, res, next) {
  var username = req.session.username;
  // console.log(username)
  model.connect(function (db) {
    db.collection('users').find().toArray(function (err, docs) {
      if (err) {
        console.log("数据库连接错误", err);
      }
      // console.log("用户列表", docs);
      res.render('index', { username: username });
    })
  })
}); */

/* GET book listing. */
router.get('/', function (req, res, next) {
  var username = req.session.username || ""
  model.connect(function (db) {
    db.collection('articles').find().toArray(function (err, docs) {
      var list = docs
      list.map(function (ele, index) {
        ele.time = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
      })
      res.render('index', { username: username, list: list })
    })
  })

});

在这里插入图片描述

2.2 实现首页的分页展示,首页路由index.js
router.get('/', function (req, res, next) {
  var username = req.session.username || ""
  // 首页传来的数据,需要显式第几页,默认为1
  var page = req.query.page || 1
  // console.log("当前是第", page)
  var data = {
    // 总共有多少页
    total_num: 0,
    curPage: page,  //当前页
    list: []  //当前页的文章列表
  }
  var pageSize = 2 //默认每页显示两条数据
  model.connect(function (db) {
    // 1 查询所有文章
    db.collection('articles').find().toArray(function (err, docs) {
      // 文章数目
      data.total_num = Math.ceil(docs.length / pageSize)
      // 2 查询当前页的文章列表
      model.connect(function (db) {
        // 倒序查询,每次查两条,当前游标需要跳过多少数据
        db.collection('articles').find().sort({ _id: -1 }).skip((data.curPage - 1) * pageSize).limit(pageSize).toArray(function (err, docs2) {
          // 所有的文章列表修改时间样式
          docs2.map(function (ele, index) {
            ele.time = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
          })
          data.list = docs2
          res.render('index', { username: username, data: data })
        })
      })
    })
  })
});
2.2 实现首页的分页展示,首页index.ejs
// 修改1:遍历后端传来的书籍list
<% data.list.map(function(item,index){ %>

// 修改2:页码
<div class="pages">
      <%  for(let i=1;i<=data.total_num;i++){ %>
      <a href="/?page=<%= i %> "><%= i %></a>
      <%  } %>
    </div>
// 修改3:详情
<a href="/detail?id=<%= item.id %>"><%- item.title %></a>

在这里插入图片描述
index.js中添加详情路由/detail以及detail.ejs页面

//   /detail路由使用 详情页面detail.ejs渲染
router.get('/detail', function (req, res, next) {
  var username = req.session.username;
  var id = parseInt(req.query.id)
  console.log(id)
  model.connect(function (db) {
    db.collection('articles').findOne({ id: id }, function (err, docs) {
      if (err) {
        console.log("查询失败")
      }
      else {
        docs["time"] = moment(docs[id]).format('YYYY-MM-DD HH:mm:ss')
        res.render('detail', { username: username, item: docs });
      }
    })
  })
});

detail.ejs

<head>
    <title>详情页</title>
    <%- include head %>
</head>
<body>
    <%- include('headerbar',{username:username}) %>
    <div class="detail">
        <div class="title"><%- item.title %></div>
        <div class="desc">
            <!-- <span>作者</span> -->
            <span>作者:<%- item.username %></span>
            <!-- 插入时间 -->
            <span>发布时间<%- item.time %></span>
        </div>
        <div class="content"><%- item.content %></div>
    </div>
</body>
</html>
2.3 删除功能,定义删除接口/article/del,并将当前页码与书籍信息传递过去

修改首页index.ejs 删除a链接的路由接口,并传送数据

<a href="/article/del?id=<%= item._id %>&page=<%= data.curPage %>">删除</a></span>

添加article.js路由的删除接口

// 删除书籍接口

router.get('/del', function (req, res, next) {
    var id = parseInt(req.query.id)
    var page = req.query.page
    /* 验证数据暂时忽略 */
    /* 数据库删除数据 */
    model.connect(function (db) {
        db.collection('articles').deleteOne({ id: id }, function (err, ret) {
            if (err) {
                console.log("删除书籍失败");
            }
            else {
                console.log("删除书籍成功");
            }
            res.redirect('/?page=' + page)
        })
    })
});

这里需要重新修改index.js中显示分页数据,可能删除完当前页的list为空,需要跳到上一页或是第一页

if (docs2.length == 0) {
            res.redirect('/?page=' + ((data.curPage - 1) || 1))
          }
2.4 改:修改书籍信息,跳转write页面并显示书籍详情,重新插入书籍数据

修改首页index.ejs 编辑a链接的路由接口,并传送数据

<a href="/write?id=<%= item._id %>&page=<%= data.curPage %>">删除</a></span>

修改index.js路由的编辑接口 ,复用写文章的路由write

router.get('/write', function (req, res, next) {
  var username = req.session.username;
  var id = parseInt(req.query.id)
  var page = req.query.page
  var item = {
    title: "",
    content: ""
  }
  if (id) {
    model.connect(function (db) {
      db.collection('articles').findOne({ id: id }, function (err, docs) {
        if (err) {
          console.log("查询失败")
        }
        else {
          item = docs
          item["page"] = page
          res.render('write', { username: username, item: item });
        }
      })
    })
  }
  else { //新增
    item["page"] = 0
    res.render('write', { username: username, item: item });
  }
});

在write.ejs页面中显示是新增书籍还是编辑书籍,使用两个隐藏域传参

<div class="article">
        <form action="/article/add" method="post">
        	<input type="hidden" value="<%- item.id %>" name="id">
            <input type="hidden" value="<%- item.page %>" name="page">
            <input type="text" name="title" value="<%=item.title%>" placeholder="请输入文章标题">
            <textarea name="content" id="" class="xheditor"><%- item.content %></textarea>
            <% if(item.id){ %>
            <input type="submit" value="修改">
            <% }else{ %>
            <input type="submit" value="发布">
            <% } %>
        </form>
    </div>

修改add接口用以判断当前传参是新增还是修改,


// 插入书籍接口或修改书籍
router.post('/add', function (req, res, next) {
    var id = parseInt(req.body.id)
    if (id) {
        // 修改
        var page = req.body.page
        console.log("page", page)
        var title = req.body.title
        var content = req.body.content
        model.connect(function (db) {
            db.collection('articles').updateOne({ id: id }, {
                $set: {
                    title: title,
                    content: content
                }
            }, function (err, ret) {
                if (err) {
                    console.log("修改书籍失败");
                    res.redirect('/write')
                }
                else {
                    console.log("修改书籍成功");
                    res.redirect('/?page=' + page)
                }
            })
        })
    }
    else {
        // 新增
        var data = {
            title: req.body.title,
            content: req.body.content,
            // 使用时间戳作为插入书籍的时间
            id: Date.now(),
            username: req.session.username
        }
        // res.send(data)
        /* 验证数据暂时忽略 */
        /* 数据库插入数据 */
        model.connect(function (db) {
            db.collection('articles').insertOne(data, function (err, ret) {
                if (err) {
                    console.log("插入书籍失败");
                    return res.redirect('/write')
                }
                else {
                    console.log("插入书籍成功");
                    return res.redirect('/')
                }

            })
        })
    }
});

2.5 文件上传

xhediter配置,修改write.ejs,文件上传的接口为/article/ipload,一次仅上传一张

$('.xheditor').xheditor({
        tools: 'full',
        skin: 'default',
        upImgUrl: '/article/upload',
        html5Upload: false,
        upMultiple: 1
    });

文件上传处理插件multiparty npm install multiparty -g
nodejs模块ffs进行文件的读写

// 文件上传接口
router.post('/upload', function (req, res, next) {
    var form = new multiparty.Form()
    form.parse(req, function (err, fields, files) {
        if (err) {
            console.log("文件上传失败", err)
        }
        else {
            console.log("文件列表", files)
            var file = files.filedata[0]
            var rs = fs.createReadStream(file.path)
            var newPath = '/uploads/' + file.originalFilename
            var ws = fs.createWriteStream('./public' + newPath)
            rs.pipe(ws)
            ws.on('close', function () {
                console.log("文件上传成功")
                // 给xhediter的反馈
                res.send({ err: "", msg: newPath })
            })
        }
    })
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值