# Express

# express 介绍

# 安装 express

项目文件夹中,执行 npm i express。即可下载安装express。

注意:express不能安装在express文件夹中。否则安装失败。

# 使用Express构造Web服务器

使用Express构建Web服务器步骤:

  1. 加载 express 模块

  2. 创建 express 服务器

  3. 开启服务器

  4. 监听浏览器请求并进行处理

// 使用express 搭建web服务器
// 1) 加载 express 模块
const express = require('express');

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

// 3) 开启服务器
app.listen(3006, () => console.log('express服务器开始工作了'));

// 4) 监听浏览器请求并进行处理

app.get('GET请求的地址', 处理函数);

app.post('POST请求的地址', 处理函数);

# express封装的新方法

express之所以能够实现web服务器的搭建,是因为其内部对核心模块http进行了封装。

封装之后,express提供了非常方便好用的方法。

比如前面用到的 app.get() 和 app.post() 就是express封装的新方法。

下面再介绍一个 res.send() 方法

  • 该方法可以代替之前的 res.end 方法,而且比 res.end 方法更好用
  • res.send() 用于做出响应
  • 响应的内容同样不能为数字
  • 如果响应的是JS对象,那么方法内部会自动将对象转成JSON格式。
  • 而且会自动加Content-Type响应头
  • 如果已经做出响应了,就不要再次做出响应了。
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));

// 写接口
app.get('/api/test', (req, res) => {
  // res.end('hello world,哈哈哈'); // 响应中文会乱码,必须自己加响应头
  // res.end(JSON.stringify({ status: 0, message: '注册成功' })); // 只能响应字符串或者buffer类型

  // express提供的send方法,可以解决上面的两个问题
  res.send({ status: 0, message: '注册成功' }); // send方法会自动设置响应头;并且会自动把对象转成JSON字符串
});

请注意,在express中,我们仍然可以使用http模块中的方法和属性,比如req.url。

# 案例 - 大事件的登录注册接口

# 创建数据表

字段 类型 长度 不是null 主键 其他
id int 🔑 √ 自动递增
username varchar 10
password char 32
user_pic longtext
nickname varchar 10
email varchar 30

# 使用ApiPost模拟注册请求

image-20210122173242412

# 写接口

// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));

// 配置 + 写接口
// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
  // 1. 接口要接收数据
  // 2. 判断账号是否已经被占用了
  // 3. 如果没有被占用,把账号密码添加到数据库
});

# 服务端使用 req.body 接收请求体

请求体就是客户端提交的数据(username和password)。

// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));

// 配置 + 写接口

app.use(express.urlencoded({ extended: true }));

// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
  // 1. 接口要接收数据
  console.log(req.body); // { username: 'laotang', password: '123456' }
  // 2. 判断账号是否已经被占用了
  // 3. 如果没有被占用,把账号密码添加到数据库
});


代码写完,一定要使用ApiPost发送请求,测试代码。

# 验证用户名是否存在

思路:根据用户名查询,看是否能够查到数据。

  • 没有查询数据,说明这个用户名不存在,能够使用
  • 如果查到数据库,说明这个用户名已经存在,不能使用
// 完成接口项目
// 前面三行启动服务
const express = require('express');
const app = express();
app.listen(3006, () => console.log('启动了'));

// 配置 + 写接口

app.use(express.urlencoded({ extended: true }));

// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
  // 1. 接口要接收数据
  console.log(req.body); // { username: 'laotang', password: '123456' }
  let { username, password } = req.body;
  // 2. 判断账号是否已经被占用了
  db('select * from user where username="${username}"', (err, result) => {
    if (err) throw err;
    // console.log(result); // 查到信息,result是非空数组;没有查到信息,result是空数组
    if (result.length > 0) {
      res.send({ status: 1, message: '用户名被占用了' });
    } else {
      // 没有被占用
      // 3. 如果没有被占用,把账号密码添加到数据库
      
    }
  })
});

# 完成注册

如果用户名可用,则添加到数据表中,完成注册

// -------------------- 注册接口 ----------------------
// 请求体:username password
app.post('/api/reguser', (req, res) => {
  // 1. 接口要接收数据
  // console.log(req.body); // { username: 'laotang', password: '123456' }
  let { username, password } = req.body;
  // 2. 判断账号是否已经被占用了
  db(`select * from user where username='${username}'`, (err, result) => {
    if (err) throw err;
    // console.log(result); // 查到信息,result是非空数组;没有查到信息,result是空数组
    if (result.length > 0) {
      res.send({ status: 1, message: '用户名被占用了' });
    } else {
      // 没有被占用
      // 3. 如果没有被占用,把账号密码添加到数据库
      db(`insert into user set username='${username}', password='${password}'`, (e, r) => {
        if (e) throw e;
        res.send({ status: 0, message: '注册成功' });
      });
    }
  });
});

# 对密码进行md5加密

安全起见,数据表中不能存储明文密码。必须存储加密后的密码,而且应该使用一种不可逆的加密方案。

常用的加密方式是 md5。

  • 下载安装第三方加密模块,并解构里面的 md5 方法

    let { md5 } = require('utility')

  • 对密码进行加密

    password = md5(password)

# 案例 - 大事件类别管理案例

目前只完成接口功能即可,不需要考虑token 的问题。

# 启动服务,把接口的骨架完成

创建了index.js

// 三个步骤,启动服务
var express = require('express');
var app = express();
app.listen(8888, function () {
    console.log('服务器启动了')
});

// 配置 + 写接口

// ----------------- 获取分类的接口 ------------------
/**
 * 请求方式:GET
 * 接口地址: /my/category/list
 */
app.get('/my/category/list', (req, res) => {
    
});


// ----------------- 删除分类的接口 ------------------
/**
 * 请求方式:GET
 * 接口地址: /my/category/delete
 * 请求参数: id(分类id)
 */
app.get('/my/category/delete', (req, res) => {
    
});


// ----------------- 添加分类的接口 ------------------
/**
 * 请求方式:POST
 * 接口地址: /my/category/add
 * 请求参数: name(类别名称) | alias(类别别名)
 */
app.post('/my/category/add', (req, res) => {
    
});


// ----------------- 修改分类的接口 ------------------
/**
 * 请求方式:POST
 * 接口地址: /my/category/update
 * 请求参数: name(类别名称) | alias(类别别名)  |  id(分类的id)
 */
app.post('/my/category/update', (req, res) => {
    
});

# 设计数据表并添加模拟数据

创建 category表

字段 类型 长度 不是null 主键 自动递增
id int 🔑
name varchar 10
alias varchar 10

自行添加几条模拟数据。

# 封装db.js 工具

把之前封装好的 db.js 复制过来即可。

function db (sql, params, cb) {
    var mysql = require('mysql');
    var conn = mysql.createConnection({
        host: 'localhost',
        user: 'root',
        password: '12345678',
        database: 'sy120'
    });
    conn.connect();
    conn.query(sql, params, cb);
    conn.end();
}

module.exports = db;

# 完成获取分类列表数据的接口

// ----------------- 获取分类的接口 ------------------
/**
 * 请求方式:GET
 * 接口地址: /my/category/list
 */
app.get('/my/category/list', (req, res) => {
    // 调用db函数,查询所有的分类
    db('select * from category', null, function (err, result) {
        if (err) throw err;
        // 没有错误的话,做出响应
        res.send({
            status: 0,
            message: '获取分类成功',
            data: result
        });
    });
});

# 删除分类的接口

客户端发送请求,并且传递id参数

image-20201225145042976

服务端代码:

// ----------------- 删除分类的接口 ------------------
/**
 * 请求方式:GET
 * 接口地址: /my/category/delete
 * 请求参数: id(分类id)
 */
app.get('/my/category/delete', (req, res) => {
    // 获取id参数
    var id = req.query.id;
    // 删除数据表中的数据
    db('delete from category where id=?', id, function (err, result) {
        // 做出响应
        if (err) throw err;
        if (result.affectedRows > 0) {
            res.send({status: 0, message: '删除分类成功'});
        } else {
            res.send({status: 1, message: '删除分类失败'})
        }
    });
});

# 添加分类的接口

image-20201225150033489

服务端代码:

// 解析POST参数(接收POST参数,并转成成对象,然后把转换后的结果赋值给req.body)
app.use(express.urlencoded({ extended: true }));
// ----------------- 添加分类的接口 ------------------
/**
 * 请求方式:POST
 * 接口地址: /my/category/add
 * 请求参数: name(类别名称) | alias(类别别名)
 */
app.post('/my/category/add', (req, res) => {
    // 1. 接收客户端提交的数据(name和alias)
    // console.log(req.body); // { name: '娱乐', alias: 'yule' }
    // 2. 添加到数据库
    db('insert into category set ?', req.body, function (err, result) {
        if (err) throw err;
        // 3. 做出响应
        res.send({ status: 0, message: '添加分类成功' })
    });
});

# 更新分类接口

image-20201225153614367

服务端代码:

/**
 * 请求方式:POST
 * 接口地址: /my/category/update
 * 请求参数: name(类别名称) | alias(类别别名)  |  id(分类的id)
 */
app.post('/my/category/update', (req, res) => {
    // 1. 接收客户端提交的数据(id、name、alias)
    // console.log(req.body); // { id: '1', name: '科技', alias: 'keji' }
    // 2. 执行update语句,修改数据
    db('update category set ? where id=?', [req.body, req.body.id], function (err, result) {
        if (err) throw err;
        if (result.affectedRows > 0) {
            res.send({ status: 0, message: '修改分类成功' })
        } else {
            res.send({ status: 1, message: '修改分类失败' })
        }
    })
});

# Express路由

  • 路由:即请求和处理程序的映射关系。

  • 使用路由的好处:

    • 降低匹配次数,提高性能
    • 分类管理接口,更易维护与升级
  • 使用步骤:

/**
 * 使用路由文件的步骤
 *  1. 加载express模块
 *  2. 创建 router 对象
 *  3. 把接口挂载到 router 对象上
 *  4. 导出 router 对象
 * 
 * app.js 中
 *  5. 加载路由模块,并注册成中间件
 */

  • 注意事项:
    • 路由文件如果没有导出 router,那么在 入口文件中不要注册中间件,否则报错
    • 哪个路由文件中使用了db,自己加载(谁用谁加载)

image-20201108092224934

# 中间件介绍

  • 中间件(Middleware ),特指业务流程的中间处理环节。
  • 中间件,是express最大的特色,也是最重要的一个设计
  • 很多第三方模块,都可以当做express的中间件,配合express,开发更简单。
  • 一个express应用,是由各种各样的中间件组合完成的
  • 中间件,本质上就是一个函数

# 中间件原理

为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。

image-2020033130641861

  • 在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节
  • 我们称其中的每一个处理环节就是一个中间件。
  • 这样做的目的既提高了生产效率也保证了可维护性。

express中间件原理:

image-2020033104703510

# 中间件的几种形式

// 下面的中间件,只为当前接口 /my/userinfo 这个接口服务
app.get('/my/userinfo', 中间件函数);

// 下面的几个中间件,是处理 /api/login 接口的
app.post('/api/login', 中间件函数, 中间件函数, 中间件函数, 中间件函数 .....);

// app.use 中的中间件,可以处理所有的GET请求和所有的POST请求,没有指定路径,那么处理所有接口
app.use(中间件函数);

// 下面的中间件函数,只处理 /api 开头的接口
app.use('/api', 中间件函数);

// 下面的中间件函数,处理 /abcd 、 /abd 这两个接口
app.use('/abc?d', 中间件函数);

app.get或者app.post表示写接口,必须写接口地址;

app.use() 参数1路由前缀,可以省略。另外无论是GET还是POST方式的请求,都会进入该中间件。

# 中间件语法

  • 中间件就是一个函数
  • 中间件函数中有四个基本参数, err、req、res、next
    • 如果写两个参数,那么两个参数肯定是 req和res
    • 如果写三个参数,那么三个参数肯定是 req,res和next
    • 如果写四个参数,那么就是全部的参数。
  • 把写好的中间件函数,传递给 app.get()app.post()或app.use()使用

# 中间件的特点

  • 每个中间件函数,共享req对象、共享res对象
    • js代码中,所有的req对象是一个对象;所有的res是一个对象
  • 不调用next(),则程序执行到当前中间件函数后,不再向后执行
    • 注意中间件的顺序,因为有可能因为顺序原因,你的中间件函数不会执行
    • 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
  • 客户端发送过来的请求,可能连续调用多个中间件进行处理
  • 使用app.use()注册的中间件,GET和POST请求都可以触发
  • 错误处理中间件,必须传递 err、req、res、next四个参数,而且要放到所有接口的后面
    • 如果前面的中间件,没有给next传参,并且代码也没有错误,请求将不会进入到错误处理中间件
    • 如果前面的中间件,给next传递了实参(无论是什么实参),程序会绕过后面的所有中间件,直接进入到最后的错误处理中间件。

# 中间件分类

  • 应用级别的中间件
  • 路由级别的中间件
  • 错误处理中间件
  • 内置中间件(express自带的,比如 express.urlencoded({ extended: true })
  • 第三方中间件(比如multer、express-jwt、express-session、jsonwebtoken....)

实际开发中,自己写中间件的机会并不大,一般都有对应的第三方中间件。