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总结: