Node.js学习笔记

文章目录

Node.js基础

初识Node.js

javaScript可以做后端开发,但是要借助运行环境,node.js

安装

Node.js (nodejs.org)

请添加图片描述

查看安装成功

node -v

node js运行js代码

请添加图片描述

请添加图片描述

请添加图片描述

fs 文件系统模块

用来操作文件的模块

获得fs系统模块对象

const fs = require('fs')

读取文件

fs.readFile()
示例代码
// 1.导入fs 模块,来操作文件
const fs = require('fs');

// 2.调用 fs.readFile() 方法读取文件
// 参数1:读取文件的存放路径
// 参数2:读取文件的时候采用的编码格式,一般默认指定utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./file/1.txt','utf8',function (err,dataStr){
//  2.1打印失败的结果
    console.log(err)
    console.log('--------------')
//  2.2打印成功的结果
    console.log(dataStr)
})
运行测试

请添加图片描述

判断文件读取成功
// 1.导入fs 模块,来操作文件
const fs = require('fs');

// 2.调用 fs.readFile() 方法读取文件
// 参数1:读取文件的存放路径
// 参数2:读取文件的时候采用的编码格式,一般默认指定utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./file/1.txt','utf8',function (err,result){
    if(err){
        return console.log('文件读取失败' + err.message)
    }
    console.log('文件读取成功,内容是:' + result)
})

写入文件

fs.writeFile(file,data[,options],callback)
示例代码
//1.导入 fs 文件系统模块
const fs = require('fs')

// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数

fs.writeFile('./file/2.txt','adbc',function (err){
    // 2.1 如果文件写入成功,则 err的值等于 null
    // 2.2 如果文件写入失败,则 err的值等于一个错误对象
    console.log(err)
})

判断文件是否写入成功
//1.导入 fs 文件系统模块
const fs = require('fs')

// 2.调用 fs.writeFile()方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数

fs.writeFile('./file/2.txt','adbc',function (err){
    // 2.1 如果文件写入成功,则 err的值等于 null
    // 2.2 如果文件写入失败,则 err的值等于一个错误对象
    // console.log(err)
    if(err){
        return console.log('文件写入失败!' + err.message)
    }
    console.log('文件写入成功!')
})

案例题目练习(1)
const fs = require('fs')

fs.readFile('./file/成绩.txt','utf8',function (err,dataStr){
//     判断读取成功
    if(err){
        return console.log('读取失败!' + err.message)
    }

    // console.log('读取文件成功:' + dataStr)
//     先把成绩的数据,按照空格进行分割
    const arrOld = dataStr.split(' ')
//     循环分割后的数组,对每一项数据,进行字符串的替换操作
    const arrNew = []
    arrOld.forEach(item => {
        arrNew.push(item.replace('=',': '))
    })
//     把新数组中的每一项,进行合并,得到一个新的字符串
    const newStr = arrNew.join('\n')
    console.log(newStr)
})

演示路径问题

const fs = require('fs')

// 出现路径拼接错误的问题,是因为提供了 ./或 ../ 开头的相对路径
// 如果要解决这个问题,可以直接提供一个完整的文件存放路径就行
// fs.readFile('./file/1.txt','utf8',function (err,dataStr){
//     if(err){
//         return console.log('读取失败' + err.message)
//     }
//     console.log('读取文件成功' + dataStr)
// })
// ****************************************************************************************
// 移植性非常差,不利于维护
// fs.readFile('E:\\gitCode\\Node_JS_Study\\file\\1.txt','utf8',function (err,dataStr){
//     if(err){
//         return console.log('读取失败' + err.message)
//     }
//     console.log('读取文件成功' + dataStr)
// })
// ****************************************************************************************
//__dirname 表示当前文件所处的目录
console.log(__dirname)  // E:\gitCode\Node_JS_Study
fs.readFile(__dirname + '/file/1.txt','utf8',function (err,dataStr){
    if(err){
        return console.log('读取失败' + err.message)
    }
    console.log('读取文件成功' + dataStr)
})

path 路径模块

获得path路径模块对象

const path = require('path)

path.join

const path = require('path')
const dir = '/usr/local';
const file = 'index.html';

const fullPath = path.join(dir, file);

console.log(fullPath) //\usr\local\index.html
//注意 ../ 会抵消前面的路径
const pathStr = path.join('/a','/b/c','../','./d','e')
console.log(pathStr)    // \a\b\d\e
//path.join会在不同操作系统中自动替换分隔符,比如windows替换为‘\’,linux替换为'/'

path.basename()

//获取扩展名的文件名
const path = require('path')
const fpath = '/a/b/c/index.html'

// const fullName = path.basename(fpath)
// console.log(fullName)

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



path.extname()

// 获得扩展名部分
const path = require('path')
const fpath = '/a/b/c/index.html'

const fext = path.extname(fpath)
console.log(fext) // .html

综合案例——(拆分HTML文件)

可以将html文件拆分为html、js、css文件

// 1.1 导入 fs 模块
const fs = require('fs')
// 1.2 导入 path 模块
const path = require('path')

// 1.3 定义正则表达式,分别匹配 <style></style> 和 <script></script> 标签
const regStyle = /<style>[\s\S]*?<\/style>/
const regScript = /<script>[\s\S]*<\/script>/

// 2.1 调用 fs.readFile() 方法读取文件
fs.readFile(path.join(__dirname, './clock/index.html'), 'utf8', function(err, dataStr) {
  // 2.2 读取 HTML 文件失败
  if (err) return console.log('读取HTML文件失败!' + err.message)
  // 2.3 读取文件成功后,调用对应的三个方法,分别拆解出 css, js, html 文件
  resolveCSS(dataStr)
  resolveJS(dataStr)
  resolveHTML(dataStr)
})

// 3.1 定义处理 css 样式的方法
function resolveCSS(htmlStr) {
  // 3.2 使用正则提取需要的内容
  const regStyle = /<style>[\s\S]*<\/style>/
  const r1 = regStyle.exec(htmlStr)
  // 3.3 将提取出来的样式字符串,进行字符串的 replace 替换操作
  const newCSS = r1[0].replace('<style>', '').replace('</style>', '')
  // // 3.4 调用 fs.writeFile() 方法,将提取的样式,写入到 clock 目录中 index.css 的文件里面
  fs.writeFile(path.join(__dirname, './clock/index.css'), newCSS, function(err) {
    if (err) return console.log('写入 CSS 样式失败!' + err.message)
    console.log('写入样式文件成功!')
  })
}

// 4.1 定义处理 js 脚本的方法
function resolveJS(htmlStr) {
  // 4.2 通过正则,提取对应的 <script></script> 标签内容
  const r2 = regScript.exec(htmlStr)
  // 4.3 将提取出来的内容,做进一步的处理
  const newJS = r2[0].replace('<script>', '').replace('</script>', '')
  // 4.4 将处理的结果,写入到 clock 目录中的 index.js 文件里面
  fs.writeFile(path.join(__dirname, './clock/index.js'), newJS, function(err) {
    if (err) return console.log('写入 JavaScript 脚本失败!' + err.message)
    console.log('写入 JS 脚本成功!')
  })
}

// 5.1 定义处理 HTML 结构的方法
function resolveHTML(htmlStr) {
  // 5.2 将字符串调用 replace 方法,把内嵌的 style 和 script 标签,替换为外联的 link 和 script 标签
  const newHTML = htmlStr.replace(regStyle, '<link rel="stylesheet" href="./index.css" />').replace(regScript, '<script src="./index.js"></script>')
  // 5.3 写入 index.html 这个文件
  fs.writeFile(path.join(__dirname, './clock/index.html'), newHTML, function(err) {
    if (err) return console.log('写入 HTML 文件失败!' + err.message)
    console.log('写入 HTML 页面成功!')
  })
}

http 模块

服务器和电脑的区别就在于——电脑中有没有安装web服务器软件

网络节点中,负责消费资源的电脑,叫做客户端;负责对外提供网络资源的电脑,叫做服务器

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

如果要希望使用 http 模块创建 Web 服务器,则需要先导入它:

获取http模块对象

const http = require('http')

创建最基本的web服务器

//1. 导入 http 模块
const http = require('http')
//2. 创建 web 服务器实例
const server = http.createServer()
//3. 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request',function (req,res){
    console.log('Someone visit our web server')
})
//4. 启动服务器
server.listen(80,function(){
    console.log('server running at http://127.0.0.1:80')
})
req请求对象
const http = require('http')
const server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request',req => {
// 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)
})
server.listen(80,() => {
    console.log('server running at http://127.0.0.1')
})
res响应对象
const http = require('http')
const server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
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)
})
server.listen(80,() => {
    console.log('server running at http://127.0.0.1')
})
解决中文乱码问题
const http = require('http')

const server = http.createServer()

server.on('request',(req,res)=>{
    //定义一个字符串,包含中文的内容
    const str = '您请求的 URL地址是 ' + req.url + '请求的method类型为' + req.method
    // 为了防止中文乱码的问题,设置响应头
    res.setHeader('Content-Type','text/html; charset=utf-8')
    // res.end() 将内容响应给客户端
    res.end(str)
})

server.listen(80,()=> {
    console.log('server running at http://127.0.0.1')
})
根据不同的url响应不同的html内容
const http = require('http')

const server = http.createServer()

server.on('request',(req,res)=>{
// 获取请求的url地址
    const url = req.url
// 设置默认的内容为 404 Not found
    let content = '<h1>404 Not Found!</h1>'
    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(80,()=> {
    console.log('server running at http://127.0.0.1')
})

案例-实现clock时钟的web服务器

  1. 核心思路

把文件的实际存放路径,作为每个资源的请求url地址

服务器充当的角色,就是一个字符串的搬运工

服务器就当一个读文件的功能

const fs = require('fs');
const path = require('path')
const http = require('http')

const server = http.createServer()

server.on('request',(req,res)=>{
    // 获取到客户端的url地址
    // /clock/index.html
    // /clock/index.css
    // /clock/index.js
    const url = req.url
    // 把请求的url地址,映射为本地文件的存放路径
    let fpath = ''
    if(url === '/'){
        fpath = path.join(__dirname,'./clock/index.html');
    }else{
        fpath = path.join(__dirname,'/clock',url)
    }
    // 根据“映射”过来的文件路径读取文件的内容
    fs.readFile(fpath,'utf-8',(err,dataStr) => {
        //读取失败,向客户端响应固定的“错误消息”
        if(err) return res.end('404 Not Found')
        //如果没有失败,则发送客户端
        res.end(dataStr)
    })

})

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

模块化

模块化的基本概念

比如小霸王游戏机的游戏机和游戏卡,分模块

比如vue中的组件

编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块

例如

  • 使用什么样的语法格式来引用模块
  • 在模块中使用什么样的语法格式向外暴露成员

Node.js中模块的分类

内置模块

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

自定义模块

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

第三方模块

由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块

加载模块案例

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

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

// 加载第三方模块(关于第三方模块的下载和使用,会在后面的课程中进行专门的讲解)
const moment = require('moment')

使用require()方法加载其他模块时,会执行被加载模块中的代码

注意:在使用 require 加载用户自定义模块期间,可以省略.js的后缀名

// 加载用户的自定义模块
const custom = require('./custom')

模块作用域

向外共享模块作用域成员
  1. module对象
console.log(module)

Module {
id: ‘.’,
path: ‘E:\gitCode\Study_Node.js\day02’,
exports: {},
filename: ‘E:\gitCode\Study_Node.js\day02\10.演示module对象.js’,
loaded: false,
children: [],
paths: [
‘E:\gitCode\Study_Node.js\day02\node_modules’,
‘E:\gitCode\Study_Node.js\node_modules’,
‘E:\gitCode\node_modules’,
‘E:\node_modules’
]
}

  1. moudle.exports对象

在自定义模块中,可以使用module.exports对象,将模块的成员共享出去,供外界使用

外界用require()方法导入自定义模块时,得到的就是module.exports所指的对象

共享成员时的注意点

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

exports == moudle.exports

由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象,默认情况下,exports 和 module.exports 指向同一个对象,最终共享的结果,还是以 moudle.exports 指向的对象为准

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGQfPazb-1680527715183)(C:\Users\guigui\AppData\Local\Temp\1680351528738.png)]

export和module.exports的使用误区

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

最终我们得到的依然是module.exports,但是module.exports == exports,所以,exports也指向也新的对象

模块化规范(commonJS)

commonJS规定

  1. 每个模块内部,module变量代表当前模块
  2. module变量是一个对象,他的exports属性(即module.exports)是对外的接口
  3. 加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载模块。

npm与包

概念

什么是包

Node.js中的第三方模块又叫做包

别人开发出来的东西,就像python中的包

包是基于内置模块封装出来的,提供了更高级,更方便的API,极大地提高了开发效率

包和内置模块之间的关系,类似于jQuery和浏览器内置API之间的关系

从哪里下载包

全球最大的包共享平台

npm (npmjs.com)

如何下载包

npm,inc提供了一个包管理工具,这个包管理工具的名字叫做Node Package Manager(简称 npm 包管理工具),这个包管理工具随着Node.js的安装包一起被安装到了用户的电脑上。

查看包管理工具版本号:

C:\Users\guigui>npm -v
9.6.1

npm 初体验

格式化时间的传统做法

15.dateFormat.js

//1.定义格式化时间的方法
function dateFormat(dtStr){
    const dt = new Date(dtStr)

    const y = dt.getFullYear()
    const m = padZero(dt.getMonth() + 1);
    const d = padZero(dt.getDate());

    const hh = padZero(dt.getHours());
    const mm = padZero(dt.getMinutes());
    const ss = padZero(dt.getSeconds());

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

//定义补零的函数
function padZero(n){
    return n > 9 ? n : '0' + n
}

module.exports = {
    dateFormat
}
// 导入自定义的格式化时间的模块
const TIME = require('./15.dateFormat');

// 调用方法,进行时间的格式化
const dt = new Date();
const newDT = TIME.dateFormat(dt);
console.log(newDT)
格式化时间的高级做法(包)

使用npm包管理工具,在项目中安装格式化时间的包 moment

使用require()导入格式化时间的包

参考moment的官方API文档对时间进行格式化

安装包

npm install moment
// 导入 moment 包
const moment = require('moment')

// 参考 moment 官方 API文档,调用对应的方法,对时间进行格式化
// 通知 moment()方法得到当前的时间
// 针对当前的时间,调用format()方法,按照指定的格式进行时间的格式化
const dt = moment().format('YYYY-MM-DD HH:mm:ss')

console.log(dt) //输出2023-04-01 21:44:21
node_modules和package-lock.json

node_modules

node_modules用来存放所有已安装到项目中的包。require()导入第三方包时,就是从这个目录中查找并加载包

注意:

node_modules包含了项目中最大的体积容量,所以我们在上传git时,git会默认ignore此包

package-lock.json

用来记录node_modules目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等

安装指定版本的包

默认情况下,使用npm install命令安装包的时候,会自动安装最新版本的包。如果需要安装指定版本的包,可以在包名之后,通过@符号指定具体的版本,例如

npm install moment@2.22.2

第一位数字:大版本

第二位数字:功能版本

第三位数字:Bug修复版本

package.json

运行npm install 命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中

npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件:

npm init -y
dependencies节点

专门用来记录您使用npm install命令安装了哪些包

一次性安装所有包

当我们使用git拉取项目代码时,我们只有package.json文件,而没有node_modules文件,也就是丢失了安装包内容,只保留了安装包名称,所以我们就需要用package.json文件来安装所需的包

npm install
卸载包

例如,卸载moment包

npm uninstall moment
devDependencies节点安装

如果某些包只在项目开发阶段用到,上线后不会用到,那建议把这些包记录到devDependencies节点中

简写形式

npm i 包名 -D

完整语法

npm install 包名 --save-dev
解决下包慢的问题(镜像)

配置淘宝镜像

npm config set registry=https://registry.npm.taobao.org/
nrm镜像源管理
# 通过 npm 包管理器,将nrm 安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
nrm ls
# 将下包的镜像源切换为 taobao 镜像
nrm use taobao
开发依赖包,核心依赖包

devDependencies

dependencies

全局包

只有工具性质的包,才有全局安装的必要性,因为他们提供了好用的终端命令

安装全局包

npm install 包名 -g

卸载全局包

npm uninstall 包名 -g

查看全局包位置

npm root -g
规范的包结构

一个规范的包,它的组成结构,必须符合以下3点要求

  1. 包必须以单独的目录而存在
  2. 包的顶级目录必须包含package.json这个包管理配置文件
  3. package.json中必须包含name,version,main这三个属性,分别代表包的名字,版本号,包的入口
开发属于自己的包
初始化包的基本结构
  1. 新建itheima-tools文件夹,作为包的根目录
  2. 在itheima-tools文件夹中,新建如下三个文件:
    • package.json(包管理配置文件)
    • index.js(包的入口文件)
    • README.md(包的说明文档)
      请添加图片描述
在index.js中定义转义HTML的方法
function htmlEscape(htmlstr){
    return htmlstr.replace(/<|>|"|&/g,(match) =>{
        switch (match){
            case '<':
                return '&lt;'
            case '>':
                return '&gt;'
            case '"':
                return '&quot;'
            case '&':
                return '&amp;'
        }
    })
}
在index.js中定义还原html字符串的函数
function htmlUnEscape(str){
    return str.replace(/&lt;|&gt;|&quot;|&amp;/g,(match)=>{
        switch (match){
            case '&lt;':
                return '<'
            case '&gt;':
                return '<'
            case '&quot;':
                return '"'
            case '&amp':
                return '&'
        }
    })
}
将不同的功能进行模块化拆分
//定义格式化时间函数
function dateFormat(dateStr){
    const dt = new Date(dateStr)

    const y = dt.getFullYear()
    const m = padZero(dt.getMonth() + 1)
    const d = padZero(dt.getDate())

    const hh = padZero(dt.getHours())
    const mm = padZero(dt.getMinutes())
    const ss = padZero(dt.getDate())

    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
//定义一个补零的函数
function padZero(n){
    return n > 9 ? n : '0' + n
}

module.exports = {
    dateFormat
}
// 定义转义html字符的函数
function htmlEscape(htmlstr){
    return htmlstr.replace(/<|>|"|&/g,(match) =>{
        switch (match){
            case '<':
                return '&lt;'
            case '>':
                return '&gt;'
            case '"':
                return '&quot;'
            case '&':
                return '&amp;'
        }
    })
}

// 定义还原html字符串的函数
function htmlUnEscape(str){
    return str.replace(/&lt;|&gt;|&quot;|&amp;/g,(match)=>{
        switch (match){
            case '&lt;':
                return '<'
            case '&gt;':
                return '<'
            case '&quot;':
                return '"'
            case '&amp':
                return '&'
        }
    })
}

module.exports = {
    htmlEscape,
    htmlUnEscape
}
// 这是包的入口文件
const date = require('./src/dateFormat')
const escape = require('./src/htmlEscape')

module.exports = {
    // es6语法、展开运算符
    ...date,
    ...escape
}
编写包的说明文档
## 安装
```
npm install itheima-tools
```

## 导入
```js
const itheima = require('itheima-tools')
```

## 格式化时间
```js
//调用dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date());
// 结果 2023-04-02 21:17:02
console.log(dtStr)
```

## 转义 html 中的特殊字符
```js
// 待转换的html字符串
const htmlStr = '<h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>'
// 调用htmlEscape方法进行转换
const str = itheima.htmlEscape(htmlStr);
// 转换的结果 &lt;h1 title=&quot;abc&quot;&gt;这是h1标签&lt;span&gt;123&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;

console.log(str)
```

## 还原 html 中的特殊字符

```js
// 待还原的html字符串
const str2 = itheima.htmlUnEscape(str);
// 输出结果 <h1 title="abc">这是h1标签<span>123undefinednbsp;</span></h1>
console.log(str2)
```

# 开源协议
ISC
发布包到官网

在官网注册

登录

注意:在使用这个命令之前,需要把下包的服务器的地址切换为npm的官方服务器。

npm login

# 下面就是一个错误案例,登录到淘宝的服务器了,所以需要使用nrm来切换服务器
PS E:\gitCode\Study_Node.js> npm login 
npm notice Log in on https://registry.npm.taobao.org/
Username:

发布

注意事项

  1. 需要转到包的根目录
  2. 包名不能在官网重复
  3. 包名与package.json中的name名字对应
npm publish
删除已发布的包
npm unpublish 包名 --force 
  1. npm unpublish 命令只能删除 72 小时以内发布的包
  2. npm unpublish 删除的包,在 24 小时内不允许重复发布
  3. 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包

模块的加载机制

模块在第一次加载后会被缓存。这也意味着多次调用require()不会导致模块的代码被执行多次

内置模块加载机制

内置模块加载优先级最高

自定义模块的加载机制

需要使用./或…/开头,不然会被node.js识别为内置模块,从而报错

// 正确
require('./03.自定义模块')

// 错误
require('03.自定义模块')

在使用require()导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:

  1. 按照确切的文件名进行加载
  2. 补全.js扩展名进行加载
  3. 补全.json扩展名进行加载
  4. 补全.Node扩展名进行加载加载失败,终端报错
  5. 加载失败,终端报错

express

初识Express

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

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

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

中文官网

http://www.expressjs.com.cn/

安装

npm i express@4.17.1

基本应用案例,web服务器

//1.导入express
const express = require('express');
//2.创建web服务器
const app = express();
//3.启动web服务器,监听80,并启用回调
app.listen(80,() =>{
    console.log('express server running at http://127.0.0.1:80')
})

监听与响应

//1.导入express
const express = require('express');
//2.创建web服务器
const app = express();

//4.监听客户端的get 和 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('请求成功')
})
//3.启动web服务器,监听80,并启用回调
app.listen(80,() =>{
    console.log('express server running at http://127.0.0.1:80')
})
监听GET请求

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

监听POST请求

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

把内容响应给客户端

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

获取URL中携带的查询参数

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

//1.导入express
const express = require('express');
//2.创建web服务器
const app = express();

app.get('/',(req,res) =>{
    // 通过 req.query 可以获取到客户端发送过来的查询参数
    // 注意:默认情况下,req.query 是一个空对象
    console.log(req.query)
    res.send(req.query)
})
//3.启动web服务器,监听80,并启用回调
app.listen(80,() =>{
    console.log('express server running at http://127.0.0.1:80')
})

postMan测试

http://127.0.0.1/?user=123&password=123

返回

{

​ “user”: “123”,

​ “password”: “123”

}

获取URL中的动态参数

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

//1.导入express
const express = require('express');
//2.创建web服务器
const app = express();

// 这里的:id是一个动态的参数
app.get('/user/:id/:name',(req,res) =>{
    // req.params是动态匹配到的 URL 参数,默认也是一个空对象
    console.log(req.params)
    res.send(req.params)
})
//3.启动web服务器,监听80,并启用回调
app.listen(80,() =>{
    console.log('express server running at http://127.0.0.1:80')
})

postMan测试

http://127.0.0.1/user/1/taotao

测试结果

{

​ “id”: “1”,

​ “name”: “taotao”

}

Express 路由

Express 中间件

使用 Express 写接口

编写get接口

// 导入express模块
const express = require('express')
// 创建服务器实例对象
const app = express()
//导入路由模块
const router = require('./16.apiRouter')
//把路由模块,注册到app上
app.use('/api',router)

app.listen(80, () => {
    console.log('express server running at http://127.0.0.1')
})
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

postman测试

http://localhost/api/get?1=1

请求头带参数

请求响应

{

​ “status”: 0,

​ “msg”: “GET请求成功!”,

​ “data”: {

​ “1”: “1”

​ }

}

编写post接口

// 导入express模块
const express = require('express')
// 创建服务器实例对象
const app = express()
//配置解析表单的中间件
app.use(express.urlencoded({extended:false}))

//导入路由模块
const router = require('./16.apiRouter')
//把路由模块,注册到app上
app.use('/api',router)

app.listen(80, () => {
    console.log('express server running at http://127.0.0.1')
})
// 定义Post接口
router.post('/post',(req,res)=>{
    //通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body
    //调用 res.send()方法,向客户端响应处理的结果
    res.send({
        status:0,           //状态,0 表示成功,1 表示失败
        msg:'Post请求成功!',  //状态描述
        data:body          //需要响应给客户端的具体数据
    })
})

postman发送数据为x-www.form-urlencoded

响应结果为

{

​ “status”: 0,

​ “msg”: “Post请求成功!”,

​ “data”: {

​ “1”: “2”,

​ “name”: “12”

​ }

}

接口的跨域问题

启动上面的案例服务器

前端写ajax请求

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/jquery/3.6.4/jquery.min.js"></script>
</head>
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<script>
    $(function () {
        //测试GET接口
        $('#btnGET').on('click', function () {
            $.ajax({
                type: 'GET',
                url: 'http://127.0.0.1/api/get',
                data: {name: 'zs', age: 20},
                success: function (res) {
                    console.log(res)
                }
            })
        })
        //测试POST接口
        $('#btnPOST').on('click', function () {
            $.ajax({
                type: 'POST',
                url: 'http://127.0.0.1/api/post',
                data: {bookname: '水浒传', author: '施耐庵'},
                success: function (res) {
                    console.log(res)
                }
            })
        })
    })

</script>
</body>
</html>
跨域报错

只要协议、域名、端口号任意不同,就会导致跨域问题

Access to XMLHttpRequest at ‘http://127.0.0.1/api/get?name=zs&age=20’ from origin ‘http://localhost:63342’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

解决跨域

刚才编写的 GET和 POST接口,存在一个很严重的问题: 不支持跨域请求解决接口跨域问题的方案主要有两种:

  1. CORS(主流的解决方案,推荐使用)
  2. JSONP (有缺陷的解决方案: 只支持 GET 请求)

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

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

  1. 运行 npm install cors 安装中间件
  2. 使用 const cors = require(‘cors’) 导入中间件
  3. 在路由之前调用 app.use(cors()) 配置中间件
// 导入express模块
const express = require('express')
// 创建服务器实例对象
const app = express()
//配置解析表单的中间件
app.use(express.urlencoded({extended:false}))
// 一定要在路由之前,配置cors这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())

//导入路由模块
const router = require('./16.apiRouter')
//把路由模块,注册到app上
app.use('/api',router)

app.listen(80, () => {
    console.log('express server running at http://127.0.0.1')
})
cors的注意事项
  1. CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口.
  2. CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2的浏览器,才能正常访问开启了 CORS 的服务端接口 (例如: IE10+、Chrome4+、FireFox3.5+)。

cos跨域资源共享

CORS 响应头部 - Access-Control-Allow-Origin

允许所有域名访问

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

只允许百度访问这个页面

res.setHeader('Access-Control-Allow-Origin','www.baidu.com')
CORS 响应头部 - Access-Control-Allow-Headers

默认情况下,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 三者之一

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

// 允许客户端额外向服务器发送 Content-Type 请求头和 X-Custom-Header 请求头
// 注意: 多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
CORS 响应头部 - Access-Control-Allow-Methods

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

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

// 只允许 POST、GET、DELETE、HEAD 请求方法 
res.setHeader('Access-Control-Allow-Methods','pST,GET,DELETE,HEAD')
// 允许所有的 HTTP 请求方法
res.setHeader('Access-Control-Allow-Methods','*')
简单请求

同时满足以下两大条件的请求,就属于简单请求:

请求方式:GET、POST、HEAD 三者之一

HTTP 头部信息不超过以下几种字段: 无自定义头部字段、Accept、Accept-Language、Content-Language、DPR.Downlink、Save-Data Viewport-Width、 Width 、ContentType (只有三个值application/x-www-formurlencoded、multipart/form-data、text/plain)

预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求:

  1. 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
  2. 请求头中包含自定义头部字段
  3. 向服务器发送了 application/json 格式的数据

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

简单请求和预检请求的区别
  1. 简单请求的特点:客户端与服务器之间只会发生一次请求
  2. 预检请求的特点: 客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求

这里我们使用delete来做案例

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/jquery/3.6.4/jquery.min.js"></script>
</head>
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<button id="btnDelete">DELETE</button>
<script>
    $(function () {
        //测试GET接口
        $('#btnGET').on('click', function () {
            $.ajax({
                type: 'GET',
                url: 'http://127.0.0.1/api/get',
                data: {name: 'zs', age: 20},
                success: function (res) {
                    console.log(res)
                }
            })
        })
        //测试POST接口
        $('#btnPOST').on('click', function () {
            $.ajax({
                type: 'POST',
                url: 'http://127.0.0.1/api/post',
                data: {bookname: '水浒传', author: '施耐庵'},
                success: function (res) {
                    console.log(res)
                }
            })
        })

        // 3.未删除按钮绑定点击事件处理函数
        $('#btnDelete').on('click',function (){
            $.ajax({
                type:'DELETE',
                url:'http://127.0.0.1/api/delete',
                success:function(res){
                    console.log(res)
                }
            })
        })
    })

</script>
</body>
</html>
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,           //状态,0 表示成功,1 表示失败
        msg:'Post请求成功!',  //状态描述
        data:body          //需要响应给客户端的具体数据
    })
})

// 定义Delete接口
router.delete('/delete',(req,res)=>{
    //通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body
    //调用 res.send()方法,向客户端响应处理的结果
    res.send({
        status:0,           //状态,0 表示成功,1 表示失败
        msg:'Delete请求成功!',  //状态描述
    })
})
module.exports = router
// 导入express模块
const express = require('express')
// 创建服务器实例对象
const app = express()
//配置解析表单的中间件
app.use(express.urlencoded({extended:false}))
// 一定要在路由之前,配置cors这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())

//导入路由模块
const router = require('./16.apiRouter')
//把路由模块,注册到app上
app.use('/api',router)

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

运行服务器

测试delete请求后,network栏应该弹出两个请求,分别是option和delete请求

JSONP接口

回顾JSONP 的概念与特点

概念:浏览器端通过

特点:

  1. JSONP不属于真正的Ajax 请求,因为它没有使用XMLHttpRequest 这个对象
  2. JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求L
注意事项

创建JSONP 接口的注意事项

如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明SONP 的接口。否则JSONP 接口会被处理成开启了 CORS 的接口。示例代码如下:

实现JSONP接口的步骤
  1. 获取客户端发送过来的回调函数的名字
  2. 得到要通过JSONP 形式发送给客户端的数据
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串
  4. 把上一步拼接得到的字符串,响应给客户端的
// 导入express模块
const express = require('express')
// 创建服务器实例对象
const app = express()
//配置解析表单的中间件
app.use(express.urlencoded({extended:false}))
// 必须在配置cors中间件之前,配置JSONP的接口
app.get('/api/jsonp',(req,res)=>{
    //TODO:定义JSONP接口具体的实现过程

    // 1,获取客户端发送过来的回调函数的名字
    const funcName = req.query.callback
    // 2,得到要通过 JSONP 形式发送给客户端的数据
    const data = { name: 'zs' , age: 22 }
    // 3,根据前两步得到的数据,拼接出一个函数调用的字符串
    const scriptStr ="${funcName}($(JSON.stringify(data)})"
    // 4.把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行
    res .send(scriptStr)
})
// 一定要在路由之前,配置cors这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())

//导入路由模块
const router = require('./16.apiRouter')
//把路由模块,注册到app上
app.use('/api',router)

app.listen(80, () => {
    console.log('express server running at http://127.0.0.1')
})
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/jquery/3.6.4/jquery.min.js"></script>
</head>
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<button id="btnDelete">DELETE</button>
<button id="btnJSONP">JSONP</button>
<script>
    $(function () {
        //测试GET接口
        $('#btnGET').on('click', function () {
            $.ajax({
                type: 'GET',
                url: 'http://127.0.0.1/api/get',
                data: {name: 'zs', age: 20},
                success: function (res) {
                    console.log(res)
                }
            })
        })
        //测试POST接口
        $('#btnPOST').on('click', function () {
            $.ajax({
                type: 'POST',
                url: 'http://127.0.0.1/api/post',
                data: {bookname: '水浒传', author: '施耐庵'},
                success: function (res) {
                    console.log(res)
                }
            })
        })

        // 3.未删除按钮绑定点击事件处理函数
        $('#btnDelete').on('click',function (){
            $.ajax({
                type:'DELETE',
                url:'http://127.0.0.1/api/delete',
                success:function(res){
                    console.log(res)
                }
            })
        })
        // 4.为jsonp绑定点击事件
        $('#btnJSONP').on('click',function (){
            $.ajax({
                type:'GET',
                url:'http://127.0.0.1/api/jsonp',
                dataType:'jsonp',
                success:function(res){
                    console.log(res)
                }
            })
        })
    })

</script>
</body>
</html>
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,           //状态,0 表示成功,1 表示失败
        msg:'Post请求成功!',  //状态描述
        data:body          //需要响应给客户端的具体数据
    })
})

// 定义Delete接口
router.delete('/delete',(req,res)=>{
    //通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body
    //调用 res.send()方法,向客户端响应处理的结果
    res.send({
        status:0,           //状态,0 表示成功,1 表示失败
        msg:'Delete请求成功!',  //状态描述
    })
})
module.exports = router

mysql数据库

在Express中操作mysql

在项目中操作数据库的步骤
  1. 安装操作MySQL数据库的第三方模块 (mysql)
  2. 通过 mysql 模块连接到 MySQL 数据库
  3. 通过 mysql 模块执行 SQL 语句

安装 mysql 模块

mysql 横块是托管于 npm 上的第三方横块。它提供了在 Nodejs 项目中连接和操作 MySQL 数据库的能力想要在项目中使用它,需要先运行如下命令,将 mysql 安装为项目的依赖包:

npm install mysql
配置mysql模块

在使用 mysql 模块操作 MySQL 数据库之前,必须先对 mysql 模块进行必要的配置,主要的配置步骤如下:

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

####测试mysql模块能否正常工作

调用 db.query0函数,指定要执行的 SQL 语句,通过回调函数拿到执行的结果:

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

// 测试mysql模块否正工作
db .query('select 1', (err, results) => {
//     mysql模块工作期间报错了
    if (err) return console.log(err.message)
        // 只要能打印出 [ RowDataPacket {1: 1 ] 的结果,就证明数据库连接正常
        console.log(results)
})

正常返回

[ RowDataPacket { ‘1’: 1 } ]

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

// 测试mysql模块否正工作
// db .query('select 1', (err, results) => {
// //     mysql模块工作期间报错了
//     if (err) return console.log(err.message)
//         // 只要能打印出 [ RowDataPacket {1: 1 ] 的结果,就证明数据库连接正常
//         console.log(results)
// })

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

返回结果

[
RowDataPacket { id: 1, email: ‘test@example.com’, password: ‘12345’ },
RowDataPacket {
id: 2,
email: ‘user@example.com’,
password: ‘$2y 10 10 10gtkodBkZfghR6I3UShGTRuV9S5fLptGTN60nUb83IUBr0rYTO49G.’
},
RowDataPacket {
id: 3,
email: ‘user@example.com’,
password: ‘$2y 10 10 10IhJKZZYsEjVxPAKyJjiO0.ZymiQCC1hxvmGEJFPj.UpLmc03ROjRu’
},
]

插入数据

向 user 表中新增数据,其中 email 为 Spider-Man,password 为 pcc321。示例代码如下:

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

// 测试mysql模块否正工作
// db .query('select 1', (err, results) => {
// //     mysql模块工作期间报错了
//     if (err) return console.log(err.message)
//         // 只要能打印出 [ RowDataPacket {1: 1 ] 的结果,就证明数据库连接正常
//         console.log(results)
// })

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

// 1.要插入到 user 表中的数据对象
const user = {email: 'Spider-Man', password: 'pcc321'}
// 2.待执行的 SQL 语句,其中英文的 ? 表示占位符
const sqlStr = 'INSERT INTO user (email, password) VALUES (?, ?)'
// 3.使用数组的形式,依次为 ? 占位符指定具体的值
db.query(sqlStr, [user.email, user.password], (err, results) => {
    if (err) return console.log(err.message)    //失败
    if(results.affectedRows === 1) {console.log('插入数据成功') }//成功
})

返回结果

插入数据成功!

插入数据的便捷方式

向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:

// 1.导入 mysql 模块
const mysql = require('mysql')
// 2.建立与 MySQL 据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',//数据库的 IP 地址
    user: 'root', //登录数据库的账号
    password: '12345',// 登录数据库的密码
    database: 'test' // 指定要操作哪个数据库'
})
//,要插入到 user 表中的数据对象
const user = {email: 'Spider-Man2', password: 'pcc43212'}
// 2.待执行的 SQL 语句,其中英文的 ? 表示占位符
const sqlstr = 'INSERT INTO user SET ?'
// 3.直接将数据对象当作占位符的值
db.query(sqlstr, user, (err, results) => {
    if (err) return console.log(err.message) // 失败
    if (results.affectedRows === 1) {
        console.log('插入数据成功')// 成功
    }
})

返回内容

插入数据成功

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

//1.要更新的数据对象
const user = {email: 'Spider-Man', password: '000'}
// 2.要执行的 SQL 语句
const sqlStr = 'UPDATE user SET password=? WHERE email=?'
// 3.调用 d.query() 执行 SQL 语的同时,使用数组依次为占位符指定具体的值
db.query(sqlStr, [user.password, user.email], (err, results) => {
    if (err) return console.log(err.message) //失败
    if (results.affectedRows === 1) {
        console.log('更新数据成功!')
    } // 成功
})

返回内容

更新数据成功!

更新数据快捷方式

更新表数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速更新表数据

// 2.建立与 MySQL 据库的连接
const db = mysql.createPool({
    host: '127.0.0.1',//数据库的 IP 地址
    user: 'root', //登录数据库的账号
    password: '12345',// 登录数据库的密码
    database: 'test' // 指定要操作哪个数据库'
})

//1.要更新的数据对象
const user = {email: 'Spider-Man', password: '0001'}
// 2.要执行的 SQL 语句
const sqlStr = 'UPDATE user SET ? WHERE email=?'
// 3.调用 d.query() 执行 SQL 语的同时,使用数组依次为占位符指定具体的值
db.query(sqlStr, [user, user.email], (err, results) => {
    if (err) return console.log(err.message) //失败
    if (results.affectedRows === 1) {
        console.log('更新数据成功!')
    } // 成功
})

返回内容

更新数据成功!

删除数据

在删除数据时,推荐根据 id 这样的唯一标识,来删除对应的数据。示例如下:

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

// 1.要执行的 SQL 语句
const sqlStr = 'DELETE FROM user WHERE email=?'
// 2.调用 db.query() 执行 SQL 语句的同时,为占位符指定具体的值
// 注意: 如果 SQL 语句中有多个占位符,则必须使用数组为每个占位符指定具体的值
// 如果 SQL 语句中只有一个占位符,则可以省略数组
db.query(sqlStr, 'Spider-Man', (err, results) => {
    if (err) return console.log(err.message) // 失败
    if (results.affectedRows === 1) {
        console.log('删除数据成功!')
    } // 成功
})

删除数据成功!

标记删除

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

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

当用户执行了删除的动作时,我们并没有执行 DELETE 语句把数据删除掉,而是执行了 UPDATE 语句,将这条数据对应的status字段标记为删除即可

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

// 标记删除:使用 UPDATE 语句替代 DELETE 语句;只更新数据的状态,并没有真正删除
db.query('UPDATE USERS SET status=1 WHERE id=?', 6, (err, results) => {
    if (err) return console.log(err.message) // 失败
    if (results.affectedRows === 1) {
        console.log('删除数据成功')
    }
})// 成功

这里我没有相关status字段,所以不再演示

前后端的身份认证

web开发模式

目前主流的 Web开发模式有两种

分别是:基于服务端染的传统Web开发模式

基于前后端分离的新型Web开发模式

服务端渲染的 Web 开发模式

服务端渲染的概念:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据。代码示例如下:

app.get(' /index.html', (req, res) => {
// 1.要渲染的数据
    const user = {name: 'zs', age: 20}
// 2.服务器端通过字符串的拼接,动态生成 HTML 内容
    const html = '<h1>姓名: $(user.name},年龄: $(user.age}</h1>'
//3,把生成好的页面内容响应给客户端。因此,客户端拿到的是带有真实数据的 HTML 页面
    res.send(htm1)
})

优点

前端耗时少。因为服务器端负责动态生成 HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。

有利于SEO。因为服务器端响应的是完整的 HTML页面内容,所以爬虫更容易爬取获得信息,更有利于 SEO.

缺点

占用服务器端资源。即服务器端完成 HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力

不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发

前后端分离的 Web 开发模式

前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式

前后端分离的优缺点

优点

开发体验好。前端专注于UI页面的开发,后端专注于api的开发,且前端有更多的选择性

用户体验好。Ajax 技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新

减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的

缺点

不利于 SEO。因为完整的 HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息(解决方案:利用 VueReact等前端框架的 SSR (server side render) 技术能够很好的解决 SEO 问题!)

选择开发模式

不谈业务场景而盲目选择使用何种开发模式都是耍流氓

比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的 SEO,则这时我们就需要使用服务器端渲染,。

而类似后台管理项目,交互性比较强,不需要考虑 SEO,那么就可以使用前后端分离的开发模式。

另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染+其他页面前后端分离的开发模式

什么是身份认证

身份认证 (Authentication)又称“身份验证”,“鉴权”,是指通过一定的手段,完成对用户身份的确认。

日常生活中的身份认证随处可见,例如: 高铁的验票乘车,手机的密码或指纹解锁,支付宝或微信的支付密码等。

在 Web 开发中,也涉及到用户身份的认证,例如: 各大网站的手机验证码登录、邮箱密码登录、二维码登录等

为什么需要身份认证

身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。

在互联网项目开发中,如何对用户的身份进行认证,是一个值得深入探讨的问题。例如,如何才能保证网站不会错误的将马云的存款数额”显示到“马化腾的账户”上。

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

对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案

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

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

Session 认证机制
HTTP协议的无状态性

了解HTTP协议的无状态性是进一步学习 Session 认证机制的必要前提HTTP协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态

如何突破 HTTP 无状态的限制

对于超市来说,为了方便收银员在进行结算时给 VIP 用户打折,超市可以为每个VIP 用户发放会员卡

注意: 现实生活中的会员卡身份认证方式,在 Web 开发中的专业术语叫做 Cookie。

什么是 Cookie

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

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

Cookie的几大特性

  1. 自动发送
  2. 域名独立
  3. 过期时限
  4. 4kb限制
Cookie在身份认证中的作用

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份

Cookie 不具有安全性

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

注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等

提高身份认证的安全性

为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用。

这种“会员卡 + 刷卡认证”的设计理念,就是Session 认证机制的精髓

在Express 中使用 Session 认证
安装express-session 中间件

在Express 项目中,只需要安装 express-session 中间件,即可在项目中使用 Session 认证

npm install express-session
配置 express-session 中间件

express-session 中间件安装成功后,需要通过 app.use0 来注册 session 中间件,示例代码如下:

const session = require('express-session')
app.use(
  session({
    secret: 'itheima',
    resave: false,
    saveUninitialized: true,
  })
)
向 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 这个属性
  req.session.user = req.body // 用户的信息
  req.session.islogin = true // 用户的登录状态

  res.send({ status: 0, msg: '登录成功' })
})
从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.username,
  })
})
清空 session

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

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

只会清空当前用户的session

JWT认证机制
了解 Session 认证的局限性

Session 认证机制需要配合 Cokie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。

注意:

当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用WT 认证机制

什么是JWT

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

JWT工作原理

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

请添加图片描述

JWT 的组成部分

WT 通常由三部分组成,分别是 Header (头部)、Payload (有效荷载)、Signature (签名)三者之间使用英文的“”分隔,格式如下:

Header .Payload.Signature

下面是JWT 字符串的示例:

eyJhbGcioiJIUzI1NiIsInR5cCZCI6MSwidXNlcm5hbwu101JhZG1pbiIsInBhc3N3b3JkIo1I1wibm11a25hbwuioilms6xlt7TIt7Q1LCJ1J1C2VyX3BoYy1611sImIhdcI6MTU300AZNIY4Mi1ZXhwIjoxNTc4MDcyNjgIKdZ3359KBL3XeuBxuI
JWT 的三个部分各自代表的含义

JWT 的三个组成部分,从前到后分别是 Header、Payload、Signature。其中:

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

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

JWT的使用方式

客户端收到服务器返回的 Jwt 之后,通常会将它储存在 localStorage 或 sessionStorage 中

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

Authorization: Bearer <token>
在 Express 中使用JWT
安装JWT 相关的包

运行如下命令,安装如下两个JWT 相关的包:

npm install jsonwebtoken express-jwt
  1. jsonwebtoken 用于生成 JWT 字符串
  2. express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
在 Express 中使用JWT

导入JWT 相关的包

使用 require()函数,分别导入JWT 相关的两个包:

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义 secret 密钥

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

  1. 当生成JWT 字符串的时候,需要使用 secret 密对用户的信息进行加密,最终得到加密好的 WT 字符串
  2. 当把JWT 字符串解析还原成JSON 对象的时候,需要使用 secret 钥进行解密
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'Yamaha No1 ^_^'
在登录成功后生成JWT 字符串

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

app.post('/api/login', function (req, res) {
	// ...省略登录失败情况下的代码
	// 用户登录成功之后,生成 JWT 字符串,通过 token 属性响应给客户端	
  const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
  })
})
将JWT字符串还原为JSON对象

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

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

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

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

// 登录接口
app.post('/api/login', function (req, res) {
  // ...省略录失败情况下的代码
// 用户登录成功之后,生成 JWT 字符串,通过 token 属性响应给客户端
  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 字符串
  })
})

// 这是一个有权限的 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, // 要发送给客户端的用户信息
  })
})

postman测试

http://127.0.0.1:8888/api/login

携带头urlencoded

username:admin

password:000000

返回结果

{

​ “status”: 200,

​ “message”: “登录成功!”,

​ “token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjgxODkxMjk5LCJleHAiOjE2ODE4OTEzMjl9.qgpLCIk_cFNIzasLZ9xaMmAb7k_l3enJoMf-nLNXSTc”

}

postman通过此token发送getinfo请求

http://127.0.0.1:8888/admin/getinfo

携带header

Authorization

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjgxODkxMjk5LCJleHAiOjE2ODE4OTEzMjl9.qgpLCIk_cFNIzasLZ9xaMmAb7k_l3enJoMf-nLNXSTc

返回结果

{

​ “status”: 200,

​ “message”: “获取用户信息成功!”,

​ “data”: {

​ “username”: “admin”,

​ “iat”: 1681891299,

​ “exp”: 1681891329

​ }

}

捕获解析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: '未知的错误',
  })
})

登录业务项目实现

开发文档

http://escook.cn:8088/#/

个别npm包注意版本问题

@hapi/joi

替换为joi

npm uninstall @hapi/joi

npm install joi@17.4.0
const joi = require('joi')

/**
 * string() 值必须是字符串
 * alphanum() 值只能是包含 a-zA-Z0-9 的字符串
 * min(length) 最小长度
 * max(length) 最大长度
 * required() 值是必填项,不能为 undefined
 * pattern(正则表达式) 值必须符合正则表达式的规则
 */

// 用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
// 密码的验证规则
const password = joi.string().pattern(/^[\S]{6,12}$/).required()

// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
    // 表示需要对 req.body 中的数据进行验证
    body: {
        username,
        password,
    },
}
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

const joi = require('joi')

// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())

// 配置解析表单数据中间件,注意:这个中间件,只能处理application/x-www-form-urlencoded格式的表单
app.use(express.urlencoded({ extended: false }))

// 一定要在路由之前,封装 res.cc 函数
// 响应数据的中间件
app.use(function (req, res, next) {

    // status 默认值为 1,表示失败的情况
    // err 的值,可能是一个错误对象,也可能是一个错误的描述字符串
    res.cc = function (err, status = 1) {
        res.send({
            // 状态
            status,
            // 状态描述,判断 err 是 错误对象 还是 字符串
            message: err instanceof Error ? err.message : err,
        })

    }
    next()
})

// 导入并使用用户路由模块
const userRouter = require('./router/user')
app.use('/api',userRouter)  //使头路径带“api”

// 错误中间件
app.use(function (err, req, res, next) {
    // 数据验证失败
    if (err instanceof joi.ValidationError) return res.cc(err)
    // 未知错误
    res.cc(err)
})


// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3007, function () {
    console.log('api server running at http://127.0.0.1:3007')
})

案例自我总结

更改密码

他这个业务顺序挺有意思,先登录获得token,然后根据token查id,然后根据id,输入oldPwd用密码解码的方式解码oldPwd密码是否等于这个id的密码,然后再进行newPwd的密码更改

他这个加密方式采用的是bcryptjs,所以每次验证都要解密

postman测试

要先使用login接口登录,然后用updatePwd接口,header带上Authorization,body带上id,oldPwd,newPwd

更新头像

postman测试时xxx文件avatar参数中value值格式案例:

data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=

发布文章form-data

form-data数据添加图片文件

请添加图片描述

前后端互通运行

后端

请添加图片描述

前端

建议使用vs打开“大事件”前端项目,使用live server直接运行index页面即可

请添加图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼鬼骑士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值