# 模块

 

## fs模块

 

writeFile方法

 

`fs.writeFile(file, data[, options], callback)`

 

readFile方法

 

`fs.readFile(path[, options], callback)`

 

## path模块

 

######   join方法

 

用来将多个路径片段拼接成一个完整的路径

 

`path.join('/a','/b/c','../')`

 

传入../会抵消掉前面一层路径

 

此时输出为\a\b

 

######   basername方法

 

用来从路径字符串中,将文件名解析出来

 

一般是用来从一串路径中获取最后一部分

 

可以传入第二个参数文件扩展名,传入后会删除文件扩展名只返回文件名

 

## http模块

 

###### createServer方法

 

创建一台web服务器

 

###### on方法

 

一般传入request事件,监听客户端发来的网络请求

 

```js

const http = require('http')

 

const server = http.createServer()

 

 

server.on('request',function(req,res){

console.log('a')

})

 

server.listen(80,function(){

console.log('访问成功')

})

 

 

```

 

### 根据不同的url响应不同的html页面

 

1. 获取请求的url地址

2. 设置默认内容为404not found

3. 判断用户请求是否为/index.html页面

4. 判断用户请求的是否为/about.html关于页面

5. 设置Content-Type响应头,防止中文乱码

6. 使用res.end把内容响应给客户端

 

```js

const http = require('http')

 

const server = http.createServer()

 

server.on('request',function(req,res){

const url = req.url

let content = '<h1>404 Not found<h1>'

if(url == '/'||url == '/index.html'){

content = '<h1>首页<h1>'

}

res.setHeader('Content-Type','text/html;charset=utf-8')

res.end(content)

})

 

server.listen(80,function(){

console.log('访问成功')

})

 

 

```

 

 

 

```js

const http = require('http')

const path = require('path')

const fs = require('fs')

 

const server = http.createServer()

 

server.on('request',function(req,res){

var __dirname = './pages'

const url = req.url

const fpath = path.join(__dirname,url)

fs.readFile(fpath,'utf8',(err,dataStr)=>{

if(err) return res.end('404 Not Found')

res.end(dataStr)

})

 

})

 

server.listen(80,function(){

console.log('启动成功')

})

 

 

```

 

# 使用express框架

 

expressnpm第三方框架

 

## 基础模板

 

```js

const express = require('express')

const app = express()

 

app.listen(80,()=>{

console.log('服务器启动成功')

})

```

 

## 监听请求

 

通过app.get()方法,可以监听客户端的GET请求,具体语法格式如下:

 

```js

app.get('请求的url',function(req,res){回调函数})

```

 

post同理

 

## 响应请求

 

通过res.send()方法,可以把处理好的内容,发送给客户端

 

```js

app.get('/user',(req,res)=>{

    res.send({name:'zs',age:20,gender:''})

})

 

app.post('/user',(req.res)=>{

    res.send('发送成功')

})

```

 

## 获取url中携带的查询参数

 

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:

 

默认情况下req.query是一个空对象

 

## 获取url的动态参数

 

通过req.params对象, 可以访问到URL中,通过:匹配的动态参数

 

```js

app.get('/user/:id',(req,res)=>{

    console.log(req.params)

})

```

 

req.params默认是一个空对象

 

此方法允许多个动态参数嵌套

 

## express静态资源托管

 

### express.static()

 

语法如下

 

`app.use(express.static('public'))`

 

其中public是文件夹名

 

通过多次调用该方法可以托管多个静态资源文件夹

 

### 挂载路径前缀

 

之前使用的方法在访问时路径不会包含文件夹,可以通过日下方法将文件夹名挂载到路径中

 

`app.use('/public',express.static('public'))`

 

## express路由

 

express的路由是由三部分组成的,分别是请求类型,请求的URL地址,处理函数,格式如下

 

`app.METHOD(PATH,HANDLER)`

 

```js

app.get('/',function(req,res){

    res.send('hello,world')

})

```

 

### 路由的匹配过程

 

在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功才会调用回调函数

 

路由的使用

 

express中使用路由最简单的的方式,就是把路由挂载到app上,示例代码如下

 

### 模块化路由

 

为了方便对路由进行管理,express建议使用独立的路由模块

 

1. 创建路由模块对应的.js文件

2. 调用express.Router()函数创建路由对象

3. 向路由对象上挂载具体的路由

4. 使用module.exports向外共享路由对象

5. 使用app.use()函数注册路由模块

 

以下为router.js模块内容

 

```js

const express = require('express') //引入express模块

const router = express.Router()  //创建路由对象

 

router.get('user/list',function(req,res){ //挂载获取用户列表的路由

res.send('get user list')

})

 

module.exports = router  //向外到处路由对象

```

 

### 注册路由模块

 

```js

const userRouter = require('/router/user.js')

app.use(userRouter)

```

 

### 为路由模块添加统一的访问前缀

 

与挂载路由前缀相同,直接添加字符串即可

 

`app.use('/api',userRouter)`

 

## 中间件

 

中间件特质业务流程的中间处理环节

 

当一个请求到达express服务器后,可以连续调用多个中间件,从而对这次请求进行预处理

 

### 中间件的格式

 

本质上express是一个function函数

 

```js

app.get('/',function(req,res,next){

 

next()

 

})

```

 

中间件函数的形参列表中必须包含next参数,而路由处理函数中只包含reqres

 

### next函数的作用

 

next函数是实现多个中间件连续调用的关键,它标识把流转关系转交给下一个中间件或路由

 

### 定义中间件函数

 

```js

const mw = function(req,res,next{

console.log()

next()

})

```

 

### 全局生效中间件

 

客户端发起的任何请求到达服务器后都会触发的中间件,叫做全局生效的中间件

 

通过调用app.use(中间件函数),即可定义一个全局生效的中间件

 

```js

const mw = function(req,res,next){

console.log()

next()

}

 

 

app.use(mw)

```

 

### 定义全局中间件的简化形式

 

可以直接在use中写入函数来注册全局生效中间件

 

```js

app.use((req,res,next)=>{

console.log()

next()

})

```

 

### 中间件的作用

 

多个中间件之间共享一份reqres,这样可以在上游的reqres中添加一下自定义的方法属性从而在下游使用

 

### 定义多个全局中间件

 

使用app.use()即可,会按照上下顺序依次执行

 

### 局部中间件

 

不适用app.use定义的中间件叫做局部生效的中间件

 

```js

const mw = function(req,res,next){

console.log()

next()

}

 

app.get('/',mw,(req.res)=>{

    res.send('hello,world')

})

```

 

mw中间件只在这一个路由函数中生效

 

### 定义多个局部中间件

 

可以直接写入多个中间件参数,也可以传入一个数组

 

```js

app.get('/',mw,mw2,(req.res)=>{

    res.send('hello,world')

})

//或者

app.get('/',[mw,mw2],(req.res)=>{

    res.send('hello,world')

})

```

 

区分先后顺序

 

### 中间件的注意点

 

1. 要在路由之前注册中间件

2. 客户端发来的请求可以连续调用多个中间件处理

3. 执行完中间件业务代码后不要忘记调用next函数

4. 为了防止代码逻辑混乱,在next函数后不要再调用另外的代码

5. 调用多个中间件时,多个中间件共享reqres

 

### 中间件的分类

 

#### 应用级别

 

通过app.get,app.post等绑定到app实例上的中间件都叫做应用级别

 

#### 路由级别

 

绑定到expressRouter上的中间件叫做路由级别中间件,它的用法和应用级别没有任何区别

 

#### 错误级别

 

专门用来捕获异常,防止项目崩溃的中间件

 

错误级别的中间件function包含四个形参,从前到后分别为(err,req,res,next

 

#### express内置中间件

 

1. express.static

 

用于托管静态资源,没有兼容性,在任何版本的express中都可以正常使用

 

2. express.json

 

解析json格式的请求体数据(有兼容性,只在4.16.0+版本中使用)

 

3. express.urlencoded

 

用于解析urlencoded格式的请求体数据(有兼容性,仅在4.16.0+版本中使用)

 

使用方法

 

为固定写法

 

```js

app.use(express.json())

 

app.use(express.urlencoded({extended:false}))

//它们会将json数据挂载到req.body

```

 

#### 第三方中间件

 

## 解决跨域问题

 

### CORS解决跨域问题

 

#### 使用cors中间件解决跨域问题

 

1. npm i cors

2. const cors = require('cors')//导入中间件

3. 在路由之前调用app.use(cors())配置中间件

 

#### 什么是cors

 

cors(跨域资源共享),由一系列http响应头组成,这些http响应头决定浏览器是否组织js代码跨域获取资源

 

#### cors的响应头

 

##### Access-Control-Allow-Origin

 

响应头中可以携带一个Access-Control-Allow-Origin,语法如下

 

```

Access-Control-Allow-Origin:<origin>|*

```

 

其中,origin参数的值制定了允许访问该资源的外域url

 

##### Access-Control-Allow-Headers

 

默认情况下,cors仅支持客户端向服务器提交固定的9个请求头

 

如果客户端向服务端发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对请求头进行声明,否则这次请求会失败

 

```js

res.setHeader('Access-Control-Allow-Headers','Content-Type,X-Custom-Header')

```

 

##### Access-Control-Allow-Methods

 

默认情况下,cors仅支持客户端发起GETPOSTHEAD请求

 

如果客户端希望通过PUT,DELETE等方式请求服务器资源,则需要在服务器端,通过Access-Control-Allow-Methods来知名实际请求所允许的HTTP方法

 

```js

res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')

 

res.setHeader('Access-Control-Allow-Methods','*')

```

 

### 跨域

 

#### 简单请求

 

1. 请求方式:GET,POST,HEAD三者之一

2. HTTP头部信息不超过以下几个字段:无自定义头部字段,AcceptAccept-LanguageDPRDownlinkSave-DataView-WidthContent-Type(只有三个值application/x-www-form-urlencodedmultipart/form-data/text/plian

 

#### 预检请求

 

1. 请求方式为GET,POST,HEAD之外的请求Method类型

2. 请求头中包含了自定义请求头

3. 向服务器发送了application/json格式的请求

 

什么是预检请求?

 

在浏览器和服务器正式通信之前,浏览器会先发送OPTION请求进行遇见,以获知服务器是否允许该请求,所以这一次的OPTION请求称为“预检请求”。服务器成功预检请求后,才会发送真正的请求,并且携带真实数据。

 

#### 简单请求和预检请求的区别

 

简单请求:客户端和服务器只会发送一次请求

 

预检请求:客户端和服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求

 

### JSONP接口

 

#### 什么是JSONP

 

浏览器通过\<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做JSONP

 

#### 特点

 

1. JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest对象

2. JSONP仅支持GET请求,不支持POST,PUT,DELETE请求

 

#### 创建JSONP接口的注意事项

 

如果项目中已经配置了CORS跨域请求资源,为了防止冲突,必须在配置CORS中间件之前生命JSONP的接口。否则JSONP就被处理成开启了CORS的接口

 

```js

//优先创建JSONP接口(这个接口不会被处理成CORS接口)

app.get('/api/jsonp',(req,res)=>{})

 

//再配置CORS中间件(后续所有接口都被处理成CORS接口)

app.use(cors())

 

//这是一个开启了cors的接口

app.get('/api/get',(req,res)=>{})

```

 

#### 实现JSONP接口的步骤

 

1. 获取客户端发送过来的回调函数的名字

2. 得到要通过JSONP形式发送给客户的数据

3. 根据两步得到的数据,拼接处一个函数调用的字符串

4. 把上一步拼接得到的字符串,相应给客户端的script标签进行解析执行

 

```js

app.get('/api/JSONP',(req,res)=>{

    //获取客户端发送过来的回调函数的名字

const funcName = req.query.callback

    //得到要通过JSONP形式发送给客户的数据

const data = {name:"zs",age:22}

    //根据两步得到的数据,拼接处一个函数调用的字符串

const scrpitStr = '${funcName}(${JSON.stringify(data)})'

    //把上一步拼接得到的字符串,相应给客户端的script标签进行解析执行

res.send(scrpitStr)

})

```

 

# mysql的使用

 

## 安装

 

```

npm i mysql

```

 

## 配置

 

```js

const mysql = require('mysql')

 

//建立连接

const db = mysql.createPool({

host:'127.0.0.1',

user:'root',

password:'bujieshi.',

database:'test'

})

```

 

## 使用

 

```js

db.query('SELECT 1',(err,results)=>{

    if(err) return console.log(err.message)

    console.log(results)

})

```

 

```js

const mysql = require('mysql')

 

 

const db = mysql.createPool({

host:'127.0.0.1',

user:'root',

password:'bujieshi.',

database:'test'

})

 

//要插入的数据对象

const user = {username:'Spider-Man',password:'pc123'}

// 待执行的sql语句,其中英文的?表示占位符

const sqlStr = 'INSERT INTO users(username,password) VALUES (?,?)'

// 使用数组的形式,依次为?占位符指定具体的值

db.query(sqlStr,[user.username,user.password],(err,results)=>{

if(err) return console.log(err.message)

    //affectedRows指影响的行数

if(results.affectedRows === 1) console.log('插入成功')

})

```

 

### 插入数据的便捷方式

 

```js

const user = {username:'Spider-Man',password:'pc123'}

// 待执行的sql语句,其中英文的?表示占位符

const sqlStr = 'INSERT INTO users SET ?'

 

db.query(sqlStr,user,(err,results)=>{

if(err) return console.log(err.message)

if(results.affectedRows === 1) console.log('插入成功')

})

```

 

# 身份认证

 

## session

 

### http的无状态性

 

http的每次请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次http请求的状态

 

### 如何突破http的无状态性

 

#### cookie

 

cookie是一个存储在用户浏览器中的一段不超过4kb的字符串,它由一个名称,一个值,和其他几个用于控制Cookie有效期,安全性,适用范围的可选属性组成

 

不同域名下的Cookie是独立的,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器

 

##### 特点

 

1. 自动发送

2. 域名独立

3. 过期时限、

4. 4kb限制

 

##### cookie不具有安全性

 

由于cookie是存储在浏览器中的,而且浏览器也提供了独写CookieAPI,因此Cookie很容易被伪造,不具有安全性,因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器

 

## session的工作原理

 

1. 客户端登录,提交账号密码

2. 服务器验证账号密码

3. 将登陆后的用户信息储存到服务器,同时生成对应的Cookie字符串

4. 服务器将Cookie相应给客户端

5. 浏览器自动将Cookie存储到当前域名下

6. 客户端再次发起请求时,通过请求头自动把当前域名下所有可用的Cookie发送给服务器

7. 服务器根据头中携带的Cookie从内存中查找对应的用户信息

8. 用户的身份认证成功后,服务器针对当前用户生成特定的响应内容

9. 服务器将当前用户对应的页面响应给浏览器

 

## session中间件的使用

 

### 安装

 

```

npm i express-session

```

 

### 配置

 

通过app.use()来注册session中间件

 

```js

var session = require('express-session')

 

app.use(session({

secret:'keyboard cat',//session属性的值可以为任意字符串

resave:false,//固定写法

saveUninitialized:true//固定写法

}))

```

 

### session中存数据

 

express-session中间件配置成功后,即可通过req.session来访问和使用session对象,从而存储用户的关键信息

 

```js

const express = require('express')

var session = require('express-session')

 

const app = express()

 

app.use(session({

secret:'vscode',//session属性的值可以为任意字符串

resave:false,//固定写法

saveUninitialized:true//固定写法

}))

 

app.post('api/login',(req.res)=>{

if(req.body.username != admin || req.body.password !== '000'){

return res.send(status:1,msg:'登陆失败')

}

req.session.user = req.body //将用户的信息,存储到Session

req.session.islogin = true//将用户的登录状态,存储到Session

res.send({status:0,msg:'登陆成功'})

})

```

 

注意,只有配置了session中间件后才能使用req.session

 

### session中取数据

 

```js

app.get('/api/username',(req,res)=>{

if(!req.session.islogin){

return res.send({status:1,msg:'fail'})

}

res.send({status:0,msg:'success',username:req.session.username})

})

```

 

### 清空session

 

调用req.session.destory()函数,即可清空服务器保存的session信息

 

```js

app.post('/api/logout',(req,res)=>{

    //清空当前用户的session

req.session.destory()

res.send({

status:0,

msg:'退出登录成功'

})

})

```

 

 

 

```js

const express = require('express')

var session = require('express-session')

 

const app = express()

 

app.use(session({

secret:'vscode',//session属性的值可以为任意字符串

resave:false,//固定写法

saveUninitialized:true//固定写法

}))

 

app.post('/api/login',(req,res)=>{

console.log('req',req)

if(req.body.username != 'admin' || req.body.password !== '000'){

return res.send({status:1,msg:'登陆失败'})

}

req.session.user = req.body //将用户的信息,存储到Session

req.session.islogin = true//将用户的登录状态,存储到Session

res.send({status:0,msg:'登陆成功'})

})

 

app.get('/api/username',(req,res)=>{

if(!req.session.islogin){

return res.send({status:1,msg:'fail'})

}

res.send({status:0,msg:'success',username:req.session.username})

})

 

app.post('/api/logout',(req,res)=>{

req.session.destory()

res.send({

status:0,

msg:'退出登录成功'

})

})

 

app.listen(80,()=>{

console.log('服务器启动成功')

})

```

 

 

 

## JWT认证机制

 

### session的局限性

 

session认证需要配合cookie实现。而cookie不支持跨域,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域session认证

 

### 什么是JWT

 

全称(JSON Web Token),是目前最流行的跨域认证解决方案

 

### JWT工作原理

 

1. 客户端登录:提交账号密码

2. 服务器验证账号密码

3. 验证通过之后将用户信息对象,经过加密之后生成Token字符串

4. 服务器响应:将生成的Token发送给客户端

5. Token存储到LocalStroageSessionStroage

6. 客户端再次发起请求,通过请求头的Authorization字段,将Token发给服务器

7. 服务器把Token字符串还原成用户信息对象

8. 用户的身份认证成功后,服务器针对当前用户生成的响应内容

9. 服务器响应:把当前用户对应页面内容响应给浏览器

 

### JWT的组成部分

 

JWT通常由三部分组成,分别是Header头部,Payload有效负荷,Signature签名。三者之间使用英文的`.`分隔,格式如下

 

```

Header.Payload.Signature

```

 

其中:

 

Payload才是真正的用户信息,它是用户信息经过加密之后生成的字符串

 

HeaderSignature是安全相关的部分,只是为了保证Token的安全性

 

### JWT的使用方式

 

客户端收到服务器返回的JWT之后,通常会把它存储在localStorageSessionStroage中。

 

此后,客户端每次与服务器通信,都要带上这个JWT字符串,从而进行身份认证。推荐的做法是把JWT放在HTTP请求头的Authorization字段中,格式如下

 

```

Authorization:Bearer <token>

```

 

### express中使用JWT

 

#### 安装

 

需要装两个包

 

```

npm i jsonwebtoken express-jwt

```

 

其中,jsonwebtoken用于生成JWT字符串

 

express-jwt用于将JWT字符串还原成JSON对象

 

#### 配置

 

```js

//导入用于生成JWT字符串的包

const jwt = require('jsonwebtoken')

//导入用于将客户端发送过来的JWT字符串,解析还原成JSON对象的包

const expressJWT = require('express-jwt')

```

 

#### 定义secret密钥

 

为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的secret密钥

 

1. 当生成JWT字符串的时候,需要使用secret密钥对用户的信息进行加密,最终得到加密好的JWT字符串

2. 当把JWT字符串解析还原成JSON对象得到时候,需要使用secret密钥解密

 

```js

const express = require('express');

const path = require('path')

const jwt = require('jsonwebtoken')

const expressJWT = require('express-jwt')

const cors = require('cors')

 

var app = express();

app.use(cors())

app.use("/assets/",express.static("./assets/"))

app.use(express.static('views'))

 

//解析post表单数据的中间件

const bodyParser = require('body-parser')

app.use(bodyParser.urlenencoded({extended:false}))

 

// 定义secret密钥,建议将密钥命名为secretKey

const secretKey = 'i am a crazy bird'

 

 

 

 

 

app.listen(80,()=>{

console.log('服务器启动成功')

})

```

 

#### 在登陆成功后生成JWT字符串

 

调用jsonwebtoken包提供的sign()方法,将用户的信息加密成JWT字符串,相应给客户端

 

```js

app.post('/api/login',(req,res)=>{

//...生咯登陆失败的情况代码

//用户登录成功之后,生成JWT字符串,通过token属性相应给客户端

res.send({

status:200,

message:"登陆成功",

//调用jwt.sign()生成的JWT字符串,三个参数分别是用户信息对象,加密密钥,配置对象

token:jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})

})

})

//其中expiresIntoken的有效期

```

 

```js

app.post('/api/login',(req,res)=>{

//...生咯登陆失败的情况代码

//用户登录成功之后,生成JWT字符串,通过token属性相应给客户端

const userinfo = req.body

if(userinfo.username != 'admin' || userinfo.password != '000'){

return res.send({

status:400,

message:'登陆失败'

})

}

const tokenStr = jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})

res.send({

status:200,

message:"登陆成功",

//调用jwt.sign()生成的JWT字符串,三个参数分别是用户信息对象,加密密钥,配置对象

token:tokenStr

})

})

```

 

#### JWT字符串还原成JSON对象

 

段每次在访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发送到服务端进行身份认证

 

此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象

 

```js

//使用app.use()来注册中间件

//express.JWT({secret:secretKey})就是用来解析token的中间件

//.unless({path:[/^\/api\//]})用来指定哪些接口不需要访问权限

app.use(expressJWT({secret:secretKey})).unless({path:[/^\/api\//]})

```

 

使用req.user获取用户信息

 

express-jwt这个中间件配置完成后,即可在那些有权限的接口中,使用req.user对象,来访问从JWT字符串中解析出来的用户信息了,示例代码如下:

 

```js

app.get('admin/getinfo',function(req,res)=>{

        console.log(req.user)

res.send({

     status:200,

        message:'获取用户信息成功',

        data:req.user

})

})

```