# 模块
## 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框架
express为npm第三方框架
## 基础模板
```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参数,而路由处理函数中只包含req和res
### 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()
})
```
### 中间件的作用
多个中间件之间共享一份req和res,这样可以在上游的req和res中添加一下自定义的方法属性从而在下游使用
### 定义多个全局中间件
使用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. 调用多个中间件时,多个中间件共享req和res
### 中间件的分类
#### 应用级别
通过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仅支持客户端发起GET,POST,HEAD请求
如果客户端希望通过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头部信息不超过以下几个字段:无自定义头部字段,Accept,Accept-Language,DPR,Downlink,Save-Data,View-Width,Content-Type(只有三个值application/x-www-form-urlencoded,multipart/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是存储在浏览器中的,而且浏览器也提供了独写Cookie的API,因此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存储到LocalStroage或SessionStroage
6. 客户端再次发起请求,通过请求头的Authorization字段,将Token发给服务器
7. 服务器把Token字符串还原成用户信息对象
8. 用户的身份认证成功后,服务器针对当前用户生成的响应内容
9. 服务器响应:把当前用户对应页面内容响应给浏览器
### JWT的组成部分
JWT通常由三部分组成,分别是Header头部,Payload有效负荷,Signature签名。三者之间使用英文的`.`分隔,格式如下
```
Header.Payload.Signature
```
其中:
Payload才是真正的用户信息,它是用户信息经过加密之后生成的字符串
Header和Signature是安全相关的部分,只是为了保证Token的安全性
### JWT的使用方式
客户端收到服务器返回的JWT之后,通常会把它存储在localStorage或SessionStroage中。
此后,客户端每次与服务器通信,都要带上这个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'})
})
})
//其中expiresIn为token的有效期
```
```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
})
})
```