初识 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 第三方工具包用来控制过期时间用的两个属性。