node.js中的express

express 介绍

Express 是一个第三方模块,用于快速搭建服务器(替代http模块)  

Express 是一个基于 Node.js 平台,快速、开放、极简的 web 开发框架

Express保留了http模块的基本API,使用express的时候,也能使用http的API

express还额外封装了一些新方法,能让我们更方便的搭建服务器

express提供了中间件功能,其他很多强大的第三方模块都是基于express开发的

安装 express

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

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

使用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之所以能够实现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。

Express路由

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

 使用路由的好处

降低匹配次数,提高性能

分类管理接口,更易维护与升级

login.js

// --------------------- 使用路由的步骤 ----------------------
// 1. 加载express
const express = require('express');
// 2. 创建路由对象,实则是个函数类型
const router = express.Router();

// 3. 写接口,把接口挂载到 router 上
router.post('/reguser', (req, res) => {});
router.post('/login', (req, res) => {});

// 一定要导出 router
module.exports = router;

index.js

// 三行必须的代码,启动服务
const express = require('express');
const app = express();

// 加载自定义的路由模块,注册中间件
let loginRouter = require('./routers/login');
app.use('/api', loginRouter);

app.listen(3006, () => console.log('启动了'));

Express中间件

 中间件(Middleware ),特指业务流程的中间处理环节。

中间件,是express最大的特色,也是最重要的一个设计

很多第三方模块,都可以当做express的中间件,配合express,开发更简单。

一个express应用,是由各种各样的中间件组合完成的

中间件,本质上就是一个函数

中间件原理

中间件的几种形式

// 下面的中间件,只为当前接口 /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', 中间件函数);

中间件语法

中间件就是一个函数

中间件函数中有四个基本参数, err、req、res、next

中间件分类

应用级别的中间件(index.js 中的中间件,全局有效

路由级别的中间件(路由文件中的中间件,只在当前路由文件中有效

错误处理中间件(四个参数都要填,一般放到最后

内置中间件(express自带的,比如 express.urlencoded({ extended: true })

第三方中间件(比如multer、express-jwt、express-session、....)

设置忽略文件(.gitignore

# 只忽略根目录里面的 node_modules
/node_modules

# 忽略所有叫做 node_modules 的文件夹
node_modules/

# 忽略所有的 .a 文件
*.a

# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a

# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO

# 忽略任何目录下名为 build 的文件夹
build/

# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt

# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf

验证用户名是否存在

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

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

// 配置 + 写接口

app.use(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)

完成登录接口

/**
 * 登录接口
 * 请求方式:POST
 * 接口地址:/api/login
 * 请求体:username | password
 */
app.post('/api/login', (req, res) => {
  // console.log(req.body); // { username: 'laotang', password: '123456' }
  let { username, password } = req.body;
  password = md5(password);
  // 使用username和加密的密码当做条件,查询。
  let sql = `select * from user where username='${username}' and password='${password}'`;
  db(sql, (err, result) => {
    if (err) throw err;
    // console.log(result); // 没有查到结果得到空数组;  查到结果得到非空数组
    if (result.length > 0) {
      res.send({ status: 0, message: '登录成功' })
    } else {
      res.send({ status: 1, message: '账号或密码错误' })
    }
  })
});

创建token

使用第三方模块 jsonwebtoken 创建token字符串。

下载安装 npm i jsonwebtoken

加载模块 const jwt = require('jsonwebtoken');

if (result.length > 0) {
  // 登录成功,生成token
  // 在token中保存用户的id
  // token前必须加 “Bearer ”,注意空格
  let token = 'Bearer ' + jwt.sign({ id: result[0].id }, 'sfsws23s', { expiresIn: '2h' });
  res.send({ status: 0, message: '登录成功', token })
} else {
  res.send({ status: 1, message: '账号或密码错误' })
}

验证中间件


// 必须在这里,注册中间件,完成数据的验证
router.use((req, res, next) => {
  // 获取username和password
  let { username, password } = req.body;
  // 验证用户名
  if (!/^[a-zA-Z][0-9a-zA-Z_]{1,9}$/.test(username)) {
    next('用户名只能包含数组字母下划线,长度2~10位,字母开头');
  } else if (!/^\S{6,12}$/.test(password)) {
    next('密码6~12位且不能出现空格');
  } else {
    next();
  }
});

错误处理中间件

// 错误处理中间件
router.use((err, req, res, next) => {
  // err 就是前面next过来的参数值
  res.send({ status: 1, message: err })
});

代码实现认证

下载安装:

npm i express-jwt 
const jwt = require('express-jwt');
// app.use(jwt().unless());
// jwt() 用于解析token,并将 token 中保存的数据 赋值给 req.user
// unless() 完成身份认证
app.use(jwt({
  secret: 'sfsws23s', // 生成token时的 钥匙,必须统一
  algorithms: ['HS256'] // 必填,加密算法,无需了解
}).unless({
  path: ['/api/login', '/api/reguser'] // 除了这两个接口,其他都需要认证
}));

上述代码完成后,当一个请求发送到服务器后,就会验证请求头中的 Authorization 字段

如果没有问题

  将token中保存的 用户id 赋值给 req.user

  next()。

所以,还需要在index.js 最后,加入错误处理中间件,来提示token方面的错误

app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    // res.status(401).send('invalid token...');
    res.status(401).send({ status: 1, message: '身份认证失败!' });
  }
});

案例 - 个人中心接口

使用路由模块  /routers/user.js

// user路由文件
const express = require('express');
const router = express.Router();


// 导出
module.exports = router;

index.js中加载路由模块,注册中间件

app.use('/my/user', require('./routers/user'));

获取用户信息接口

// *************************** 获取用户信息 ***************************/
/**
 * 请求方式:GET
 * 接口地址:/my/user/userinfo
 * 参数: 无
 */
router.get('/userinfo', (req, res) => {
  // console.log(req.user); // { id: 1, iat: 1611537302, exp: 1611544502 }
  // return;
  // 查询当前登录账号的信息,并不是查询所有人的信息
  db('select * from user where id=' + req.user.id, (err, result) => {
    if (err) throw err;
    res.send({
      status: 0,
      message: '获取用户信息成功',
      data: result[0]
    })
  });
});

更换头像接口

// *************************** 更换头像接口 ***************************/
/**
 * 请求方式:POST
 * 接口地址:/my/user/avatar
 * Content-Type: application/x-www-form-urlencoded
 * 请求体:avatar
 */
router.post('/avatar', (req, res) => {
  // console.log(req.body); // { avatar: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHg' }
  let sql = `update user set user_pic='${req.body.avatar}' where id=${req.user.id}`;
  db(sql, (err, result) => {
    if (err) throw err;
    res.send({ status: 0, message: '更换头像成功' })
  })
});

案例 - 文章相关接口

使用路由模块  /routers/article.js

// category路由文件
const express = require('express');
const router = express.Router();


// 导出
module.exports = router;

index.js中加载路由模块,注册中间件

app.use('/my/article', require('./routers/article'));

分页获取文章接口

// ---------------- 分页获取文章列表 ----------------
// 接口要求:
/**
 * 请求方式:GET
 * 请求的url:/my/article/list
 * 请求参数:
 *  - pagenum -- 页码值
 *  - pagesize -- 每页显示多少条数据
 *  - cate_id -- 文章分类的Id
 *  - state -- 文章的状态,可选“草稿”或“已发布”
 */
router.get('/list', (req, res) => {
    // console.log(req.query);
    // 设置变量,接收请求参数
    let { pagenum, pagesize, cate_id, state } = req.query;
    // console.log(cate_id, state);
    // 根据cate_id 和 state制作SQL语句的条件
    let w = '';
    if (cate_id) {
        w += ` and cate_id=${cate_id} `;
    }
    if (state) {
        w += ` and state='${state}' `;
    }
    // 分页查询数据的SQL(该SQL用到了连表查询,并且使用了很多变量组合)
    let sql1 = `select a.id, a.title, a.state, a.pub_date, c.name cate_name from article a
    join category c on a.cate_id=c.id
    where author_id=${req.user.id} and is_delete=0 ${w}
    limit ${(pagenum - 1) * pagesize}, ${pagesize}`;
    // 查询总记录数的SQL,查询条件和前面查询数据的条件 必须要一致
    let sql2 = `select count(*) total from article a
    join category c on a.cate_id=c.id
    where author_id=${req.user.id} and is_delete=0 ${w}`;
    // 分别执行两条SQL(因为db查询数据库是异步方法,必须嵌套查询)
    db(sql1, (err, result1) => {
        if (err) throw err;
        db(sql2, (e, result2) => {
            if (e) throw e;
            res.send({
                status: 0,
                message: '获取文章列表数据成功',
                data: result1,
                total: result2[0].total
            });
        })
    })
});

添加文章接口

这是我们遇到的一个请求体为FormData类型的接口。

安装:npm i multer

加载:const multer= require('multer')

配置上传文件路径:const upload = multer({ desc: 'uploads/' })

接口中使用:

router.post('/add', upload.single('cover_img'), (req, res) => {
  // upload.single() 方法用于处理单文件上传
  // cover_img 图片字段的名字
  
  // 通过 req.body 接收文本类型的请求体,比如 title,content等
  // 通过 req.file 获取上传文件信息
});
var multer = require('multer')
var upload = multer({ dest: 'uploads/' }); // 配置上传文件的目录
const moment = require('moment');

router.post('/add', upload.single('cover_img'), (req, res) => {
  // req.body 表示文本信息
  // req.file 表示上传的文件信息
  // console.log(req.file); // req.file.filename 表示上传之后的文件名

  // 把数据添加到数据表中存起来
  // req.body = { title: 'xx', content: 'xx', cate_id: 1, state: 'xx' }
  let { title, content, cate_id, state } = req.body; 
  // 其他字段
  let pub_date = moment().format('YYYY-MM-DD HH:mm:ss');
  let cover_img = req.file.filename;
  let author_id = req.user.id;
  // console.log(obj);
  // return;
  let sql = `insert into article set title='${title}', content='${content}', cate_id=${cate_id}, state='${state}', pub_date='${pub_date}', cover_img='${cover_img}', author_id=${author_id}`;
  db(sql (err, result) => {
    if (err) throw err;
    if (result.affectedRows > 0) {
      res.send({ status: 0, message: '发布成功' })
    } else {
      res.send({ status: 1, message: '发布失败' })
    }
  })
});

删除文章接口

// ---------------- 删除文件接口 --------------------
/**
 * 请求方式:GET
 * 接口地址:/my/article/delete/2
 * 请求参数:id,url参数
 */
// router.get('/delete/:id/:age/:name', (req, res) => {
router.get('/delete/:id', (req, res) => {
  let id = req.params.id;
  let sql = `update article set is_delete=1 where id=${id} and author_id=${req.user.id}`;
  db(, (err, result) => {
    if (err) throw err;
    if (result.affectedRows > 0) {
      res.send({ status: 0, message: '删除成功' })
    } else {
      res.send({ status: 1, message: '删除失败' })
    }
  })
});

更新文章接口

router.post('/update', upload.single('cover_img'), (req, res) => {
  // 和添加文章接口差不多,要注意,客户端多提交了文章id,这是我们修改文章的条件
  // req.body = { title: 'xx', content: 'xx', cate_id: 1, state: 'xx', id: 6 }
  let { title, content, cate_id, state, id } = req.body; 
  // 其他字段(发布时间,不是修改时间,所以不需要改了,用户id也不需要改)
  let cover_img = req.file.filename;
  // console.log(obj);
  // return;
  let sql = `update article set title='${title}', content='${content}', cate_id=${cate_id}, state='${state}', cover_img='${cover_img}' where id=${id}`;
  db(sql, (err, result) => {
    if (err) throw err;
    if (result.affectedRows > 0) {
      res.send({ status: 0, message: '修改文章成功' })
    } else {
      res.send({ status: 1, message: '修改文章失败' })
    }
  })
});

Express总结:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值