一、核心模块-文件读写
- 核心模块
就是nodejs自带的模块,在安装完nodejs之后,就可以任意使用啦。相当于学习js时使用的Math对象一样 - 自定义模块
程序员自己写的模块。相当于我们在学习js时的自定义函数 - 第三方包
其他程序员写好的模块,nodejs生态提供了一个专门的工具npm来管理第三方模块,例如我们前面学习的
artTemplate等
const fs = require("fs");
二、文件读写综合案例
1、fs模块异步读取文件
路径:
- 同步与异步的概念理解
- fs.readFile异步读取文件基本写法
- fs.readFile异步读取图片
- 观察并处理错误
2、同步与异步的概念理解
- 同步:多个功能按先后顺序执行,后面任务等待前面任务执行完毕后才会执行。如平常大家讨论问题时说:让我说完,意思是说我说完你再说。优缺点:如果前面任务执行太久会阻止后面任务的执行,降低程序执行性能,造成用户体验差,优点是编程方式简单,便于理解执行过程。
- 异步:和同步相反,也就是在发出一个功能调用时,不管没有没得到结果,都继续往下执行。就像两个人吵架一样,各说各的,不管对方有没有说完。优缺点:不会造成阻塞,提升了程序执行性能,用户体验好,缺点是编程方式复杂,执行过程理解难度较大,会形成回调函数的嵌套。
- 异步的表现形式:以一个回调函数的方式来实现异步任务的执行
3、fs.readFile异步读取文件基本写法
语法:
fs.readFile(path[, options], callback)
参数:path,读取文件的路径(相对路径或绝对路径)
options:读取文件的参数配置,通常用一个utf8字符串表示即可
callback:读取完毕的回调函数(err,data)=>{}
4、fs.readFile异步读取文件基本写法
场景一:
在一个js文件中编写代码,读取同级目录下的1.txt文件中的内容打印到小黑窗上
步骤:
① 在day01文件夹下 创建 /异步读取文件/rd.js 和 /异步读取文件/1.txt两个文件
② 在1.txt中随便输入一个字符串内容
③ 在rd.js中导入fs核心模块 const fs = require(‘fs’)
④ fs.readFile(‘./1.txt’,’utf8’,(err,data)=>{ consloe.log(data); })
⑤ 进入异步读取文件目录打开小黑窗,执行 node rd.js 看结果
注意:读取文本内容文件时,utf8参数要加上,不然打印出来的是一个Buffer二进制内容
// 1 引入文件读写模块 fs
const fs = require("fs");
// 2 调用 api 异步读取 文本文件 记得加上第二个参数 utf8
fs.readFile("./1.txt", "utf8", function (err, data) {
// 如果 读取失败 err 里面有值 否则读取成功 err 没有值
if (err) {
// 读取失败了 失败的一些错误处理
console.log("失败");
console.log(err);
} else {
// 读取成功了 正常输出即可
console.log("成功");
console.log(data);
}
})
场景二:
在一个js文件中编写代码,读取同级目录下的1.jpg文件中的内容打印到小黑窗上
步骤:
① 在day01文件夹下 准备 /异步读取文件/rdimg.js 和 /异步读取文件/1.jpg 两个文件
② 在rdimg.js中导入fs核心模块 const fs = require(‘fs’)
③ fs.readFile(‘./1.jpg’,(err,data)=>{ consloe.log(data); })
④ 进入异步读取文件目录打开小黑窗,执行 node rdimg.js 看结果
const fs = require("fs");
// 读取图片 不需要加上 utf8
fs.readFile("./img/pic.jpg", function (err, data) {
if (err) {
console.log("失败");
console.log(err);
} else {
console.log("成功");
console.log(data);
}
})
小结:
fs.readFile(path,[,options],callback)语法中
- 异步读取文件方法的三个参数分别是什么作用?
读取文件的路径,必填
读取文件的编码,可选
文件读取完毕后的回调函数必选,必填 - 其中在读取文本内容时,第二个参数通常指定什么?
utf8,注意如果读取的是一个非文本内容文件时不需要指定第二个参数
5、异步读取文件处理错误
- (err,data)=>{ } 中有两个参数
参数1:err代表读取文件出错后的错误对象
参数2:data代表读取文件成功后的内容,如果读取失败则data的值为为undefined - err参数特点:如果文件读取成功则err为null,如果读取失败则err存储的具体的错误信息对象
通过如下if判断来处理错误
const fs = require("fs");
// 读取图片 不需要加上 utf8
fs.readFile("./imgs/pic.jpg", function (err, data) {
if (err) {
console.log("读取文件失败");
return; //阻止后面代码继续运行
} else {
// 读取的数据
console.log(data);
console.log("读取成功");
}
})
6、fs模块同步读取文件
路径:
- fs.readFileSync同步读取文件基本写法
- fs.readFileSync同步读取图片
- 观察并处理错误
语法:
let res = fs.readFileSync(path[, options])
参数:
path,读取文件的路径(相对路径或绝对路径)
options:读取文件的参数配置,通常用一个utf8字符串表示即可
fs.readFileSync同步读取文件基本写法
场景一:
在一个js文件中编写代码,读取同级目录下的1.txt文件中的内容打印到小黑窗上
步骤:
① 在day01文件夹下 创建 /同步读取文件/rd.js 和 /同步读取文件/1.txt两个文件
② 在1.txt中随便输入一个字符串内容
③ 在rd.js中导入fs核心模块 const fs = require(‘fs’)
④ let res = fs.readFileSync(‘./1.txt’,’utf8’)读取到内容后直接返回,所以使用变量保存之
⑤ 进入同步读取文件目录打开小黑窗,执行 node rd.js 看结果
const fs = require("fs");
// 同步读取文件
// fs.readFileSync("路径", "编码");
const res = fs.readFileSync("./1.txt", "utf8");
console.log(res);
场景二:
在一个js文件中编写代码,读取同级目录下的1.jpg文件中的内容打印到小黑窗上
步骤:
① 在day01文件夹下 准备 /同步读取文件/rdimg.js 和 /同步读取文件/1.jpg 两个文件
② 在rdimg.js中导入fs核心模块 const fs = require(‘fs’)
③ let res = fs.readFile(‘./1.jpg’) 读取到内容后直接返回,所以使用变量保存之
④ 进入同步读取文件目录打开小黑窗,执行 node rdimg.js 看结果
注意:读取文本内容文件时,utf8参数要加上,不然打印出来的是一个Buffer二进制内容
const fs = require("fs");
// 同步读取文件
// fs.readFileSync("路径", "编码");
const res = fs.readFileSync("./1.jpg");
console.log(res);
使用try{}catch{}处理同步读取文件的错误
- try catch语法: try{ 放有可能出错的代码 } catch(err){ 报错后执行的代码,通常是输出错误 }
- 处理同步读取文件的错误
const fd = require("fs")
try{
let res = fs.readFileSync("./1txt",'utf8')
}catch(err){
console.log("读取文件错误:"+err)
}
7、fs.writeFile异步写入内容
语法:
fs.writeFile(path,data[, options], callback)
特点:writeFile方法是覆盖式写入,后面的内容会将前面的内容覆盖
参数:path,被写入文件的路径(相对路径或绝对路径)
data,要写入的内容,字符串格式
options:写入文件的参数配置,默认是utf8编码
callback:写入完毕的回调函数(err)=>{}
场景:在一个js文件中编写代码,将一段文本写入到同级目录下的1.txt文件中
步骤:
① 在day01文件夹下 创建 /异步覆盖写入文件/wd.js 和 /异步覆盖写入文件/1.txt两个文件
② 在wd.js中导入fs核心模块 const fs = require(‘fs’)
③ fs.writeFile(‘./1.txt’,’白日依山尽’,’utf8’,(err)=>{ consloe.log(‘写入成功’); })
④ 进入异步覆盖写入文件目录打开小黑窗,执行 node wd.js 看结果
const fs = require('fs');
// 异步
fs.writeFile("./1.txt", '白日依山尽', function (err) {
if (err) {
console.log("写入文件失败");
console.log(err);
} else {
console.log("写入文件成功");
}
})
8、fs.writeFileSync同步写入内容
语法:
fs.writeFileSync(path,data[, options])
特点:writeFile方法是覆盖式写入,后面的内容会将前面的内容覆盖
参数:path,被写入文件的路径(相对路径或绝对路径)
data,要写入的内容,字符串格式
options:写入文件的参数配置,默认是utf8编码
场景:
在一个js文件中编写代码,将一段文本写入到同级目录下的1.txt文件中
步骤:
① 在day01文件夹下 创建 /同步覆盖式写入文件/wd.js 和 /同步覆盖式写入文件/1.txt两个文件
② 在wd.js中导入fs核心模块 const fs = require(‘fs’)
③ fs.writeFileSync(‘./1.txt’,’白日依山尽’,’utf8’ })
④ 进入同步覆盖式写入文件目录打开小黑窗,执行 node wd.js 看结果
注意:同步写入方法返回值为:undefined
const fs = require('fs');
// 同步
fs.writeFileSync("./1.txt", '白日依山尽', function (err) {
if (err) {
console.log("写入文件失败");
console.log(err);
} else {
console.log("写入文件成功");
}
})
9、fs.appendFile异步追加内容
语法:
fs.appendFile(path,data[, options], callback)
特点:appendFile方法是追加式写入,后面的内容不会将前面的内容覆盖,只会追加到后面
参数:path,被写入文件的路径(相对路径或绝对路径)
data,要写入的内容,字符串格式
options:写入文件的参数配置,默认是utf8编码
callback:写入完毕的回调函数(err)=>{}
场景:
在一个js文件中编写代码,将一段文本追加到同级目录下的1.txt文件中
步骤:
① 在day01文件夹下 创建 /异步追加写入文件/wd.js 和 /异步追加写入文件/1.txt两个文件
② 1.txt中默认输入一段文本:白日依山尽
③ 在wd.js中导入fs核心模块 const fs = require(‘fs’)
④ fs.appendFile(‘./1.txt’,’黄河入海流’,’utf8’,(err)=>{ consloe.log(‘追加成功’); })
⑤ 进入异步追加写入文件目录打开小黑窗,执行 node wd.js 看结果
const fs = require("fs");
// 文件追加 异步
fs.appendFile("./assets/1.txt", "2222", function (err) {
if (err) {
console.log("出错");
console.log(err);
} else {
console.log("成功");
}
})
10、fs.appendFileSync同步追加内容
语法:
fs.appendFileSync(path,data[, options])
特点:appendFileSync方法是追加式写入,后面的内容不会将前面的内容覆盖,只会追加到后面
参数:path,被写入文件的路径(相对路径或绝对路径)
data,要写入的内容,字符串格式
options:写入文件的参数配置,默认是utf8编码
场景:在一个js文件中编写代码,将一段文本追加到同级目录下的1.txt文件中
步骤:
① 在day01文件夹下 创建 /同步追加写入文件/wd.js 和 /同步追加写入文件/1.txt两个文件
② 1.txt中默认输入一段文本:白日依山尽
③ 在wd.js中导入fs核心模块 const fs = require(‘fs’)
④ fs.appendFileSync(‘./1.txt’,’黄河入海流’,’utf8’)
⑤ 进入同步追加写入文件目录打开小黑窗,执行 node wd.js 看结果
const fs = require("fs");
// 文件追加
fs.appendFileSync("./1.txt", "黄河入海流","utf8");
总结
- 异步读取文件: fs.readFile(path,utf8编码,回调函数)
- 同步读取文件:fs.readFileSync(path,utf8编码)
- 异步写入内容:fs.writeFile(path,content,utf8,回调函数)
- 同步写入内容:fs.writeFileSync(path,content,utf8)
- 异步追加内容:fs.appendFile(path,content,utf8,回调函数)
- 同步追加内容:fs.appendFileSync(path,content,utf8)
- readFile的回调函数:(err,data)=>{}
- readFileSync没有回调函数,结果直接返回
- writeFile和appendFile的回调函数(err)=>{}
- writeFileSync和appendFileSync没有回调函数,结果返回undefined
案例
描述一:请把json文件的数据的按男女分别保存在一个Json文件里
JSON文件
[
{
"name": "路飞",
"gender": "男"
},
{
"name": "乔巴",
"gender": "男"
},
{
"name": "娜美",
"gender": "女"
},
{
"name": "罗宾",
"gender": "女"
}
]
const fs = require("fs");
// 1 读取 本地文件 读取成功了 数据也是 数组!!
const heroListStr = fs.readFileSync("./assets/hero.json", "utf8");
// 2 将字符串 转成数组格式
const heroList = JSON.parse(heroListStr);
const manList = heroList.filter(item => item.gender === "男");
const womenList = heroList.filter(item => item.gender === "女");
// 生成男.json
fs.writeFileSync("./male.json", JSON.stringify(manList));
// 生成女.json
fs.writeFileSync("./female.json", JSON.stringify(womenList));
描述二:创建一个index.html文件,用node.js的数据替换html指定内容生成一个新的页面
const fs = require("fs");
// 获取要处理的数据 字符串 读取文件来获取
let html1 = fs.readFileSync("./assets/index.html", "utf8");
const obj = {
title: "自己的页面",
name: "大大的标题"
}
// 替换 {{title}}
html1 = html1.replace("{{title}}", obj.title);
html1 = html1.replace("{{name}}", obj.name);
fs.writeFileSync("./assets/index2.html", html1);
三、核心模块-路径处理(__dirname)
path是node的核心模块之一
作用:用来处理路径的拼接,分析,取后缀名
API列表:
快捷测试: 在任意一个小黑窗中 输入 node回车,进入到node执行环境,输入path.方法带上参数即可进行测试__dirname:
获取js文件在node中执行时的绝对路径
应用:
将相对路径的参照固定以js文件所在的绝对路径为参照,使用path.join()将1.txt拼接成一个绝对路径
改造代码:
const fs = require("fs");
// 路径处理模块
const path = require("path");
// const url = __dirname + "/assets/1.txt";
// 工作比较常用和常见的 处理路径的写法!! 严谨!!
const url = path.join(__dirname, "assets", "1.txt");
console.log(fs.readFileSync(url, "utf8"));
四、服务器相关概念
1、服务器与客户端
- 服务器
提供网络服务的一台机器,通过在自己的电脑上安装特殊的软件(或者是运行某段特殊的代码)来提供服务,简单来说:服务器 = 电脑 + 能给其它电脑提供服务的软件。 - 客户端与服务器
提供服务的是服务器,享受服务的是客户端
2、服务器的类型
根据服务不同,服务器的类型也不同,也就是提供服务的软件也不同
- web服务器(重点学习)
安装apache/tomcat/iis或者在nodejs环境写代码来提供:图片/视频/音频浏览/新闻数据浏览等服务的服务器 - ftp服务器
安装serv-U软件,为其它电脑提供文件下载,共享服务 - 数据库服务器
安装mysql软件,为其它电脑提供数据库服务
3、web服务器
- 用户通过浏览器来享受web服务器提供的图片,视频,音频等网页内容浏览的服务
- 用户用url地址来访问某个web服务器上的资源
- 浏览器端发起请求,web服务器收到请求后,响应这个请求,并将处理结果返回给浏览器
- 浏览器端与web服务器是通过http(或者是https)协议来进行请求和响应的
4、IP地址
- 全称:Internet Protocol Address(IP)
- 作用:标识一个网络设备(计算机、手机、电视)在某一个具体的网络当中的地址,要访问某个电脑上的资源,先要找到它的ip
- 在同一个网络中,计算机的IP是不允许相同的,都是唯一的
- IP分类:IPV4(互联网协议第6版)、 IPV6(互联网协议第6版)
- IPV4格式:[0-255].[0-255].[0-255].[0-255] 即为四个 0-255 的数字组成
例如:127.0.0.1(本机访问) 或 192.168.106.2(局域网访问) 或 220.181.38.149(外网访问) - IPV6格式:X:X:X:X:X:X:X:X,每个X 以十六进制表示
例如:FF01:0:0:0:0:0:0:1101 → FF01::1101,可以把连续的一段0压缩为“::”
5、域名
- 域名:ip地址的别名
- 由于ip地址不好记忆,我所以给它们取个好记的别名
- 127.0.0.1 的别名为 localhost,系统自动解析的,不需要做额外的配置
- 220.181.38.149的别名为 www.baidu.com,需要域名解析系统把域名翻译成IP地址,需要额外购买和配置
6、端口
- 理解:如果理解IP地址(一台服务器)是一栋大商场,端口就是商场中的商铺的编号
- 一个网络设备可以有65536个端口,范围是从[0,65535])
- 不同的端口被不同的软件占用,以提供不同的服务
- 服务器要提供服务必须要通过指定的端口
-客户端与服务器都需要通过特定端口要进行通信(http://157.122.54.189:9092) - 端口是可以编程分配
- 有一些端口号是被预定了的
- http: 80
- https:443
- mysql:3306
7、HTTP协议
- 协议:制定客户端与服务器之间的通讯规则,不同的协议的作用也不同,我们主要了解HTTP协议
- http协议
(1)定义:HTTP(HyperText Transfer Protocol) 超文本传输协议,浏览器与web服务器都要遵守的协议
(2)HTTP 协议中明确规定了请求数据和响应数据的格式(报文) - 浏览器 请求 资源 要遵守 http 协议: 请求报文(请求行,请求头,请求体)
- 服务器 返回 资源 要遵守 http 协议: 响应报文(响应行,响应头,响应体)
五、核心模块Http实现一个Web服务器
① 引入http核心模块
② 使用createServer来创建服务
③使用listen来启动服务,端口为8001(端口可以修改 0 - 65535)
④ 在浏览器输入http://127.0.0.1:8001回车即可看到服务器响应回来的信息:OK
也可以通过ipconfig查看你的本机ip,通过 http://局域网ip:8001回车访问
// 1 引入核心模块 http
const http = require("http");
// 2 创建一个服务器 返回一个服务器对象
const app = http.createServer(function (request, response) {
// request 表示 接收用户的请求 请求的url 请求的类型 请求的参数
// response 表示 返回给用户的响应 返回用户什么数据 html标签 json
response.end("hello");
});
// 3 指定监听 开启哪个端口 提供服务
app.listen(8000, () => {
console.log("端口开启成功 8000端口");
});
1、工作原理
使用http模块在本机上创建一个Web服务器,它来接收浏览器的请求,并给出响应。
2、Http模块创建Web服务器代码解析
- 引入核心模块require(‘http’),得到的http是一个对象
- http.createServer方法创建一个http服务
createServer方法参数是一个回调函数:当有http请求进来时,它会自动被调用,请求一次,它就被调用一次
req参数:客户端的请求相关数据
res参数:设置对本次请求的响应相关数据 - server.listen() 用来监听端口
如果监听成功,则回调函数会执行一次。
如果不成功(例如端口被占用),会报错。
3、res.end()设置响应体
- 语法:res.end(响应的数据)
end()只能传入buffer或者是String类型的数据 - 作用:设置响应体并结束本次请求
- 用法:在createServer的回调函数中使用
const app = http.createServer((req, res) => {
res.end("OK");
});
- 注意:一次请求只能有一个res.end()响应,多个以第一个响应的数据为准,同时服务器报错终止
4、res.setHeader()设置响应头解决中文乱码
- 作用:设置响应头信息,控制浏览器的一些行为
- 语法:res.setHeader(响应头,响应值),两个参数的具体值都是http协议规定的
- 用法:在createServer的回调函数中使用
- 注意:
① res.setHeader()在res.end()之前才有效
② res.setHeader()可以设置多次 - 应用举例:解决响应中文字符在浏览器显示成乱码问题
res.setHeader(“Content-Type”,”text/html;charset=utf8”):
const app = http.createServer((req, res) => {
res.setHeader("Content-Type", "text/html;charset=utf8");
res.end("OK");
});
5、res.statusCode设置状态码
- 场景:浏览器输入了一个不存在的url,则服务器返回404状态码
- 语法:res.statusCode = 状态码,状态码是http协议规定的
状态码: 500 (服务器异常) 404(资源找不到) - 注意:res.statusCode只有在res.end()前执行才有效
6、req.url
- 通过 req.url来获取当前请求的url路径
- req.url在哪儿?
7、req.url具体用法和效果
- 代码
- 不同的请求url通过req.url获取的结果
8、修改了服务器的代码要重启
- 修改了服务器的代码要重启
- 场景:更改res.end()的内容,重启后,再次观察是否有更新
停止服务: 在小黑窗中按下ctrl+c 停止服务
重启服务:就是重新运行程序(按下向上的箭头,再回车)
六、Web服务器对静态资源的解析响应
1、URL地址的组成
2、URL格式
URL格式为: 协议://主机地址[:端口]/路径?查询字符串#锚点
- 协议: http 或者是 https
- 主机地址: IP地址 或者 域名
- 端口号: http默认端口80,https默认端口443,默认端口可以省略不写
- 路径:服务器文件夹上的资源。(.html/.css/.images/.js/接口)
- 参数(查询字符串):问号后面的部分,是键值对的形式
- 锚点: #后面表示 网页内部的锚点链接
3、静态资源
- 静态资源一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png…
- 处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
4、不同的URL返回不同资源思路分析
- 场景:用户在访问服务器上不同的url时,能返回不同的内容
- 思路:在服务器端收到客户端发的请求之后,分析url是什么,然后分别对应处理
实现: - 准备目录结构
- 在server.js中通过http模块开启web服务器,端口监听8001
- 在createServer的回调函数中通过req.url获取不同请求路径完成不同文件读取后响应给浏览器
- 浏览器输入不同url进行效果查看
5、content-type作用
在http协议中,content-type用来告诉对方本次传输的数据的类型是什么
- 在请求头中设置content-type来告诉服务器,本次请求携带的数据是什么类型的
- 在响应头中设置content-type来告诉浏览器,本次返回的数据是什么类型的
- res.setHeader方法可以设置content-type这个响应头,浏览器根据不同类型做出不同解析
※ 请求头和响应头都可以设置content-type
常见的几种文件类型及content-type
- content-type类型查询地址
- 常见的几种文件类型及content-type
.html:res.setHeader(‘content-type’, ‘text/html;charset=utf8’)
.css:res.setHeader(‘content-type’, ‘text/css;charset=utf8’)
.js:res.setHeader(‘content-type’, ‘application/javascript’)
.png:res.setHeader(‘content-type’, ‘image/png’)
json数据:res.setHeader(‘content-type’, ‘application/json;charset=utf-8’)
※ 如果读取.html的文件内容,但是content-type设置为了text/css则浏览器将不会当作是html页面来渲染了
6、.html文件中的二次请求
- 二次请求
浏览器从服务器获取html文件之后,如果这个html文件中还引用了其它的外部资源(图片,样式文件等)
则浏览器会重新再发请求给服务器获取这些资源
7、批量处理.html中的二次请求
- 把所有的静态资源(.html,.png,.css,.js)全放在一个指定的目录public中
- 收到用户的请求之后,去public目录下去找对应的文件
找到,把内容读出来返回给用户
找不到,报404 - 目录如下:
|-public
|-public/index.html
|-public/style.css
|-public/01.html
|-server.js
在上面的目录结构中,我们把所有的静态资源全放在public下面,然后使用server.js来启动web服务器。
七、Web服务器处理接口响应
1、静态资源与接口
- 静态资源
它们一般表现为一个一个的文件。例如index.html, style.css, index.js, mp4, .png…
处理请求静态资源时,服务器一般就直接读出资源的内容,再返回给客户端浏览器
对于静态资源,我们请求方式是get请求 - 动态资源:接口
接口不是以某个具体的文件存在的,而是服务器上的一段代码
访问接口时,服务器会执行这段代码,然后把代码的执行结果返回给客户端浏览器
对于动态资源,我们常用的请求方式是get和post请求
2、接口请求方式
- 发送接口请求的类型
get:在地址栏中直接访问这个url就是get方式。
post:通过表单提交,可以设置$.ajax的type为post - web服务器通过req.method获取当前请求的类型
3、接口的各种表现形式
get请求不带参数的接口: http://127.0.0.1:8003/getHero
get请求带参数的接口: http://127.0.0.1:8003/getHero?heroName=后羿
post请求接口 http://127.0.0.1:8003/addHero (参数在请求报文体中传输)
4、不带参数的接口
- 场景:在server.js中写代码,提供一个名为getHero的Get接口(http://127.0.0.1:8003/getHero)它以json字符串格式返回db/data.json的内容
- 步骤:
① 在server.js中使用http创建一个web服务器,端口为8003
② 在createServer的回调函数中通过fs.readFileSync读取db/data.json中的内容
③ 使用res.end()将json字符串响应会客户端浏览器
④ 增加url和method判断完成/getHero路径的get请求限定
5、带参数的Get接口
- 场景:在server.js中写代码,提供一个名为getHero的Get接口(http://127.0.0.1:8003/getHero?name=xxx)它以json字符串格式返回db/data.json的name为xxx的内容
- 前置知识:
get请求的参数是附加在url后面
URLSearchParams 解析参数 - 场景:在server.js中写代码,提供一个名为getHero的Get接口(http://127.0.0.1:8003/getHero?name=xxx)它以json字符串格式返回db/data.json的name为xxx的内容
- 步骤:
① 在server.js中使用http创建一个web服务器,端口为8003
② 在createServer的回调函数中解析出url?后面的参数值,通过URLSearchParams解析出参数值
③ 通过fs.readFileSync读取db/data.json中的内容,并通过filter方法过滤出名称为xxx的数据
④ 使用res.end()将json字符串响应回客户端浏览器
6、URLSearchParams 解析参数
URLSearchParams可以将查询字符串解析成一个对象,通过get方法获取到指定参数值
参考网址:点击查看
查询字符串格式: name=后裔&age=20
示例
let query = 'name=后裔';
// 通过urlSearchParams解析数值
let urlSearch = new URLSearchParams(query);
// 通过get方法获取到查询字符串name参数的值
let namevalue = urlSearch.get('name');
7、POST接口
- 场景:在server.js中写代码,提供一个名为add的接口(http://localhost:8083/add),它以post的方式请求接口,并传入name和skinname值,把数据保存到db/data.json中去。
- 前置知识
①post请求参数在请求体中,内容比较大(上传图片,上传文件…)
②后端是一段一段接收数据的,并不像get在请求行中传递的数据:直接写在url中的查询字符串内,可以立即通过req.url来解析出来
③在接收参数的过程中,会涉及req对象的两个事件data,end
data事件,每次收到一部分参数数据就会触发一次这个事件。
end事件,全部的参数数据接收完成之后会执行一次。 - 在接收参数的过程中,会涉及req对象的两个事件data,end
data事件,每次收到一部分参数数据就会触发一次这个事件。
end事件,全部的参数数据接收完成之后会执行一次。 - 场景:
在server.js中写代码,提供一个名为add的接口(http://localhost:8083/add),它以post的方式请求接口,并传入name和skinname值,把数据保存到db/data.json中去。
步骤:
在server.js中使用http创建一个web服务器,端口为8083
在createServer的回调函数中通过data和end事件获取post请求体中的参数
通过fs.writeFileSync将数据写入到/data/hero.json中
使用res.end()将成功或失败的结果响应回浏览器
使用postman进行接口测试
const http = require("http");
const fs = require("fs");
const app = http.createServer((req, res) => {
if (req.method === "POST") {
let result = "";
req.on("data", chunk => result += chunk);
req.on("end", () => {
console.log(result); // cname=aaa&skin_name=bbb
const usp = new URLSearchParams(result);
const hero = {
cname: usp.get("cname"),
skin_name: usp.get("skin_name"),
id: Date.now()
}
// 获取本地文件
const data = fs.readFileSync("./data/hero.json");
const list = JSON.parse(data);
list.push(hero);
fs.writeFileSync("./data/hero.json", JSON.stringify(list));
res.end("添加成功");
});
} else {
res.statusCode = 404;
res.end("找不到")
}
});
七、模块化的作用讲解
1、模块的作用
能够对一类功能做很好的分类管理
能够保护成员不被污染
不用考虑导入顺序
按需导入,可以随时更换模块,维护方便
2、模块化的发展
- 之前
es5不支持模块化,让前端人员很为难;
为了让es5支持模块化,我们一般会借用第三方库来实现:
sea.js
require.js - 现在
es6原生语法也支持模块化(并不表示浏览器也直接支持模块化 — 需要单独设置一下)
Nodejs内部也支持模块化(与es6的模块化有些不同之处),具体的语法在后面来介绍。
八、自定义模块的导出和导入
node.js中有三种模块:核心模块,自定义模块,第三方模块
1、基本语法规范
场景:使用自定义模块实现在index.js中使用tool.js中的加法函数
核心代码:
js文件名最好能够见名知意,例如,负责加减乘除运算的模块名称最好叫做 calc.js
自定义模块名称不能与核心模块同名,例如: fs.js 是不行的
模块中的成员可以通过module.exports关键字按需导出(想导出哪个由编码者决定)
使用固定关键字 require()导入模块,参数带有路径
2、两种导出方式的写法
在阅读其它人的代码时,可能会遇到这两种不同的写法。所以我们还是有必要了解一下的。
module.exports 关键字写法
exports 关键字写法
3、module.exports和export的关系
- exports是module.exports的别名 exports === module.exports // 输出是 true
- 初始exports和module.exports是指向同一块内存区域,其内容都是一个空对象
- 如果在对象上添加属性两个对象的用法是一样的
// 1 mymodule.js
exports.f = function(){}
exports.pi = 3.145926
// 2 mymodule.js
exports.exports.f = function(){}
exports.exports.pi = 3.145926
- 如果直接给对象赋值(例如:exports={a:1,b:2})exports和module.exports 就会不相等
- 在引入某模块时:以该模块代码中module.exports指向的内容为准。
九、第三方包管理工具npm的使用
1、npm工具是什么及其作用
- npm是node的包管理工具,它的出现是为了解决 Node中第三方包共享的问题
- 当我们谈到npm时,我们在说两个东西:
npm网站。这是一个第三方模块的"不花钱的超市",我们可以自由地下载,上传模块。
npm包管理工具,帮助我们去npm网站上下载,上传第三方包 - npm这个工具在安装node时,就会自动安装上
node-v
2、npm安装和使用包的基本用法
- 场景:通过npm下载一个格式化日期包并使用它
① 创建一个新的空文件夹(注意命名不能是中文或者)
注意:文件夹不能包含中文或者特殊字符
如果之前已经初始化,则可以省略
② 执行npm init --yes 初始化package.json
③ 去npmjs.com网站 搜索 关键字 date 找到合适的格式化日期包
④ 按照文档完成下载(使用npm install 包名称 下载)
⑤ 通过require使用第三方包
3、npm init命令
在某个目录下开启小黑窗,输入npm init 最后生成一个package.json文件
npm init
如果你希望直接采用默认信息,可以使用:
npm init -y
说明:
- 这个命令只需要运行一次,它的目的仅仅是生成一个package.json文件。
- 如果项目根目录下已经有了package.json文件,就不需要再去运行这个命令了。
- 这个package.json文件后期是可以手动修改的。
4、package.json文件
- 它的整体内容是一个json字符串,用来对当前项目进行整体描述
name: 表示这个项目的名字。如是它是一个第三方包的话,它就决定了我们在require()时应该要写什么内容。
version:版本号
keywords:关键字
author: 作者
descrption: 描述
dependencies: 存放当前下载的包和版本,执行npm install 包 下载后自动保存到此节点
5、npm install 命令
- 作用:npm install可以安装一个第三方包
- 语法格式: npm install 第三方包 或 npm install 第三方包@版本号
- 举例:
npm install dayjs 安装最新版本
npm install dayjs@1.10.5 指定安装版本 1.10.5
6、node_modules文件夹
- 作用
在使用npm install 命令时,会从npm网站下载对应的包,这些包的源代码会保存到这个文件夹中 - 执行逻辑
① 当键入npm install XXX之后,这里假设这个XXX包是存在的,也没有出现任何的网络错误
② 当前目录如果没有node_modules文件夹则创建它
③ 下载XXX包保存到node_modules中,如果XXX还有其他依赖包也一并下载下来保存到node_modules中 - 注意:我们在给别人分享代码的时候,通常会把node_modules文件夹排除,其他人拿到代码后,直接执行npm install回车后就会自动下载所有的包(会查找 package.json中保存好的所有包)
7、区分本地包和全局包
(1)本地安装(或者叫局部安装):
包安装在当前项目的根目录下(与package.json同级)的node_modules中
命令:npm install 包名
(2)全局安装:
包被安装到了全局的node_modules中
命令:npm install -g 包名 或者 npm install 包名 -g
(3)查看全局的node_modules路径:
npm root -g // 查看全局包的安装目录
npm list -g --depth 0 //查看全局安装过的包
全局安装的包:一般可提供直接在小黑窗执行的命令。我们通过对一些工具类的包采用这种方式安装,如:nodemon,nrm,yarn等。
本地安装的包:是与具体的项目有关的, 我们需要在开发过程中使用这些具体的功能。 如:前面用过的日期格式化包。
一个经验法则:要用到该包的命令执行任务的就需要全局安装, 要通过require引入使用的就需要本地安装。
8、全局包nodemon
(1)作用
- nodemon:别人开发的一个第三方包,它能替代node命令去帮我们执行一个js文件,并自动检测到我们的代码如果有修改就会自动重新运行我们的代码
- nodemon包地址:https://www.npmjs.com/package/nodemon
(2)用法
前置回顾:之前启动方式 node server.js
现在可以使用: nodemon server.js启动服务器
如何使用nodemon以及使用后的效果是什么:
① 全局安装nodemon: npm install -g nodemon 注意:此操作需要联网
② 进入server.js所在文件夹打开小黑窗执行 nodemon server.js即可启动服务器
③ 修改server.js 后nodemon会自动重启 server.js
注意: 默认情况下,nodemon会监控server.js同级目录下的任何内容发生改变都会导致重启,可以加上–ignore来排除一些文件不被监控 例如:忽略监控data.json文件,nodemon --ignore data.json server.js
(3)常见错误
nodemon运行 提示错误:无法加载文件…
解决办法
以管理员身份运行PowerShell
执行:get-ExecutionPolicy,回复Restricted,表示状态是禁止的
执行:set-ExecutionPolicy RemoteSigned
输入Y
9、全局包nrm
(1)作用:
- nrm:别人开发的一个第三方包,可以帮助我们切换安装包的下载源地址
- 为什么要切换包的下载源:
因为下载包时,默认是从npm官网(国外的网站)下载,速度会比较慢,我们可以手动去切换安装来源为国内的地址,这样能加快下载速度。 - nrm包的地址:https://www.npmjs.com/package/nrm
(2)用法
第一步: 全局安装
npm install nrm -g
第二步:列出所有的源信息
(*)标注的就是当前使用的源
使用 nrm ls 命令可以查看
第三步:根据需要切换源
使用 nrm use taotao 切换到taobao源
验证:此时使用npm install 下载包时就会去taobao源下载,由于在国内,下载速度会快一些
10、npm卸载包
- 卸载包分为两种:本地包和全局包的卸载
- 本地包卸载:
进入到你想要卸载的包所在的文件夹,到package.json这一层即可
打开cmd小黑窗
在小黑窗中执行 npm uninstall 包名 简写: npm un 包名
npm un 包名1 包名2 - 全局包的卸载:
在任意地方打开小黑窗
输入 npm uninstall 全局包名 -g 简写:npm un 全局包名 -g
npm un 包名1 包名2 -g
11、npm包从创建到发布
(1)npm发包到npmjs.com上
- 场景:将本地开发的名为calc67的包发布到npmjs网站上
- 前置操作:请先去npmjs.com上注册一个用户,并通过邮箱验证
- 操作步骤:
① 项目初始化: 创建文件夹 calc67,小黑窗中输入:npm init --yes
注意:文件夹名称不能是中文或带有特殊字符,
通过 npm view calc67 检查一下npmjs上如果已经有calc65这个包了,则换一个名字
② 在calc67中创建一个calc.js文件并定义加法函数并导出(module.exports={add})
③ 切换当前npm源到npmjs上: nrm use npm
④ 连接npm: npm adduser (输入npmjs.com上注册时的用户名,密码,邮箱)
⑤ 把包上传到npm: npm publish
⑥ 上传成功验证:在小黑窗能看到 +calc67@1.0.0 表示成功
⑦ 通过 npm install calc67测试下载
(2)上传包出错的可能
- 这个包名被别人先用了。
- 包的版本号不对:每次publish时,包的版本号都应该要大于之前的版本号。
- 文件过大。你可能需要创建.npmignore文件来设置在打包时要忽略哪些文件。如下是一个demo
.npmignore文件中的内容
/node_modules
npm-debug.log
/src
/examples
/build
(3)能够使用npm更新calc67这个包
修改calc.js代码,保存。
更新版本号。可直接在package.json中修改:只能改大,不能改小。
执行 npm publish完成更新
注意: npm publish执行前确保已经在小黑窗中通过adduser登录到了 npmjs.com上
(4)能够使用npm删除calc65这个包
① 在calc67这个文件里打开小黑窗
② 执行:npm unpublish 包名称 --force //强制删除
发布一个包24小时以内是不可以删除的,超过了24小时就允许删除了
注意:npm unpublish --force执行前确保已经在小黑窗中通过adduser登录到了 npmjs.com上
十、核心模块、自定义模块、第三方包的require规则
1、核心模块加载规则
加载核心模块的格式是 const xxx = require(“模块名”) ,如: require(‘fs’)
加载核心模块,直接从内存中加载,并缓存
同一个模块第一次require之后,就会缓存一份,下一次require时就直接从缓存中去取。
2、自定义模块加载规则
- 如果require()中的参数是一个路径,则根据路径加载自定义模块,并缓存
const tool = require('./tool.js')
const tool1 = require('./tool.js')
consloe.log(tool === tool1) // true
- require加载自定义模块,没有扩展名的情况下,查找文件顺序:
以require(‘./main’) 为例
先加载 main 文件
如果没有再加载 main.js
如果没有再加载 main.json,
如果没有再加载 main.node(c/c++编写的模块)
找不到就报错。
3、第三方模块加载规则
- 加载第三方模块的格式是 const xxx = require(“模块名”) ,如:require(“date-format”) 并缓存
- 以加载date-format这个第三方包为例node判断date-format不是核心模块,就会按照如下方式查找
① node 会去本级目录 node_modules下查找date-format模块
② 如果找不到,则查找上级目录node_modules下查找date-format模块
③ 直到查找到盘符根目录(例如:F:)下
④ 如果每一级目录都找不到就报模块找不到错误,如果在某一级目录找到了,则停止查找
十一、Express基本用法
1、使用Express创建web服务器基本步骤
① 先回顾使用nodejs创建服务器
② 使用Express创建服务器
// 1. 引入express框架
const express = require("express");
// 2. 通过构造函数创建服务器
const app = express();
// 3. 设置请求回调,express中有一个send方法可以响应数据,就算返回中文也不需要设置响应头,
//因为框架已经帮我们处理了
app.get("/", (req, res) =>{ res.send("你好,express"); } );
// 4. 监听8080端口
app.listen(8080);
其中1,2,4步是固定的,第3步根据具体需求来决定
2、接收Get 和 Post请求
Express给我们提供了非常方便的处理请求的方法
语法格式: app.method(‘请求路径’,回调函数)
举例:
① 接收get请求: app.get(‘/getlist,(req,res)=>{ res.send(‘响应内容’) })
② 接收post请求: app.post(’/update,(req,res)=>{ res.send(‘响应内容’)})
注意:express中建议大家使用send方法,因为它会自动根据内容决定响应Content-Type的类型
当然,之前的res.end()也是可以使用的
3、Express路由
路由格式
const app = express()
// 定义路由
app.METHOD(PATH,HANDLER)
app 是 express 实例 。(const app = express())
METHOD 是一个 HTTP 请求方法。 全小写格式。如:post,get,delete等
PATH 是请求路径
HANDLER 是当路由匹配到时需要执行的处理函数。(req,res)=>{ }
4、req对象
- req.url : 获取请求url
- req.method:获取请求方法
- req.query: 获取url传入的查询字符串参数
请求的url如果带有参数则会自动解析到query中,例如:/getHero?heroname=妲己 - req.params : 获取url传入的路由参数
- req.body:获取post请求体中的参数
需要配合内置中间件完成 app.use(express.urlencoded()) 和 app.use(express.json()) - res.send() : 作用类似于http模块中的res.end()
举例:res.send(‘要响应的数据’) 它会自动增加content-type响应头,支持直接js对象参数作为响应数据
res.send({name:“张三”}) - res.json(): 直接将一个js对象或者数据以json字符串返回:res.json({name:“张三”})
- res.status(): 设置响应状态码: res.status(404)
- res.set() : 设置响应头
res.set(‘content-type’,‘text/html;charset=utf8’)
res.set(‘Access-Control-Allow-Origin’, “*”);
十二、Express处理get和post请求参数
1、GET方法获取参数
场景:假设访问URL为/getUser?name=tom 需要获取name的值
关键技术:可以通过req.query获取,res.query 是一个对象
代码如下:
// 访问路劲为/getUser?name=tom
app.get('/getUser',(req,res)=>{
consloe.log(req.query)//输出{name:'tom'}
res.send('获取到name参数的值为='+req.query.name)
})
案例:使用express实现获取英雄皮肤
需求:使用express 结合 data.json 完成如下两个要求
开启GET接口 通过 http://127.0.0.1:3001/getHeroSkin?heroName=后羿获取后裔的皮肤数据响应会客户端
分析:
① 需要使用express基本步骤开启web服务器,监听端口:3001
② app.get(‘/getHeroSkin’,(req,res)=>{ }) 定义get请求接口用于获取皮肤数据逻辑代码
③ 使用 res.set(‘Access-Control-Allow-Origin’, “*”); 完成允许CORS跨域设置
④ 使用req.query获取heroName参数值
⑤ 获取data.json中的数据完成过滤后res.send()响应回去
const express = requir('express');
const app = express();
app.get('/getHeroSkin',(req,res)=>{
res.set('Access-Control-Allow-Origin', "*");
const { heroName } = req.query;
res.send(heroName)
});
app.listen(3001,()=>{
consloe.log("开启成功,访问地址:http://127.0.0.1:3001/getHeroSkin");
));
2、POST方法获取参数
(1) 使用 express.urlencoded() 内置中间件来处理
场景:假设我们对/postdata发起post请求,请求参数也是txt=tom,如何获取txt参数的值?
步骤:
① 在express对象实例上app上注册urlencoded中间件:
app.use(express.urlencoded())
②使用req.body就可以获取到post请求体中的参数:
express.urlencoded()要和客户端的conent-type:applicaiton/x-www-form-urlencoded格式匹配
案例:使用express实现向data.json添加英雄皮肤
需求:使用express 结合 data.json 完成如下两个要求
开启POST接口通过 http://127.0.0.1:3001/addHeroSkin 将请求体数据{cname:’妲己’,skin_name:’妲己皮肤’} 添加到皮肤数据中
分析:
① 需要使用express基本步骤开启web服务器,端口:3001
② app.post(‘/addHeroSkin ’,(req,res)=>{}) 定义post请求接口,编写添加皮肤数据逻辑代码
③ 使用 res.set(‘Access-Control-Allow-Origin’, “*”); 完成允许CORS跨域设置
④ 通过req.body接收参数值,添加data.json中
⑤ 通过res.send()响应添加成功
const express = require('express')
const app = express()
const fs = require('fs')
app.post('/addHeroSkin',(req,res)=>{
res.set('Access-Control-Allow-Origin', "*");
const data = req.body
fs.writeFileSync("./hero.json", JSON.stringify(data));
res.send('添加成功');
})
app.listen(3001,()=>{
console.log('Example app listening at http://localhost:3001')
})
(2)使用 express.json() 内置中间件来处理
场景:假设我们对/postdata发起post请求,请求参数{txt:‘张三’},如何获取txt参数的值?
步骤:
① 在express对象实例上app上注册json中间件:
app.use(express.json())
② 使用req.body就可以获取到post请求体中的参数
express.json()要和客户端的conent-type:applicaiton/json格式匹配
3、express.urlencoded() 和 express.json()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
十三、Express处理静态资源
1、使用Express托管
将右下角图中的public中的所有静态资源使用Express托管
只需一句代码就可以搞定了: express.static(‘public’)
具体使用:
// 加载express
const express = require('express')
// 调用express()得到一个app,类似于http.createServer()
const app = express();
// 设置请求对应的处理函数
app.use(express,static('public'))
// 监听端口号,启动Web服务
app.listen(3000,()=> return console.log('开启成功,访问地址:http://127.0.0.1:3000'))
此时,所有放在public下的内容可以直接访问
浏览器输入:http://127.0.0.1:3000/index.html 即可访问到index.html
注意,此时在url中并不需要出现public这级目录
2、添加限制前缀
将右下角图中的public中的所有静态资源使用Express托管-添加限制前缀
要求:浏览器输入:http://127.0.0.1:3000/static/index.html 才能可访问到index.html
关键代码:
// 限制访问前缀
app.use('/static',express.static('public'))
这意味着想要访问public下的内容,必须要在请求url中加上/static
3、POST方法-文件上传中间件-multer
- 步骤:
打开multer文档
安装 multer包:
npm install multer
根据文档进行使用,核心就三步
- multer.diskStorage方法 自定义文件存储路径和文件名
destination:自定义文件存储路径
filename:自定义文件存储名称
4、理解接口传参 - ajax请求服务器接口时content-type常见有三种取值:
- express处理不同content-type的方案:
5、路由中间件的使用 - 使用场景:接口数量较多时,需要分类别管理,此时就需要用到路由中间件
- 如下5组接口,并且每组接口都有特定的访问前缀
- 用法:
const Router = express.Router() ; // 定义路由对象
Router.get(‘/’,(req,res)=>{}); // 创建路径和对于的处理函数
app.use(Router); // 全局使用 或者 app.use(‘/路径,Router) // 局部使用
- 实现思路
① 整理接口名。对众多的接口名进行整理和分类
② 通过nodejs的模块化(分别写在不同的文件中),分模块定义路由中间件,并导出
③ 在主文件中,导入并使用路由中间件
十四、Express中间件
1、理解
express中间件是一个特殊的url地址处理函数,一个 express 应用,就是由许许多多的中间件来完成的
作用
- 执行任何代码。
- 修改请求和响应对象。
- 终结请求-响应循环(结束请求)。
- 调用堆栈中的下一个中间件
2、express中间件-语法格式
中间件本质就是一个函数,它被当作 app.use(中间件函数) 的参数来使用
定义格式
// 具名函数格式:
const handler = (req,res,next)={
console.log(Data.now());
next();
}
app.use(handler)
//匿名函数格式:
app.use((req,res,next)=>{
console.log(Data.now());
next();
})
req:请求报文对象
res:响应报文对象
next()作用:只有显示调用了next()才会进入到下一个中间件或者路由处理函数的执行,否则就到此为止
3、四种匹配规则
- app.use(中间件)是应用级中间件,所有的请求都能匹配。
- app.use(‘/apiname’,中间件) 。匹配请求路径是/apiname的请求。
- app.get(‘/apiname’,中间件) 。匹配get类型并且请求路径是/apiname的请求,就是我们前面说的路由。
- app.get(‘/apiname’,中间件1,中间件2) 。一个路由中使用多个中间件。
4、中间件分类
- 应用级中间件
app.use((req,res,next)=>{})
app.get('/getlist',(req,res,next)=>{ },(req,res)={ })
- 内置中间件
app.use(express.static('public'))
app.ues(express.erlencode())
app.use(express.json())
- 路由级中间件
let router = express.Router()
router.get('/getlist',(req,res)=>{ })
app.use(router)
- 错误处理中间件
app.use((err,req,res,next)=>{ })
错误中间件可以用来统一捕获当前请求处理过程中的一些异常
定义格式:
app.use((err,req,res,next)=>{
console.error(err,stack)
res.status(500).send('Something break')
})
注意: 错误中间件要定义在其他处理逻辑的后面(写到最后面)
- 第三方中间件
multer //文件上传
5、RESTful接口
RESTful API是目前比较成熟的一套互联网应用程序的API设计理论
REST本身并没有创造新的技术、组件或服务,REST指的是一组架构约束条件和原则