node.js 学习笔记
node.js服务端笔记文档学会写接口,path、包、模块化、fs、express、中间件、JWT、开发模式、cors。
gitee:代码接口笔记
1什么是node.js
nodejs 是基于ChromeV8,引擎的一个javaScript 运行环境。node.js 无法使用DOM和BOM的浏览器进行操作。fs模块
fs文件系统模块?
Fs 模块是node.js 官方提供的,用来操作文件的模块,他提供了一系列的方法和属性,用来满足用户对文件的操作需求。
1、fs.radeFile()方法用来读取指定文件的内容。
const fs = require('fs');// 参数1, 是读取文件夹的路径类型是String类型。// 参数2, 读取文件的采用的编码格式,一般为utf8格式。// 参数3, 是一个回调函数,// 回调函数第一参数是错误信息,如果null表示读取成功了,没有错误信息。一般判断null表示文件读取成功。// 回调函数第二参数是文件内容,如果读取失败返回 undefined,否则读取文件内容成功。fs.readFile('../File/文件.txt','utf8',(res, data) => {console.log(32455,res)if(res == null) {console.log('读取文件成功')console.log(324,data)}})。
2、fs.writFile() 方法用来向指定文件中写入内容的。
fs.writeFile 没有文件则进行创建文件,但是如果创建的路径中没有文件夹则会创建失败,有文件就进行追加内容,这个方法只能创建文件不能用来创建路径。
如果要在js中使用fs模块需要引入模块
const fs = require('fs')// 但是如果创建的路径中没有文件夹则会创建失败 [Error: ENOENT: no such file or directory, open '../File1/我爱你1.txt']// 如果重复调用 writeFile 方法新的内容会吧原来的内容给覆盖掉。// 参数一是读取文件的路径类型是String 必填选项。// 参数二是往文件里写入的内容。// 参数三是回调函数。// 回调函数参数第一个是返回的错误信息。默认返回null表示,文件写入成功没有错误信息。回调第二个参数是undefined。fs.writeFile('../File1/我爱你1.txt','我不爱你真的加的',(res) => {console.log(243,res)})
3、__dirname解决路径拼接错误
在使用fs模块处理路径的时候。 如果使用相对路径会发生路径拼接错误的一些问题。原因就是在你去执行代码的时候,会执行node命令时所处的路径,动态拼接出操作文件的完整路径。
__dirname 表示当前文件夹所处的路径直接从盘符开始算起。
// cd ../ 返回上一层文件再去运行会报错// Error: ENOENT: no such file or directory, open './1.txt'// 因为nodejsnote 文件里没有 ../File/文件.txt// 解决方案就是,不要使用相对路经进行拼接,直接给出完整的一个路径。直接从盘符开始算起,他就没有这个相对路径拼接的一个问题了。因为你执行的时候,他就相对于当前文件去找他对应的一个文件。const fs = require('fs');// __dirname 表示当前文件夹所处的路径直接从盘符开始算起。console.log(4343,__dirname)fs.readFile(`${__dirname}/1.txt`,'utf8',(res, data) => {console.log(32455,res)if(res == null) {console.log('读取文件成功')console.log(324,data)}})
path模块
path 模块是nodejs官方提供,用来处理路径的,他提供了一系列的方法和属性,用来满足用户对于路径的处理。path.join([…path]) 用来将多个路径完整的拼接成一个字符串。path.basename(path,[ext]) 可以获取路径中的最后一部分,方法用来从路径字符串中,将中文名解析出来path.extname() 用来获取路径中的扩展名部分
const fs = require('fs');const path = require('path')// 表示当前文件夹所处的路径直接从盘符开始算起。// `${__dirname}../fs/1.txt` => [Error: ENOENT: no such file or directory, open '/Users/sunzhihao/Desktop/nodejsnote/path../fs/1.txt']// `${__dirname}../fs/1.txt` => path.join(__dirname,'../fs/1.txt')console.log(4343,path.join(__dirname,'../fs/1.txt')) // /Users/sunzhihao/Desktop/nodejsnote/fs/1.txtfs.readFile(path.join(__dirname,'../fs/1.txt'),'utf8',(res, data) => {console.log(32455,res)if(res == null) {console.log('读取文件成功')console.log(324,data)}})// 看输出const fpath = '/a/b/c/index.html'console.log(433,path.basename(fpath)) // 433 index.htmlconsole.log(466,path.basename(fpath,'.html')) // 466 indexconsole.log(444,path.extname(fpath)) // 444 .html
http模块
http模块由nodejs提供,用来创建web服务的,通过http提供的http.createServer()方法,可以把一台电脑变为web服务器,对外部提供web资源。
1、什么是ip地址
ip地址指的是我们每台计算机的唯一地址,IP地址具有唯一性,类似于手机号,只有知道ip地址才能进行数据通信
IP地址格式通常用点分十进制。形成a.b.c.d 的性质,都是0-255的十进制。
互联网中的每台web服务器都有自己的IP的地址,可以输入 ping 命令,可以查到百度服务器的IP地址
域名和Ip地址能够唯一标记网络上的计算机,单IP地址是一长串数字,不直观不好记,于是发明了字符行的地址方案,叫域名 Domain Name 地址
IP地址和域名是一一对应的关系。
域名服务器就是提供IP地址和域名之间的转换服务的服务器
127.0.0.1 == locallhost
2、端口号
计算机的端口号,就好像是现实生活中的门牌号一样,通过门牌号,外卖小哥可以把东西准确的送到你的手中。
一台电脑闹可以运行非常多的服务,每个web服务都有一个唯一的端口号,客户端发送过来的网络请求,通过端口号,可以准确的交给对应的web服务进行处理。
每个端口号不能同时被多个web服务占用。
url中的80端口可以省略
3、创建http服务
怎么启动服务node index.js
node 加上文件名字就可以了
//引入我们的 http 模块const http = require('http')// 创建web 服务器const server = http.createServer()// 为服务实例绑定 request 事件监听客户的请求server.on('request', ( req, res) => {// const url = req.url// const method = req.method// 为了防止中文乱码设置响应头 鎮ㄧ殑url鏄�/璇锋眰鏂规硶鏄疓ETres.setHeader('Content-Type','text/html; charset=utf-8')// console.log('start server',url,method)// // 将内容返回给客户端// res.end(`您的url是${url}请求方法是${method}`)let content = '<h1>404 Not found</h1>'console.log('url',req.url)if(req.url == '/' || req.url == '/index.html') {content = '<h1>首页</h1>'}else if(req.url == '/about') {content = '<h1>about</h1>'}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')})
4、防止中文乱码设置响应头
// 为了防止中文乱码设置响应头 鎮ㄧ殑url鏄�/璇锋眰鏂规硶鏄疓ETserver.on('request', ( req, res) => {res.setHeader('Content-Type','text/html; charset=utf-8')})
什么是包
1、包的概念
Nodejs 中的第三方模块又叫包 相当于别名 (电脑等于计算机)包是是第三方开发出来,免费供别人使用由于node js 的内置模块仅提供了一些底层的API、导致基于内置模块开发慢,效率低包是基于内置模块封装出来的,提供了更高级的更方便的api,提高开发效率国外有一家IT公司叫做 npm inc 这家公司有个著名网站,/ 他是全球最大的包共享平台。
npm i moment 安装时间格式化的包const moment = require('moment') 导入需要的包moment().format('YYYY--MM-DD HH:mm:ss')
2、第一次安装包多了哪些文件
node_modules 文件用 来存放已经安装到项目中的包,require 导入第三方包,就是从这个目录开始查找并加载包的。
package-lock.json 配置文件用来记录 node_modules 目录下的每一个包的下载信息,列入包的名字,版本号,下载地址。
3、安装指定版本的包
通过@符号指定版本
npm install moment@2.0.0
4、包的版本号
是由 点分十进制定义的 2.0.0
第一位大版本
第二位 功能修复
第三位 bug 修复版本
只要版本号赠长了后面版本号就是 0
5、快速创建package.json
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理
npm init -y
注意:
①上述命令只能在英文的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。
②运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
npm规定,在项目根目录中,必须提供一个叫做package.json的包管理配置文件。用来记录与项目有关的一些配置
信息。例如:
·项目的名称、版本号、描述等
·项目中都用到了哪些包
·哪些包只在开发期间会用到
·那些包在开发和部署时都需要用到
6、npm install命令
可以运行npm install命令(或npmi)一次性安装所有的依赖包:
1/执行npm install命冷时,npm包管理工具会先读取package.json中的dependencies节点,
2/读取到记录的所有依赖包名称和版本号之后,npm包管理工具会把这些包一次性下载到项目中
3 npm install
卸载包 npm uninstall moment
注意:npm uninstall命令执行成功后,会把卸载的包,自动从package.json的dependencies中移除掉。
如果安装慢可以使用淘宝镜像 npm install --registry=https://registry.
当然也可以将淘宝镜像下载到本地
7、devDependencies节点
如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。
与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies节点中。
您可以使用如下的命令,将包记录到devDependencies节点中:
安装指定的包,并记录到devDependencies节点中
npm 包名 -D
注意:上述命冷是简写形式,等价于下面完整的写法:
4 npm install 包名 --save-dev
npm install moment 默认会安装到里面 dependencies 里面开发和项目上线都会用到
package.json dependencies 节点
专门介绍了安装了哪些包 如果你使用了moment包但是没有安装会报错 error : cannot find module moment
8、项目包
那些被安装到项目的node modules目录中的包,都是项目包。
项目包又分为两类,分别是:
开发依赖包(被记录到devDependencies节点中的包,只在开发期间会用到)
核心依赖包(被记录到dependencies节点中的包,在开发期间和项目上线之后都会用到)
1 npm i 包名 -D
开发依赖包(会被记录到devDependencies节点下)
2 npm i 包名
核心依赖包(会被记录到dependencies节点下】
9、全局包
在执行npm install命令时,如果提供了-g参数,则会把包安装为全局包。
全局包会被安装到C.\Users用户目录AppData\Roaming\npm\node modules目录下。
1 npm i包名-g
#全局安装指定的包
2 npm uninstall包名-g 卸载全局安装的包
注意:
①只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。
10、规范的包结构
在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合以下3点要求:
①
包必须以单独的目录而存在
②
包的顶级目录下要必须包含package.json这个包管理配置文件
③package.json中必须包含name,version,main这三个属性,分别代表包的名字、版本号、包的入口。
注意:以上3点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:
/zh-Hans/docs/package-json
模块化
模块化的概念
模块化指的是解决一个复杂的问题时候,从顶层向下逐层把系统分成若干模块的过程,对于整个系统来说,模块可以租合,可以分解,可以更换的单元。
在代码中模块化就是遵守固定的规则,把一个大文件拆成独立独立相互依赖的多个小模块。
好处
提高代码的可维护性 可复用性 按需加载
如果大家都遵循同样的模块化代码规范,可以降低大家之间的沟通成本极大方便了各个模块的相互调用。
模块化的规范指的就是 使用什么样的方式向外暴露成员 使用什么样的语法格式来引用模块。
模块化又分为
内置模块 内置模块是nodejs提供的例如 fs path http
自定义模块 用户创建的每个.js文件称为自定义模块
第三方模块 (由第三方开发出来的模块,并非官方提供的,也不是自己创建的,使用的话需下载)
加载模块
加载模块使用require()方法可以加载需要的内置模块,用户自定义模块,第三方模块
使用require() 方法加载其他模块时,会执行被加载模块重的代码
const fs = require(‘fs’) 加载内置模块
const fs = require(‘./index.js’) 加载用户自定义模块 使用require方法可以省略js后缀名
const _ = require(‘lodash’) 加载第三方模块
模块化作用域
和函数作用域类似,在自定义模块中定义的变量,方法,只能在当前模块中访问无法被外界访问,这种模块级别的访问限制叫做模块化作用域。
module 对象
每个.js 自定义模块都有一个module对象,它里面存储了和当前模块有关信息
node.js 遵循了commonjs模块规范化,commonjs 规定了模块的特性和各模块之间如何相互依赖
每个模块内部module变量代表当前模块
module变量是一个对象 它的 exports 属性 等于 module.exports = 是对外的接口
require 是用来加载模块的
console.log(module)// 打印Module {id: '.',path: '/Users/sunzhihao/Desktop/nodejsnote/模块化',exports: {},parent: null,filename: '/Users/sunzhihao/Desktop/nodejsnote/模块化/模块化的基本概念.js',loaded: false,children: [],paths: ['/Users/sunzhihao/Desktop/nodejsnote/模块化/node_modules','/Users/sunzhihao/Desktop/nodejsnote/node_modules','/Users/sunzhihao/Desktop/node_modules','/Users/sunzhihao/node_modules','/Users/node_modules','/node_modules']}
向外共享模块作用域中的成员
// module.exports 在自定义模块可以通过 module.exports 将模块内的成员给共享出去,共外界使用。// 在外界使用require方法导入自定义模块时,得到的就是module.export 所指的对象。// 文件1 代码 module.exports.aa = '我是数据'// 文件2 代码const a = require('./1')console.log(a) // { aa: '我是数据' }// 共享成员的注意点 导入的结果永远以 module.exports = 指向的对象为准// 文件1 代码 module.exports.aa = '我是数据'module.exports = {bb:333}// 文件2 代码const a = require('./1')console.log(a) // { aa: '我是数据' }
exports 对象
// 由于module.exports写的复杂,为了简化成员,node提供了exports对象 // module.exports 和 exports指向的同一个对象 结果永远以 module.exports = 指向的对象为准// 文件1 代码 exports.aa = '我是数据'// 文件2 代码const a = require('./1')console.log(a) // { aa: '我是数据' }
模块的加载机制
内置模块的加载机制
内置模块是由Node.js官方提供的模块,内置模块的加载优先级最高。
例如,require('fs)始终返回内置的fs模块,即使在node modules目录下有名字相同的包也叫做fs。
自定义模块的加载机制
使用require加载自定义模块时,必须指定以./或…/开头的路径标识符。在加载自定义模块时,如果没有指定./或…/
这样的路径标识符,则node会把它当作内置模块或第三方模块进行加载。
同时,在使用require导入自定义模块时,如果省略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:
①按照确切的文件名进行加载
②补全js扩展名进行加载
③补全json扩展名进行加载
④补全node扩展名进行加载
⑤加载失败,终端报错
第三方模块的加载机制
如果传递给require 的模块标识符不是一个内置模块,也没有以’./”或’…/'开头,则Node.js会从当前模块的父
目录开始,尝试从/node modules文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在’C.Users\itheima\project\foo.js’文件里调用了require('tools),则Node.js会按以下顺序查找:
①C:\Users\itheima\project\node modules\tools
②C:\Users\itheima\node modules\tools
③C:\Users\node modules\tools
4C:\node modules\tools
找不到报错
目录作为模块
当把目录作为模块标识符,传递给require 进行加载的时候,有三种加载方式:
①在被加载的目录下查找一个叫做package.json的文件,并寻找main属性,作为require加载的入口
②如果目录里没有package.json文件,或者main入口不存在或无法解析,则Node.js将会试图加载目录下的index.js文件。
③如果以上两步都失败了,则Node.js会在终端打印错误消息,报告模块的缺失:Error:Cannot find module ‘Xx’
{ "name": "pack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": {"test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": {"moment": "^2.29.4" }}main 设置aaa.js 找对应文件夹里的 /aaa.js 没有设置 main 默认找 index.js 注意 json 中不能使用单引号
Express 服务端框架
什么是express
官方给出的概念:Express是基于Node.js平台,快速、开放、极简的Web开发框架。
通俗的理解:Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。
Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法。
Express的中文官网:http:l
进一步理解Express
思考:不使用Express能否创建Web服务器?
答案:能,使用Node.js提供的原生http模块即可。
思考:既生瑜何生亮(有了http内置模块,为什么还有用Express)?
答案:http内置模块用起来很复杂,开发效率低;Express是基于内置的http模块进一步封装出来的,能够极大的提高开发效率。
思考:http内置模块与Express是什么关系?
答案:类似于浏览器中Neb API和jQuery的关系。后者是基于前者进一步封装出来的。
Express能做什么
对于前端程序员来说,最常见的两种服务器,分别是:
Web网站服务器:专门对外提供Web网页资源的服务器。
AP接口服务器:专门对外提供API接口的服务器。
使用Express,我们可以方便、快速的创建Web网站的服务器或API接口的服务器。
安装nodemon
在终端中,运行如下命令,即可将nodemon安装为全局可用的工具:
npm install -g nodemon
使用nodemon
当基于Node.js编写了一个网站应用的时候,传统的方式,是运行node app.js命令,来启动项目。这样做的坏处是:
代码被修改之后,需要手动重启项目。
现在,我们可以将node命令替换为nodemon命令,使用nodemon app.js来启动项目。这样做的好处是:代码
被修改之后,会被nodemon监听到,从而实现自动重启项目的效果。
node app.js
将上面的终端命令,替换为下面的终端命令,即可实现自动重启项目的效果
nodemon app.js
创建express定义get接口
创建文件夹
npm init -y
{"name": "demo","version": "1.0.0","main": "index.js","scripts": {"dev": "nodemon index.js"},"keywords": [],"author": "","license": "ISC","description": "","dependencies": {"body-parser": "^1.20.1","express": "^4.18.2","nodemon": "^2.0.20"}}
npm install
启动npm run dev
index.js 创建服务
const express = require('express')const app = express()// 前端问号拼接参数app.get('/user', (req, res) => {// 前端访问 http://localhost/user?id=232423&kk=9999// req.query 可以接收到前端传来的参数默认是一个空对象console.log(req.query) // { id: '232423', kk: '9999' }// res.send 向前端发送数据res.send({name: 'get', age: 20 })})//获取URL中的动态参数app.get('/banner/:id/:name', (req, res) => {// 前端访问 url http://localhost/banner/123/szh//通过req.params对象,可以访问到URL中,通过:匹配到的动态参数:console.log(req.params) // { id: '123', name: 'szh' }// res.send 向前端发送数据res.send({name: 'get', age: 20 })})app.listen(80, () => {// 监听80端口console.log('启动成功 http://localhost')})
创建express定义post接口
const express = require('express')const app = express()app.post('/list', (req, res) => {res.send({name: 'post', age: 20 })})app.listen(80, () => {// 监听80端口console.log('启动成功 http://localhost')})
创建express定义post接口获取参数undefined
使用内置中间件解决注意json属性名一定要加上双引号const express = require('express')const app = express()//注意:除了错误的中间件,其他的中间件,必须在路由之前进行配置app.use(express.json()) // 不配置req.body 拿不到json数据app.use(express.urlencoded({extended:false})) // 可以解析form-data的数据url-encode格式的数据app.post('/list', (req, res) => {// req.body 可以拿到前端传过来的请求体 json 格式的console.log(23234, req.body) // undefined// 如果默认不配置解析表单数据的中间件,则req.body 值为 undefined// 可以解析json但是无法解析url-encode格式的数据 form-data 格式的数据 不是那种对象包裹的数据// 使用中间件app.use(express.urlencoded({extended:false}))res.send({name: 'post', age: 20 })})app.listen(80, () => {console.log('启动成功 http://172.0.0.1')})
也可以使用第三方的中间件解决req.body 值为 undefined
npm i
body-parser
"body-parser": "^1.20.1",const bodyParser = require('body-parser')app.use(bodyParser.urlencoded({extended:false}))
托管静态资源
express.static()
express提供了一个非常好用的函数,叫做express.static通过它,我们可以非常方便地创建一个静态资源服务器,
例如,通过如下代码就可以将public目录下的图片、CSS文件、JavaScript文件对外开放访问了:
app.use(express.static('public'))
现在,你就可以访问public目录中的所有文件了:
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/login.js
注意:Express在指定的静态目录中查找文件,并对外提供资源的访问路径。
因此,存放静态文件的目录名不会出现在URL中。
托管多个静态资源目录
如果要托管多个静态资源目录,请多次调用express…static0函数:
访问静态资源文件时,express.static函数会根据目录的添加顺序查找所需的文件。
public 文件找不到就去找file文件
app.use(express.static(‘./public’))
app.use(express.static(‘./file’))
http://localhost/index1.html 可以访问
托管静态资源挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use(‘/public’,express.static(‘./public’))
现在,你就可以通过带有/public前缀地址来访问public目录中的文件了:
http://localhost:3000/public/images/kitten.jpg
http://localhost:3000/public/css/style.css
http://localhost:3000/public/js/app.js
http://localhost/public/index1.html
路由的概念
3.Express中的路由
在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数,格式如下:
1 app.METHOD(PATH,HANDLER)
使用router路由模块
模块化路由
为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤斁如下:
①创建路由模块对应的js文件
②调用express.Router()函数创建路由对象
③向路由对象上挂载具体的路由
④使用module.exports向外共享路由对象
⑤使用app.use()函数注册路由模块
router.js
const express = require('express')var router = express.Router() // 使用路由而不是 expressrouter.get('/getList/list',function (req,res) {res.send('get list success')})router.get('/getBanner/list',function (req,res) {res.send('get banner success')})module.exports = router // 通过module对象将router抛出去
Index.js
const express = require('express')const app = express()const getList = require('./router')app.use(getList) // app.use 用来注册路由// app.use('/api', getList) 如果想要有统一前缀第一个参数// 访问就要在路径前面加上 /qpi http://localhost/api/getBanner/listapp.listen(80, () => {console.log('启动成功 http://172.0.0.1')})
中间件
什么是中间件
中间件指的是中间处理业务部分的逻辑
Express中间件的调用流程
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
请求 > 中间件1 > 中间件2 > 处理响应 > 处理完毕响应 > 请求客户端
中间件的概念
Express的中间件,本质上就是一个function处理函数,Express中间件的格式如下:
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
app.post('/list', (req, res, next) => { res.send({name:'post',age:20})})
注意:中间件函数的形参列表中,必须包含next参数。而路由处理函数中只包含req和res。
中间件的作用
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添
加自定义的属性或方法,供下游的中间件或路由进行使用。
局部生效的中间件
const express = require('express')const app = express()const middleware = function (req, res, next) {console.log('我是局部中间件')// 处理逻辑next() // 调用表示可以执行下面这个接口}app.get('/user', middleware, (req, res) => {res.send({a: '局部中间件' })})app.listen(80, () => {console.log('启动成功 http://172.0.0.1')})
定义多个局部中间件
// 可以在路由中,通过如下两种等价的方式,使用多个局部中间件://以下两种写法是"完全等价"的,可根据自己的喜好,选择任意一种方式进行使用const mw1 = function (req, res, next) {console.log('我是局部中间件1')// 处理逻辑next() // 调用表示可以执行下面这个接口}const mw2 = function (req, res, next) {console.log('我是局部中间件2')// 处理逻辑next() // 调用表示可以执行下面这个接口}// 定义一个数组传入中间件函数app.get('/'mw1,mw2,(req,res)=>res.send('Home page.')}app.get('/',[mw1,mw2],(req,res)=>{res.send('Home page.')
定义全局中间
定义全局中间件中间件成功就会进行下一步
中间件相当于vue 的路由拦截
const express = require('express')const app = express()const mw = function (req, res, next) { console.log('第一个中间件') // next() 不使用next() 报错: textStatus: timeout连接网络时发生异常: http://localhost/api/getBanner/list // 通过req可以添加属性和方法 让下一个接口使用 let time = new Date().getTime() req.time = timenext()}app.use(mw) // 注册全局中间件
中间件传递数据
const express = require('express')const app = express()const mw = function (req, res, next) { let time = new Date().getTime() req.time = timenext()}// list 接口app.post('/list', (req, res) => {console.log('可以拿到中间件添加的属性', req.time) // 可以拿到中间件添加的属性 1675137876666res.send({name: 'post', age: 20 })})app.use(mw) // 注册全局中间件app.listen(80, () => {console.log('启动成功 http://172.0.0.1')})
定义多个全局中间件
可以使用app.use 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行
调用,示例代码如下:
const express = require('express')const app = express()// 可以定义第二个中间件 放到第一个中间件的下面即可app.use((req, res, next) => { console.log('第二个中间件') next()})
了解中间件的5个使用注意事项
①一定要在路由之前注册中间件
②客户端发送过来的请求,可以连续调用多个中间件进行处理
③执行完中间件的业务代码之后,不要忘记调用next0函数
④为了防止代码逻辑混乱,调用next0函数后不要再写额外的代码
⑤连续调用多个中间件时,多个中间件之间,共享req和res对象
中间件的分类
为了方便大家理解和记忆中间件的使用,Express官方把常见的中间件用法,分成了5大类,分别是:
①应用级别的中间件
②路由级别的中间件
③错误级别的中间件
④Express内置的中间件
⑤第三方的中间件
1.应用级别的中间件
通过app.use或app.get或app.post,绑定到app实例上的中间件,叫做应用级别的中间件.
2.路由级别的中间件
绑定到express.Router(实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不
过,应用级别中间件是绑定到app实例上,路由级别中间件绑定到router实例上.
3.错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(error,req,res,next)。
注意错误中间件必须放在所有的接口最下面执行,其他中间件可以放到最上面。
const express = require('express')const app = express()// 接口代码 报错会被错误级别的中间件 捕获 而不会让整个项目无法启动app.get('/error', (req, res) => {throw new Error('服务器错误')res.send({a: 24324 })})app.use(function (error, req, res, next) {console.log('error.message', error)res.send({error: error.message })// 服务器错误next()})app.listen(80, () => {console.log('启动成功 http://172.0.0.1')})
4.Express内置的中间件
自Express4.16.0版本开始,Express内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验:
①express…static快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性)
②express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
③express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
配置解析application./json格试数据的内置中间件
2
app.use(express.json())
配置解析application/x-www-form-urlencoded格式数据的内置中间件
4
app.use(express.urlencoded({extended:false })
5.第三方的中间件是别人编写的你来使用。
也可以使用第三方的中间件解决req.body 值为 undefined
npm i
body-parser
无法解析req.body可以替换第三方的中间件非Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。例如:在express(@4.16.0之前的版本中,经常使用body-parser这个第三方中间件,来解析请求体数据。使用步骤如下:①运行npm install body-parser安装中间件②使用require导入中间件③调用app.use0注册并使用中间件"body-parser": "^1.20.1",const bodyParser = require('body-parser')app.use(bodyParser.urlencoded({extended:false}))
http请求
http请求分为三部分:请求行,请求头, 请求体
请求行:
是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。
为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL。
是协议名称及版本号。
请求头
HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
与缓存相关的规则信息,均包含在header中
请求体:
报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数。
常见的请求头
Accept: 览器支持的 MIME 媒体类型, 比如 text/html,application/json,image/webp,/ 等(比如text/plain只支持文本的数据)
Accept-Encoding: 浏览器发给服务器,声明浏览器支持的编码类型,gzip, deflate
Accept-Language: 客户端接受的语言格式,比如 zh-CN
Connection: keep-alive , 开启HTTP持久连接
Host:服务器的域名
Origin:告诉服务器请求从哪里发起的,仅包括协议和域名 CORS跨域请求中可以看到response有对应的header,Access-Control-Allow-Origin
Referer:告诉服务器请求的原始资源的URI,其用于所有类型的请求,并且包括:协议+域名+查询参数; 很多抢购服务会用这个做限制,必须通过某个入来进来才有效
User-Agent: 服务器通过这个请求头判断用户的软件的应用类型、操作系统、软件开发商以及版本号、浏览器内核信息等; 风控系统、反作弊系统、反爬虫系统等基本会采集这类信息做参考
Cookie: 表示服务端给客户端传的http请求状态,也是多个key=value形式组合,比如登录后的令牌等
Content-Type: HTTP请求提交的内容类型,一般只有post提交时才需要设置,比如文件上传,表单提交等
响应头
报文头包含若干个属性 格式为“属性名:属性值”
常见的响应头
Allow: 服务器支持哪些请求方法
Content-Length: 响应体的字节长度
Content-Type: 响应体的MIME类型
Content-Encoding: 设置数据使用的编码类型
Date: 设置消息发送的日期和时间
Expires: 设置响应体的过期时间,一个GMT时间,表示该缓存的有效时间
cache-control: Expires的作用一致,都是指明当前资源的有效期, 控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据,优先级高于Expires,控制粒度更细,如max-age=240,即4分钟
Location:表示客户应当到哪里去获取资源,一般同时设置状态代码为3xx
Server: 服务器名称
Transfer-Encoding:chunked 表示输出的内容长度不能确定,静态网页一般没,基本出现在动态网页里面
Access-Control-Allow-Origin: 定哪些站点可以参与跨站资源共享
Content-type
用来指定不同格式的请求响应信息,俗称 MIME媒体类型
常见的取值
text/html :HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
application/json:JSON数据格式
application/pdf :pdf格式
application/octet-stream :二进制流数据,一般是文件下载
application/x-www-form-urlencoded:form表单默认的提交数据的格式,会编码成key=value格式
multipart/form-data: 表单中需要上传文件的文件格式类型
CORS跨域资源共享
接口的跨域问题
刚才编写的GET和POST接口,存在一个很严重的问题:不支持跨域请求。
只要协议 域名 端口有一个不一致就会发生跨域。
解决接口跨域问题的方案主要有两种:
①CORS(主流的解决方案,推荐使用)
②JSONP(有缺陷的解决方案:只支持GET请求)
使用cors中间件解决跨域问题
cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。
使用步骤分为如下3步:
①运行npm install cors安装中间件②使用const cors=require(cors)导入中间件③在路由之前调用app.use(cors())配置中间件const cors = require('cors') // cors解决跨域问题app.use(cors())
什么是CORS
CORS(Cross-Origin Resource Sharing)跨域资源共享由一系列HTTP响应头组成,这些HTTP响应头决定
浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,
就可以解除浏览器端的跨域访问限制。
CORS的注意事项
①CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。
②CORS在浏览器中有兼容性。只有支持XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服
务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
JSONP的概念与特点
概念:浏览器端通过
CORS响应头部
CORS响应头部Access–Control-Allow-Origin
// 5.CORS响应头部-Access-Control-Allow-Origin// 如果指定了Access--Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求,示例代码如下:res.setHeader('Access-Control-Allow-Origin','*')// 全局中间件app.use(function (req,res,next) {res.setHeader('Access-Control-Allow-Origin','*')res.setHeader('Access-Control-Allow-Headers','Content-Type,X-Custom-Header')next()})
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-Control–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-Contro-Alow-Methods
来指明实际请求所允许使用的HTTP方法。
示例代码如下:
只允许POST、GET、DELETE、HEAD清求方法res.setHeader('Access-Control-Allow-Methods','POST,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、
Content-Type(只有三个值application/x-www-form-urlencoded, multipart/form-data, text/plain)
预检请求
公只要符合以下任何一个条件的请求,都需要进行预检请求:
①请求方式为GET、POST、HEAD之外的请求Method类型
②请求头中包含自定义头部字段
③向服务器发送了application/json格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一
次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的情求,并目携带真实数据。
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
delete 走两遍请求
Request Method: OPTIONS
Request Method: DELETE
Jwt
安装JWT相关的包
运行如下命令,安装如下两个JWT相关的包:
npm install jsonwebtoken express-jwt
其中:
jsonwebtoken用于生成JWT字符串
express-jwt用于将JWT字符串解析还原成JSON对象
“jsonwebtoken”: “^8.5.1”,
“@escook/express-joi”: “^1.1.1”
什么是JWT
WT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
总结:用户的信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Tokn字符串的形式来认证用户的身份。
JWT的组成部分
JWT通常由三部分组成,分别是Header(头部)、Payload(有效荷载)、Signature(签名)。
JWT的三个部分各自代表的含义(token 的组成)
JWT的三个组成部分,从前到后分别是Header、Payload、Signature。
其中:
Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
·Header和Signature是安全性相关的部分,只是为了保证Token的安全性。
JWT的web使用方式
客户端收到服务器返回的JWT之后,通常会将它储存在localStorage或sessionStorage中。
此后,客户端每次与服务器通信,都要带上这个WT的字符串,从而进行身份认证。推荐的做法是把JWT放在HTTP
请求头的Authorization字段中,格式如下:
Authorization: Bearer + token
JWT服务端使用方式
const express = require('express')const app = express()// 安装并且导入JWT的两个包const jwt = require('jsonwebtoken')const expressJWTfn = require('express-jwt')// 定义secret密钥const secretKey = 'woshimima'// 解密// 将JWT字符串还原为JSON对象// 客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发// 送到服务器进行身份认证。// 此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成SON对象:// 1使用app.use()来注册冲间件// 2 expressJWT({secret:secretKey})就是用来解析Token的中间件// 3 unless({path:[/^\/api\/]})用来指定哪些接口不需要访问权限// 4 app.use(expressJWT({secret:secretKey }).unless({path:[/a\/api\//]})// 注意 只要使用expressJWT中间件就可以解析出来用户对象挂在到req.user身上// 使用 .unless({ path: [/^\/api\//] }) 指定哪些接口不需要进行 Token 的身份认证 app.use(expressJWT({secret: jwtSecretKey }).unless({path: [/^\/api\//] }))/*.在登录成功后生成JWT字符串调用jsonwebtoken包提供的sign方法,将用户的信息加密成JWT字符串,响应给客户端:参数1:用户的信息对象参数2:加密的秘钥参数3:配置对象,可以配置当前token的有效期 30s 1h*/// 注意:不要把密码加入到token加密中const tokenStr = jwt.sign({username: req.body.username }, secretKey, {expiresIn: '30s' })res.send({success: '登陆成功', code: 200, token: tokenStr })
开发模式
服务端渲染的Web开发模式
服务端渲染的概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不
需要使用Ajax这样的技术额外请求页面的数据。
服务端渲染的优缺点
优点:
①前端耗时少。因为服务器端负责动态生成HTML内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
②有利于SEO。因为服务器端响应的是完整的HTML页面内容,所以爬虫更容易爬取获得信息,更有利于SEO。
缺点:
①占用服务器端资源。即服务器端完成HTML页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
②不利于前后端分离,开发效率低。,使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于
项目高效开发。
前后端分离的Web开发模式
前后端分离的概念:前后端分离的开发模式,依赖于Ajax技术的广泛应用。简而言之,前后端分离的Wb开发模式,
就是后端只负责提供API接口,前端使用AjaX调用接口的开发模式。
4.前后端分离的优缺点
优点:
①开发体验好。前端专注于U川页面的开发,后端专注于pi的开发,且前端有更多的选择性。
②用户体验好。Ajx技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
③减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
①不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方
案:利用Vue、React等前端框架的SSR(server side render)技术能够很好的解决SEO问题!)
如何选择Web开发模式
不谈业务场景而盲目选择使用何种开发模式都是耍流氓。
比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染;
而类似后台管理项目,交互性比较强,不需要考虑SEO,那么就可以使用前后端分离的开发模式。
另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了
首屏服务器端渲染+其他页面前后端分离的开发模式。
身份认证
什么是身份认证
身份认证(Authentication)又称"身份验证”、"鉴权”,是指通过一定的手段,完成对用户身份的确认。
日常生活中的身份认证随处可见,例如:高铁的验票乘车,手机的密码或指纹解锁,支付宝或微信的支付密码等。
在Web开发中,也涉及到用户身份的认证,例如:各大网站的手机验证码登录、邮箱密码登录、二维码登录等。
为什么需要身份认证
身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户。例如,你去找快递员取快递,你要怎么证明这份快递是你的。
在互联网项目开发中,如何对用户的身份进行认证,是一个值得深入探讨的问题。例如,如何才能保证网站不会错误的将“马云的存款数额”显示到“马化腾的账户”上。
不同开发模式下的身份认证
对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案:
①服务端渲染推荐使用Session认证机制
②前后端分离推荐使用JWT认证机制
HTTP协议的无状态性
了解HTTP协议的无状态性是进一步学习Session认证机制的必要前提。
HTTP协议的无状态性,指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
如何突破HTTP无状态的限制
对于超市来说,为了方便收银员在进行结算时给VIP用户打折,超市可以为每个VP用户发放会员卡。
辨别用户的VIP身份
注意:现实生活中的会员卡身份认证方式,在Web开发中的专业术语叫做Cookie。
了解Session认证的局限性
Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以,当涉及到前端跨域请求后端接
口的时候,需要做很多额外的配置,才能实现跨域Session认证。
注意:
·当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。
·当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用WT认证机制。
什么是Cookie
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称(Name)、一个值(Value)和其它几个用
于控制Cookie有效期、安全性、使用范围的可选属性组成。
不同域名下的Cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
Cookiel的几大特性:
自动发送
域名独立
过期时限
k4b限制
Cookie在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动
将Cookie保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cook,通过请求头的形式发送给
服务器,服务器即可验明客户端的身份。
Cookie不具有安全性
由于Cookie是存储在浏览器中的,而且浏览器也提供了读写Cookie的AP叫,因此Cookie很容易被伪造,不具有安全
性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。
注意:干万不要使用Cookie存储重要且隐私的数据!比如用户的身份信息、密码等。
提高身份认证的安全性
为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的
会员卡,才能被正常使用。
这种“会员卡+刷卡认证”的设计理念,就是Session认证机制的精髓。
nodejs 怎么连接mysql数据库
const express = require('express') //引入express 模块const app = express() //创建实例const mysql = require('mysql')//引入mysql 模块// 创建数据库连接 填入数据库信息 //填自己数据库的信息!!!!!!!!!!!const conn = mysql.createConnection({user:'root',//用户名password:'admin123',//密码host:'localhost',//主机(默认都是local host)database:'node' //数据库名})// 测试连接conn.connect(err=>{console.log(err,'如果为null 就是连接成功');})// 开启服务器app.listen(3100,()=>{console.log('服务器在3000端口开启。。。。。');})//3.测试mysql模块能否正常工作// 调用db.query0函数,指定要执行的SQL语句,通过回调函数拿到执行的结果:conn.query('select 1',(err,results)=>{if (err)return console.log(88,err.message)//只要能打印出[RowDataPacket《·1':1}】的结果,就证明数据库连接正常console.log(45,results)})// 定义路由(说白了就是网址)app.get('/add',(req,res)=>{// 向数据库 student 表里增加 name age 数据let sqlStr = "INSERT INTO student ( name, age )VALUES('小刚',4345)"//执行mysql 语句conn.query(sqlStr,(err)=>{console.log(err,'如果为null,sql语句执行成功')})//成功后的页面显示res.send('插入成功')})// 查询信息app.get('/find',(req,res)=>{let sql = `SELECT * FROM student `conn.query(sql,(err,results)=>{//返回的查询信息为result 然后将其显示在页面上res.send(results)})})// 更换用户头像app.get('/update',(req,res)=>{// 根据id 更新student 表里的 user_piclet sql = `update student set user_pic=? where id=?`conn.query(sql,[req.body.avatar,req.body.id],(req,sqlRes) => {// 执行 SQL 语句失败 if (err) return (err) // 执行 SQL 语句成功,但是影响行数不等于 1 if (sqlRes.affectedRows !== 1) return ('更新头像失败!') // 更新用户头像成功 return ('更新头像成功!', 0)})})// 删除数据app.get('/delete/:id', (req, res) => {let sql = `DELETE FROM student where id=?`conn.query(sql,[req.params.id], function (err, results) {if (err) return console.log(88, err.message)console.log(45, results)res.send({mas: '删除成功', code: 200, data: results })})})
node.js服务端笔记文档学会写接口 学习分类:path 包 模块化 fs express 中间件 jwt 开发模式 cors。