语法格式。
fs.readFile(path[,options],callback)
//参数一:必传,字符串,表示文件的路径
//参数二:可选参数,表示以什么编码格式来读取文件
//参数三:必传参数,文件读取完成后,通过回调函数拿到读取的结果
实例代码
console.log("使用fs.readFile()读取文件内容:")
//导入fs模块,来操作文件
const fs=require('fs')
//调用fs.readFile()方法来读取文件
fs.readFile("a.txt","UTF-8",function(err,data){
//如果文件读取失败,err是一个错误对象。读取文件成功返回null
if(err){
return console.log("读取文件失败:"+err.message)
}
//如果文件读取成功返回文件内容
console.log(data)
})
语法格式
使用fs.writeFile()方法,可以向指定的文件中写入内容,语法格式如下:
fs.writeFile(file,data[,options],callback)
//参数一:必传参数,需要指定一个文件路径的字符串,表示文件的存放路径
//参数二:必传参数,表示要写入的内容
//参数三:可选参数,表示以什么格式写入文件内容,默认是utf8
//参数四:必传参数,表示文件写入完后都会执行回调函数
实例代码
console.log("使用fs.writeFile()往文件写入内容:")
//导入fs模块,来操作文件
const fs=require('fs')
//调用fs.writeFile()方法来往文件中写入内容。文件不存在时会创建一个文件,路径不存在出错。只能创建文件不能创建文件夹
//重复使用fs.writrFile()往一个文件中写入,新写入的会覆盖原来的文件内容
fs.writeFile("a.txt","哈哈哈哈","UTF-8",function(err){
//如果文件文件写入成功,则err的值为null.写入失败err则是一个错误对象
if(err){
return console.log("往文件写入内容失败:"+err.message)
}
console.log("文件写入成功!")
})
//引入fs模块,操作文件
const fs=require('fs')
//调用fs.readFile()方法来读取文件
fs.readFile("a.txt","UTF-8",function(err,data){
//如果文件读取失败,err是一个错误对象。读取文件成功返回null
if(err){
return console.log("读取文件失败:"+err.message)
}
//如果文件读取成功返回文件内容
console.log(data)
//先把读取到的数据按照空格分割
const arrOld=data.split(" ");
const arrNew=[];
arrOld.forEach(item=>{
//对数组中的=替换为:
arrNew.push(item.replace("=",":"))
})
//对数组中的
const arrStr=arrNew.join("\r\n")
//写入文件中
fs.writeFile("a.txt",arrStr,"UTF-8",function(err){
//如果文件文件写入成功,则err的值为null.写入失败err则是一个错误对象
if(err){
return console.log("往文件写入内容失败:"+err.message)
}
console.log("文件写入成功!")
})
})
在使用fs模块操作文件时,如果提供的操作陆是以./或…/开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行node命令时所处的目录,动态拼接出被操作的文件的完整路径。
//出现路径拼接错误问题,使用为提供了./或../开头的相对路径。
//可以提供一个完整的路径,不适用./或../相对路径
//但是移植性非常差,不利于维护
//使用 __dirname 表示当前文件所处的目录
console.log(__dirname)
path模块时Node.js官方提供的,用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
如果要在JavaScript代码中,使用path模块来处理路径,则需要使用如下的方式先导入它。
const path = require("path")
//引入path模块,操作路径
const path=require('path')
const pathStr=path.join('/a','/b/c','../','/d/e');
console.log(pathStr)
const pathStr2=path.join(__dirname,"a.txt")
console.log(pathStr2)
//使用path.dasename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名。
const name=path.basename(pathStr2,".txt")
//第一个参数:必传参数,表示一个路径的字符串
//第二个参数:可选参数,表示文件扩展名,如果传了第二个参数,就会在最后得到的String中去掉后缀
//返回:路径字符串中的最后一部分
console.log(name)
//通过path.extname()方法获取文件的后缀名
console.log(path.extname(pathStr2))
今后凡是涉及到路径拼接的操作,都要使用path.join()方法进行处理。不要直接使用+进行字符串的拼接。
http模块时Node.js官网提供的,用来创建web服务器的模块。通过http模块提供的http.createServer()
方法,就能方便的把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务。
如果要希望使用http模块来创建web服务器,则需要先导入它:
const http = require("http")
代码示例:
//导入http模块
const http = require("http")
//创建web服务实例
const server=http.createServer();
//为服务器实例绑定request事件,监听客户端的请求
server.on("request",function(req,res){
console.log("Someone visit our web server.")
//req是请求对象,包含了与客户端相关的数据和属性
//req.url 是客户端请求的url地址
const url=req.url;
console.log("请求的url地址为:"+url)
//req.method 是客户端请求的 method 类型
const method=req.method;
console.log("请求的method类型为:"+method)
//res是响应对象,它包含了与服务器相关的数据和属性
const str="你请求的地址是"+url+",你请求的method类型为:"+method;
res.end(str)
})
//调用server.listen(端口号,cb回调)方法,即可启动web服务
server.listen(8080,()=>{
console.log("WEB Server is runing onLine 127.0.0.1:8080")
})
http模块中解决中文乱码问题:
//设置响应头,解决中文乱码问题
res.setHeader("Content-Type","text/html;charset=utf-8")
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
把代码进行模块化拆分的好处:
Node.js中根据模块的来源不同,将模块分为了3打雷,分别是:
使用强大的require()方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用,例如:
//加载内置fs模块
const fs=require("fs")
//加载用户自定义模块,不写.js后缀名也能加载用户自定义模块
const custom=require("./custom.js")
//加载第三方模块
const moment=require("moment")
注意:使用require()方法加载其他模块时,会执行被加载模块中的代码。
和函数作用域类似,在自定义模块中定义的变量,方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
模块作用域的好处:防止全局变量污染的问题。
在每个.js自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息。
Module {
id: '.',
path: 'D:\\deskBook',
exports: {},
filename: 'D:\\deskBook\\a.js',
loaded: false,
children: [],
paths: [ 'D:\\deskBook\\node_modules', 'D:\\node_modules' ]
}
在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。
外界用require()方法导入自定义模块时,得到的就是module.exports所指向的对象。
//向module.exports对象上挂载username属性
module.exports.username="zs"
//向module.exports对象上挂载hello方法
module.exports.hello=function(){
consloe.log("hello方法打印")
}
module.exports={
name="ls"
ha()=>{
consloe.log("he方法打印")
}
}
使用require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准。
由于module.exprots单词写起来比较复杂,为了简化向外共享成员的代码,Node提供了exports对象,默认情况下,exporte和module.exports指向同一个对象。最终共享的结果,还是以module.exports指向的对象为准。
npm包可以通过网站:www.npmjs.com 来搜索任何需要的包。还提供了一个下载地址 https://registry.npmjs.org/ 可以通过这个服务器下载所有需要的包。
在项目中安装npm包
npm install 包的完整名称
rem 可以使用简写
npm i 包的完整名称
rem 默认使用该命令下载的包是最新的包,可以在命令后面追加@版本号
const moment = require("moment")
moment().formant("YYYY-MM-DD HH-mm-SS")
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件。
//作用:在执行命令所在处的目录中,快速新建 package.json 文件,目录路径只能在英文路径下
npm init -y
运行npm install命令安装包的时候,npm包管理工具会自动把包的名称和版本号,记录到package.json中。
可以运行 npm install命令(或npm i)一次性安装所有的依赖包:
//执行 npm install 命令时,npm包管理会先读取 package.json 中的 dependencies 节点。
//读取到记录的所有依赖包名称和版本号之后,npm 包管理工具会把这些包一次性下载到项目中
npm install
可以运行 npm uninstall 命令,来卸载指定的包:
//使用 npm uninstall 具体包名 来卸载包
npm uninstall moment
注意:npm uninstall 命令执行成功后,会把卸载的包,自动从package.json的dependencies 中移除掉。
如果某些包只是在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies节点中。与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到dependencies节点中。
你可以使用如下命令,将包记录到devDependencies节点中:
//安装指定的包,并记录到devDependencies节点中
npm i 包名 -D
//注意:上述命令是简写形式,等价于下面完整的写法
npm install 包名 --save-dev
使用淘宝npm镜像服务器。
切换npm下包镜像源
# 默认下载镜像源:https://registry.npmjs.org/
# 查看当前的下包镜像源
npm config get registry
# 将下包的镜像源切换为淘宝镜像源
npm config set registry=https://registry.npm.taobao.org/
# 检查镜像源是否下载成功
npm config get registry
为了更方便的切换下包的镜像,我们可以安装nrm这个小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源。
# 通过npm 包管理器,将nrm安装为全局可用工具
npm i nrm -g
# 查看所有可用的镜像源
nrm ls
# 将下包的镜像源切换为taobao镜像
nrm use taobao
使用npm安装的包可用分为两大类:项目包和全局包
那些被安装到项目的 node_modules 目录中的包,都是项目包。
项目包又分为两类:开发依赖包和核心依赖包。
开发依赖包(被记录到devDependencies 节点中的包,只在开发期间会用到)
核心依赖包(被记录到dependencies 节点中的包,在开发期间和项目上线之后都会用到)
在执行npm install 命令是,如果提供了 -g 参数,则会把包安装为全局包
# 安装全局包
npm i 包名 -g
# 卸载全局安装包
npm uninstall 包名 -g
只有工具性质的包,才有全局安装的必要性。因为他们提供了好用的终端命令。
判断某个包是否需要全局安装后才能使用,可用参考官方提供的使用说明即可。
i5ting_toc是一个可用包md文档转为html页面的小工具:
# 全局安装i5ting_toc
npm install -g i5ting_toc
# 调用 i5ting_toc,轻松使用 md 转 html 的功能
i5ting_toc -f 要转换的md文件路径 -o
在清楚了包的概念,以及如何下载和使用包之后,接下来我们深入了解一下包的内部结构。
一个规范的包,它的组成结构,必须符合一下3个要求:
更多规范和约束,可以参考如下网址:
https://yarnpkg.com/zh-Hans/docs/package-json
初始化package.json文件
{
"name":"Tools",//包名称
"version":"1.0.0",//包的版本号
"main":"index.js",//包的入口文件
"description":"提供了格式化时间,HTMLEscape的功能",//包的简介
"Keywords":["itheima","dateFormat","escape"],//搜索的关键字
"license":"ISC"//包遵循的开源许可协议
}
转义HTML的方法。在index.js文件中定义
//转义HTML的方法
function htmlEscape(htmlStr){
return htmlStr.replace(/<|>|"|&/g,(match)=>{
switch(match){
case '<':
return '<'
case '>':
return '>'
case '"'
return '"'
case '&':
return '&'
}
})
}
//还原HTML的方法
function htmlUnEscape(str){
return str.replace(/<|>|"|&/g,(match)=>{
switch(match){
case '<':
return '<'
case '>':
return '>'
case '"'
return '"'
case '&':
return '&'
}
})
}
//向外暴露需要的成员
module.exports={
htmlEscape,htmlUnEscape
}
ES6新特性:...对象名
在一个对象中使用 ...另一个对象
表示把另一个对象的属性 全部添加到该对象中。
//建议先检查当前下载npm下载包的服务器地址是否是npm官方服务器
npm login 登录名 密码 邮箱
在运行npm login 命令之前,必须先把下包的服务器地址切换为npm的官方服务器。否则会导致发布包失败!
在登录了用户时,将终端切换到包的根目录之后,运行 npm publish
命令,即可将包发布到npm上(注意:包名不能重复)。
运行 npm unpublish 包名 --force
命令,即可从npm删除已发布的包。
通过该命令只能删除72小时以内发布的包,使用该命令删除的包,在24小时内不允许重复发布。发布包的时候要慎重,尽量不要往npm上发布没有意义的包。
模块在第一次加载后会被缓存。这也意味着多次的require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,他们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块是由Node.js 官方提供的模块,内置模块的加载优先级最高。
例如:require(“fs”) 始终放回内置的fs模块,即使在node_modules目录下有名字相同的包也叫做fs。
使用require() 加载自定义模块是,必须指定义 ./ 或 …/这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用require() 导入自定义模块是,如果忽略了文件的扩展名,则Node.js会按顺序分别尝试加载以下的文件:
如果传递给require()的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘…/’ 开头,则Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式。
官方给出的概念:Express是基于Node.js 平台,快速、开放、极简的Web开发框架。
通俗的理解:Express 的作用和Node.js 内置的 http模块类似,是专门用来创建Web 服务器的。
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建Web服务器的便捷方法。
Express的中文官网:http://www.expressjs.com.cn/
//导入 express
const express = require("express")
//创建web服务器
const app = express()
//启动服务器
app.listen(80, () => {
console.log("express server is running port 80")
})
通过 app.get()方法,可以监听客户端GET 请求,具体的语法格式如下:
//监听客户端的GET请求
//参数一:客户端请求的url地址
//参数二:请求对应的处理函数
// req:请求对象(包含了于请求相关的属性和方法)
// res:响应对象(包含了于响应相关的属性和方法)
app.get("/", function (req, res) {
//通过res.send() 方法可以把数据发送给客户端
res.send("")
})
通过 app.post()方法,可以监听客户端POST 请求,具体的语法格式如下:
//监听客户端的GET请求
//参数一:客户端请求的url地址
//参数二:请求对应的处理函数
// req:请求对象(包含了于请求相关的属性和方法)
// res:响应对象(包含了于响应相关的属性和方法)
app.post("/login", function (req, res) {
//通过res.send() 方法可以把数据发送给客户端
res.send("")
})
通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数。
app.get("/", function (req, res) {
//通过res.send() 方法可以把数据发送给客户端
res.send("通过Get方法,请求了/地址")
//通过req.query对象获取到地址栏中传递的参数:?name=zs&age=20
//默认req.query是空对象
console.log(req.query)
})
通过req.params 对象,可以访问到URL中,通过:匹配的动态参数。(rest接口)
app.get("/user/:id", function (req, res) {
//通过res.send() 方法可以把数据发送给客户端
res.send("通过Get方法,请求了/user/:id地址")
//req.params 是动态匹配到的 URL 参数,默认是一个空对象。rest请求风格
console.log(req.params)
})
//请求地址:http://localhost/user/12
//打印出结果为:{ id: '12' }
express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器,例如:通过如下代码就可以将 public 目录下的图片,css文件,javascript文件对外开发访问了。
//静态资源托管
//该静态资源的Tool 需要在Node 执行的js文件同级目录,请求Tool中的静态资源时不需要带Tool,只需在地址栏路径中写:http:ip:prot/{Tool中的资源名称}
app.use(express.static("Tool"))
//托管多个静态资源目录,如果两个目录都有共同的文件,请求时会根据先执行的app.use中查找对应的静态资源
app.use(express.static("files"))
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下方式:
//第一个参数代表请求这个静态资源时要携带的地址栏前缀
app.use("/Tool",express.static("Tool"))
在编写调试 Node.js 项目的时候,如果修改了项目代码,则需要频繁的手动 close 掉,然后再重新启动,非常繁琐。
现在,我们可以使用 nodemon (https://www.npmjs.com/package/nodemon) 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试心
//全局安装 nodemon
npm install -g nodemon
当基于Node.js编写了一个网站应用的时候,传统的方式,是运行node app.js 命令,来启动项目。这样做的坏处是:代码被修改之后,需要手动重启项目。
现在,我们可以将node命令替换为nodemon命令,使用 nodemon app.js 来启动项目。这样做的好处是:代码被修改之后,会被 nodemon 监听到,从而实现自动重启项目的效果。
//使用nodemon启动项目,在更改项目中的文件时,保存会自动重启项目
nodemon index.js
在Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express 中的路由分3部分组成,分别是请求的类型、请求的URL地址、处理函数,格式如下:
//METHOD:请求类型
//PATH:请求的URL地址
//HANDLER:处理函数
app.METHOD(PATH,HANDLER)
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则 Express 会将这次请求,转交给对应的function函数进行处理。
app.get("/", (req, res) => {res.send("通过Get方法,请求了/地址")})
app.post("/login", (req, res) => {res.send("通过Post方法,请求了/login地址")})
为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
将路由抽离为单独模块的步骤如下:
//这是路由模块
const express = require("express")
//创建路由对象
const router = express.Router();
//挂载具体的路由
router.get("/", ((req, res) => {
res.send("Get 请求/")
}))
router.post("/login", ((req, res) => {
res.send("post 请求/login")
}))
//向外导出路由对象
module.exports = router
//导入 express
const express = require("express")
//导入自定义的路由模块
const router = require("./router");
//创建web服务器
const app = express()
//app.use()函数的作用,就是用来注册全局中间件
//注册路由模块,使它生效
app.use(router)
//启动服务器
app.listen(80, () => {
console.log("express server is running port 80")
})
//app.use()函数的作用,就是用来注册全局中间件
//注册路由模块,使它生效
//第一个参数:/api 代表给路由模块中的所有接口都添加统一的请求前缀
app.use("/api", router)
中间件(Middleware ) ,特指业务流程的中间处理环节。
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express的中间件,本质就是一个 function 处理函数。
app.get("/",function(req,res,next){ next() })
注意:中间件函数的形参列表中,必须包含next 参数。而路由处理函数中只包含req和res。
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
使用app.use(中间件函数),即可定义一个全局生效的中间件。实例代码。
const middleware = function (req, res, next) {
console.log("我是一个全局中间件")
//把流转关系,转交给下一个中间件或路由
next()
}
//创建web服务器
const app = express()
//注册全局中间件
app.use(middleware)
//创建web服务器
const app = express()
//注册全局中间件
app.use(function (req, res, next) {
console.log("我是一个全局中间件")
//获取到请求到服务器的时间
//为req对象挂载,自定义属性,从而把时间共享给后面的所有路由
req.startTime = Date.now();
//把流转关系,转交给下一个中间件或路由
next()
});
定义多个全局中间件,只需要多次使用app.use()方法即可,他会根据调用的顺序来依次调用。
//注册全局中间件
app.use(function (req, res, next) {
console.log("我是第一个全局中间件件")
//把流转关系,转交给下一个中间件或路由
next()
});
//注册全局中间件
app.use(function (req, res, next) {
console.log("我是第二个全局中间件件")
//获取到请求到服务器的时间
//为req对象挂载,自定义属性,从而把时间共享给后面的所有路由
req.startTime = Date.now();
//把流转关系,转交给下一个中间件或路由
next()
});
不适用 app.use() 定义中间件,叫做局部生效中间件。局部生效的中间,在全局中间件之后调用。
//定义中间件函数
const mw = (req, res, next) => {
console.log("我是局部中间件函数")
next()
}
//挂载具体的路由
//使用三个参数的时候,第一个参数代表请求地址,第二个参数代表局部中间件函数,第三个参数代表回调函数
router.get("/", mw, ((req, res) => {
const error = new Error();
error.message = "发生错误了"
error.code = 404
error.stack = "stack"
throw error
res.send("Get 请求/, time:" + req.startTime)
}))
可以在路由中,通过如下两种等价的方式,使用多个局部中间件。
router.get("/", mw, mw1, ((req, res) => {}))
router.get("/", [mw, mw1], ((req, res) => {}))
为了方便大家的理解和记忆中间件的使用,Express 官方把常用的中间件用法,分为了5大类:
通过app.use() 或 app.get() 或 app.post(),绑定到app实例上的中间件,叫做应用级别的中间件。
绑定到express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件时绑定到app实例上,路由级别中间件绑定到router实例上。
//创建路由对象,必须时express.Router() 获取到的router对象设置路由级别的中间件才有效
const router = express.Router();
router.use(function (req, res, next) {
console.log("我是路由级别中间件")
//把流转关系,转交给下一个中间件或路由
next()
})
错误级别中间的作用,专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的 function 处理函数中,必须有4个形参,形参顺序从前到后,分别时(err,req,res,next)。
//app.use()函数的作用,就是用来注册全局中间件
//注册路由模块,使它生效
//第一个参数:/api 代表给路由模块中的所有接口都添加统一的请求前缀
app.use("/api", router)
//注册全局错误级别中间件,捕获整个项目的异常
app.use(function (err, req, res, next) {
console.log("我是一个全局错误级别的中间件")
console.log(err)
res.send("发生错误:" + err.message)
});
注意:错误级别中间件,必须注册在所有路由之后
Express内置了3个常用的中间件,极大提高了Express项目的开发效率和体育:
//配置解析 application/json 格式数据的内置中间件
app.use(express.json())
//配置解析 application/x-www-from-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({extended: false}))
router.post("/login", ((req, res) => {
//在服务端可以通过req.body 获取json格式的表单数据和url-encoded格式的数据
//默认情况下不配置解析表单数据的中间件,则 req.body 默认为:undefined
console.log(req.body)
res.send("post 请求/login, time:" + req.startTime)
}))
注意:在配置路由之前配置,除了错误级别的中间件在路由配置之后。
非Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
使用第三方中间件步骤:
定义全局中间件:
//这是解析表单数据的中间件
app.use((req, res, next) => {
//具体业务处理
……
})
监听req的data事件:
在中间件中,需要监听req对象的data 事件,来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
//定义变量,用来存储客户端发送过来的请求数据
let data = "";
//监听req对象的data事件(客户端发送过来的新的请求体数据)
req.on("data", (chunk) => {
//拼接请求体数据,隐式转换为字符串
data += chunk
})
监听req的end事件:
当请求体数据接收完毕之后,会自动触发req的end事件。因此,我们可以在req的end 事件中,拿到并处理完整的请求体数据。示例代码如下:
//监听req的end事件
req.on("end", () => {
//在data中存放的是完整的请求体数据
console.log(data)
//TODO: 把字符串格式的请求体数据,解析成对象格式
})
Node.js 内置了一个querystring模块,专门用来处理查询字符串。通过这个模块提供的 parse()函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:
//导入 Node.js 内置的querystring 模块
const querystring = require("querystring");
//调用 querystring.parse() 方法,把查询字符串解析为对象
const body = querystring.parse(data);
console.log(body)
上游的中间件和下游的中间件及路由之间,共享同一份req和res。因此,我们可以将解析出来的数据,挂载为 req的自定义属性,命名为req.body,供下游使用。示例代码如下:
//导入 Node.js 内置的querystring 模块
const querystring = require("querystring");
//监听req的end事件
req.on("end", () => {
//在data中存放的是完整的请求体数据
console.log(data)
//TODO: 把字符串格式的请求体数据,解析成对象格式
//调用 querystring.parse() 方法,把查询字符串解析为对象
const body = querystring.parse(data);
// console.log(body)
req.body = body
//把流转关系,转交给下一个中间件或路由
next()
})
//这是中间件模块
//导入 Node.js 内置的querystring 模块
const querystring = require("querystring");
const bodyParser = function (req, res, next) {
//定义变量,用来存储客户端发送过来的请求数据
let data = "";
//监听req对象的data事件(客户端发送过来的新的请求体数据)
req.on("data", (chunk) => {
//拼接请求体数据,隐式转换为字符串
data += chunk
})
//监听req的end事件
req.on("end", () => {
//在data中存放的是完整的请求体数据
console.log(data)
//TODO: 把字符串格式的请求体数据,解析成对象格式
//调用 querystring.parse() 方法,把查询字符串解析为对象
req.body = querystring.parse(data)
//把流转关系,转交给下一个中间件或路由
next()
})
}
//向外暴露需要的成员
module.exports = bodyParser
//这是路由模块
const express = require("express")
//创建路由对象
const router = express.Router();
router.post("/login", ((req, res) => {
//在服务端可以通过req.body 获取json格式的表单数据和url-encoded格式的数据
//默认情况下不配置解析表单数据的中间件,则 req.body 默认为:undefined
console.log(req.body)
res.send("post 请求/login")
}))
//向外导出路由对象
module.exports = router
//-----------------------------------------------------
//导入 express
const express = require("express")
//导入自定义的路由模块
const router = require("./router");
//导入自定义中间件模块
const mw = require("./mw");
//创建web服务器
const app = express()
//app.use()函数的作用,就是用来注册全局中间件
app.use(mw)
//注册路由模块,使它生效
//第一个参数:/api 代表给路由模块中的所有接口都添加统一的请求前缀
app.use("/api", router)
//启动服务器
app.listen(80, () => {
console.log("express server is running port 80 in IP:127.0.0.1")
})
解决接口跨域问题的方案主要有2种:
CORS 是Express的一个第三方中间件。通过安装和配置 cors 中间件,跨域很方便地解决跨域问题。
使用步骤:
//导入cors
const cors = require("cors");
//一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
app.use(cors())
CORS(Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP 响应头组成,这些HTTP 响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的HTTP 响应托,就跨域解除浏览器端的跨域访问限制。
CORS主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了CORS的接口。
CORS在浏览器中有兼容性。只有支持XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
Access-Control-Allow-Origin: | *
其中,origin 参数的值指定了允许访问该资源的外域URL。
例如:
//只允许来自 http://baidu.com 的请求
res.setHeader('Access-Control-Allow-Origin','http://baidu.com')
//如果指定了 Access-Control-Allow-Origin 字段的值为通配符 * ,表示允许来自任何域的请求。
res.setHeader('Access-Control-Allow-Origin','*')
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')
Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起GET、POST、HEAD请求。
如果客户端希望通过PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法。
//只允许 POST、GET、DELETE、HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
//允许所有的HTTP请求方式
res.setHeader('Access-Control-Allow-Methods','*')
客户端在请求CORS接口时,根据请求方式和请求头的不同,跨域将CORS 的请求分为两大类,分别是:
同时满足以下两大条件的请求,就属于简单请求:
请求方式: 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)
只要符合以下任何一种条件的请求,都需要进行预检请求:
在浏览器与服务器正式通信之前,浏览器会先发送ОPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
概念:浏览器端通过
特点:
如果项目中已经配置了CORS 跨域资源共享,为了防止冲突,**必须在配置CORS中间件之前声明JSONP的接口。**否则JSONP接口会被处理成开启了CORS的接口。
//前端发送jsonp请求
$("#btnJSONP").click(() => {
$.ajax({
type: "GET",
url: "http://127.0.0.1/api/JSONP",
//表示要发起一个JSONP的请求
dataType:"JSONP",
success: function (res) {
console.log(res)
}
})
})
//node响应请求
//必须在CORS中间件之前,配置JSONP的接口
app.get("/api/JSONP",(req, res) => {
//TODO: 定义JSONP接口具体的实现过程
//得到函数的名称
const callback = req.query.callback;
//定义要发送到客户端的数据对象
const data = {name: 'zs', age: 20}
//拼接出一个函数的调用
const scriptStr = `${callback}(${JSON.stringify(data)})`
//把拼接的字符串响应给客户端
res.send(scriptStr)
})
//导入mysql模块
const mysql = require("mysql");
//设置数据库信息
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'fanzc131',
database: 'cloud'
});
//开连接
connection.connect();
//第一个参数是要执行的sql语句
//第二个参数是回调函数
connection.query('SELECT * from payment', function (error, results, fields) {
//error:默认为null,即没有出错时为null
//如果出错就抛出错误
if (error) throw error;
//results:代表执行sql语句之后的返回结果
console.log('The solution is: ', results);
});
//关闭连接
connection.end();
//导入mysql模块
const mysql = require("mysql");
//设置数据库信息
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'fanzc131',
database: 'cloud'
});
//开连接
connection.connect();
//插入数据
const sql = "insert into payment(id,serial) values(?,?)"
const data = {id: 4, serial: "我是通过Node插入的数据"}
//第一个参数是要执行的sql语句
//第二个参数是,执行sql语句中? 占位符中具体的值
//第三个参数是执行sql语句后的返回结果
connection.query(sql, [data.id, data.serial], function (error, results, fields) {
//如果出错就抛出错误
if (error) throw error;
//results.affectedRows 代表影响的行数
if (results.affectedRows === 1) console.log("插入数据成功")
});
//使用便捷的方式插入数据
const sqlL = "insert into payment set ?"
const dataA = {id: 5, serial: "我是通过Node插入的数据"}
//第一个参数是要执行的sql语句:insert into XX set ?
//第二个参数是,跟表中字段相同的一个对象
//第三个参数是执行sql语句后的返回结果
connection.query(sqlL, dataA, function (error, results, fields) {
//如果出错就抛出错误
if (error) throw error.sqlMessage;
//results.affectedRows 代表影响的行数
if (results.affectedRows === 1) console.log("插入数据成功")
});
//关闭连接
connection.end();
//导入mysql模块
const mysql = require("mysql");
//设置数据库信息
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'fanzc131',
database: 'cloud'
});
//开连接
connection.connect();
const sql = "update payment set serial=? where id = ?"
const data = {id: 4, serial: "我是通过Node插入的数据!"}
//第一个参数是要执行的sql语句
//第二个参数是,sql中 ? 占位符的具体值的一个数组
//第三个参数是执行sql语句后的返回结果
connection.query(sql, [data.serial, data.id], function (error, results, fields) {
//如果出错就抛出错误
if (error) throw error;
//results.affectedRows 代表影响的行数
if (results.affectedRows === 1) console.log("更新数据成功")
});
//更新数据的便捷方式
const sqlL = "update payment set ? where id = ?"
const dataA = {id: 4, serial: "我是通过Node插入的数据?"}
//第一个参数是要执行的sql语句:set 后面的? 不直接全部写出所有的键值对,而是使用?占位符
//第二个参数是,sql中 ? 占位符的具体值的一个数组,对应set后面的占位符,使用与数据库表相对应的对象
//第三个参数是执行sql语句后的返回结果
connection.query(sqlL, [dataA, dataA.id], function (error, results, fields) {
//如果出错就抛出错误
if (error) throw error;
//results.affectedRows 代表影响的行数
if (results.affectedRows === 1) console.log("更新数据成功")
});
//关闭连接
connection.end();
//导入mysql模块
const mysql = require("mysql");
//设置数据库信息
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'fanzc131',
database: 'cloud'
});
//开连接
connection.connect();
const sql = "delete from payment where id = ?"
//第一个参数是要执行的sql语句
//第二个参数是,sql中 ? 占位符的具体值,如果SQL语句中有多个占位符,则必须使用数组,若只有一个占位符,则可省略数组
//第三个参数是执行sql语句后的返回结果
connection.query(sql, 4, function (error, results, fields) {
//如果出错就抛出错误
if (error) throw error;
//results.affectedRows 代表影响的行数
if (results.affectedRows === 1) console.log("删除数据成功")
});
//关闭连接
connection.end();