Express & MongoDB 实战


开发环境配置

配置 Express

安装 express & express 脚手架

npm install express -g

npm i express-generator -g

通过查看版本号,检验是否安装成功:

express --version

通过express -h查看命令行的指令含义:

Usage: express [options] [dir]

Options:

        --version        输出版本号
    -e, --ejs            添加对 ejs 模板引擎的支持
        --pug            添加对 pug 模板引擎的支持
        --hbs            添加对 handlebars 模板引擎的支持
    -H, --hogan          添加对 hogan.js 模板引擎的支持
    -v, --view <engine>  添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
        --no-view        创建不带视图引擎的项目
    -c, --css <engine>   添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
        --git            添加 .gitignore
    -f, --force          强制在非空目录下创建
    -h, --help           输出使用方法

创建了一个名为 helloworld 的 Express 应用,并使用ejs模板引擎:

express -e helloworld

根据提示,依次进入目录,安装依赖,运行程序:

cd helloworld

npm install

DEBUG=helloworld:* npm start

在此之前,可以安装Node自动重启工具 nodemon,它的作用是监听代码文件的变动,当代码改变之后,自动重启。

npm install -g  nodemon

在项目的package.json中做如下改变即可:

"scripts": {
    "start": "nodemon ./bin/www"
},

运行项目后,打开http://localhost:3000/,端口可在bin/www.js中更改

配置 MongoDB

在官网下载对应设备的安装包:mongodb-win32-x86_64-2012plus-4.2.5-signed.msi

安装过程中,建议取消勾选install mongoDB compass,安装完毕后,直接在命令行中键入mongo,之后在数据库的命令解释器中:

# 查看当前数据库
show dbs

# 选择数据库,无则新建
use project

# 创建表单
db.createCollection("users")

# 插入一条数据
db.users.insertOne( {name:'mahoo12138'} );

# 查看数据库表单列表
show collections

# 查看表单数据
db.users.find()

# 删除一条表单数据
db.users.deleteOne({"name":"mahoo12138"})

更多数据库操作,请查阅官方文档:https://docs.mongodb.com/manual/reference/method/

Express 连接 MongoDB

在 express 项目中,安装mongodb

 npm install mongodb --save

之后创建model/index.js,对数据库连接进行封装:

const MongoClient = require('mongodb').MongoClient;

// 数据库URL
const url = 'mongodb://localhost:27017';

// 数据库名称
const dbName = 'project';

function connect(callback) {
    // 使用 connect 方法连接服务器
    MongoClient.connect(url, (err, client) => {
        if (err) {
            console.log('数据库连接错误:', err)
        }else {
            console.log("数据库连接成功!");

            const db = client.db(dbName);
            callback && callback(db)
            client.close();
        }

    });
}
// 使用 CommonJS 语法导出
module.exports = {
    connect
}

相关函数调用,可查询官方文档:http://mongodb.github.io/node-mongodb-native/3.5/

测试连接

在 express 项目routers/index.js中:

// 导入封装好的数据库连接函数
var model = require('../model')

// 插入如下代码,作为测试
router.get('/', function(req, res, next) {

  model.connect((db) => {
    db.collection('users').find().toArray((err,docs)=>{
      console.log('用户列表',docs)
    })
  })

  res.render('index', { title: 'Express' });
});

刷新http://localhost:3000/页面,控制台中出现,如下内容则连接成功:

GET / 304 51.163 ms - -
数据库连接成功
用户列表 [ { _id: 5e8c030bb81464cd1885fe73, name: 'mahoo12138' } ]
GET /stylesheets/style.css 304 5.073 ms - -

业务逻辑

添加注册界面

在项目路径views/下添加一个register.ejs文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%= title %> - 注册</title>
    <%- include head %>
</head>
<body>
    <h2>注册页面</h2>
    <form action="/users/register" method="post">
        <input type="text" name="username" value="" placeholder="请输入用户名">
        <input type="password" name="password" value="" placeholder="请输入密码">
        <input type="password" name="apassword" value="" placeholder="请重复输入密码">
        <input type="submit" name="" value="注册">
    </form>
    <div>已有账号?<a href="/login">立即登录</a></div>
</body>
</html>

上述代码中将head标签中的内容抽离到了head.ejs中,同样也将index.ejs中引入的样式表放入模板中,之后引入即可:

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel='stylesheet' href='/stylesheets/style.css' />

在项目路径routers/index.js中添加代码测试:

// 渲染注册页
router.get('/register', function(req, res, next) {
  res.render('regist', { title: 'Express' });
});

测试成功后,在routers/users.js中编写用户注册逻辑:

router.post('/register', function(req, res, next) {
  if (req.body.password === req.body.apassword){
    console.log('两次输入密码一致!');
    var data = {
      username: req.body.username,
      password: req.body.password
    }
    model.connect(db => {
      db.collection('users').insertOne(data,(err, ret) => {
        if (err){
          console.log('注册失败!');
          res.redirect('/register')
        }else{
          console.log('注册成功!');
          // 跳转到登陆界面
          res.redirect('/login')
        }
      })
    })
  }else{
    res.send("两次输入密码不一致!")
  }
});

添加登录界面

稍稍修改注册界面,也就有了登录界面:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%= title %> - 登录</title>
    <%- include head %>
</head>
<body>
    <h2>登陆界面</h2>
    <form action="/users/login" method="post">
        <input type="text" name="username" value="" placeholder="请输入用户名">
        <input type="password" name="password" value="" placeholder="请输入密码">
        <input type="submit" name="" value="登录">
    </form>
    <div>没有账号?<a href="/register">立即注册</a></div>
</body>
</html>

routers/users.js中编写用户登录逻辑,登录时需要查询数据库,当查询结果大于 0 即已注册,跳转即可:

router.post('/login',(req,res,next)=>{
    var data = {
      username: req.body.username,
      password: req.body.password
    }
    console.log(data);
    model.connect(db => {
      db.collection('users').find(data).toArray((err, docs)=>{
        if(err){
          res.redirect('/login')
        }else{
          if(docs.length > 0){
            res.redirect('/')
          }
          else{
          res.redirect('/login')
          }
        }
      })
 })
})

登录拦截

安装 express 会话插件:

npm install express-session --save

相关文档,可查看存储仓库的README.md

/app.js中,添加如下代码:

var session = require('express-session')
app.use(cookieParser('mahoo project'));
app.use(session({
  secret: 'mahoo project',    /* 对会话id相关的 cookie 进行签名 */
  resave: false,
  saveUninitialized: true,    /* 是否保存未初始化的会话 */ 
  // cookie: { secure: true }
  cookie: { maxAge: 1000 * 60 * 5}  /* 指定会话有效期 5min */
})) 
// 注意要写在下行代码之前
app.use(express.static(path.join(__dirname, 'public')));

登录后的主页面需要显示用户名,需要传递一个变量:

router.get('/', function(req, res, next) {
   res.render('index', { title: 'Express',username: req.session.username})
});

需求是登录后才能查看主页面,编辑routers/index.js添加拦截:

app.get('*',function(req, res, next) {
  var path = req.path
  console.log('链接: ',path);
  if(path !== '/login' && path !== '/register'){
    console.log('用户名:',req.session.username);
    if(!req.session.username){
      res.redirect('/login')
    }
  }
  console.log("不拦截");
  next()
});

// 注意,代码应在此代码前
app.use('/', indexRouter);

我们先 code 一个导航条nav.ejs

<div class="nav">
    <div class="nav-left">
      <span><%= title %></span>
    </div>
    <div class="nav-right">
      <div class="user">
        用户 <%= username %> 已登录
      </div>
      <div class="add-btn">
        <button>写文章</button>
      </div>
      <div class="exit-btn">
        <button><a href="/user/logout">退出</a></button>
      </div>
    </div>
  </div>

之后在主页面中引入:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%- include head %>
  </head>
  <body>
    <%- include('nav',{username: username}) %> 
    <!-- 这里是将传入该文件的 username 作为 username 再传入 nav.ejs -->
  </body>
</html>

发布文章界面

新建一个模板views/write.ejs,同时引入开源的富文本编辑器xheditor下载地址,按照文档加入 script 代码,并在 textarea 标签中添加xheditor属性:

<!DOCTYPE html>
<html lang="en">
<head>
    <title><%= username %> - 写文章</title>
    <% include head %> 
</head>
<body>
    <%- include('nav',{username: username}) %> 
    <h2>文章界面</h2>
    <div class="editor">
        <form action="/articles/add" method="post">
            <input type="text" name="title" placeholder="请输入标题!" class="title">
            <textarea class="xheditor" name="content" placeholder="请输入文章内容!"></textarea>
            <input class="submit-btn" type="submit" value="发布">
        </form>
    </div>

    <script type="text/javascript" src="/xheditor/jquery/jquery-1.11.2.min.js"></script>
    <script type="text/javascript" src="/xheditor/xheditor-1.2.2.min.js"></script>
    <script type="text/javascript" src="/xheditor/xheditor_lang/zh-cn.js"></script>
</body>
</html>

再新建一个路由文件/routers/articles.js管理文章的增删改查:

var express = require('express');
var router = express.Router();
var model = require('../model')

router.post('/add', function(req, res, next) {
  var passage = {
      title: req.body.title,
      content: req.body.content,
      id: Date.now(),
      username: req.session.username
  }
  model.connect((db)=>{
      db.collection('articles').insertOne(passage,(err,ret)=>{
        if(err){
            console.log("发布成功!");
            res.redirect('/write')
        }else{
            res.redirect('/')
        }
      })
  })
});

module.exports = router;

/app.js中,按照 users.js 的配置添加articles.js

var articlesRouter = require('./routes/articles');
app.use('/articles', articlesRouter);

之后在 Mongo shell 添加一个表单articles

db.createCollection("articles")

最后在write页面中发布文章测试,并在 Mongo shell 核对文章数据。

主页文章列表

routers/index.js中添加获取文章列表的查询逻辑:

router.get('/', function(req, res, next) {
  model.connect((db)=>{
    db.collection('articles').find().toArray((err,docs)=>{
      console.log('文章列表',err);
      var list = docs;
      list.map((ele,index)=>{
        ele['time'] = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
      })
      res.render('index', {
        title: 'Express',
        username: req.session.username,
        list: list
      });
    })
  })
});

注意,在处理时间戳时,使用到了moment开源模块,详细文档可查询官方文档。

之后在views/index.ejs中,添加如下代码,使用的是 ejs 模板语法:

<div class="article">
    <% list.map(function(item,index){ %> 
        <div class="row">
            <span><%= index + 1 %> </span>  
            <span><%= item.username %></span>  
            <span><%= item.title %></span>  
            <span><%= item.time %></span>  
            <span>
                <a href="">编辑</a>
                <a href="">删除</a>
            </span>  
        </div>
    <%  }) %> 
</div>

分页显示

分页显示时,每页显示两条数据,分页查询的几个函数,sort({_id:-1})设置倒序查询,limit()设置查询数量,skip()跳转多少条数据进行查询,详细看代码:

router.get('/', function(req, res, next) {
var page = req.query.page || 1    //接收前端查询请求,首次刷新默认第一页
var data = {
  total: 0, //总页数
  curPage: page,  //当前页数
  list:[]
}
var pageSize = 2;
  model.connect((db)=>{
    db.collection('articles').find().toArray((err,docs)=>{
      data.total = Math.ceil(docs.length / pageSize)
      // 第一次查询数据,获取文章数量,得到页码总数
      model.connect((db)=>{
        db.collection('articles').find().sort({_id:-1}).limit(pageSize)
        .skip((page - 1)*pageSize).toArray((err,docs2)=>{
        docs2.map((ele,index)=>{
            ele['time'] = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
          })
          // 第二次分页查询,利用sort(),limit(),skip()
          data.list = docs2
          res.render('index', {
            title: 'Express',
            username: req.session.username,
            data: data
          });
        })
      })
    })
  })
});

在前端页面上添加页面显示,点击页码带参查询:

<div class="article">
    <!-- 省略 -->
    <div class="page">
        <% for(let i = 1;i <= data.total; i++){ %>
               <a href="/?page=<%= i %>"><%= i %></a>
        <% } %> 
    </div>
</div>

删除文章

修改删除文章的超链接,传入删除文章的 id 以及当前页码:

<a href="/articles/delete?id=<%= item.id %>&page=<%= data.curPage %>">删除</a>

当一页的两条数据都被删除后,页面还是停留在当前页面的,我们需要跳转到上一页,则需要在查询数据时,做一个判断,在获取文章页面的第二次查询数据库时,如果查询到的数据大小为 0 ,则跳转:

db.collection('articles').find().sort({_id:-1}).limit(pageSize)
    .skip((page - 1)*pageSize).toArray((err,docs2)=>{
    // --------------------------------------------------------
    if(docs2.length == 0){
        res.redirect('/?page='+((page-1) || 1))
    }else{
        docs2.map((ele,index)=>{
            ele['time'] = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
        })
        data.list = docs2
    }
    // --------------------------------------------------------
    res.render('index', {
        title: 'Express',
        username: req.session.username,
        data: data
    });
})

编辑文章

修改编辑文章的超链接,传入编辑文章的 id 以及当前页码,因为编辑文章和添加文章的页面类似,从简而言,直接复用即可:

<a href="/write?id=<%= item.id %>&page=<%= data.curPage %>">编辑</a>

文章作者: Mahoo Huang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mahoo Huang !
评论
 上一篇
利用ADB将安卓投屏到电脑进行调试开发 利用ADB将安卓投屏到电脑进行调试开发
保证手机与电脑在同一个局域网下,然后查看手机的IP地址: 投屏准备数据线连接手机,并确保手机与电脑在同一个局域网下,然后查看手机的IP地址 通过设置-WLAN,查看当前WiF信息详细,查看IP地址 通过adb命令查看:adb shell
2020-04-27
下一篇 
超乎你想象的Stm32中的TIM定时器 超乎你想象的Stm32中的TIM定时器
注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出! 定时器分类STM32F1 系列中,除了互联型的产品,共有 8 个定
2020-03-13
  目录