# bigevent **Repository Path**: lihui_tinghao/bigevent ## Basic Information - **Project Name**: bigevent - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-01-23 - **Last Updated**: 2021-01-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Express ## express 介绍 - Express 是一个第三方模块,用于快速搭建服务器(替代http模块) - Express 是一个基于 Node.js 平台,快速、开放、极简的 **web 开发框架**。 - Express保留了http模块的基本API,使用express的时候,也能使用http的API - 使用express的时候,仍然可以使用http模块的方法,比如 res.end()、req.url - express还额外封装了一些新方法,能让我们更方便的搭建服务器 - express提供了中间件功能,其他很多强大的第三方模块都是基于express开发的 - [Express 官网](http://expressjs.com/) - [Express 中文文档(非官方)](http://www.expressjs.com.cn/) - [Express GitHub仓库](https://github.com/expressjs/express) - [菜鸟教程](https://www.runoob.com/w3cnote/express-4-x-api.html) - [腾讯云开发者手册](https://cloud.tencent.com/developer/doc/1079) - 百度自行搜索 ## 安装 express 项目文件夹中,执行 `npm i express`。即可下载安装express。 > 注意:express不能安装在express文件夹中。否则安装失败。 ## 使用Express构造Web服务器 使用Express构建Web服务器步骤: 1) 加载 express 模块 2) 创建 express 服务器 3) 开启服务器 4) 监听浏览器请求并进行处理 ```js // 使用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响应头 - 如果已经做出响应了,就不要再次做出响应了。 ```js 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。 ## 案例 - 大事件的登录注册接口 ### 使用GIT管理项目 ``` - bigevent-server - index.js - db.js - .gitignore - package.json - package-lock.json ---- 被忽略 - node_modules ---- 被忽略 ``` 搭建好项目目录之后。使用Git初始化。 设置忽略文件(`.gitignore`),这个忽略文件中记录的文件、文件夹不会添加到暂存区,不会提交到本地仓库,当然也就不会推送到远程仓库。 ``` # git的忽略文件 # 忽略文件中指定的 文件、文件夹 不会被添加到暂存区,不会提交到本地仓库,不会推送到远程仓库 node_modules package-lock.json ``` 设置好忽略文件之后,下一步add、commit、push。即可。 **如果误把该忽略的文件add了,commit了。怎么办?** - `git rm -r --cached 文件` (只移除本地仓库的文件,不删除工作区的文件) - `git add .` (重新添加一次) - `git commit -m 'xxx'` (重新提交即可) 这样做完,如果发现vscode文件颜色没有变化,重启vscode再看看。 **忽略文件的语法** [官方文档](https://git-scm.com/book/zh/v2/Git-%E5%9F%BA%E7%A1%80-%E8%AE%B0%E5%BD%95%E6%AF%8F%E6%AC%A1%E6%9B%B4%E6%96%B0%E5%88%B0%E4%BB%93%E5%BA%93) ``` # 只忽略根目录里面的 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 ``` ### 创建数据表 | 字段 | 类型 | 长度 | 不是null | 主键 | 其他 | | -------- | -------- | ---- | -------- | ---- | ---------- | | id | int | | √ | 🔑 | √ 自动递增 | | username | varchar | 10 | √ | | | | password | char | 32 | √ | | | | user_pic | longtext | | | | | | nickname | varchar | 10 | | | | | email | varchar | 30 | | | | ### 使用ApiPost模拟注册请求 ![image-20210122173242412](assets/image-20210122173242412.png) ### 写接口 ```js // 完成接口项目 // 前面三行启动服务 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)。 ```js // 完成接口项目 // 前面三行启动服务 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' } // 2. 判断账号是否已经被占用了 // 3. 如果没有被占用,把账号密码添加到数据库 }); ``` > 代码写完,一定要使用ApiPost发送请求,测试代码。 ### 验证用户名是否存在 思路:根据用户名查询,看是否能够查到数据。 - 没有查询数据,说明这个用户名不存在,能够使用 - 如果查到数据库,说明这个用户名已经存在,不能使用 ```js // 完成接口项目 // 前面三行启动服务 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. 如果没有被占用,把账号密码添加到数据库 } }) }); ``` ### 完成注册 如果用户名可用,则添加到数据表中,完成注册 ```js // -------------------- 注册接口 ---------------------- // 请求体: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 ```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 复制过来即可。 ```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; ``` ### 完成获取分类列表数据的接口 ```js // ----------------- 获取分类的接口 ------------------ /** * 请求方式: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](assets/image-20201225145042976.png) 服务端代码: ```js // ----------------- 删除分类的接口 ------------------ /** * 请求方式: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](assets/image-20201225150033489.png) 服务端代码: ```js // 解析POST参数(接收POST参数,并转成成对象,然后把转换后的结果赋值给req.body) app.use(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](assets/image-20201225153614367.png) 服务端代码: ```js /** * 请求方式: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](assets/image-20201108092224934.png) ## 中间件介绍 - 中间件(Middleware ),特指业务流程的中间处理环节。 - 中间件,是express最大的特色,也是最重要的一个设计 - 很多第三方模块,都可以当做express的中间件,配合express,开发更简单。 - 一个express应用,是由各种各样的中间件组合完成的 - 中间件,本质上就是一个函数 ## 中间件原理 为了理解中间件,我们先来看一下我们现实生活中的自来水厂的净水流程。 ![image-2020033130641861](assets/image-20200331130641861.png) - 在上图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节 - 我们称其中的每一个处理环节就是一个中间件。 - 这样做的目的既提高了生产效率也保证了可维护性。 express中间件原理: ![image-2020033104703510](assets/image-20200331004703510.png) ## 中间件的几种形式 ```js // 下面的中间件,只为当前接口 /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自带的,比如 `urlencoded({ extended: true })`) - 第三方中间件(比如multer、express-jwt、express-session、jsonwebtoken....) > 实际开发中,自己写中间件的机会并不大,一般都有对应的第三方中间件。