【Node.js】初學 2

8. Express

8.1 Express 简介

express 是基于 node.js 平台的 web 开发框架。

express 的作用和 node.js 内置的 http 模块类似,是专门用来创建 web 服务器的。

express 的本质:就是一个 npm 上的第三方包,提供了快速创建 web 服务器的方法。

express 中文官网:http://www.expressjs.com.cn     

express 与 http 内置模块区别?http 内置模块使用起来复杂开发效率低,express 是基于内置的 http 模块进一步封装的,能提高开发效率。

express 与 http 内置模块是什么关系?类似于浏览器中 Web API 和 jQuery 的关系,后者是基于前者封装出来的。

 

express 能做什么?最常见的两种服务器:Web 网站服务器(专门对外提供 web 网页资源的服务器)、API 接口服务器(专门对外提供 API 接口的服务器)。

8.2 Express 的基本使用

1. 安装

在项目所处目录中,运行终端命令,即可将 express 安装到项目中。

npm i express

2. 创建基本的 Web 服务器

// 1. 导入 express 模块
const express = require('express')

// 2. 创建 web 服务器 
const app = express()

// 3. 启动 web 服务器, 调用 app.listen(端口号, 启动成功后的回调函数)
app.listen(80, () => {
    console.log('服务器已启动:http://127.0.0.1:80')
})

3. 监听 GET 与 POST 请求

通过 app.get()  方法,可以监听客户端的 GET 请求。

通过 app.post()  方法,可以监听客户端的 POST 请求。

语法:

app.get('请求URL', function(req, res) {   /* 处理函数 */    } )              

app.post('请求URL', function(req, res) {   /* 处理函数 */    } )              

其中,有两个参数,分别是:

参数一:客户端请求的 URL 地址。

参数二:请求对应的处理函数。里面有两个形参,req请求res响应

    req:请求对象,包含了与请求相关的属性和方法。

    res:响应对象,包含了与响应相关的属性和方法。

4. 把内容响应给客户端     res.send( )

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

// 3. 监听客户端的 GET 和 POST 请求
// get 请求
app.get('/user', (req, res) => {
    // 调用 express 提供的 res.send() 方法,向客户端响应一个 JSON 对象
    res.send( {name:'加菲猫', like:'千层面'} )
})
// psot 请求
app.post('/user', (req, res) => {
    // 向客户端发送 文本字符串
    res.send('您请求成功,请求方法是 POST')
})

5. 获取 URL 中携带的查询参数     req.query 

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

req.query 默认是一个空对象。

客户端使用 xxxx?name=Tom&age=20  这种查询字符串形式,发送到服务器的参数。

可以通过 req.query 对象访问,例如:req.query.name    req.query.age 

// req.query 对象 获取 url 中的参数
app.get('/q', (req, res) => {
    // req.query 默认是一个空对象。
    // 客户端使用 xxxx?name=Tom&age=20  这种查询字符串形式,发送到服务器的参数。
    // 可以通过 req.query 对象访问,例如:req.query.name    req.query.age 
    console.log(req.query)           // 打印 req.query 对象的内容
    console.log(req.query.name)      // 打印 name 的值 Tom
    res.send(req.query)
})

6. 获取 URL 中的动态参数    req.params     :动态参数

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

req.params 默认是一个空对象
里面存放着通过 : 动态匹配到的参数值
// req.params 对象 获取 URL 中的动态参数
// 注意:这里的 :u_id 是一个动态的参数
app.get('/user/:u_id', (req, res) => {
    // req.params 默认是一个空对象
    // req.params 是动态匹配到的 URL 参数,里面存放着通过 : 动态匹配到的参数值
    console.log(req.params)
    res.send(req.params)        // 例如 /user/tom 然后里面就是 "u_id":"tom"
})

// 注意:
// :u_id 不是固定写法,冒号 : 才是固定的写法,后面的参数名必须合法。
// 动态参数可以有多个,例如:/user/:u_id/:name 然后请求 /user/tom/TOM123 就是{"u_id":"tom","name":"TOM123"}

8.3 托管静态资源 express.static()

1. 托管静态资源目录

express 提供了 express.static() 函数,通过它,可以创建一个静态资源服务器。

例如,通过如下代码就可以将 public 目录下的图片、css文件、JavaScript文件对外开放了。

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

现在,就可以访问 public 目录里的所有文件了。

http://localhost:3000/images/bg.png

http://localhost:3000/css/index.css

http://localhost:3000/js/index.js

注意:Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在 URL 中

也就是 public 这层文件夹不会出现在访问路径中。

2. 托管多个静态资源目录

如果要托管多个静态资源目录,就多次调用 express.static() 函数。

app.use( express.static('./public') )
app.use( express.static('./files') )

访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件。

例子:先添加了 ./40_static 然后添加了 ./40_static_2 目录,当访问目录下 index.html 文件时,

因为按顺序来,所以先去 ./40_static 目录下找 index.html,找到了,就不继续找了,找不到就找第二个文件夹 ./40_static_2 的,以此类推。

3. 挂载路径前缀

在托管的静态资源访问路径之前,挂载路径前缀。则可以使用以下方式:
// 挂载路径前缀
app.use('/home', express.static('./40_static'))
/*  
* 在调用 app.use() 的时候,可以提供一个参数,就是在前面加一个前缀字符串 /home,
* 然后访问目录里面任何资源时,都需要在前面挂载一个 /home 访问前缀才行。
*  现在就可以通过带有 /home 前缀地址来访问 40_static 目录下的文件了
*  http://localhost/home/index.html
*  http://localhost/home/img/bg.jpg
*/

8.4 nodemon   自动重启项目

使用 nodemon 命令启动的项目,当更改代码时,会自动重启项目,无需手动。

传统方式是运行 node xxx 命令来启动项目,当更改代码后还需要手动重启项目。

安装:npm i -g nodemon

启动:nodemon xxx.js 

如果启动失败,使用语句: npx nodemon xxx.js  启动,或删除 C:\Users\用户\AppData\Roaming\npm\nodemon.ps1 文件。

8.5 Express 路由 与 路由模块化

1. Express 路由

路由,就是映射关系。

类似于打电话查话费按数字1键就可以提供查话费服务,按键与服务之间的映射关系

Express 中,路由指的是 客户端的请求与服务器处理函数 之间的映射关系

路由分3部分组成,分别是:请求的类型、请求的 URL 地址、处理函数。

格式: app.METHOD(PATH, HANDLER)      

例如:app.get('/home', function(){...})

其中 METHOD和PATH 是和客户端有关,HANDLER是和服务端有关

// 匹配 GET 请求,请求 URL 为 / 
app.get('/', (req,res) => {
    res.send('get 请求,url 是 /')
})

最简单的用法:

把路由挂载到 app 上。

// 挂载路由
app.get('/home', (req, res) => { res.send('hello get /home')  })
app.post('/home', (req, res) => { res.send('hello post /home')  })

2. 模块化路由

为了方便对路由进行模块化管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。

步骤如下:

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

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

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

4、使用 module.exports 向外共享路由对象。这样外界 require 这个文件时,得到的就是这个 router 对象。

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

创建路由模块:

// 创建路由模块 //
// 1. 导入 express 模块
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/home', (req, res) => {
    res.send('GET /home')
})
router.post('/game', (req, res) => {
    res.send('POST /game')
})

// 4. 向外导出路由对象,将 router 路由对象向外共享出去
module.exports = router

注册路由模块:

const express = require('express')
const app = express()

// 1. 导入路由模块
const my_router = require('./43_路由的模块化.js')
// 2. 使用 app.use() 注册路由模块
app.use(my_router)

// 注意: app.use() 函数的作用,是注册全局中间件。
// 比如之前的 app.use(express.static('./bg')) 向外托管静态资源

app.listen(80 , () => {
    console.log('http://127.0.0.1:80')
})

为路由模块添加前缀

类似于托管静态资源时,在前面加前缀,路由模块添加前缀的方式是:

// 3. 为路由模块添加前缀
app.use('/user', my_router)     // 之后访问它,就得以 /user 开头,例如:http://localhost/user/home

8.6 Express 中间件

1. 中间件概念

中间件(Middleware),特指业务流程的中间处理环节。

类似于现实生活中的过滤网,输入污水然后处理最后输出,处理方式是一层过滤二层过滤,中间的过滤网可以叫做 中间件。

Express 中间件的调用流程:

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

客户端发请求--->[ 中间件1 -> 中间件2 ->中间件3...-> 处理完毕,结果以路由的形式响应给客户端] ---> 响应给客户端

Express 中间件的格式:

本质上就是一个 function 处理函数,中间件函数的形参列表中,必须包含 next 参数

格式:

app.get('/', function (req, res) {                // 路由处理函数

})        
app.get('/', function (req, res, next) {          // 中间件函数
    next();
})

如果包含了 next 参数,就是中间件函数,如果没有,就不是,是路由处理函数。

next 函数的作用:

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

 

2. 定义中间件函数

// 1. 定义中间件函数
const mw = function(req, res, next) {
    console.log('这是最简单的中间件函数')
    // 把流转关系,转交给下一个 中间件或路由。如果不调用,那就无法流转到下一个中间件了。
    next()
}

 

3. 全局生效的中间件    app.use()

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

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

 

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

之前是用一个常量变量来接收中间件函数,简化形式是直接调用 app.use() 函数,在里面提供一个中间件函数就可以了。

// 全局生效的中间件
app.use((req,res,next) => {
    console.log('这是简化的中间件函数')
    next()   // 流转关系交给下一个中间件或路由
})

 

5. 中间件的作用

中间件路由共享同一个 reqres。为其添加自定义属性与方法,供下面的中间件和路由使用。就不需要每个路由都写一遍了。

多个中间件之间,共享同一个 req 和 res,基于这样的特性,可以在上游的中间件中,统一为 res 和 req 对象添加自定义的属性或方法。然后供下游的中间件或路由进行使用。

// 4. 中间件的作用
/* 
* 多个中间件之间,共享同一个 req 和 res。
* 基于这样的特性,可以在上游的中间件中,统一为 res 和 req 对象添加自定义的属性或方法。
* 然后供下游的中间件或路由进行使用。
*/
app.use((req,res,next) => {
    // 获取到请求到达服务器的时间
    const my_time = Date.now()
    // 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由。下游的路由都可以使用这个
    req.startTime = my_time
    next()
})
app.get('/time', (req,res) => {
    res.send('您访问了 /time,现在时间戳:'+ req.startTime) //使用了上游中间件的自定义属性
})
app.get('/time2', (req,res) => {
    res.send('您访问了 /time2,现在时间戳:'+ req.startTime)
})

 

6. 定义多个全局中间件

使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器后,会按照中间件定义的 先后顺序 依次调用。

// 5. 定义多个全局中间件
// 使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器后,会按照中间件定义的 先后顺序 依次调用。
app.use((req,res,next) => {
    console.log('调用了第 1 个中间件')
    next()
})
app.use((req,res,next) => {
    console.log('调用了第 2 个中间件')
    next()
})
app.get('/many',(req,res) => {
    res.send('请求/many 路由,会多次触发上面的 中间件,并按照先后顺序依次调用。')
})

 

7. 局部生效 的中间件

不使用 app.use() 定义的中间件,都叫做 局部生效的中间件

这个中间件只会在当前路由內生效,不会影响到其他的路由。

语法:

app.get( 请求URL地址, 中间件函数, 路由处理函数)

// 不使用 app.use() 定义的中间件,叫做 局部生效的中间件。
// 1. 定义中间件函数 mw1
const mw1 = function(req, res, next) {
    console.log('这是中间件函数')
    next()
}

// 2. 创建路由
// mw1 这个中间件只在“当前路由中生效”,这种用法属于“局部生效中间件”
app.get('/', mw1 ,(req, res) => {
    res.send('get / 路由')
})
// mw1 中间件不会影响下面的路由,因为 mw1 是局部生效的中间件
app.get('/user', (req, res) => {
    res.send('get /user 路由')
})

8. 定义多个 局部生效的中间件

可以在路由中,通过如下两种等价的方式,使用多个局部中间件。

/* 
    2、 定义多个 局部生效的中间件
*/
// 可以在路由中,通过如下两种等价的方式,使用多个局部中间件。

// 以下两种写法是完全等价的,任意一种都可以。
app.get('/', mw1, mw2, (req,res) => { res.send('路由 get /') })
app.get('/', [mw1, mw2], (req,res) => { res.send('路由 get /') })


/*----------------------------------------------------
// 其中 mw1 和 mw2 都是中间件函数
const mw1 = (req, res, next) =>{
    console.log('第二个中间件函数')
    next()
}
const mw1 = (req, res, next) =>{
    console.log('第二个中间件函数')
    next()
}
*/

9. 中间件注意事项

1、一定要在路由之前注册中间件。代码从上到下执行。

2、可以连续调用多个中间件进行处理。

3、执行完中间件的业务代码,不要忘记调用 next() 函数。不然就作废了,无法转交给下一个中间件或路由。

4、为了防止代码逻辑混乱。next() 后面不要写额外的代码。

5、连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象。

8.7 Express 中间件 分类

Express 官方把常见的中间件分为 5 大类。

1. 应用级别的中间件

通过 app.use()app.get()app.post() ,绑定到 app 实例上的中间件,叫做应用级别中间件。

// 应用级别的中间件(全局中间件)
app.use( (req,res,next) => { 
    next()
})
// 应用级别的中间件(局部中间件)
app.get('/', mw1, (req,res,next) => { 
    res.send('请求 / get')
})

2. 路由级别的中间件

绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有区别,只不过是,应用级别中间件绑定到 app 实例上,而路由级别中间件是绑定到 router 实例上。

// 路由级别中间件
router.use( (req,res,next) => {
    console.log('路由级别中间件')
    next()
})
app.use('/', router)

3. 错误级别的中间件

错误级别中间件的处理函数中,必须有 4 个形参,按前后顺序分别是:(err, req, res, next)

作用是,专门捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。

注意:错误级别的中间件,必须注册在所有路由之后。否则捕获不到错误。除了错误级别的中间件,其他中间件必须在路由之前。

app.get('/', (req,res) => {
    throw new Error('服务器内部发生了错误!')          // 抛出一个自定义异常错误
    res.send('此时这条是执行不到的,因为上面就报错了')
})
// 错误级别的中间件
app.use( (err,req,res,next) => {
    console.log('发生了错误:' + err.message)        //在服务器打印错误内容
    res.send('错误:' + err.message)                //向客户端响应错误内容
})

之前的中间件,放在前面是为了在路由还没执行前处理一些代码再执行路由,而这个错误中间件要放在路由后面才能捕获错误。

只要发生错误,就立即进入了错误级别的中间件里去执行了,比如先打印错误信息,然后向客户端响应消息。

4. Express 内置的中间件

从Express 4.16版本开始,内置了 3 个常用的中间件。

(1) express.static 快速托管静态资源的内置中间件,例如 html css 图片等。

(2) express.json 解析 JSON 格式的请求体数据。

    语法:app.use(express.json())

(3) express.urlencoded 解析 URL-encoded 格式的请求体数据。

    语法:app.use(express.urlencoded({extnded: false}) )

// (2) express.json 中间件。请求体要放到body里发送。
// 注意:处理错误级别中间件,其他的都要写在路由前面
// 通过 express.json() 中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
app.post('/user', (req,res) => {
    // 在服务器可用使用 req.body 属性,来接收客户端发送过来的请求体数据
    // 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
    console.log(req.body)
    res.send('ok user JSON')
})
// *在 postcode 插件中发送请求,然后点击 body 里的 raw,写一些 json 格式的数据,然后点击发送。



// (3) express.urlencoded 中间件。解析 URL-encoded 格式的请求体数据
// 这个不是 JSON 格式,而是 body 里的 x-www-form-urlencoded 格式,以键值对形式,向服务器发送数据
app.use(express.urlencoded( {extended: false} ))
app.post('/user-e', (req,res) => {
    // 在服务器端,可以通过 req.body 来获取 JSON 格式的表单数据和 url-encoded 格式的数据
    console.log(req.body)
    res.send('ok user URL-encoded')    
})

5. 第三方的中间件

非官方内置的中间件。

例如 在 4.16 版本之前,经常使用的 body-parser 这个第三方中间件,来解析请求体数据

// 例如 在 4.16 版本之前,经常使用的 body-parser 这个第三方中间件,来解析请求体数据
// 步骤如下:npm i body-parser、然后使用 require 导入中间件、调用 app.use() 注册并使用中间件
const parser = require('body-parser')
app.use(parser.urlencoded( {extended: flase} ))     // 第三方中间件用法
app.use(express.urlencoded( {extended: false} ))    // 内置中间件,这个内置的是基于body-parser封装的
// Express 内置的 express.urlencoded 中间件,是基于第三方 body-parser 封装出来的。

8.8 Express 自定义中间件

将自定义中间件封装为模块。

function MY_zjj(req,res,next) {
    // 处理一些东西
}
// 向外暴露出去
module.exports = MY_zjj

// -------- 分割线 ----------

// 1. 导入自己封装的中间件模块
const my_zjj = require('./自定义中间件.js')
// 2. 将自定义中间件函数,注册为全局可用的中间件
app.use(my_zjj)

app.post('/', (req,res) => { 
    //路由
})

8.9 Express API 接口

get请求参数在query里,post请求参数在body里。

get的数据存放在req.query中 post的数据存放在req.body中,且需要配置urlencoded中间件

1. 编写 GET 接口

创建基本的 API 接口

// 路由模块
const express = require('express')
const router = express.Router()

// 在这里挂载对应的路由
router.get('/get', (req,res) => {
    // 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
    const query = req.query
    // 调用 res.send() 方法,向客户端响应处理的结果
    res.send({
        status: 0,          // 0 表示成功,1 表示失败
        msg: 'GET 请求成功!',     // 状态的描述
        data: query         // 需要响应给客户端的数据                          
    })
})
// 向外共享出去
module.exports = router

 创建基本的服务器

// 启动服务器
const express = require('express')
const app = express()

// 导入并注册路由模块
const apiRouter = require('./50_APIRouter')
app.use('/api', apiRouter)      // 注册路由,前缀为 /api

app.listen(80, () => {
    console.log('server runing at http://127.0.0.1')
})

 然后访问 127.0.0.1/api/get ,带上查询字符串,即可返回相应数据,127.0.0.1/api/get?name=hm&age=18

2. 编写 POST 接口

 创建基本的 API 接口

 post请求参数在 body 里。

注意:要获取 URL-encoded 格式的请求体数据,必须配置中间件 app.use(express.urlencoded( {extended: flase} ) ),

如果不配置,就无法通过 req.body 获取到请求体里的数据, body 里的

// 挂载对应路由  POST接口
router.post('/post', (req,res) => {
    // 1. 获取客户端通过请求体,发送到服务器的 URl-encoded 数据
    const body = req.body
    // 2. 调用 res.send() 方法,把数据响应给客户端
    res.send({
        status: 0,       // 0 成功,1 失败
        msg: 'POST 请求成功!',     // 状态描述信息
        data: body
    })
})

 

3. 接口 跨域问题  CORS

上面编写的 GET 和 POST 接口,存在一个严重问题,不支持跨域请求

解决接口跨域问题的方案主要有两种:
1、CORS 主流的解决方案,推荐使用
2、JSONP 有缺陷的解决方案,只支持GET请求            

使用 cors 中间件解决跨域问题:
1、运行 npm install cors 安装中间件
2、使用 const cors = require('cors') 导入中间件
3、在路由之前调用 app.use(cors()) 配置中间件

什么是 CORS?
CORS(跨域资源共享),由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源策略,会默认阻止网页跨域获取资源,但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以接触浏览器端的跨域访问限制。
配置 Access-Countrol-Allow-* 相关的响应头

CORS 注意事项
CORS 主要在服务器端进行配置,客户端浏览器无需配置,即可请求开启了 CORS 的接口。

 

例如,服务器端

const express = require('express')
const app = express()
app.use(express.urlencoded( {extended: false}))       // 解析表单的中间件 URL-encoded 格式

// 一定要在路由之前,配置 CORS 这个中间件,解决跨域问题
const cors = require('cors')
app.use(cors())

// 导入并注册路由
const router = require('./50_APIRouter')
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
    console.log('server runing at http://127.0.0.1:80')
})

路由模块 API:

// 路由模块
const express = require('express')
const router = express.Router()

// 在这里挂载对应的路由
router.get('/get', (req,res) => {
    // 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
    const query = req.query
    // 调用 res.send() 方法,向客户端响应处理的结果
    res.send({
        status: 0,          // 0 表示成功,1 表示失败
        msg: 'GET 请求成功!',     // 状态的描述
        data: query         // 需要响应给客户端的数据                          
    })
})
// 挂载对应路由  POST接口
router.post('/post', (req,res) => {
    // 1. 获取客户端通过请求体,发送到服务器的 URl-encoded 数据
    const body = req.body
    // 2. 调用 res.send() 方法,把数据响应给客户端
    res.send({
        status: 0,       // 0 成功,1 失败
        msg: 'POST 请求成功!',     // 状态描述信息
        data: body
    })
})
// 向外共享出去
module.exports = router

HTMl 页面,测试接口:

HTML 页面 测试接口.html
    <button id="btnGET">GET</button>
    <button id="btnPOST">POST</button>
    <script>
        $(function() {
            // 1. 测试 GET 接口
            $('#btnGET').on('click', function() {
                $.ajax({
                    type: 'GET',
                    url: 'http://127.0.0.1/api/get',
                    data: {
                        name: 'mea', age: 16
                    },
                    success: function(res) {
                        console.log(res)
                    }
                })
            })
            // 2. 测试 PSOT 接口
            $('#btnPOST').on('click', function() {
                $.ajax({
                    type: 'POST',
                    url: 'http://127.0.0.1/api/post',
                    data: {
                        name: 'moo', age: 16
                    },
                    success: function(res) {
                        console.log(res)
                    }
                })
            })
        })
    </script>

使用 file:// 协议打开 html 文件,发送请求时,服务器端与其不同,协议不同 http,域名不同 127.0.0.1,端口号不同 80,由于同源策略,所以存在跨域问题。

使用 CORS 跨域解决方案,有了相应的响应头,就解决了跨域问题。

8.10 CORS 跨域

CORS 跨域资源共享

1、CORS 响应头部 Access-Countrol-Allow-Origin

格式:Access-Countorl-Allow-Origin: <origin> | *

参数值为 URL,其中 origin 参数的值指定了允许访问该资源的外域 URl

例如,只允许来自 http://google.com 的请求:

res.setHeader('Access-Countrol-Allow-Origin', 'http://google.com')

如果指定了参数值为 * 通配符,则表示允许来自任何域的请求:

res.setHeader('Access-Countrol-Allow-Origin', '*')

 

2、CORS 响应头部 Access-Countrol-Allow-Headers

默认情况下,CORS 仅支持客户端向服务器发送 9 个响应头。

Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type。

格式:Access-Countorl-Allow-Headers: xxx

如果发送额外的请求头信息,则需要在服务端,对额外的请求头进行声明。

例如,允许客户端额外向服务端发送 Content-Type 请求头和 x-Custom-Header 请求头:

rse.setHeader('Access-Countrol-Allow-Header', 'Content-Type, x-Custom-Header')

 

3、CORS 响应头部 Access-Countrol-Allow-Methods

设置允许的请求类型。

格式:Access-Countrol-Allow-Methods: POST

默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求

如果客户端通过 PUT、DELETE 等方式请求服务器资源,则需要在服务端指明允许使用的 HTTP 方法。

例如:只允许 POST、DELETE 请求方法

res.setHeader('Access-Countrol-Allow-Methods', 'PSOT, DELETE')

通配符 * 允许所有的 HTTP 请求方法

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

8.11 CORS 请求的分类

客户端在请求 CORS 接口时,根据请求方式与请求头不同,可将请求分类两大类。

简单请求预检请求

1、简单请求

同时满足以下两个条件的请求,就是简单请求

(1) 请求方式:GETPSOTHEAD 三者之一

(2) HTTP 头部信息,无自定义头部字段,不超过这 9 个响应头 Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type。

2、预检请求

符合其中一个条件的请求,都需要进行预检请求。

(1) 请求方式为 GET、PSOT、HEAD 之外的请求 Method 类型

(2) 请求头中包含自定义头部字段

(3) 向服务器发送了 application/json 格式的数据

在浏览器与客户端正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求。所以这一次 OPTION 请求称为预检请求。服务器成功响应预检请求后,才会发送真正的请求,并携带真实的数据。

3、简单请求与预检请求 的区别

简单请求的特点:客户端与服务器之前只会发生 1 次请求。

预检请求的特点:客户端与服务器之前会发生 2 次请求。OPTION 预检请求成功之后,才会发起真正的请求。

8.12 JSONP 请求

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

特点:JSONP 不属于 ajax 请求,因为它没有使用 XMLHttpRequest 这个对象。

       JSONP 仅支持 GET 请求。

实现 JSONP 接口的主要部分代码:

// 实现 JSONP 接口的代码
// 必须在配置 cors 中间件之前,配置 JSONP 的接口,否则就不是 JSONP 的了
app.get('/api/jsonp', (req,res) => {
    // 1. 获取客户端发送过来的回调函数的名字
    const funcName = req.query.callback
    // 2. 得到要通过 JSONP 形式发送给客户端的数据
    const data = { name: 'mea' }
    // 3. 根据前两步得到的数据,拼接出一个 函数调用 字符串
    const scriptStr = `${funcName}( ${JSON.stringify(data)} )`
    // 4. 把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行
    res.send(scriptStr)
})
使用 jQuery 调用 ajax 函数发 JSONP 请求.html
<button id="btnJSONP">GET JSONP</button>
    <script>
        $(function() {
            // 1. 请求 JSONP 接口
            $('#btnJSONP').on('click', function() {
                $.ajax({
                    method: 'GET',
                    url: 'http://127.0.0.1/api/jsonp',
                    dataType: 'jsonp',  // 表示要发起 JSONP 的请求
                    success: function(res) {
                        console.log(res)
                        console.log('请求中...')
                    }
                })
            })
        })
    </script>

 

9. 数据库

数据库(database)是用来组织、存储和管理数据的仓库。

常见的数据库:MySQL 数据库(免费开源,社区版)、Oracle、SQL Server、Mongodb

其中,MySQL、Oracle、SQL Server 属于传统型数据库(又叫做:关系型数据库SQL 数据库),这三者设计理念相同。而 MongoDB 属于新型数据库非关系型数据库NoSQL数据库),在一定程度上弥补了传统型数据库的缺陷。

传统型数据库中,数据的组织结构分为:数据库(database)数据表(table)数据行(row)字段(field),这 4 大部分组成。

一个数据库里,有若干个表。每个表中存储的哪些信息,由字段来决定,id username 等等。表中的行,代表每一条具体的数据。

1. SQL

SQL,结构化查询语言,是专门访问和处理数据库的编程语言

1、SQL 是一门数据库编程语言

2、使用 SQL 语言编写的代码,叫做 SQL 语句

3、SQL 语言只能在关系型数据库中使用(例如 MySQL、Oracle等),非关系型不支持SQL语言(例如Mongodb)

作用有:增删改查。

select 查询、 insert into 插入、update 更新、delete 删除

1、SELECT 查询

注意:SQL 语句中的关键字对大小写不敏感,select 等效于 SELECT

查询 表中所有数据:SELECT *   
SELECT * FROM 表名
查询 表中的列(字段)的所有数据(多个列用 , 号分隔):SELECT xxx, xxx      
SELECT 列名 FROM 表名
SELECT id, name FROM users

2、INSERT INTO 插入

向指定的表中插入数据,列和值一一对应,多个列和值用 , 号分隔

INSERT INTO 表名 (列1, 列2...) VALUES (值1, 值2....) 

INSERT INTO users (username, userpswd) VALUES ('mo','123');

3、UPDATE 修改

修改表中的数据

UPDATE 表名 SET 列名 = 新值 WHERE 列名 = 某值      

gUPDATE 指定表,SET 指定列与值,WHERE 指定条件。

更新一行的某个列,例如:修改 username_roma 为 na ,条件是 id=3 的。换句话说,就是把 id 为 3 的 username_roma 改为 na

UPDATE users SET username_roma = 'na' WHERE id = 3;

更新一行的多个列,例如:把 id 为 3 的 username 和 userpswd 同时改为 Moli 和 123456

UPDATE users SET username = 'Moli', userpswd = '123456' WHEERE id = 3;

注意:如果不写 WHERE 条件,则所有的列都会被修改,例如所有的 userpswd 为 123456。

4、DELETE 删除

删除表中的数据

DELETE FROM 表名 WHERE 列名 = 某值

例如,修改 id 为 4 的:

DELETE FROM users WHERE id = 4;

注意:不加 WHERE 条件会删除所有的数据!

5、ORDER BY 排序

asc 表示升序,默认情况下就是升序,desc 表示降序

SELECT * FROM 表 ORDER BY 列              为升序 (或 ORDER BY 列 ASC)

SELECT * FROM 表 ORDER BY 列 DESC    为降序

对表中的数据,按照 id 字段进行排序:select * from users order by id;

多重排序:

多个排序的添加,用英文逗号分隔

例如,对 users 表中的数据,先按照 username 字段的汉字进行降序排序,再按照 username_roma 的罗马音进行升序排序 :

SELECT * FROM users ORDER BY username DESC, username_roma ASC;                    (这个 ASC 可省略,写上更加语义化,简单明了)

6、COUNT(*) 统计

COUNT(*) 函数用于返回查询结果的总数据条数
格式:SELECT COUNT(*) FROM 表名 WHERE 列 = 某值
例如:返回id < 5 的查询结果的总条数
select count(*) from users WHERE id < 5;

7、AS 设置别名

给查询出来的列名称设置别名,可以使用 AS 关键字。

例如,将 count(*) 设置别名为 id_sum

select count(*) AS id_sum from users where id < 5;

然后显示的列名就是 id_sum 了,而不是 count(*)

例如,SELECT username AS Uname, userpswd AS Upd FROM users;

返回的列名分别是 Uname 和 Upd

10.  mysql 模块

在项目中操作数据库的步骤;

1、安装 MySQL 的第三方模块(npm i mysql)

2、通过 mysql 模块连接到 MySQL 数据库

3、通过 mysql 模块执行 SQL 语句

1. 安装 mysql 模块

npm install mysql

2. 配置 mysql 模块

先导入模块,配置 ip、账号、密码、目标数据库等信息。

 
// 配置 mysql 模块
// 1. 导入 mysql 模块
const msyql = require('mysql')

// 2. 建立与 MySQL 数据库的连接
const db = msyql.createPool({
    host: '127.0.0.1',          // 数据库的 IP 地址
    user: 'root',               // 登陆数据库的账号
    password: 'admin123',       // 登陆数据库的密码
    database: 'my_db'           // 要操作的数据库
})

// 3. 测试 mysql 模块是否正常工作
// 调用 db.query() 函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果:
// 回调函数参数 err 是错误信息, result 是正常执行后的结果
db.query('SELECT 1', (err,result) => {
    // mysql 模块工作期间报错了,则err 里有错误信息,err 为真
    if (err) return console.log(err.message)        // 如果失败,err成立则打印错误信息
    // 能够成功的执行 SQL 语句
    // 只要能打印出 [ RowDataPacket { '1': 1 } ] 的结果,就证明数据库连接正常
    console.log(result)
})
// 注意: SELECT 1 这个语句没有实质性的作用,只是来测试是否可以连通的。

3. mysql 模块 查询数据

调用 db.query() 函数执行 SQL 语句

例如查询 users 表中所有数据:db.query( 'select * from users', (err, result) => { .... })                  

其中,err 表示失败后的错误对象,result 表示成功后的对象。

注意:如果执行的是 select 查询语句,则执行的结果是【数组】
// 3. 查询数据

const sqlStr = 'select * from users'    // 查询 users 表中所有的数据
db.query(sqlStr, (err, result) => {
    // 查询失败
    if(err) return console.log(err.message)
    // 查询成功
    // 注意:如果执行的是 select 查询语句,则执行的结果是【数组】
    console.log(result)
})

4. mysql 模块 插入数据

注意:如果执行的是 insert into 插入语句,则执行的结果是【对象】

// 向 user 表新增一条数据,定义要插入到 users 表中的数据对象
const user = { uName:'加菲猫', uPswd:'123456'}
// 待执行的 SQL 语句,其中英文问号 ? 表示占位符
const sqlStr = 'INSERT INTO users (username, userpswd) VALUES (?,?)'
// 执行 SQL 语句
// 使用数组的形式,依次为 ? 占位符指定具体的值
db.query(sqlStr, [user.uName, user.uPswd], (err,result) => {
    // 执行 SQL 语句失败
    if(err) return console.log(err.message)
    // 执行 SQL 语句成功
    // 注意:如果执行的是 insert into 插入语句,则执行的结果是【对象】
    // 这个 result.affectedRows 对象属性返回的是影响的行数
    if(result.affectedRows === 1){
        console.log('插入数据成功!')
    }
})

插入数据 的便携方式

如果 数据对象的每个属性数据表的字段 一一对应,则可以快速插入数据:

// 如果 数据对象的每个属性 和 数据表的字段 一一对应,则可以快速插入数据
const user = { username: '汤姆猫', userpswd:'123456'}
const sqlStr = 'INSERT INTO users SET ?'
db.query(sqlStr, user, (err,result) => {
    if(err) return console.log(err.message)
    if(result.affectedRows === 1) {
        console.log('插入成功!')
    }
})

5. mysql 模块 更新数据

注意:执行了 UPTATE 语句,则执行的结果是一个【对象】,可以通过 affectedRows 判断是否成功。影响的行数。
// 定义要更新的新数据对象
const user = { id: 8, username: '小栗帽', userpswd: '654321'}
// 定义待执行的 SQL 语句   使用占位符
const sqlStr = 'UPDATE users SET username=? ,userpswd=? WHERE id=?'
// 更新
db.query(sqlStr, [user.username, user.userpswd, user.id], (err,result) => {
    if(err) return console.log(err.message)
    if(result.affectedRows === 1) {
        console.log('更新成功!')
    }
})
// 更新数据的便携方式
const user = { id: 8, username: '小栗帽', userpswd: 'qqqqqq'}
// 使用占位符。把 user 信息对象都做更新,条件是 id
const sqlStr = 'UPDATE users SET ? WHERE id=?'
// 把 user 对象传给第一个占位符 ? ,把 id 传给第二个占位符 ?
db.query(sqlStr, [user, user.id], (err,result) => {
    if(err) return console.log(err.message)
    if(result.affectedRows === 1) {
        console.log('更新成功!')
    }
})

6. mysql 模块 删除数据

推荐根据 id 这样的唯一标识(主键)来删除对应数据。

注意:执行了 DELETE 语句,则执行的结果是一个【对象】,可以通过 affectedRows 判断是否成功。影响的行数。

注意:如果 SQL 语句中有多个占位符,则必须以数组的形式来指定每个占位符具体的值

          如果 SQL 语句只有一个占位符,则可以省略数组

// 6. 删除数据
// 推荐根据 id 这样的唯一标识(主键)来删除对应数据。
// 1、要执行的 SQL 语句
const sqlStr = 'DELETE FROM users WHERE id=?'
// 2、调用 db.query() 执行 SQL 语句的同时,为占位符指定具体的值
// 注意:如果 SQL 语句中有多个占位符,则必须以数组的形式来指定每个占位符具体的值
//       如果 SQL 语句只有一个占位符,则可以省略数组
db.query(sqlStr, 9, (err,result) => {
    if(err) return console.log('删除失败!')
    if(result.affectedRows === 1) {
        console.log('删除成功!')
    }
})

7. mysql 模块 删除数据 标记删除

使用 DELETE 语句,会真正把数据删掉,需要谨慎操作,为了保险起见,推荐使用 标记删除,来模拟删除的动作,逻辑删除

标记删除,就是在表中设置类似 status 这样的字段,来标记是否被删除了

例如,当用户执行删除时,status 字段值改为 1,标记为删除了,也就是逻辑删除

db.query('UPDATE users SET status=? WHERE id=?', [1,233], (err,result) => {
    if(err) return console.log(err.message)
    if(result.affectedRows === 1) {
        console.log('标记删除成功!status 改为了 1')
    }
})

 

posted @ 2023-03-13 17:57  nekmea  阅读(30)  评论(0编辑  收藏  举报