什么是Node
Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
Node环境的主要模块
一. fs文件操作系统
:操作文件模块,提供一些操作文件的方法和属性。
1.1 fs.readFile 读取文件
// 导入fs模块
const fs = require('fs')
//readFile(目录,解码格式,读取结果的回调)
fs.readFile('./2.txt', 'utf-8', function (err, success) {
// 当读取失败时,err为一个包含其原因属性的对象;成功时为Null;以下操作可以试你辨别结果。
if (err) {
console.log('读取失败:', err.message)
}
console.log(success)
})
1.2 fs.writeFile 写入文件
注意:
1.重复写入会覆盖上次写入的内容;
2.只能创建写入的文件,不能创建写入的目录即必须事先创建好目录。
const fs = require('fs')
('写入路径',写入内容,写入回调)
fs.writeFile('./2.txt', 'helloNode', function (err) {
if (err) {
console.log('写入失败')
}
console.log('写入成功')
})
综合demo:
将源文件内容 :小明=98 小暗=100 小正=60,转换为以下格式并写入指定位置的文件中。
小明:98
小暗:100
小正:60
const fs = require('fs')
// 读取原数据
fs.readFile('./成绩案例.txt', 'utf-8', function (err, success) {
if (err) {
console.log('读取错误', err.message)
}
console.log('读出成功', success)
// 1.将其内容转换为数组;
let oldArr = success.split(' ')
let newArr = []
// 2.对每一项都进行替换操作,将替换完成项添至新数组。
for (let i in oldArr) {
newArr.push(oldArr[i].replace('=', ':'))
}
// 3.将数组转换成字符回车分割('\r\n')
let a = newArr.join('\r\n')
// 4.将字符写入到指定目录下
fs.writeFile('./成绩处理完毕.txt', a, function (err) {
if (err) {
console.log('写入失败')
}
console.log('写入成功')
})
})
2. 使用相对路径出现的路径拼接问题
代码在运行的时候,会以执行 node 命令时所处的目录动态拼接,出被操作文件的完整路径
如:在A\B\C该目录中打开终端
如果我们在C目录下启用NodeJS文件通过./ 读取内容则拼接的目录为 : A\B\C\内容文件
但如果我们在 \B目录启用 \C NodeJS文件通过./ 拼接的目录为 : A\B\内容文件
解决:使用__dirname返回当前执行的JS文件目录,
这样无论在哪执行Node命令都以当前Js的目录作为基准,以这个位置进行向上、下层的匹配。
fs.readFile(__dirname + './1.txt', 'utf-8', function (erro, success) {
if (erro) {
console.log('读取失败' + erro.message)
}
二. path路径模块
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。
2.1 路径拼接 path.join
path.join('路径片段','路径片段')
// 当前Js文件目录下的
const path = require('path')
const fs = require('fs')
// 注意 ../ 会抵消前面一层路径
// ./ 会被忽略
const pathStr = path.join('/a', '/b/c', '../../', './d', 'e')
console.log(pathStr) // \a\d\e
2.2 返回指定目录的文件名 path.basename(url,【扩展名】)
2.3 返回指定文件的扩展名 path.extname()
let url = \a\b\lol.txt
console.log(path.basename(url)) // 'lol.txt'
//basename(url, '.txt'),除拓展名返回
console.log(path.basename(url, '.txt')) // 'lol'
console.log(path.extname(url)) // .txt
fs.path综合案例:
将以下HTML文件的css、js进行抽离 => 通过linK href导入样式,通过script src导入js
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#time {
color: pink;
}
</style>
</head>
<body>
<p id="time">look</p>
</body>
<script>
let id = document.querySelector('#time')
setInterval(() => {
id.innerHTML = new Date()
}, 1000);
</script>
</html>
const fs = require('fs')
const path = require('path')
// 1 设定匹配style和script内容的正则
let regStyle = /<style>[\s\S]*<\/style>/
let regJs = /<script>[\s\S]*<\/script>/
fs.readFile(path.join(__dirname, '/demo.html'), 'utf8', function (err, success) {
if (err) { console.log('读取失败' + err) }
getX({
content: success,
tag: 'style',
resolveUrl: path.join(__dirname, '/html/index.css'),
reg: regStyle
})
getX({
content: success,
tag: 'script',
resolveUrl: path.join(__dirname, '/html/index.js'),
reg: regJs
})
filterHtml(success)
})
//2.分解指定的内容并写入到指定位置。
let getX = function ({ content, tag, resolveUrl, reg }) {
// 2.1过滤指定标签的内容
let filterTagContent = reg.exec(content)
// 2.2过滤内容中的标签
let endContent = filterTagContent[0].replace('<' + tag + '>', ' ').replace('</' + tag + '>', ' ')
// 2.3 写入最终的内容至指定位置
fs.writeFile(resolveUrl, endContent, function (err) {
if (err) {
console.log('写入失败')
}
console.log('写入成功')
})
}
// 3.过滤Html将其内部的样式和JS为外部导入式
function filterHtml (content) {
let html = content.replace(regStyle, "<link rel='stylesheet' href='./index.css'/>").replace(regJs, "<script src='./index.js'></script>")
fs.writeFile(path.join(__dirname, '/html/index.html'), html, function (err) {
if (err) {
console.log('写入失败')
}
console.log('写入成功')
})
}
三、http 模块
将本机转换为服务器,可以提供一些服务器功能
1、配置服务器的流程
//1.导入http模块
const http = require('http')
//2.创建服务器实例
const server = http.createServer()
//3.服务器绑定请求事件
server.on('request', function (req, res) {
console.log(req.method)
// req:客户端对象
let url = req.url
let method = req.method
console.log(`a requset : from:${url},method:${method}`)
// 解决响应中文乱码问题
res.setHeader('content-Type', 'text/html;charset=utf-8')
// 4.设定响应
res.end('服务是要💴的')
})
//5.启动801端口服务
server.listen(801, () => {
console.log('server running at http://127.0.0.1:801')
})
2.设置不同的请求文件地址的响应值
let http = require('http')
const server = http.createServer()
server.on('request', (req, res) => {
// 获取用户请求的文件地址
let url = req.url
let resContent = '404您请求的资源不存在'
if (url == '/' || url == '/index.html') {
resContent = '首页'
}
else if (url == '/about.html') {
resContent = '关于'
}
res.setHeader('content-Type', 'text/html;charset=utf-8')
res.end(resContent)
})
server.listen(201, () => {
console.log('server running at http://127.0.0.1:201')
})
四、express模块
express模块基于HTTP模块所封装出的服务器模块能够极大的提高开发效率。
作用:
使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器
··Web 网站服务器:专门对外提供 Web 网页资源的服务器。
·· API 接口服务器:专门对外提供 API 接口的服务器。
##基本使用
//1.导入内置express模块
const express = require('express')
//2.创建实例对象
const server = express()
//3.开启服务
server.listen(80, () => {
console.log('服务端口已启动http://127.0.0.1')
})
监听POST和GET请求的方法
//参数1:请求的Url地址
//参数2:请求对应的处理函数
server.get('/getNaem', (req, res) => {
// 用户在url地址后通过/key=value&key=value进行传递静态参数
// 通过REQ.QUERY可以接受传递的静态参数
res.send(req.query)
})
server.post('/getName', (req, res) => {
res.send('ws')
})
//用户通过url/key/key传递 ':'后对应的动态参数
//通过req.params接受动态参数的值
server.get('/getName/:id/:name', (req, res) => {
res.send(req.params)
})
静态资源托管
对HTTP模块的静态资源托管进行的封装,本质还是通过鉴定请求的url后根据设定路径响应资源。
如下:将file目录进行暴露,客户端可匹配该目录的内容。
const express = require('express')
const app = express()
// 在这里,调用 express.static() 方法,快速的对外提供静态资源
/*
express.static(响应的资源目录前缀,映射目录)
*/
app.use('/files', express.static('./files'))
app.use(express.static('./clock'))
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
多静态资源部署
/*
当部署多个目录的静态资源,不同目录中存在相同文件名时,匹配率先部署目录中的文件;后方目录文件不在进行匹配。
如想分别匹配同名文件,需为url地址添加目录前缀名作为区分。
*/
//use(路径前缀,映射目录)
server.use('/html', express.static('./html'))
server.use('/helloworld', express.static('./helloworld'))
Express路由
在 Express 中,路由指的是客户端的求请与服务器处理函数之间的映射关系。(根据请求执行对应的处理函数
Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数
路由的使用:
// 全局文件下挂载的路由
const express = require('express')
const app = express()
// 挂载路由*2
app.get('/', (req, res) => {
res.send('hello world.')
})
app.post('/', (req, res) => {
res.send('Post Request.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
由于我们可能设定多路由规则,故将路由进行抽离。
# routeModule.js
// 这是路由模块
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = router
2.全局注册路由
#app.js
const express = require('express')
const app = express()
// app.use('/files', express.static('./files'))
// 1. 导入路由模块
const router = require('./03.router')
// 2. 注册路由模块
app.use('/api', router)
// 注意: app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('http://127.0.0.1')
})
路由的匹配规则:
服务器按照请求的先后顺序进行路由的匹配,匹配时需请求方法与请求Url都符合才会执行对应的处理函数。
为路由添加前缀
为该模块的路由统一添加前缀路径'/api',这样不同模块之间也可以存在相同路由规则。
server.use('/api', router.router)
路由中间件
中间件(Middleware ):特指业务流程的中间处理环节
路由中间件:当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
全局中间件:
使用app.use()注册的中间件;
所有请求都进行该预处理
const express = require('express')
const app = express()
// 定义第一个全局中间件
app.use((req, res, next) => {
console.log('调用了第1个全局中间件')
//next 后 进入下一个处理
next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
console.log('调用了第2个全局中间件')
next()
})
// 定义一个路由
app.get('/user', (req, res) => {
console.log('User page.')
res.send('User page.')
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
/*
调用了第1个全局中间件
调用了第2个全局中间件
User page.
*/
局部中间件
不使用app.use()注册的中间件;
在路由处理函数单独使用的预处理函数
const express = require('express')
const router = express.Router()
//中间件处理函数必须在定义路由前定义
const a = function (req, res, next) {
req.games = ['lol', 'cs']
// next执行后才能进入下个处理
next()
}
const b = function (req, res, next) {
for (let i in req.games) {
req.games[i] += '职业选手'
}
next()
}
// 局部多个中间件处理
router.post('/play', [a, b], (req, res) => {
console.log('您的请求正在处理')
res.send(req.games)
})
module.exports.router = router
错误中间件
当服务器发生错误时,错误中间件可以捕获错误。
注意:错误中间件注册与所有路由之后
router.post('/err', (req, res) => {
throw new Error('服务器被黑客黑了')
})
// 错误中间件:捕获项目中出现的错误。
// 注意:需要放在所有配置路由后使用。
router.use((err, req, res, next) => {
console.log(err.message)
res.send(err.message)
next()
})
内置中间件
第三方中间件
中间件的使用指南:
除错误中间件注册在所有路由配置之后,其他中间件都应注册在设定路由之前。
每个中间件函数处理完毕后都应next()进入下个处理函数。
所有中间件函数的req,res都是继承的,故上游中间件可以为下游中间件及最后的路由处理函数提供资源
请求资源跨域
由于三源协议导致浏览器只能请求同协议 、同地址、同端口号的服务器资源。
解决跨域
script
、link
、img
可以进行跨域请求并成功接收响应。
1.jsonp处理
通过<Script>的src属性添加请求。
服务器将响应的值返还到<Script>内,从而实现跨域请求。
//# 服务端
const router = require('./接口router')
const express = require('express')
const server = express()
server.get('/api/jsonp', (req, res) => {
var data = '111';
//向Script标签中直接传输了111.
res.send(data)
})
server.use(cors())
server.listen(80, () => {
console.log('服务已启动:http://127.0.0.1')
})
//客户端
<script src="http://127.0.0.1/api">
// 在此会接受服务端发过来的111,但由于并不是一条可解析的语句从而语法错误
</script>
1.展现:服务发送的内容必须为js可解析的语句
调整
//# 服务端
const router = require('./接口router')
const express = require('express')
const server = express()
server.get('/api/jsonp', (req, res) => {
var data = '111';
let jsStr = `console.log(${data})`
// 响应了一条固定的Js语句
res.send(jsStr)
})
server.use(cors())
server.listen(80, () => {
console.log('服务已启动:http://127.0.0.1')
})
//客户端
<script src="http://127.0.0.1/api">
// console.log(111)
</script>
2.展现:服务端写死返回的处理语句。
调整
//# 服务端
const router = require('./接口router')
const express = require('express')
const server = express()
server.get('/api/jsonp', (req, res) => {
var data = '111';
// 响应客户端预先定义好的函数调用语句。
res.send('sayHello()')
})
server.use(cors())
server.listen(80, () => {
console.log('服务已启动:http://127.0.0.1')
})
//客户端
<script>
function sayHello(){
//自定义操作
}
</script>
<script src="http://127.0.0.1/api">
// 响应:sayHello()
</script>
3.展现 : 客户端预先定义函数,服务端返回对应的函数处理语句,
缺点: 如果客户端处理函数名发生改变则服务器需同步匹配
调整
//# 服务端
const router = require('./接口router')
const express = require('express')
const server = express()
server.get('/api/jsonp', (req, res) => {
// 客户端将函数名携带在请求里
let fnName = req.query.fnName
let obj = {name:"ws"}
// 拼接返回的函数调用语句
let jsStr = ` ${fnName}(Json.string(jsStr)) // logName({name:'ws'}) `
res.send('sayHello()')
})
server.use(cors())
server.listen(80, () => {
console.log('服务已启动:http://127.0.0.1')
})
// 客户端
<script>
function logName(obj){
console.log(obj.name)
}
</script>
<script src="http://127.0.0.1/api?fnName = logName">
// logName({name:'ws'})//'ws'
</script>
展现: 通过请求携带参数告诉服务器拼接的响应函数名 。
缺点:因为整个文档只有这么一个Script元素,所以解析器只会解析一次。
客户端只会发送一次请求,而不能像按钮一样按需请求。
调整:
//客户端
<body>
<button id="json">Json</button>
</body>
<script>
let jsonBtn = document.querySelector('#json')
// 1.点击时添加一个script实例,并且指定sr=''配置请求
jsonBtn.addEventListener('click', () => {
let s = document.createElement('script')
s.src = 'http://127.0.0.1/api/jsonp?fnName=getDate'
let body = document.querySelector('body')
//2.将这个创造好的请求节点插入到文档中,通过请求节点重新发送请求
body.appendChild(s)
//3.由于点击一次就会添加一个请求节点,所以我们在请求节点使用后进行自灭
body.removeChild(s)
})
//预留回调
function getDate (data) {
console.log(data)
}
</script>
<script src="http://127.0.0.1/api/jsonp?fnName=getDate">
</script>
//# 服务端
const router = require('./接口router')
const express = require('express')
const server = express()
server.get('/api/jsonp', (req, res) => {
// 客户端将函数名携带在请求里
let fnName = req.query.fnName
let obj = {name:"ws"}
// 拼接返回的函数调用语句
let jsStr = ` ${fnName}(Json.string(jsStr)) // logName({name:'ws'}) `
res.send('sayHello()')
})
server.use(cors())
server.listen(80, () => {
console.log('服务已启动:http://127.0.0.1')
})
2.服务端cors配置
使用 cors 中间件解决跨域问题 cors 是 Express 的一个第三方中间件。
使用步骤
① 运行 npm install cors 安装中间件
② 使用 const cors = require('cors') 导入中间件
③ 在路由之前调用 app.use(cors()) 配置中间件
响应头配置
res.setHeader(Access-Control-Allow-字段名:value)
字段名 | 作用 | 可选值 |
-Origin | 允许访问该资源的外域 URL | *(所有url) 、指定Url |
-Headers | 对非官方额外的请求头进行声明 | 自定义 |
-Methods | 除get、post、head方法外,指明实际请求所允许使用的 HTTP 方法。 | PUT、DELETE |
简单请求
同时满足以下两大条件的请求,就属于简单请求:
① 请求方式:GET、POST、HEAD 三者之一
② HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、 Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-formurlencoded、multipart/form-data、text/plain)
预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求:
① 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
② 请求头中包含自定义头部字段
③ 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一 次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。