Node.js基础:从入门到实战
初识 Node.js 与内置模块
(初识)
1、知道什么是node.js
2、知道node.js可以做什么
3、node.js 中js的组成部分
(内置模块)
4、用 fs 模块读写操作文件
5、使用 path 模块处理路径
6、使用http 模块写一个基本的web服务器
初识 Node.js
回顾与思考
回忆浏览器中的 JavaScript 组成:
为什么JavaScript可以在浏览器中被执行,是因为浏览器中存在JavaScript解析引擎,而不同的浏览器使用的解析引擎是不一样的:
因此可以总结出 JavaScript 的运行环境就包括下面两个部分:
由上述内容我们可以总结出两点:
1、V8 引擎负责解析和执行 JavaScript 代码
2、内置 API 是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
那么 JavaScript 能否做后端开发呢?
当然是没有问题的,JavaScript 本身只是一门语言,这门语言所编写出来的代码想要执行的话离不开运行时环境,如果将 JavaScript 的代码放在浏览器里运行那么浏览器本身就是一个运行时环境,此时 JavaScript 就可以用来做前端开发,同时如果我们将 JavaScript 代码放在 Node.js 环境(Node.js 也是一个 JavaScript 的运行时环境,只不过是一个后端的运行时环境)当中,通过 Node.js 就可以让我们的 JavaScript 去做后端开发了。
Node.js 简介
先来看一段官方的介绍:
为什么是基于 Chrome V8 呢?之前说过,因为它最牛逼。所以使用 V8 来做 Node.js 的引擎用以解析 JavaScript 的代码,这样可以让 JavaScript 跑的更快。
所以我们的 Chrome 浏览器和 Node.js 用的是同一款 JavaScript 解析引擎,即 V8 ;
只不过 V8 用在不同的地方做的事情是不一样的,在 Chrome 浏览器中用 V8 解析 JavaScript 做的是前端的开发,而在 Node.js 中使用 V8 解析 JavaScript 则做的是后端开发,仅此而已。
Node.js 的运行时环境
可以看见,Node.js 为我们提供了后端开发所需要的所有模块的 API,因此学习 Node.js 很大程度上就是学习怎么使用这些内置的后端开发的 API。
Node.js 可以做什么
一句话,全栈!
Node.js 怎么学
Node.js 环境的安装
官方网址:Node.js
直接下载完成后双击安装即可,一路按照默认的来就行。
检测安装是否成功,打开 cmd 命令终端输入 node -v,能正常显示版本号即正常:
这样就安装完成了。
什么是终端
在 Node.js 环境中执行 JavaScript 代码
下面介绍的是比较朴素的方式,因为现在都使用 IDE 了,我用的是 WebStorm,比较推荐,IDE 的使用就不介绍了,网上很多,不再赘述。
就两步:
先来写一个 1.js 文件:
然后进入终端,在当前文件目录的命令行下,用 node 命令启动即可:
这样就可以运行 JavaScript 代码啦。
有一种便捷打开命令行的方式,即在我们的文件目录下,按住 shift 后点击鼠标右键会有一个打开 PowerShell 的选项:
点击它:
可以看见这种方式更加的快捷,那么 PowerShell 和我们之前的 CMD 方式有什么区别呢?
CMD 方式是出现的比较早的一种方式,是旧版本的 windows 里的终端,后来 windows 做了升级,改成了 PowerShell ,也就是新版本的 CMD(其实用哪个都行,但是 PowerShell 的功能更加强大一些)。
终端中常用的一些快捷键:
使用WebStorm编写 Node.js 代码没有提示问题解决
参考这篇文章:使用WebStorm编写 Node.js 代码没有提示问题解决
fs 文件系统模块
什么是 fs 文件系统模块
我们只需要知道在安装 Node.js 的时候这些模块就都被安装到电脑本地了即可,然后需要用到哪个模块,就使用 require 方法将其导入进来即可。
读取指定文件中的内容
fs.readFile() 的语法格式
示例代码如下:
// 1、到入 fs 模块操作文件
const fs = require('fs')//2、调用 fs.readFile() 方法读取文件
// 参数1、 读取文件的存放路径
// 参数2、 读取文件时候采用的编码格式,一般默认指定 utf8
// 参数3、 回调函数,拿到读取失败和成功的结果,err dataStr
fs.readFile('./1.txt', 'utf8', function (err, dataStr){// 打印失败的结果// 如果读取成功,则err 的值为null// 如果读取失败,则 err 的值为错误对象,dataStr 的值为 undefined// 因此可以通过判断 err 对象是否为 null,来判断文件读取是否成功console.log(err)console.log("----------------")// 打印成功的结果// 如果读取成功,那么dataStr就是从文件当中读取的值// 如果读取失败,那么dataStr就是undefinedconsole.log(dataStr)
})
运行结果如下:
关于fs.readFile()第三个参数是回调函数的解释:
fs.readFile('./1.txt', 'utf8', function (err, dataStr){console.log(err)console.log("----------------")console.log(dataStr)
})
这段代码中的回调函数是作为 fs.readFile 方法的最后一个参数传递进去的。根据 Node.js 中的约定,回调函数的第一个参数通常用于表示错误信息,第二个参数则是成功时返回的数据或结果(这是 fs.readFile() 函数的形式约定)。
所以,当你使用 fs.readFile 方法时,如果文件读取成功,Node.js 会将 err 参数置为 null,而将文件内容作为 dataStr 参数传递给回调函数;如果文件读取失败,Node.js 会将 err 参数设置为相应的错误对象,而 dataStr 则会是 undefined。
因此,通过检查 err 参数是否为 null,你可以轻松地判断文件读取是否成功。如果 err 为 null,则表示读取成功,可以在 dataStr 中访问文件内容;如果 err 不为 null,则表示读取失败,dataStr 会是 undefined,同时你可以查看 err 对象来了解具体的错误信息。
向指定的文件中写入内容
fs.writerFile()的语法格式
示例代码如下:
const fs = require('fs')fs.writeFile('./2.txt', 'abcd', 'utf8', function (err){// 如果文件写入成功,则 err 的值等于 null// 如果文件写入失败,则 err 的值等于一个错误对象console.log(err)
})
运行结果如下:
因为运行成功,因此 err 是 null 值。
fs 模块 - 路径动态拼接的问题
出现路径拼接错误的问题,是因为提供了 ./ 或者 …/ 开头的相对路径。
如果要解决这个问题,可以直接提供一个完整的文件存放路径(即绝对路径)即可。
但是这又会带来一种新的问题,就是代码的移植性非常差,不利于维护。
那么如何完美解决这个问题呢?可以使用 Node.js 给我们提供的 __dirname 参数:
测试也比较简单,打印出来即可知道 __dirname 就是代表当前文件所处的目录,因此不再赘述。
path 路径模块
什么是 path 路径模块
路径拼接
path.join() 的语法格式
代码示例如下:
const path = require('path')// 注意:../ 会抵消前面的路径
const pathStr = path.join('/a','/b/c','../','./d','e')console.log(pathStr) // 输出:\a\b\d\e//今后凡是涉及到路径拼接的操作,都要使用 path.join() 方法进行处理。
// 不要用 + 进行字符串拼接
const pathFile = path.join(__dirname, '/a/1.txt')console.log(pathFile)
运行结果如下:
获取路径中的文件名
path.basename() 的语法格式
示例代码:
const path = require('path')//定义文件的存放路径
const fpath = '/a/b/c/index.html'const fullName = path.basename(fpath)
console.log(fullName) //输出:index.htmlconst 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)
运行结果如下:
关于 path 模块需要注意的两个小点
http 模块
什么是 http 模块
进一步理解 http 模块的作用:
创建最基本的 web 服务器
上面的步骤细分下来其实就是下面这样:
第一步:导入 http 模块
第二步:创建 web 服务器实例
第三步:为服务器实例绑定 request 事件
第四步:启动服务器
接下来我们来代码实操一下:
//1、导入 http模块
const http = require('http')
//2、创建 web 服务器实例
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')
})
运行效果如下:
可以发现当我们在浏览器访问该地址的时候,我们的监听事件已经监听到了事件。
补充:什么是箭头函数
这是 JavaScript 中的箭头函数语法。()=>{} 是一个简单的箭头函数,它表示一个没有参数的函数,并返回一个空对象(即函数体内没有执行任何操作)。箭头函数是 ES6(ECMAScript 2015)中引入的一种新的函数声明方式,它可以更简洁地定义函数,并且在一些情况下具有更清晰的语义。
req 请求对象
代码示例如下:
const http = require('http')
server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request', (req)=>{// req.url 是客户端请求的 url 地址const url = req.url//req.method 是客户端请求的 method 类型const method = req.methodconst str = `Your request url is ${url}, and request method is ${method}`console.log(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
运行效果如下:
res 响应对象
示例代码如下:
const http = require('http')
server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request', (req, res)=>{// req.url 是客户端请求的 url 地址const url = req.url//req.method 是客户端请求的 method 类型const method = req.methodconst str = `Your request url is ${url}, and request method is ${method}`console.log(str)// 调用 res.end() 方法向客户端响应一些内容res.end(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
运行效果如下:
解决中文乱码的问题
代码示例如下:
const http = require('http')
server = http.createServer()
// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request', (req, res)=>{// req.url 是客户端请求的 url 地址const url = req.url//req.method 是客户端请求的 method 类型const method = req.methodconst str = `你的请求路径是 ${url}, 请求方法是 ${method}`console.log(str)// 发送数据前设置响应头,设置编码方式res.setHeader('Content-Type', 'text/html; charset=utf-8')// 调用 res.end() 方法向客户端响应一些内容res.end(str)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
运行效果如下:
根据不同的 url 响应不同的 html 内容
动态响应内容的代码示例:
const http = require('http')server = http.createServer()// req 是请求对象,包含了与客户端相关的数据和属性
server.on('request', (req, res)=>{//1、获取请求的 url 地址const url = req.url//2、设置默认的响应内容为 404 Not Foundlet content = '404 Not Found'//3、判断用户请求的是否为 / 或者 /index.html 页面//4、判断用户请求的是否为 /about.html 关于页面if(url === '/' || url === '/index.html'){content = '<h1>首页</h1>'}else if(url === '/about.html'){content = '<h1>关于页面</h1>'}//5、设置 Content-Type 响应头,防止中文乱码res.setHeader('Content-Type', 'text/html; charset=utf-8')//6、使用 res.end() 把内容响应给客户端res.end(content)
})server.listen(80,function (){console.log('Server running at http://127.0.0.1')
})
运行效果如下:
补充:${} 模板字符串语法
${} 是 JavaScript 中的模板字符串语法。在字符串中使用 ${} 可以插入变量或表达式的值。在这种情况下,${url} 表示将变量 url 的值插入到字符串中。这种语法使得字符串拼接更加清晰和简洁。
模块化
模块化的基本概念
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
模块化规范
Node.js 中模块的分类
加载模块
注意:在使用 require 加载用户自定义模块期间,可以省略 .js 的后缀名。
Node.js 中的模块作用域
模块作用域的好处就是:防止了全局变量污染的问题。
向外共享模块作用域中的成员
module 对象
module.exports 对象
接下来我们用代码来解释一下。
先创建两个自定义模块,一个是自定义模块.js 另一个是 test.js,然后我们 自定义模块.js 文件先不写任何内容,在 test.js 中先去 require 自定义模块.js 中的内容试一下:
运行效果如下:
可以看见打印不出任何内容,因为我们在导入另一个模块的时候,我们导入的其实是另一个模块 module 的 exports 属性,默认情况下 exports 属性就是空的,这可以从上一小节讲的 module 对象 中的图里看到。
因此我们其实可以在 自定义模块.js 中对外共享一些本模块中的成员:
此时我们在运行 test.js 查看现在是否 modules.export 为空了:
可以看见不为空了,有内容的。
共享成员时的注意点
exports 对象
代码验证如下:
console.log(exports)
console.log(module.exports)
console.log(exports === module.exports)
运行效果如下:
exports 和 module.exports 的使用误区
Node.js 中的模块化规范
npm 与包
什么是包
包的来源
为什么需要包
从哪里下载包
如何下载包
查看自己电脑上的 npm 版本:
npm 初体验
这种传统方法就不演示了,可以看出来很繁琐,因此我们直接使用第三方包来完成一样的事情:
在项目中安装包的命令
接下来我们来演示如何用 npm 下载 第三方包,这里我直接使用 WebStorm 的终端进行下载了:
可以看见此时就已经下载安装好了。
写代码来实现我们的时间格式化操作:
//1、导入需要的包
const moment = require('moment')//2、查阅官方文档明白第三方包要如何使用
const dt = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(dt)
运行效果如下:
初次安装后都多了哪些文件
可以检查一下自己的项目:
注意:程序员不要手动修改 node_modules 或者 package-lock.json 文件中的任何代码,npm 包管理工具会自动维护它们。
安装指定版本的包
注意:如果我们要切换第三方包的版本并不需要先卸载我们现有版本的包,而是直接下新的即可,新的会覆盖原来旧版本的包嗷。
包的语义化版本规范
包管理配置文件
多人协作的问题
如何记录项目中安装了哪些包
快速创建 package.json
dependencies 节点
一次性安装所有的包
卸载包
devDependencies 节点
怎么知道哪些第三方包需要被保存到 devDependencies 哪些需要被保存到 dependencies 呢?
这些其实在官方文档都会有说的,查阅官方文档就行。
为什么下包速度慢
下包速度慢解决:淘宝 NPM 镜像服务器
切换 NPM 的下包镜像源
nrm 小工具
包的分类
使用 npm 包管理工具下载的包,共分为两大类,分别是:项目包和全局包。
好用的第三方包:i5ting_toc
规范的包结构
package.json 为啥必须要包含 main 包入口的这个属性呢?
这是因为在我们项目中使用 require 导入第三方包的时候其实走的就是这个 main 属性,通过这个 main 属性我们才能正常使用这个包的功能,即它是包的入口。
开发属于自己的包
假设我们现在需要开发的包叫 itheima-tools 包,其需要实现下面的功能:
那么接下来我们就按照下面的步骤开发我们自己的包:
实现效果如下:
然后初始化 package.json :
如果你使用 WebStorm 进行文件创建的话,可以发现其是自动生成的,只要点击创建就可以了。
不难发现这个 package.json 包其实就是一个 json 配置对象:
但是我们需要改一下,改成 PPT 中的那样,否则没办法发布到 NPM 的服务器上:
上面需要解释的几个含义:
main:就是入口文件的位置
keywords:这是被发布到 npm 服务器上时别人可以检索到这个包所使用的关键字有哪些
license:所遵循的开源许可协议,npm 官方推荐使用 ISC,因此我们用这个即可
然后编写我们的包源码:
将不同的功能进行模块化拆分
src 是我们的新创建的源码文件夹,拆分的部分将全部放进该文件夹中。
编写包的说明文档
发布包
主要有以下几个步骤:
注意:在运行 npm login 命令之前,必须先把下包的服务器地址切换为 npm 的官方服务器。否则会导致发布包失败!
删除已发布的包
模块的加载机制
Express.js
初识 Express.js
Express.js 简介
进一步理解 Express
Express 能做什么
Express 安装
这里看的课的老师用的 4.17.1 的版本,我看了一下现在官网的最新版本是 4.19.2:
按照包的语义化版本规范,因为是只上升了第二位数的大小,说明相较于 4.17.1 的版本,现在最新的版本也只不过是新增了一些功能并且修复了一点 bug 而已,因此我们直接安装使用最新版即可:
查看版本:
Express 创建 web 服务器
来实现一下:
// 1、导入 Express
const express = require('express')
// 2、创建 web 服务器
const app = express()
// 3、启动 web 服务器
// 调用 app.listen(端口号,启动成功后的回调函数),启动服务器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
运行效果如下:
Express 监听 Get 和 Post 请求
示例代码如下:
// 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 服务器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
效果如下:
获取 URL 中携带的查询参数
示例代码如下:
app.get('/', (req,res)=>{//通过 req.query 可以获取到客户端发送过来的 查询参数//注意:默认情况下,req.query 是一个空对象console.log(req.query)res.send(req.query)
})
运行结果如下:
此时我们加上查询参数运行结果将变为:
获取 URL 中的动态参数
示例代码如下:
//注意:这里的 :id 是一个动态的参数
app.get('/user/:id',(req,res)=>{// req.params 是动态匹配到的 URL 参数,默认是空对象console.log(req.params)res.send(req.params)
})
运行效果如下:
我们不止可以写一个,可以写多个动态参数:
//注意:这里的 :id 是一个动态的参数
app.get('/user/:id/:name',(req,res)=>{// req.params 是动态匹配到的 URL 参数,默认是空对象console.log(req.params)res.send(req.params)
})
运行效果如下:
托管静态资源
express.static()
简单测试一下:
运行效果如下:
托管多个静态资源目录
挂载路径前缀
调试工具:nodemon 的安装与使用
Express 路由
什么是路由
广义上讲,路由就是映射关系。
比如:
在上图中,路由就是按键与服务之间的映射关系。
Express 中的路由
实际项目中的示例程序:
路由的匹配过程
路由的使用
这种方式之前就用过很多次了,只要知道有这种用法即可,实际的项目中我们不会采用这么直接的方式,因此不再赘述。
模块化路由
创建路由模块
我们新创建一个 router.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 a new user')
})
//4、向外导出路由对象
module.exports = router
注册路由模块
// 1、导入 Express
const express = require('express')
// 2、创建 web 服务器
const app = express()// 1、导入路由模块
const router = require('./router.js')
// 2、注册路由模块
app.use(router)// 3、启动 web 服务器
app.listen(80, ()=>{console.log('express server running at http://127.0.0.1')
})
运行效果如下:
可以发现路由模块化后也依然可以正常工作。
app.use() 函数的作用
一句话:该函数的作用就是用来注册全局中间件的(后面就会讲到了)。
为路由模块添加前缀
Express 中间件
什么是中间件
中间件(Middleware),特指业务流程的中间处理环节。
比如:
Express 中间件的调用流程
Express 中间件的格式
next 函数的作用
Express 中间件的初体验
代码示例如下:
const express = require('express')const app = express()// 定义一个最简单的中间件函数
const mw = function (req, res, next){console.log("这是最简单的中间件函数")//把流转关系,转交给下一个中间件或者路由//(有中间件转交给中间件,没中间件就转交给路由)next()
}app.listen(80, () => {console.log('http://localhost')
})
全局生效的中间件
示例代码如下:
const express = require('express')const app = express()// 定义一个最简单的中间件函数
const mw = function (req, res, next){console.log("这是最简单的中间件函数")//把流转关系,转交给下一个中间件或者路由//(有中间件转交给中间件,没中间件就转交给路由)next()
}//将 mw 注册为全局生效的中间件
app.use(mw)app.get('/',(req,res)=>{console.log('Here is Home Page')res.send('Home page')
})app.get('/user',(req,res)=>{res.send('User page')
})app.listen(80, () => {console.log('http://localhost')
})
运行效果如下:
可以看见访问浏览器时是成功打印了:
因为目前我们只定义了一个中间件,因此在 mw 中间件函数执行结束之后因为没有其他中间件了,于是 next() 函数将流转关系转交给了路由模块,所以先打印 “这是最简单的中间件函数” 再打印 “Here is Home Page”。
定义全局中间件的简化形式
中间件的作用
示例代码:
const express = require('express')const app = express()// 定义一个最简单的中间件函数
const mw = function (req, res, next){//获取请求到达服务器的时间const time = Date.now()//为 req 对象挂载自定义属性,从而把时间共享给后面的所有路由req.startTime = time//把流转关系,转交给下一个中间件或者路由//(有中间件转交给中间件,没中间件就转交给路由)next()
}//将 mw 注册为全局生效的中间件
app.use(mw)app.get('/',(req,res)=>{res.send('Home page'+req.startTime)
})app.get('/user',(req,res)=>{res.send('User page'+req.startTime)
})app.listen(80, () => {console.log('http://localhost')
})
运行效果:
定义多个全局中间件
局部生效的中间件
定义多个局部中间件
注意调用顺序是从前往后的,即先执行 mw1 再执行 mw2。
了解中间件使用的5个使用注意事项
中间件的分类
应用级别的中间件:
路由级别的中间件:
错误级别的中间件:
注意:错误级别的中间件,必须注册在所有路由之后!
Express 内置的中间件
express.json() 内置中间件
代码示例:
const express = require('express')
const app = express()//注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
//通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())app.post('/user',(req,res)=>{// 在服务器中可以使用 req.body 这个属性来接收客户端发送过来的请求体数据// 默认情况下,如果不配置解析表单数据的中间件(也就是 express.json()),则 req.body 默认为 undefinedconsole.log(req.body)res.send('ok')
})app.listen(80, ()=>{console.log('http://localhost')
})
运行效果,我们使用 postman 工具来进行测试:
发送 JSON 类型的请求体数据如下:
此时控制台中的输出如下:
可以看见请求体数据被完美解析。
express.urlencoded() 内置中间件
示例代码如下:
const express = require('express')
const app = express()//注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
//通过 express.urlencoded() 这个中间件,解析表单中的 JSON 格式的数据
//在这个函数内部我们还要传递一个固定的配置对象 extended ,将其设置为 false
//这是固定的写法,只需要记住,不需要问为什么
app.use(express.urlencoded({ extended: false }))app.post('/book',(req,res)=>{// 在服务器中可以使用 req.body 这个属性来接收客户端发送过来的 url-encoded 格式的数据// 默认情况下,如果不配置解析 url-encoded 表单数据的中间件,则 req.body 默认为空console.log(req.body)res.send('ok')
})app.listen(80, ()=>{console.log('http://localhost')
})
发送数据类型为:
运行效果为:
可以看见正常解析了数据。
第三方中间件
自定义中间件
实现如下:
使用 Express 写 API 接口
1、创建基本的服务器:
// app.js 主模块
// 导入 express
const express = require('express')// 创建服务器实例
const app = express()// 调用 app.listen() 方法,指定端口号并启动 web 服务器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
2、创建 API 路由模块
// apiRouter.js 路由模块
const express = require('express')const router = express.Router()//在这里挂载对于的路由module.exports = router
然后在 app.js 中导入并注册路由模块:
// app.js 主模块
// 导入 express
const express = require('express')// 创建服务器实例
const app = express()//导入路由模块
const router = require('./apiRouter')
//把路由模块注册到 app 上
app.use('/api', router)// 调用 app.listen() 方法,指定端口号并启动 web 服务器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
3、编写 Get 接口
// apiRouter.js 路由模块
const express = require('express')const router = express.Router()//在这里挂载对应的路由
router.get('/get', (req,res)=>{//通过 req.query 获取客户端通过查询字符串,发送到服务器的数据const query = req.query//调用 res.send() 方法,向客户端响应处理的结果res.send({stats: 0,//0 表示处理成功,1 表示处理失败msg: 'Get 请求成功', // 状态描述data: query //需要响应给客户端的数据})
})module.exports = router
运行结果如下:
4、编写 Post 接口
// apiRouter.js 路由模块
const express = require('express')const router = express.Router()//在这里挂载对应的路由
router.get('/get', (req,res)=>{//通过 req.query 获取客户端通过查询字符串,发送到服务器的数据const query = req.query//调用 res.send() 方法,向客户端响应处理的结果res.send({stats: 0,//0 表示处理成功,1 表示处理失败msg: 'Get 请求成功', // 状态描述data: query //需要响应给客户端的数据})
})router.post('/post', (req,res)=>{//通过 req.body 获取客户端发送到服务器的请求体数据const body = req.body//调用 res.send() 方法,向客户端响应处理的结果res.send({stats: 0,//0 表示处理成功,1 表示处理失败msg: 'Post 请求成功', // 状态描述data: body //需要响应给客户端的数据})
})module.exports = router
要解析表单数据别忘了在导入路由之前先添加配置解析表单数据的中间件:
// app.js 主模块
// 导入 express
const express = require('express')// 创建服务器实例
const app = express()//配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//导入路由模块
const router = require('./apiRouter')
//把路由模块注册到 app 上
app.use('/api', router)// 调用 app.listen() 方法,指定端口号并启动 web 服务器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
运行效果如下:
接口的跨域问题
接口跨域问题是指在Web开发中,由于浏览器的同源策略(Same-Origin Policy),导致在一个域下的网页无法直接访问另一个域下的资源。"跨域"指的是在浏览器中,当一个页面的脚本请求访问另一个域下的资源时,如果这个资源的域名、协议或端口与当前页面所在的域不一致,就会引发跨域问题。
跨域问题会影响到包括Ajax请求、Web字体加载、嵌入式框架(如iframe)加载等场景。具体来说,如果你在一个网页中使用Ajax请求另一个域下的数据,浏览器会阻止这个请求,因为涉及到跨域。
解决接口跨域问题的方法有很多,包括使用代理服务器、JSONP、CORS(跨域资源共享)等。CORS是一种比较常用的解决方案,它通过在服务器端设置一些响应头,来告诉浏览器允许跨域请求。JSONP则是一种利用
实际开发中,我们推荐使用 CORS 的方法来解决跨域问题。
使用 CORS 中间件解决跨域问题
第一步:安装
第二步和第三步:在路由之前先配置并注册 cors 中间件到 app.js 上
// app.js 主模块
// 导入 express
const express = require('express')// 创建服务器实例
const app = express()//配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//在路由之前先配置 cors 中间件解决跨域问题
const cors = require('cors')
//注册 cors 中间件
app.use(cors())//导入路由模块
const router = require('./apiRouter')
//把路由模块注册到 app 上
app.use('/api', router)// 调用 app.listen() 方法,指定端口号并启动 web 服务器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
这样就可以解决跨域问题啦。
什么是 CORS
CORS 的注意事项
CORS 响应头部 -Access-Control-Allow-Origin
CORS 响应头部 -Access-Control-Allow-Headers
CORS 响应头部 -Access-Control-Allow-Methods
CORS 请求的分类
简单请求
预检请求
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为 ”预检请求“ 。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求和预检请求之间的区别
数据库与身份认证
注意这一章节的学习中省略了 MySQL 相关的基础内容,如果不会的话建议先去系统学一下 MySQL 捏。
这里只讲 Express 中如何操作 MySQL 数据库嗷。
测试的表数据如下:
在 Express 中操作 MySQL
在项目中操作 MySQL 数据库的步骤:
安装 mysql 模块:
配置 mysql 模块:
示例代码如下:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})
测试一下 mysql 模块是否能正常工作:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})// 测试 mysql 模块能否正常工作
// select 1 这条语句没有任何意义,只是用来检查 mysql 能否正常工作
db.query('select 1',(err, results)=>{// mysql 模块工作期间报错了if(err){return console.log(err.message)}//否则就是能够正常的执行 SQL 语句console.log(results)
})
测试运行结果如下:
可以看见我们的 mysql 模块是没有问题的。
使用 mysql 模块操作 MySQL 数据库
查询数据
示例代码如下:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})// 查询 users 表中所有的数据
const str = "select * from user"
db.query(str,(err, results)=>{// mysql 模块工作期间报错了if(err){return console.log(err.message)}//否则就是能够正常的执行 SQL 语句console.log(results)
})
运行结果如下:
注意:如果执行的是 select 查询语句,则执行的结果是数组。
插入数据
示例代码:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})// 向 user 表中新增一条数据,其中username的值为 Spider,password 的值为123
const user = {username:'Spider', password:'123'}
//定义待执行的 SQL 语句
const sqlStr = 'insert into user (username, password) values (?, ?)'
//执行 SQL 语句
db.query(sqlStr,[user.username,user.password],(err,results)=>{if(err) return console.log(err.message)// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象// 可以通过 affectedRows 属性,来判断是否插入数据成功if(results.affectedRows === 1) console.log('插入数据成功')
})
插入数据的便捷方式
示例代码:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})// 向 user 表中新增一条数据,其中username的值为 Spider,password 的值为123
const user = {username:'Spider', password:'123'}
//定义待执行的 SQL 语句
//insert into 表名 set ?
const sqlStr = 'insert into user set ?'
//执行 SQL 语句
// 参数也不需要用数组形式了,直接将原对象进行插入即可(类似于一个 JavaBean)
db.query(sqlStr,user,(err,results)=>{if(err) return console.log(err.message)// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象// 可以通过 affectedRows 属性,来判断是否插入数据成功if(results.affectedRows === 1) console.log('插入数据成功')
})
运行结果如下:
数据库中也是正常的新增了数据:
更新数据
示例代码:
//mysql-cfg.js 数据库模块
//1、导入 mysql 模块
const mysql = require('mysql')//2、建议与 mysql 数据库的连接关系
const db = mysql.createPool({host: '127.0.0.1', //数据库的 ip 地址user: 'root', //登录数据库的账号password: '123456', //登录数据库的密码database: 'node' //指定要操作哪一个数据库
})// 向 user 表中修改一条数据
const user = { id: 3, username:'阿斯顿', password:'123' }
//定义待执行的 SQL 语句
const sqlStr = 'update user set username=?,password=? where id=?'
//执行 SQL 语句
db.query(sqlStr,[user.username,user.password,user.id],(err,results)=>{if(err) return console.log(err.message)// 注意:如果执行的是 update 更新语句,则 results 是一个对象// 可以通过 affectedRows 属性,来判断是否更新数据成功if(results.affectedRows === 1) console.log('更新数据成功')
})
运行结果如下:
更新数据的便捷方式
删除数据
标记删除
前后端中的身份认证
Web 开发模式
目前主流的 Web 开发模式有两种,分别是:
1、基于服务端渲染的传统 Web 开发模式
2、基于前后端分离的新型 Web 开发模式
基于服务端渲染的传统 Web 开发模式
基于前后端分离的新型 Web 开发模式
如何选择 Web 开发模式
身份认证
什么是身份认证?
为什么需要身份认证
不同开发模式下的身份认证
HTTP 协议的无状态性
如何突破 HTTP 无状态的限制
什么是 Cookie
Cookie 在身份认证中的作用
Cookie 不具有安全性
注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。
提高身份认证的安全性
Session 的工作原理
在 Express 中使用 Session 认证
配置 Session
只需要下面几步就可以使用 Session 认证了嗷:
配置对象中的 secret 属性是一串字符串,用来加密用的。
接下来我们完成一下这些事情:
安装 express-session 中间件:
开始配置 express-session 的全局中间件:
// app.js 主模块
// 导入 express
const express = require('express')// 创建服务器实例
const app = express()// 引入session
const session = require('express-session')
// 注册 session
app.use(session({secret: 'hahaha',resave: false,saveUninitialized: true
}))//配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false}))
app.use(express.json())//在路由之前先配置 cors 中间件解决跨域问题
const cors = require('cors')
//注册 cors 中间件
app.use(cors())//导入路由模块
const router = require('./apiRouter')
//把路由模块注册到 app 上
app.use('/api', router)// 调用 app.listen() 方法,指定端口号并启动 web 服务器
app.listen(80,()=>{console.log('Express server is running at http://localhost')
})
向 Session 中存数据
从 Session 中取数据
清空 Session
注意:只是清空了当前客户端的 Session ,这并不会影响到其他客户端的 Session。
因为之前说过 Session 的工作原理,每个 Session 都会对应一个客户端浏览器所自动存收的一个 Cookie,因此相当于各个客户端之间是隔离连接的,互不影响,至于底层是怎么实现的应该是在这个 express-session 第三方包中自动实现的,我们只要懂得怎么使用即可。
了解 Session 认证的局限性
什么是 JWT
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
JWT 的工作原理
JWT 的组成部分
JWT 的三个部分各自代表的含义
JWT 的使用方式
注意:千万别忘记了在 token 字符串前加 Bearer 这个字符串!然后后面还要跟一个空格才能再跟 token 字符串!!!
比如我们在 postman 中要带 token 的话,那么形式应该是下面这样:
在 Express 中使用 JWT
主要有下面几个步骤:
上图中的第三个参数配置对象里,我们配置了该 Token 的过期时间为 30s,如果要设置成分钟则后缀用 m ,小时的话设置为 h 即可。
注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上。
换句话说就是,req 本身是没有 user 属性的,但是只要配置成功了 express-jwt 这个中间件后,req 就有一个 user 属性了,该属性是由该中间件创建的。
而这个 user 对象中包含多少信息是由我们自己决定的,也就是在登录成功之后我们进行加密的用户信息对象信息有多少那么 user 对象中就有多少信息。
比如我们上面的示例中将 username 进行了加密,那么被创建的 user 对象就会只有一个 username 属性。
如果我们的访问请求携带了 token 进行 API 访问的话,其返回值会有下面两个额外的属性:
iat 和 exp ,不过不用管,这是这个 JWT 第三方工具包用来控制过期时间用的两个属性。
捕获解析 JWT 失败后产生的错误
相关文章:

Node.js基础:从入门到实战
初识 Node.js 与内置模块 (初识) 1、知道什么是node.js 2、知道node.js可以做什么 3、node.js 中js的组成部分 (内置模块) 4、用 fs 模块读写操作文件 5、使用 path 模块处理路径 6、使用http 模块写一个基本的web服务器 初识 N…...

考研408笔记总结~
目录 一.数据结构 二.计算机组成原理 三.操作系统 四.计算机网络 私以为边看视频,边做笔记会更专注些,大家需要自取。欢迎大家和我一起探讨考研的问题,包括不仅限于专业课,数学,英语等等......,想说什么…...

使用在线工具等方式下载推特视频
使用在线工具等方式下载推特视频 使用在线工具 Visit a Twitter video downloader website: Websites like twdown.net, twittervideodownloader.com, and savevideo.me offer services to download Twitter videos.Paste the Twitter video URL into the designated input bo…...

性能优化:几方面考虑
我们可以继续再考虑下关于性能优化,我们还能从哪些方面着手呢? 1. 代码层面: 使用更高效的数据结构和算法。使用缓存避免多次数据库交互减少不必要的计算和内存分配。利用并行和异步编程提高性能。使用性能分析工具定位和优化瓶颈。 2. We…...

学习大数据:论学习Spark的重要性
随着科技的不断发展,大数据已经成为了当今社会的热门话题。大数据技术的出现,为我们提供了处理海量数据的新方法,使得我们能够从这些数据中挖掘出有价值的信息。在众多的大数据处理框架中,Apache Spark无疑是最为出色的一种。本文…...

学习java第七十一天
DI:依赖注入 依赖注入是spring容器中创建对象时给其设置依赖对象的方式,比如给spring一个清单,清单中列出了需要创建B对象以及其他的一些对象(可能包含了B类型中需要依赖对象),此时spring在创建B对象的时候…...

Altium Designer PCB快捷键设置
6)PCB修改快捷键,并自定义工具栏 添加boardlayerset系统命令。 修改系统脚本, 在D:\Program Files\Altium\AD18\System下,找到advpcb.rcs文件,打开。 Tree MNPCB_LayerSets CaptionManage Layer Se&ts Popup Emp…...

玩转Matlab-Simscape(初级)- 08 - 基于Solidworks、Matlab Simulink、COMSOL的协同仿真(案例实战)
** 玩转Matlab-Simscape(初级)- 08 - 基于Solidworks、Matlab Simulink、COMSOL的协同仿真(案例实战) ** 目录 玩转Matlab-Simscape(初级)- 08 - 基于Solidworks、Matlab Simulink、COMSOL的协同仿真&…...

vue嵌套路由
一、嵌套 children配置 1.父类路由 mymusic 2.子类路由 musicson 1.创建MusicSon组件 <template><div><p>从前和后来</p><p>唯一</p><p>运气来的似有若无</p></div> </template><script>export defaul…...

视频降噪算法 hqdn3d 原理分析
视频降噪 视频降噪是一种处理技术,旨在减少视频中的噪声,提高画面质量。噪声可能来自多种源头,包括摄像机的传感器、压缩算法、传输过程中的干扰等。降噪处理对于视频监控、视频会议、电影后期制作以及任何需要高画质输出的应用场景都非常重…...

Ansys Mechanical|屈曲分析技术
屈曲分析的基本概念 当受拉杆件的应力达到屈服极限或强度极限时,将引起塑性变形或断裂。这些是由于强度不足所引起的失效。 在工程中,我们会注意到当细长杆件受压时,表现出与强度失效完全不同的性质。当杆件受压超过某一临界值时࿰…...

【大模型微调】一文掌握7种大模型微调的方法
本篇文章深入分析了大型模型微调的基本理念和多样化技术,细致介绍了LoRA、适配器调整(Adapter Tuning)、前缀调整(Prefix Tuning)等多个微调方法。详细讨论了每一种策略的基本原则、主要优点以及适宜应用场景,使得读者可以依据特定的应用要求和计算资源限…...

MySQL表突然卡死,删、查操作加载不停解决办法
今天遇到了MySQL删表的时候卡死情况。然后通过网上查阅资料和项目组沟通,了解到了有多人同时对同一张表进行了操作。我和另一个同事同时进行了删除操作,然后另两位同时进行了查询操作,然后还有一位同事用dolphin调度,用datax采集数…...

Rust 标准库的结构及其模块路径
在 Rust 中,标准库提供了一组核心功能,以帮助开发者执行常见的编程任务。当使用这些功能时,我们需要通过特定的模块路径来引用它们。下面,我们将详细介绍 Rust 标准库的结构,并提供相应的 use 路径。 Rust 标准库模块…...

003_PyCharm的安装与使用
如果你正在学习PyQt,本系列教程完全可以带你入门直至入土。 所谓从零开始,就是从软件安装、环境配置开始。 不跳过一个细节,不漏掉一行代码,不省略一个例图。 IDE 开始学习一个编程语言,我们肯定是首先得安装好它&…...

事件传递机制
IOS面试题(UIView) ----- 事件传递机制 - 简书 面试题: 在以下场景中,父视图 ParentView 上有三个子视图 ViewA、ViewB 和 ViewC。ViewA 完全位于 ParentView 的范围内,ViewB 有一半在 ParentView 的范围内,而 ViewC 完全位于 Par…...

DE2-115串口通信
目录 一、 内容概要二、 Hello Nios-II2.1 Nios-II编程2.1.1 硬件Ⅰ 搭建环境Ⅱ 编写代码 2.1.2 软件2.1.3 烧录Ⅰ硬件Ⅱ 软件 2.2 verilog编程 三、 心得体会 一、 内容概要 分别用Verilog和Nios软件编程, 实现DE2-115开发板串口输出“Hello Nios-II”字符到笔记本电脑串口助…...

Danfoss丹佛斯S90泵比例放大器
S90R042、S90R055、S90R075、S90R100、S90R130、S90R180、S90R250电气排量控制变量泵比例阀放大器,电气排量控制为高增益控制方式:通过微小变化的输入电流控制信号即可推动伺服阀主阀芯至全开口位置,进而将最大流量的控制油引入到伺服油缸。伺…...

对话YashanDB CTO陈志标:如何推动国产数据库长远发展
深圳计算科学研究院(以下简称“深算院”)是深圳市人民政府2018年11月批准建设的“十大基础研究机构”之一,由深圳市科技创新委员会主管、深圳大学举办、深圳市龙华区人民政府共建的二类事业法人单位。 崖山数据库系统YashanDB是深算院完全自主…...

ip显示地址和实际地址不一样:原因解析与应对策略
在数字化时代,IP地址作为我们在互联网上的身份标识,其重要性不言而喻。然而,有时我们会遇到ip显示地址和实际地址不一样的情况,这不仅可能影响到我们的网络体验,还可能引发一系列安全和隐私问题。那么,造成…...

visual studio snippet常用注释片段
Visual Studio 2022 添加自定义代码片段_vs2022 代码片段-CSDN博客 dclass.snippet: <?xml version"1.0" encoding"utf-8"?> <CodeSnippets xmlns"http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> …...

ubuntu下不生成core dumped
1、先用ulimit -c,如果看到0,说明没有开core dump。 所以我们输入ulimit -c unlimited,打开core dump。 再次用ulimit -c,看到unlimited了,说明core dump打开了。 注意这句ulimit -c unlimited只对当前会话有效。要永…...

python开发的学习路线
I. 基础知识学习 A. Python基础语法 变量和数据类型 学习如何定义变量,理解并使用不同的数据类型(整数、浮点数、字符串、布尔值等)。 掌握数字类型的转换和操作。 熟悉字符串的基本操作,如拼接、切片、替换和查找。 …...

vite+vue3 部署后,总是需要清除缓存的问题
1.每次部署后,需要清除缓存,才能看到最新代码,给打包文件加上hash就可以解决此问题。 vite.config.ts文件中加以下代码 build: {rollupOptions: {output: {entryFileNames: assets/[name].[hash].js,chunkFileNames: assets/[name].[hash].j…...

多态:解锁面向对象编程的无限可能
1. 概述 多态(Polymorphism)是面向对象编程的三大核心特性之一(另两个是封装和继承)。多态意味着不同的对象对同一消息做出不同的响应。简单来说,多态允许你使用父类引用指向子类对象,并且当调用方法时&am…...

学习MySQL(四):记录的增删改查
记录的增、删、改 增 -- 插入一条数据 INSERT INTO 表名(字段 1,字段2,字段3) VALUES(值 1,值2,值3) INSERT INTO 表名 VALUES(值 1,值2,值3&am…...

如何使用Python进行网页爬取
Python爬虫案例可以有很多种,但我会为你提供一个简单的案例,该案例使用Python的requests库来爬取一个网页的内容,并使用BeautifulSoup库来解析HTML并提取特定的信息。 假设我们要从某个新闻网站(例如:示例网站&#x…...

Spring的IOC(Inversion of Control)设计模式
Spring的IOC(Inversion of Control)是一种设计模式,它通过控制反转的思想来降低组件之间的耦合度。在Spring框架中,IOC容器负责管理应用程序中的对象,使得对象之间的依赖关系由容器来维护和注入。 以下是Spring IOC的…...

深度学习知识点总结
深度学习是机器学习领域中的一个重要研究方向,它致力于模拟人脑的学习过程,使机器能够像人一样具有分析学习能力,识别文字、图像和声音等数据。以下是深度学习的一些关键知识点总结: 定义与目标: 深度学习是学习样本数…...

以色列人Andi Gutmans开发的php zend
虽然目前php语言不行了【相关的文章前几年已经有人发过】,但这不是重点,重点是zend引擎的东西具有极大的技术价值,负责zend引擎实现的大佬都现在差不多都是40,50岁左右了,从1997,1998,2000到202…...