Node.js基础

目录

1. fs文件系统模块

    1.1 fs.readFile读取文件内容

    1.2 fs.readFile判断文件是否读取成功

    1.3 writeFile写入文件内容

   1.4 writeFile判断文件是否写入失败

    1.5 readFile及writeFile练习

2.fs模块-路径动态拼接问题

3.path路径模块

    3.1 什么是path路径模块

    3.2 Path.join

    3.3 path.basename

    3.4 path.extname

4.http模块

   4.1 什么是http模块

   4.2 创建最基本的web服务器

    4.3 根据不同的url响应不同的html内容

5.模块化

   5.1 模块化的概念

    5.2 Node.js中的模块化

    5.3 Node.js中的模块作用域

    5.4 向外共享模块作用域中的成员

6.Express

    6.1 初识Express

     6.1.1 什么是Express

     6.1.2 创建基本的Web服务器

     6.1.3 托管静态资源

     6.1.4 nodemon

     6.2 Express路由

      6.2.1 Express中的路由

     6.2.2 路由的匹配过程

     6.2.3 模块化路由

     6.3 Express中间件

      6.3.1 中间件的概念

     6.3.2 全局生效的中间件

    6.3.3 中间件的作用

    6.3.3 局部生效的中间件

   6.3.4 中间件的分类

6.3.5 自定义中间件

    6.4 使用Express写接口

      6.4.1 创建API路由模块

    6.4.2 CORS跨域资源共享

7. 数据库

    7.1 数据库的基本概念

     7.1.1 什么是数据库

    7.2 使用MySql Workbench管理数据库

     7.2.1 创建数据库和表

     7.2.2 向表中写入数据

    7.3 使用SQL管理数据库

     7.3.1 SQL的select语法

     7.3.2 SQL的insert into语句

     7.3.3 SQL的update语句

     7.3.4 SQL的delete语句

     7.3.5 SQL的where子句

     7.3.6 SQL的and和or运算符

     7.3.7 SQL的order by子句

    7.4 项目中操作MySql

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

     7.4.2 查询数据

     7.4.3 插入数据

     7.4.4 更新数据

    7.4.5 删除数据

8.前后端的身份认证

      8.1 不同开发模式下的身份认证

      8.2 Session认证机制

        8.2.1 HTTP协议的无状态性

        8.2.2 Cookie

     8.2.3 Session认证机制

       8.2.4 在Express中使用Session认证

    8.3 JWT认证机制

      8.3.1 JWT的工作原理

      8.3.2 JWT的组成部分

      8.3.3 JWT的使用方式

       8.3.4 在Express中使用JWT


1. fs文件系统模块

    1.1 fs.readFile读取文件内容

//1.导入fs模块,来操作文件
const fs= require('fs')

// 2.调用fs.readFile()方法读取文件
//   参数1:读取文件的路径
//   参数2:读取文件的时候采用的编码格式,默认指定utf8
//   参数3:回调函数,可以拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/1.txt','utf8',function(err,dataStr) {
    // 2.1 打印失败的结果
    // 如果读取成功,则err的值为null
    console.log(err);
    // 2.2 打印成功的结果
    console.log(dataStr);
})

    1.2 fs.readFile判断文件是否读取成功

//1.导入fs模块,来操作文件
const fs= require('fs')

// 2.调用fs.readFile()方法读取文件
//   参数1:读取文件的路径
//   参数2:读取文件的时候采用的编码格式,默认指定utf8
//   参数3:回调函数,可以拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/1.txt','utf8',function(err,dataStr) {
    // 2.1 打印失败的结果
    // 如果读取成功,则err的值为null
    // 如果读取失败,则err的值为错误对象,dataStr的值为undefined
    console.log(err);
    // 2.2 打印成功的结果
    console.log(dataStr);
})

    1.3 writeFile写入文件内容

         ① fs.writeFile()方法只能用来创建文件不能用来创建路径(即文件夹)

         ② 重复调用fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的旧内容

// 1. 导入fs文件系统模块
const fs=require('fs')

// 2. 调用fs.writeFile()方法,写入文件内容
//   参数1:指定文件的存放路径
//   参数2:表示要写入的内容
//   参数3:回调函数
fs.writeFile('./files/2.txt','abcd',function (err) {
    // 2.1 如果文件写入成功,err的值为null
    // 2.2 如果文件写入失败,err的值为一个错误对象
    console.log(err);
})

   1.4 writeFile判断文件是否写入失败

// 1. 导入fs文件系统模块
const fs=require('fs')

// 2. 调用fs.writeFile()方法,写入文件内容
//   参数1:指定文件的存放路径
//   参数2:表示要写入的内容
//   参数3:回调函数
fs.writeFile('./files/2.txt','abcd',function (err) {
    // 2.1 如果文件写入成功,err的值为null
    // 2.2 如果文件写入失败,err的值为一个错误对象
    if (err) {
        return console.log('文件写入失败'+err.message);
    }
    console.log(err);
})

    1.5 readFile及writeFile练习

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

// 2.读取fs.readFile()读取文件内容
fs.readFile('./files/成绩.txt','utf8',function(err,dataStr) {
    // 3.判断是否读取成功
    if (err) {
        return console.log('读取内容失败'+err.message);
    }
    // console.log('读取文件成功'+dataStr);//小红=99 小白=100 小黄=70 小黑=66 小绿=88
    // 4.对数据进行操作
    // 4.1 先把成绩的数据,按照空格进行分割
    const arrOld=dataStr.split(' ')
    console.log(arrOld);//[ '小红=99', '小白=100', '小黄=70', '小黑=66', '小绿=88' ]
    // 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
    const arrNew=[]
    arrOld.forEach((item)=>{
        arrNew.push(item.replace('=',':'))
    })
    // 4.3 把新数组中的每一项,进行合并,得到一个新的字符串
    const newStr=arrNew.join('\r\n')
    

    // 5.调用fs.writeFile()方法,把处理完毕的成绩,写入新的文件中
    fs.writeFile('./files/成绩2.0.txt',newStr,function (err) {
        if (err) {
            return console.log('文件写入失败'+err.message);
        }
        console.log('文件写入成功!');
        //小红:99
        //小白:100
        //小黄:70
        //小黑:66
        //小绿:88
    })
})

2.fs模块-路径动态拼接问题

       在使用fs模块操作文件时,如果提供的操作路径是以./或../开头的相对路径时,很容易出现路径拼接错误的问题。

        原因:代码在运行的时候,会执行node命令时所处的目录,动态拼接出操作文件的完整路径

        解决方案:路径前方添加__dirname拼接

3.path路径模块

    3.1 什么是path路径模块

        path 模块 是Node.js官方提供的、用来处理路径的模块。它提供了一系列的方法属性,用来满足用户对路径的处理需求。

例如:

  • path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串
  • path.basename()方法,用来从路径字符串中,将文件名解析出来
const path=require('path')

    3.2 Path.join

         使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串

const path=require('path')
const fs = require('fs')
// ../会抵消前面的路劲
// const pathStr=path.join('/a','/b/c','../','./d','e')
// console.log(pathStr);

fs.readFile(path.join(__dirname,'/files/1.txt'),'utf8',function (err,dataStr) {
    if (err) {
        return console.log('读取文件失败!',err.message);
    }
    console.log('读取文件成功!',dataStr);
})

       涉及到路径拼接操作,推荐使用path.join()方法处理

    3.3 path.basename

       使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名

const path=require('path')

// 定义文件的存放路径
const fpath='/a/b/c/index.html'

// 参数1:必选参数,一个路径的字符串
// 参数2:可选参数,表示文件的拓展名
// 返回路径中的最后一部分
// const fullName=path.basename(fpath)
// console.log(fullName);//index.html

const fullName=path.basename(fpath,'.html')
console.log(fullName);//index

    3.4 path.extname

       使用path.extname()方法,可以获取路径中的扩展名部分

const path=require('path')

// 文件的存放路径
const fpath='/a/b/c/index.html'

// 参数:表示一个路径的字符串
// 返回:返回得到的扩展名字符串
const fext=path.extname(fpath)
console.log(fext);//.html

4.http模块

   4.1 什么是http模块

       http模块是Node.js官方提供的、用来创建web服务器的模块。通过http模块提供http.createServer()方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。

const http=require('http')

   4.2 创建最基本的web服务器

       ① 导入http模块

const http=require('http')

       ② 创建web服务器实例

const server=http.createServer()

       ③ 为服务器实例绑定request事件,监听客户端请求

//使用服务器实例的.on()方法,为服务器绑定request事件
server.on('request',(req,res)=>{
   //只要有客户端来请求服务器,就会触发request事件,从而调用事件处理函数
   console.log('Someone visit our web server.)
})

        ④ 启动服务器

//调用server.listen(端口号,cb回调)方法,即可启动web服务器
server.listen(80,()=>{
   console.log('http server running at http://127.0.0.1')
})
//1. 导入http模块
const http=require('http')

// 2.创建web服务器实例
const server=http.createServer()

//3. 为服务器实例绑定request事件,监听客户端的请求
// 3.1 req是请求对象,包含了与客户端相关的数据和属性
// 3.2 res是响应对象,包含了与服务器相关的数据或属性
server.on('request',(req,res)=>{
    // req.url是客户端请求的URL地址
    const url=req.url
    // req.method是客户端请求的method类型
    const method=req.method
    const str=`Your request url is ${url},and request method is ${method}`
    console.log(str);
    // 调用res.end()方法,向客户端响应一些内容
    res.end(str)
})

// 4. 启动服务器
server.listen(8080,() =>{
    console.log('server running at http://127.0.0.1:8080');
})

    4.3 根据不同的url响应不同的html内容

const http=require('http')

const server=http.createServer()

server.on('request',function(req,res) {
    // 1.获取请求的url地址
    const url=req.url
    // 2.设置默认的响应内容为404 Not found
    let content='404 Not found!'
    // 3.判断用户请求的是否为/或/index.html首页
    // 4.判断用户请求的是否为/about.html关于页面
    if (url==='/'||url==='/index.html') {
        content='<h1>首页</h1>'
    } else if(url==='/about.html'){
        content='<h1>关于页面</h1>'
    }
    // 设置Content-Type响应头,防止中文乱码
    res.setHeader('Content-Type','text/html;charset=utf-8')
    res.end(content)
})

server.listen(8080,()=>{
    console.log('server running at http://127.0.0.1:8080');
})

5.模块化

   5.1 模块化的概念

      (1) 什么是模块化

       模块化是指解决一个复杂问题时,自顶向下逐层把系统划分若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。

      好处:

            ① 提高了代码的复用性

            ② 提高了代码的可维护性

            ③ 可以实现按需加载

        (2) 模块化规范

     模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。

      好处:降低了沟通的成本,极大方便了各个模块之间的相互调用。

    5.2 Node.js中的模块化

         (1) Node.js中模块的分类

      Node.js中根据模块来源不同,将模块分为3大类,分别是:

      ① 内置模块(内置模块是由Node.js官方提供的,例如fs、path、http等)

      ② 自定义模块 (用户创建的每一个.js文件,都是自定义模块)

      ③ 第三方模块(由第三方开发出来的模块,使用前需要先下载)

       (2)加载模块

      使用require()方法,可以加载需要的内置模块自定义模块第三方模块进行使用

//1. 加载内置的fs模块
const fs=require('fs')

//2. 加载用户的自定义模块
const cust=require(.cust.js)

//3.加载第三方模块
const moment=require('moment')

    5.3 Node.js中的模块作用域

      (1)什么是模块作用域

        和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

     (2)模块作用域的好处

     防止全局变量污染的问题

const username='张三'

function sayHello() {
    console.log('大家好,我是'+username);
}
const custom=require('./01.模块作用域')

console.log(custom);//{}

    5.4 向外共享模块作用域中的成员

      (1)module对象

      在每一个.js自定义模块中都有module对象,它里面存储了和当前模块有关的信息 a881f20b743e4a8cb19354a0d23ddc33.png

      (2)module.exports对象

      在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。外界使用require()方法导入自定义模块时,得到的就是module.exports所指向的对象

// 在一个自定义模块中,默认module.exports={}

// 向module.export对象上挂载username属性
module.exports.username='zs'
// 向module.exports对象上挂载sayHello方法
module.exports.sayHello=function () {
    console.log('Hello!');
}

const age=20

module.exports.age=age

      (3)共享成员时的注意点

      使用require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准。


module.exports.age=age

// 让module.exports指向一个全新的对象
module.exports={
    nickname:'小黑',
    sayHi(){
        console.log('Hi!');
    }
}

      默认情况下,exports和module.qxports指向同一个对象。最终共享的结果还是以module.exports指向的对象为准。

     (4)exports和module.exports的使用误区

     require()模块时,得到的永远是module.exports指向的对象

       10e76f0bfb164744a65d0d72326934c8.pngc24bd47b847f430791b82f8f051ca241.png31adbdd4490b4b2a842b7020468de434.png

exports={
   username:'zs',
   gender:'男'
}
module.exports=exports
module.exports.age='22'

//{username:'zs',gender:'男',age:'22'}

 注意:为了防止混乱,建议不要在同一个模块中同时使用exports和module.exports

      (5)Node.js中的模块化规范

      Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。

      CommonJS规定:

          ①每个模块内部,module变量代表当前模块。

          ②module变量是一个对象,它的exports属性(即module.exports)是对外的接口。

          ③加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载 模块。

6.Express

    6.1 初识Express

     6.1.1 什么是Express

      Express是基于Node.js平台,快速、开放、极简的Web开放框架。

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

      Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法

npm i express@4.17.1

     6.1.2 创建基本的Web服务器

const express=require('express')

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

// 4.监听客户端的FET和POST请求,并向客户端响应具体的内容
app.get('/user',(req,res)=>{
    // 调用express提供的res.send()方法,向客户端响应一个JSON对象
    res.send({name:'zs',age:20,gender:'男'})
})

app.post('/user',(req,res)=>{
    // 调用express提供的res.send()方法,向客户端响应一个文本字符串
    res.send('请求成功')
})

app.get('/',(req,res)=>{
    // 通过req.query可以获取到客户端发送过来的 查询参数
    // 注意:默认情况下,req.query是一个空对象
    console.log(req.query);
    res.send(req.query)
})

// 注意:这里的:id是一个动态的参数
app.get('/user/:id',(req,res)=>{
    // req.params是动态匹配到的URL参数,默认也是一个空对象
    console.log(req.params);
    res.send(req.params)
})

// 3.启动web服务器
app.listen(8080,()=>{
    console.log('express server running at http://127.0.0.1:8080');
})

     6.1.3 托管静态资源

    (1)express.static()

      使用express.static(),可以非常方便地创建一个静态资源服务器。

11db76a9b01f4673b5e584d8b355871f.png

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

    (2)托管多个静态资源目录

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

b27ccfdac4fb4c0abdd70ae0a9805bfe.png

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

    (3)挂载路径前缀

      如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

1bb90263c73d482d9af5af79c8473dd4.png

     6.1.4 nodemon

      (1)  为什么要使用nodemon

      在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close                     掉,然后再重新启动,非常繁琐。

      nodemon能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目,极大方便了开发和调试。

   (2) 安装nodemon

     在终端中,运行如下命令,即可将nodemon安装为全局可用的工具:

6452a238efc847eebaa5c4f65fb95864.png

   (3) 使用nodemon

     在终端输入nodemon xxx.js

     6.2 Express路由

      6.2.1 Express中的路由

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

      Express中的路由分3个部分,分别是请求的类型、请求的URL地址、处理函数。

b648f50218174ddfa1dc2241c7eeb2e2.png

     6.2.2 路由的匹配过程

      每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数

     路由匹配的注意点:

        ① 按照定义的先后顺序进行匹配

        ② 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数

     6.2.3 模块化路由

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

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

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

      ③ 向路由对象上挂载具体的路由

      ④ 使用module.exports向往共享路由对象

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

// 这是路由模块
// 1.导入express模块
const express=require('express')

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

// 3.挂载具体路由
router.get('/user/list',(req,res)=>{
    res.send('Get user list.')
})

router.post('/user/add',(req,res)=>{
    res.send('Post user list')
})

// 4.向外导出路由
module.exports=router

   注册路由模块

9b294c3601ea41b09cd5cdfcbaaabc15.png

     6.3 Express中间件

      6.3.1 中间件的概念

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

      Express的中间件,本质上就是一个Function处理函数。

2bd75d69a85b4652abb0bba664533097.png

      注意:中间件函数的形参列表中,必须包含next参数,而路由处理函数中只包含req和res

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

     6.3.2 全局生效的中间件

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

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

const express=require('express')

const app=express()

// 定义一个中间件函数
const mw=function(req,res,next) {
    console.log("这是最简单的中间件函数");
    // 把流转关系,转交给下一个中间件或路由
    next()
}

// 将mw注册为全局生效的中间件函数
app.use(mw)

app.get('/',(req,res)=>{
    res.send('Home page.');
})

app.get('/user',(req,res)=>{
    res.send('User page.')
})

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

    6.3.3 中间件的作用

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

const express=require('express')

const app=express()

app.use((req,res,next)=>{
    // 获取到请求到达服务器的时间
    const time=Date.now()
    // 为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
    req.startTime=time
    next()
})

app.get('/',(req,res)=>{
    res.send('Home page.'+req.startTime);
})

app.get('/user',(req,res)=>{
    res.send('User page.'+req.startTime)
})

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

    6.3.3 局部生效的中间件

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

const express=require('express')

const app=express()

const mw=(req,res,next)=>{
    console.log('调用了局部生效的中间件');
    next()
}

app.get('/',mw,(req,res)=>{
    res.send('Home page.');
})

app.get('/user',(req,res)=>{
    res.send('User page.')
})

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

   6.3.4 中间件的分类

    ① 应用级别的中间件

       通过app.use()app.get()app.post()绑定到app实例上的中间件

    ② 路由级别的中间件

       绑定到express.Router()实例上的中间件。

// 这是路由模块
// 1.导入express模块
const express=require('express')

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

router.use(function(req,res,next){
    console.log('Time:',Date.now())
    next()
})

express.use('/',router)

// 3.挂载具体路由
router.get('/user/list',(req,res)=>{
    res.send('Get user list.')
})

router.post('/user/add',(req,res)=>{
    res.send('Post user list')
})

// 4.向外导出路由
module.exports=router

     ③ 错误级别的中间件

       错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃

       格式:必须有4个形参,分别是(err,req,res,next)。

       注意:错误级别中间件必须注册在所有路由之后

// 1.定义路由
app.get('/',(req,res)=>{
    // 1.1 人为的制作错误
    throw new Error('服务器内部发生了错误')
    res.send('Home page.')
})

// 2.定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err,req,res,next)=>{
    console.log('发生了错误!'+err.message);
    res.send('Error:'+err.message)
})

     ④ Express内置的中间件

       Express内置了3个常用的中间件:

  • exprees.static 快速托管静态资源的内置中间件
  • express.json 解析JSON格式的请求体数据
  • express.urlencodeed 解析URL-encodeed格式的请求体数据  
// 配置解析 application/json格式数据的内置中间件

app.use(express.json())

// 配置解析 application/x-www-form-urlencodeed 格式数据的内置中间件

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

     使用req.body接收客户端发送过来的数据,未经数据格式转换则值未undenfind。

6.3.5 自定义中间件

const express=require('express')

const app=express()

// 导入Node.js内置的querystring
const qs=require('querystring')

// 定义解析表单数据的中间件
app.use((req,res,next)=>{
    // 定义中间件的具体业务逻辑
    // 1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
    let str=''
    // 2.监听req的data事件
    req.on('data',(chunk)=>{
        str+=chunk
    })
    // 3.监听req的end事件
    req.on('end',()=>{
        // 在str中存放的是完整的请求体数据
        // console.log(str);
        // TODO:把字符串格式的请求体数据,解析成对象格式
        const body=qs.parse(str)
        req.body=body
        next()
    })
    
})

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

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

    6.4 使用Express写接口

      6.4.1 创建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)=>{
    // 通过req.body获取请求体中包含的url-encoded格式的数据
    const body=req.body
    // 调用res.send()方法,向客户端响应结果
    res.send({
        status:0,
        msg:'POST请求成功!',
        data:body
    })
})

module.exports=router

    6.4.2 CORS跨域资源共享

     cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。

     使用步骤:

     ① 运行npm install cors 安装中间件

     ② 使用const cors=require('cors') 导入中间件

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

     CORS是由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。

7. 数据库

    7.1 数据库的基本概念

     7.1.1 什么是数据库

      数据库是用来组织存储管理数据的仓库。

    7.2 使用MySql Workbench管理数据库

     7.2.1 创建数据库和表

     字段的特殊标识:

      ① PK(Primary Key)主键、唯一标识

      ② NN(Not Null)值不允许为空

      ③ UQ(Unique)值唯一

      ④ AI (Auto Increment)值自动增长

     7.2.2 向表中写入数据

    7.3 使用SQL管理数据库

     7.3.1 SQL的select语法

   (1)语法

      select语句用于从表中查询数据。执行的结果被存储在一个结果表中,语法格式:   

--这是注释
--从from指定的【表中】,查询出【所有的】数据。*表示【所有列】
select * from 表名称

--从from指定的【表中】,查询出指定列名称(字段)的数据。
select 列名称 from 表名称

     注意:SQL语句中的关键字对大小写不敏感

    (2)select实际应用

-- 通过*把users表中所有的数据查询出来
-- select * from users

-- 从users表中把username和password对应的数据查询出来
select username,password from users

     7.3.2 SQL的insert into语句

   (1)语法

    insert into语句用于向数据表中插入新的数据行,语法格式:

-- 语法解读:指定的表中,插入如下几列数据,列的值通过values指定
-- 注意:列和值要一一对应,多个列和多个值之间,使用英文的单号隔开
insert into table_name (列1,列2,...) values (值1,值2,...)

    (2)insert into示例

--  向users表中,插入新的数据,username的值为tony stark password的值为098123
insert into users (username,password) values ('tony stark','098123')

     7.3.3 SQL的update语句

    (1)语句

     Update语句用于修改表中的数据,语法格式:

-- 语法解读:
-- 1.用update指定要更新哪个表中的数据
-- 2.用set指定列对应的新值
-- 3.用where指定更新的条件
update 表名称 set 列名称=新值 where 列名称=某值

    (2)update示例

-- 将id为4的用户密码,更新为888888
update users set password='888888' where id=4

-- 更新id为2的用户,把用户密码更新为admin123 同时把用户的状态更新为1
update users set password='admin123',status=1 where id=2  

     7.3.4 SQL的delete语句

    (1)语句

       delete语句用于删除表中的行,语法格式:

-- 语法解读:
-- 从指定的表中,根据where条件,删除对应的数据行
delete from 表名称 where 列名称=值

     (2)delete示例

-- 删除users表中,id为4的用户
delete from users where id=4

     7.3.5 SQL的where子句

    (1)语法

       where子句用于限定选择的标准。在selectdeleteupdate语句中,皆可使用where子句限定选择的标准。

-- 查询status为1的所有用户
select * from users where status=1

-- 查询id大于2的所有用户
select * from users where id>2

--查询username不等于admin的所有用户
select * from users where username<>'admin'

     7.3.6 SQL的and和or运算符

    (1)语法

      and和or可在where子语句把两个或多个条件结合起来

      and表示必须同时满足多个条件,相当于JavaScript中的&&运算符。

      or表示只要满足任意一个条件即可,相当于JavaScript中的||运算符。

    (2)示例

-- 使用and来显示所有状态为0且id小于3的用户
select * from users where status=0 and id<3

-- 使用or来显示所有status为1,或者username为zs的用户
select * from users where status=1 or username='zs'

     7.3.7 SQL的order by子句

    (1)语法

     order by语句用于根据指定的列对结果集进行排序

     order by语句默认按照升序对记录进行排序。desc对记录进行降序

-- 对users表中的数据,按照status字段进行升序排序
select * from users order by status

-- 按照id对结果进行降序排序 desc表示降序
select * from users order by id desc

     7.3.8 SQL的count(*)函数

   (1)语法

     count(*)函数用于返回查询结果的总数据条数。语法格式:

select count(*) from 表名称

   (2)示例

-- 查询users表中status为0的总数据条数
select count(*) from users where status=0

-- 使用as关键字给列起别名
select count(*) as total from users where status=0
select username as uname,password as upword from users

    7.4 项目中操作MySql

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

     ① 安装操作MySQL数据库的第三方模块(mysql)

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

     ③ 通过mysql模块执行SQL语句

// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'root',//登录数据库的账号
    password:'*********',//登录数据库的密码
    database:'my_db_01'//指定要操作哪个数据库
})

// 测试mysql模块能否正常工作
db.query('select 1',(err,results)=>{
    // mysql模块工作期间报错了
    if(err) return console.log(err.message);
    // 能够成功的执行SQL语句
    console.log(results);
})

     7.4.2 查询数据

// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'root',//登录数据库的账号
    password:'********',//登录数据库的密码
    database:'my_db_01'//指定要操作哪个数据库
})

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

     7.4.3 插入数据

// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'root',//登录数据库的账号
    password:'*****',//登录数据库的密码
    database:'my_db_01'//指定要操作哪个数据库
})



// 向users表中,新增一条数据,其中username的值为Spider-Man,password的值为pcc123
const user={username:'Spider-name',password:'pcc123'}
// 定义待执行的SQL语句 ?表示占位符 防止SQL注入及提高可复用性
const sqlStr='insert into users (username,password) values (?,?)'
// 执行SQL语句
db.query(sqlStr,[user.username,user.password],(err,results)=>{
    // 执行SQL语句失败
    if(err) return console.log(err.message);
    // 执行SQL语句成功
    // 注意:如果执行的是insert into插入语句,则results是一个对象
    // 可以通过affectedRows属性,来判断是否插入数据成功
    if(results.affectedRows===1){console.log('插入数据成功');}
})

     插入语句的便捷方法

     向表中新增数据时,如果数据对象的每个属性数据表的字段一一对应,则可以:

const user={username:'Spider-Man2',password:'pcc4321'}
// 定义待执行的SQL语句
const sqlStr='insert into users set ?'
// 执行SQL语句
db.query(sqlStr,user,(err,results)=>{
    if(err) return console.log(err.message);
    if(results.affectedRows===1){console.log('插入数据成功');}
})

     7.4.4 更新数据

// 更新用户信息
const user={id:6,username:'aaa',password:'000'}
// 定义SQL语句
const sqlStr='update users set username=?,password=? where id=?'
// 执行SQL语句
db.query(sqlStr,[user.username,user.password,user.id],(err,results)=>{
    if(err) return console.log(err.message);
    // 注意:执行update语句之后,执行的结果,也是一个对象,可以通过affectedRows判断是否更新成功
    if(results.affectedRows===1){console.log('更新成功');}
})

    更新表数据时,如果数据对象的每个属性数据表的字段一一对应,则可以:

const user={id:6,username:'aaaa',password:'0000'}
// 定义SQL语句
const sqlStr='update users set ? where id=?'
// 执行SQL语句
db.query(sqlStr,[user,user.id],(err,results)=>{
    if(err) return console.log(err.message);
    if(results.affectedRows===1){console.log('更新成功');}
})

    7.4.5 删除数据

const sqlStr='delete from users where id=?'
db.query(sqlStr,6,(err,results)=>{
    if(err) return console.log(err.message);
    // 注意:执行delete语句之后,结果也是一个对象,也包含affectedRows属性
    if(results.affectedRows===1){
        console.log('删除数据成功');
    }
})

        使用delete语句,会真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作

        使用标记删除,就是在表中设置类似于status这样的状态字段,来标记当前数据是否被删除。

        当用户执行删除的动作时,直接执行update语句,将数据对应的status字段标记为删除即可。

// 标记删除
const sqlStr='update users set status=? where id=?'
db.query(sqlStr,[1,7],(err,results)=>{
    if(err) return console.log(err,message);
    if(results.affectedRows===1){
        console.log('标记删除成功');
    }
})

8.前后端的身份认证

      8.1 不同开发模式下的身份认证

       ① 服务端渲染推荐使用Session认证机制

       ② 前后端分离推荐使用JWT认证机制

      8.2 Session认证机制

        8.2.1 HTTP协议的无状态性

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

        8.2.2 Cookie

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

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

       Cookie的几大特性:

       ① 自动发送

       ② 域名独立

       ③ 过期时限

       ④ 4KB限制

     8.2.3 Session认证机制

       8.2.4 在Express中使用Session认证

      (1)安装express-session中间件,即npm install express-session。

      (2)配置express-session中间件,通过app.use()注册session中间件

const session=require('express-session')

app.use(session({
  secret:'itheima',//任意字符串
  resave:false,//固定写法
  saveUninitialized:true,//固定写法
}))

      (3)向session中存数据

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

// 登录的 API 接口
app.post('/api/login', (req, res) => {
  // 判断用户提交的登录信息是否正确
  if (req.body.username !== 'admin' || req.body.password !== '000000') {
    return res.send({ status: 1, msg: '登录失败' })
  }

  // TODO_02:请将登录成功后的用户信息,保存到 Session 中
  // 注意:只有成功配置了express-session这个中间件之后,才能够通过
  req.session.user=req.body.username//用户的信息
  req.session.islogin=true//用户的登录状态


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

     (4)从session中取数据

       可以直接从req.session对象上获取之前存储的数据。

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
  // TODO_03:请从 Session 中获取用户的名称,响应给客户端
  if (!req.session.islogin) {
    return res.send({status:1,msg:'fail'})
  }
  res.send({
    status:0,
    msg:'success',
    username:req.session.user
  })
})

     (5)清空session

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

// 退出登录的接口
app.post('/api/logout', (req, res) => {
  // TODO_04:清空 Session 信息
  req.session.destroy()
  req.send({
    status:0,
    msg:'退出登录成功'
  })
})

    8.3 JWT认证机制

      8.3.1 JWT的工作原理

      用户的信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份。 

      8.3.2 JWT的组成部分

      jwt通常由三部分组成,分别是Header(头部)、Payloac(有效荷载)、Signature(签名)。

      三者之间使用.分隔,格式如下:

      8.3.3 JWT的使用方式

      客户端收到服务器返回的JWT后,会将它存储在localStoragesessionStorage中。之后客户端每与服务器通信,都要带上JWT,从而进行身份认证。推荐把JWT放在HTTP请求头的Authorization字段中。格式如下:

       8.3.4 在Express中使用JWT

      (1)安装JWT相关包,npm install jsonwebtoken express-jwt

        ① jsonwebtoken用于生成JWT字符串

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

      (2)导入JWT相关的包,使用require()函数:

// 1.导入用于生成JWT字符串的包
const jwt=require('jsonwebtoken')
// 2.导入用于将客户端发送过来的JWT字符串,解析还原成JSON对象的包
const expressJWT=require('express-jwt')

     (3)定义secret密钥

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

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

       ② 当把JWT字符串解析还原成JSON对象时,需要使用secret密钥进行解密

//3.secret 密钥的本质:就是一个字符串
const seretKey='cqiq No1 ^_^'

     (4)在登录成功后生成JWT字符串

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

// 登录接口
app.post('/api/login', function (req, res) {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!'
    })
  }
  // 登录成功
  // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  // 参数1:用户的信息对象
  // 参数2:加密的密钥
  // 参数3:配置对象,可以配置当前token的有效期
  // 注意:不能把密码加密到token字符中
  const tokenStr=jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr // 要发送给客户端的 token 字符串
  })
})

     (5)将JWT字符串还原为JSON对象

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

        服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象。(express-jwt版本最好为6.1.1否则会报错)

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// expressJWT({secret:secretKey})是用来解析Token的中间件
// .unless 用于配置不需要访问权限的接口
// 只要配置成功了express-jwt这个中间件,就可以把解析出来的用户信息,挂载到req.user属性上
app.use(expressJWT({secret:secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))

     (6)使用req.user获取用户信息

        当express-jwt配置成功后,即可在那些有权限的接口中,使用req.user对象,访问从JWT字符串中解析出来的用户信息。 

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.user);
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user // 要发送给客户端的用户信息
  })
})

     (7)捕获解析JWT失败后产生的错误

        当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期不合法,会产生一个解析失败的错误,影响项目的正常运行。可以通过Express的错误中间件,捕获错误并进行相关的处理。

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
  // 错误是由token解析失败导致的
  if(err.name==='UnauthorizedError'){
    return res.send({
      status:401,
      message:'无效的token'
    })
  }
  res.send({
    status:500,
    message:'未知的错误'
  })
})

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Node.js是一个建立在谷歌V8引擎的运行环境,用于解析和执行JavaScript代码。它使得JavaScript不再局限于浏览器环境,可以在后端创建动态数据。\[1\]严格来说,Node.js不是一个框架,而是一个平台。它的技术栈包括核心模块、框架(如Express、KOA)、模板引擎(如Pug、EJS)、编译型CSS(如Lass/Sass/Stylus)和数据库(如MongoDB、MySQL)等。\[2\] Node.js基础知识包括了如何执行另一个模块中的功能。例如,在一个模块中使用require函数引入另一个模块,然后可以调用该模块中的功能。例如,如果有一个b.js模块和一个first.js模块,可以在first.js模块中使用require('./b')来引入b.js模块,并调用其中的功能。执行node first.js即可运行该程序。\[3\] #### 引用[.reference_title] - *1* [【入门级基础Node基础知识总结](https://blog.csdn.net/pakerder/article/details/125191636)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [node.js基础知识](https://blog.csdn.net/weixin_45459904/article/details/107508639)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值