Appearance
Express
什么是Express?
基于node平台的web开发框架,与http模块类似,用来创建可用的服务器。由于其是第三方包,需要先下载。
针对前端领域,常见的服务器可分成两类:Web网站服务器(存放网站资源),API接口服务器(处理各种请求逻辑)
javascript
// 安装
npm i express // 可以指定版本。指定版本的命令npm i express@x.x.x x要替换为想要的版本号
GET请求
javascript
// 监听GET请求
/**
* 参数1,url 客户端请求的url地址
* 参数2,请求对应的处理函数。
* req:请求对象,包含与请求相关的属性和方法
* res:响应对象,包含与响应相关的属性和方法
*/
app.get(url,function(req,res){
res.send("返回了一个值"); // 使用send方法将响应内容返回给客户端
});
// 获取静态参数
app.get('/user',function(req,res){
// 使用req.query可以访问带参url的参数。req.query默认是空对象
const username = req.query.name; // 假设传来的地址是/user?name=Tom,那么此时username=Tom
res.send(`你的名字是${username}`);
console.log(req.query); // 如果客户端使用get发送请求,那么这个query就会接收到内容
});
// 获取动态参数
app.get('/news/:page',function(req,res){
// 如果是动态参数,则需要req.params来获取
const now_page = req.params.page // 假设传来的地址是/new/:10, 那么此时now_page=10
res.send(`当前所在页是${now_page}`);
});
POST请求
javascript
// 监听POST请求
/**
* 参数1,url 客户端请求的url地址
* 参数2,请求对应的处理函数。
* req:请求对象,包含与请求相关的属性和方法
* res:响应对象,包含与响应相关的属性和方法
*/
app.post(url,function(req,res){});
由于get请求有长度限制,且参数会暴露在地址栏中(不安全),所以在传递表单等内容的时候,更多使用post请求
静态资源
javascript
// 可以使用express.static方法关联静态资源地址
app.use(express.static('public')); // 一般会将静态资源放在public目录当中,所以这里关联了public
// 假设有public/img/logo.png, 那么我们可以访问http://127.0.0.1/img/logo.png即可访问到图片
// app.use('/video', express.static('video')); // 如果想关联多个资源目录,多次声明文件地址即可。另外,这里的第一个参数是资源前缀
// 即想要获取资源的时候必须加上这么一个前缀才能获取, 例如 http://127.0.0.1/video/mm.mp4
nodemon
每次更改项目代码都需要手动重启服务非常麻烦,我们可以用nodemon来帮我们监听代码,从而自动重启。
javascript
// 全局安装
npm i -g nodemon // 也可以npm i nodemon,这样就是本地安装了。推荐全局安装
路由
按生活的例子来讲,你喊我一声,我得响应你一声。具体到应用上来说,就是你做操作,软件得回应你的操作。那么将这种回应放到服务器上,从而组成的"客户端请求,服务器响应"的环路,就称为路由。
Express中的路由分成3部分:请求的类型、请求的URL地址、处理函数
javascript
/**
* METHOD, 就是前面说过的get,post。除此之外还有put、delete
* PATH, 请求路径
* HANDLER, 处理函数
*/
app.METHOD(PATH, HANDLER)
为了方便的管理路由,express也提供了路由方法
javascript
// 假设这里有某个路由文件。假设名称为getUserInfo.js
// 1、导入express
const express = require('express');
// 2、创建路由对象
const router = express.Router()
// 3、挂载具体路由
router.get('/user', (req,res)=>{
res.send("get");
});
// 4、导出路由对象
module.exports = router;
// 入口文件中注册路由
// 1、先导入路由
const _router = require('./router/getUserInfo.js');
// 2、注册路由模块
app.use(userRouter);
中间件
业务处理中的中间环节,必须同时具有输入和输出。在实际应用中,由于中间件能够共享同一份req和res,所以可以在下游中对其进行处理。
中间件会以声明的顺序执行,最终出口是路由
express中的use是全局中间件
javascript
// 例如下面的代码,是一个自定义的中间件
app.get('/',function(req,res,next){
next();
});
// ============分割线=============
// 也可以是非路由形式的
const addStr = function(req,res,next){
req.query.age = 18; // 假设传进来的参数只有name=Tom,
next(); // next后不要写代码
};
app.use(addStr); // 如果需要全局使用这个中间件,那就用use注册一下
// 最终路由
app.get('/user',function(req,res){
res.send(req.query); // 输出是:{"name":"Tom","age":18}
})
// ============分割线=============
//如果想要仅在某个路由中使用中间件可以这么写:
app.get('/news', addStr, function(req,res){}); // 三个参数的路由,参数2是中间件
// 实际上,只要保证参数1是路径,最后一个参数是回调函数,那么中间可以写任意多个中间件,执行顺序从左到右。格式有两种:
app.get('/news', mw1,mw2, function(req,res){});
app.get('/news', [mw1,mw2], function(req,res){});
错误中间件
当程序发生错误,很可能导致崩溃。为了捕获错误,可以使用能够处理错误的中间件
javascript
app.get('/calculate',function(req,res){
try{
const result = 2 / req.query.num; // 当num为0的时候,这段代码就会出错
}catch(e){
throw new Error("分母不能为0!");
}
});
app.use(function(err,req,res,next){
console.log("计算错误!"+err.message);
next();
})
express内置中间件
express.static
express.json,解析JSON格式的请求体数据 (仅4.16.0+版本中可用)
express.urlencoded,解析URL-encoded格式的请求体数据 (仅4.16.0+版本中可用)。这种格式所传的内容必须是键值对
4.16.0以前,需要用第三方中间件body-parser来处理请求体。 安装npm i body-parser 使用require导入中间件 app.use()注册并使用中间件
javascript
// 可以通过req.body获取前端传来的表单请求体,但前提是配置了上面两条请求体数据
app.use(express.json());
app.use(express.urlencoded({extended: false}));
router.post('/regist', function(req,res){
console.log(req.body); // 请尝试在html中写好表单并发送请求
});
监听data事件
如果上传的内容过大,一次传输无法完整处理,那么我们需要监听data,从而获知数据是否已完整获取。
javascript
let str = ""
// 监听req的data对象
req.on("data", (chunk)=>{
str += chunck
})
监听end事件
请求体数据接收完毕时会触发end事件。当接收完毕后想要进行一些操作的时候可以监听end事件。
javascript
// 监听req的data对象
req.on("end", ()=>{
console.log(str); // str取自上面的data事件,意味着当数据接收完毕就将str的值打印日志
})
解析为字符串
有时前端传来的数据可能是编码后的,服务端需要反编码获得原字符串,这时可以使用querystring模块来进行解析
javascript
// 导入
const qs = require('querystring');
// 调用
const body = qs.parse(str) // 例如str里可能包含%25这样的字眼,解析后可知%25是百分号的十六进制值
跨域
跨域,如字面意思,就是越界了。这个越界越的是"同源策略"当中规定的界。这一策略规定,在请求一个资源时必须满足三个条件全部一致,即:域名、协议、端口号。
以地址"http://127.0.0.1:8008"为例,其中http就是通信协议的一种,127.0.0.1是域名(这里暴露的是ip地址,但可以通过DNS解析成我们常见的字符串域名地址), 8008是端口号。
javascript
// 解决跨域,在前端侧可以使用cors
const cors = require('cors');
app.use(cors());
// res.setHeader('Access-Control-Allow-Origin', '*');
CORS是一组HTTP响应头,这些响应头决定浏览器是否阻止前端js代码跨域获取资源。
当请求发出,服务器响应到客户端的过程中,客户端会检查响应头,如果不符合同源策略则阻止获取数据。
请求源、请求头、请求体
- 请求源
如果指定Access-Control-Allow-Origin字段的值为通配符*,表示允许任何域请求
javascript
res.setHeader('Access-Control-Allow-Origin', '*');
- 请求头
CORS仅支持客户端向服务器发送如下9个请求头
Accept | Accept-Language | Content-Language | DPR | Downlink | Save-Data | Viewport-Width | Width | Content-Type
请求头的值仅限于这三者之一:text/plain | multipart/form-data | application/x-www-form-urlencoded
javascript
res.setHeader('Access-Control-Allow-Header', 'Content-Type, X-Custom-Header');
- 请求方式
默认只接受GET请求,POST请求,HEAD请求。如果需要支持其他请求,需要显示声明。
javascript
res.setHeader('Access-Control-Allow-Methods', '*'); // 星号,代表全部允许
res.setHeader('Access-Control-Allow-Methods', 'POST,GET,DELETE,PUT,HEAD'); // 也可以特殊指定几个,以逗号分隔
JSONP
浏览器通过<script>标签的src属性请求服务器上的数据,同时,服务器返回一个函数的调用,这样的请求方式叫做JSONP。
JSONP不属于Ajax请求(因为没用XMLHttpRequest对象),且仅支持GET请求。
如果配置了CORS,那么必须在CORS中间件之前声明JSONP接口
javascript
// 实际应用的例子如下:
app.get('/api/jsonp', (req,res)=>{
// 1、得到函数名称
const funcName = req.query.callback;
// 2、定义要发送到客户端的数据对象
const data = {name: "Tom", age: 5};
// 3、拼接出函数的调用
const scriptStr = `${funcName}(${JSON.stringfy(data)})`;
// 4、把拼接的字符串响应给客户端
res.send(scriptStr);
});
mysql模块
要使用数据库模块,需要先安装、配置。
npm i mysql
javascript
// 1、导入
const mysql = require('mysql');
// 2、与数据库建立连接
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的IP地址
user: 'root', // 登录数据库的账号
password: '****', // 登录数据库的密码
database: 'mydb', // 指定要操作的数据库
});
// 3、对数据库进行操作
db.query('SELECT 1', (err, results)=>{
if(err) return console.log(err.message);
// SELECT 1用来检测是否能够操作数据库。正常的返回值是[RowDataPacket{'1': 1}]
console.log(results);
});
// ==========分割线=============
// 下面是插入数据的例子
const user = {name: "Tom", age: 18}; // 想插入的内容
// 下面括号里的name,age是数据库里面的列名, 想输入的值用问号占位
consg sqlStr = 'INSERT INTO users (name, age) VALUES(?,?)';
// 使用数组形式传参
db.query(sqlStr, [user.name, user.age], (err, results)=>{
if(err) return console.log(err.message);
if(results.affectedRows === 1) console.log("插入数据成功!");
})
// 也有简写方式,但前提是传入的属性与数据库里的字段必须一一对应
const user = {name: "Tom", age: 18};
const sqlStr = 'INSERT INTO users SET ?';
db.query(sqlStr, user, (err, results)=>{
// 语句
});
// ==========分割线=============
// 下面是更新数据的例子。值使用之前的user
const sqlStr = 'UPDATE users SET name=?, age=? WHERE id=?';
db.query(sqlStr,[user.name,user.age,user.id], (err, results)=>{
if(err) return console.log(err.message);
if(results.affectedRows === 1) console.log("更新数据成功!");
});
// 简便写法
const sqlStr = 'UPDATE users SET ? WHERE id=?';
db.query(sqlStr,[user,user.id], (err,results)=>{
// 语句
});
// ==========分割线=============
// 下面是删除数据的例子。值使用之前的user
const sqlStr = 'DELETE FROM users WHERE id=?';
db.query(sqlStr,user.id, (err, results)=>{
if(err) return console.log(err.message);
if(results.affectedRows === 1) console.log("删除数据成功!");
});
// 由于删除会真的将数据从数据库中删除,不推荐在用户进行删除操作时立刻执行删除语句,而是先标记一下数据被删除的状态,再在以后的某个时间点对数据进行删除
// 例如,假设有一条数据,其中包含一个deletestate属性用于标记该条数据。当用户执行删除操作,则使用更新语句将deletestate标记为true,等到以后某个确认不需要该数据的时间点再进行真正的删除即可。
// 像这样标记一下再删除的操作,叫标记删除。
JWT(JSON Web Token)
JWT是跨域认证解决方案。其生成及认证步骤如下:
1、客户端登录,服务端验证
2、验证成功,加密并生成token字符串,将token返回给客户端
3、客户端保存token(localStorage或sessionStorage)
这样,一次生成与接收token就完成了。当后续用户再登录时:
1、通过请求头的Authorization字段将token发送给服务器
2、服务器还原token为用户的信息对象
3、认证成功后,针对当前用户生成响应内容
JWT字符串可分成三部分,以点分隔:Header.Payload.Signature。其中,Payload部分是用户信息,前后两部分是安全性相关的部分
Authorization: Bearer <token>。这是token请求头的样子。
在Express中使用jwt需要安装两个包,第一个包生成jwt字符串,第二个包还原jwt字符串。
npm i jsonwebtoken express-jwt
javascript
const jwt = require('jsonwebtoken');
const expressJWT = require('express-jwt');
// 使用前,我们需要一个加密密钥来加密我们的token防止被盗用
const secretKey = "i20093j0fkjdoqijwi"; // 随便生成个字符串即可,但是要记住,解密的时候还要用到。一般来说可以使用时间戳。
// 当用户登录成功,要生成并返回JWT字符串
app.post('/user/login', function(req,res){
res.send({
status: 200,
message: '登录成功',
token: jwt.sign({name: user.name,age: user.age}, secretKey,{expiresIn: '30s'})
})
});
// 使用sign生成token,参数1是用户信息对象,参数2是加密密钥,参数3是token过期时间(这里设置了30秒)。注意,用户信息中不要放密码信息
// =======分割线========
// 下面是还原jwt。由于在需要验证身份的地方都需要校验token,所以我们可以使用use注册全局中间件,从而在需要的时候使用
// 当注册成功后,req里会被挂在用户信息,可以使用req.user调用
app.use(expressJWT({secret: secretKey}).unless({path: [/^\/api\//]})); // 这里使用正则验证了路由前缀,意味着凡是/api开头的路由都不需要鉴权
// =======分割线========
// JWT解析失败,可以在所有路由之后注册一个全局的错误中间件进行处理
app.use((err,req,res,next)=>{
// token解析失败导致的错误
if(err.name === 'UnauthorizedError'){
return res.send({status: 401, message: '无效的token'});
}
// 其他原因导致的错误
res.send({status: 500, message: '未知错误'});
})