目录
1. fs文件系统模块
1.1 fs.readFile读取文件内容
//1.导入fs模块,来操作文件
const fs= require('fs')
// 2.调用fs.readFile()方法读取文件
// 参数1:读取文件的路径
// 参数2:读取文件的时候采用的编码格式,默认指定utf8
// 参数3:回调函数,可以拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/1.txt','utf8',function(err,dataStr) {
// 2.1 打印失败的结果
// 如果读取成功,则err的值为null
console.log(err);
// 2.2 打印成功的结果
console.log(dataStr);
})
1.2 fs.readFile判断文件是否读取成功
//1.导入fs模块,来操作文件
const fs= require('fs')
// 2.调用fs.readFile()方法读取文件
// 参数1:读取文件的路径
// 参数2:读取文件的时候采用的编码格式,默认指定utf8
// 参数3:回调函数,可以拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/1.txt','utf8',function(err,dataStr) {
// 2.1 打印失败的结果
// 如果读取成功,则err的值为null
// 如果读取失败,则err的值为错误对象,dataStr的值为undefined
console.log(err);
// 2.2 打印成功的结果
console.log(dataStr);
})
1.3 writeFile写入文件内容
① fs.writeFile()方法只能用来创建文件,不能用来创建路径(即文件夹)
② 重复调用fs.writeFile()写入同一个文件,新写入的内容会覆盖之前的旧内容
// 1. 导入fs文件系统模块
const fs=require('fs')
// 2. 调用fs.writeFile()方法,写入文件内容
// 参数1:指定文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数
fs.writeFile('./files/2.txt','abcd',function (err) {
// 2.1 如果文件写入成功,err的值为null
// 2.2 如果文件写入失败,err的值为一个错误对象
console.log(err);
})
1.4 writeFile判断文件是否写入失败
// 1. 导入fs文件系统模块
const fs=require('fs')
// 2. 调用fs.writeFile()方法,写入文件内容
// 参数1:指定文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数
fs.writeFile('./files/2.txt','abcd',function (err) {
// 2.1 如果文件写入成功,err的值为null
// 2.2 如果文件写入失败,err的值为一个错误对象
if (err) {
return console.log('文件写入失败'+err.message);
}
console.log(err);
})
1.5 readFile及writeFile练习
// 1.导入fs模块
const fs=require('fs');
// 2.读取fs.readFile()读取文件内容
fs.readFile('./files/成绩.txt','utf8',function(err,dataStr) {
// 3.判断是否读取成功
if (err) {
return console.log('读取内容失败'+err.message);
}
// console.log('读取文件成功'+dataStr);//小红=99 小白=100 小黄=70 小黑=66 小绿=88
// 4.对数据进行操作
// 4.1 先把成绩的数据,按照空格进行分割
const arrOld=dataStr.split(' ')
console.log(arrOld);//[ '小红=99', '小白=100', '小黄=70', '小黑=66', '小绿=88' ]
// 4.2 循环分割后的数组,对每一项数据,进行字符串的替换操作
const arrNew=[]
arrOld.forEach((item)=>{
arrNew.push(item.replace('=',':'))
})
// 4.3 把新数组中的每一项,进行合并,得到一个新的字符串
const newStr=arrNew.join('\r\n')
// 5.调用fs.writeFile()方法,把处理完毕的成绩,写入新的文件中
fs.writeFile('./files/成绩2.0.txt',newStr,function (err) {
if (err) {
return console.log('文件写入失败'+err.message);
}
console.log('文件写入成功!');
//小红:99
//小白:100
//小黄:70
//小黑:66
//小绿:88
})
})
2.fs模块-路径动态拼接问题
在使用fs模块操作文件时,如果提供的操作路径是以./或../开头的相对路径时,很容易出现路径拼接错误的问题。
原因:代码在运行的时候,会执行node命令时所处的目录,动态拼接出操作文件的完整路径
解决方案:路径前方添加__dirname拼接
3.path路径模块
3.1 什么是path路径模块
path 模块 是Node.js官方提供的、用来处理路径的模块。它提供了一系列的方法属性,用来满足用户对路径的处理需求。
例如:
- path.join()方法,用来将多个路径片段拼接成一个完整的路径字符串
- path.basename()方法,用来从路径字符串中,将文件名解析出来
const path=require('path')
3.2 Path.join
使用path.join()方法,可以把多个路径片段拼接为完整的路径字符串
const path=require('path')
const fs = require('fs')
// ../会抵消前面的路劲
// const pathStr=path.join('/a','/b/c','../','./d','e')
// console.log(pathStr);
fs.readFile(path.join(__dirname,'/files/1.txt'),'utf8',function (err,dataStr) {
if (err) {
return console.log('读取文件失败!',err.message);
}
console.log('读取文件成功!',dataStr);
})
涉及到路径拼接操作,推荐使用path.join()方法处理
3.3 path.basename
使用path.basename()方法,可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名
const path=require('path')
// 定义文件的存放路径
const fpath='/a/b/c/index.html'
// 参数1:必选参数,一个路径的字符串
// 参数2:可选参数,表示文件的拓展名
// 返回路径中的最后一部分
// const fullName=path.basename(fpath)
// console.log(fullName);//index.html
const fullName=path.basename(fpath,'.html')
console.log(fullName);//index
3.4 path.extname
使用path.extname()方法,可以获取路径中的扩展名部分
const path=require('path')
// 文件的存放路径
const fpath='/a/b/c/index.html'
// 参数:表示一个路径的字符串
// 返回:返回得到的扩展名字符串
const fext=path.extname(fpath)
console.log(fext);//.html
4.http模块
4.1 什么是http模块
http模块是Node.js官方提供的、用来创建web服务器的模块。通过http模块提供http.createServer()方法,就能方便的把一台普通的电脑,变成一台Web服务器,从而对外提供Web资源服务。
const http=require('http')
4.2 创建最基本的web服务器
① 导入http模块
const http=require('http')
② 创建web服务器实例
const server=http.createServer()
③ 为服务器实例绑定request事件,监听客户端请求
//使用服务器实例的.on()方法,为服务器绑定request事件
server.on('request',(req,res)=>{
//只要有客户端来请求服务器,就会触发request事件,从而调用事件处理函数
console.log('Someone visit our web server.)
})
④ 启动服务器
//调用server.listen(端口号,cb回调)方法,即可启动web服务器
server.listen(80,()=>{
console.log('http server running at http://127.0.0.1')
})
//1. 导入http模块
const http=require('http')
// 2.创建web服务器实例
const server=http.createServer()
//3. 为服务器实例绑定request事件,监听客户端的请求
// 3.1 req是请求对象,包含了与客户端相关的数据和属性
// 3.2 res是响应对象,包含了与服务器相关的数据或属性
server.on('request',(req,res)=>{
// req.url是客户端请求的URL地址
const url=req.url
// req.method是客户端请求的method类型
const method=req.method
const str=`Your request url is ${url},and request method is ${method}`
console.log(str);
// 调用res.end()方法,向客户端响应一些内容
res.end(str)
})
// 4. 启动服务器
server.listen(8080,() =>{
console.log('server running at http://127.0.0.1:8080');
})
4.3 根据不同的url响应不同的html内容
const http=require('http')
const server=http.createServer()
server.on('request',function(req,res) {
// 1.获取请求的url地址
const url=req.url
// 2.设置默认的响应内容为404 Not found
let 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>'
}
// 设置Content-Type响应头,防止中文乱码
res.setHeader('Content-Type','text/html;charset=utf-8')
res.end(content)
})
server.listen(8080,()=>{
console.log('server running at http://127.0.0.1:8080');
})
5.模块化
5.1 模块化的概念
(1) 什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
好处:
① 提高了代码的复用性
② 提高了代码的可维护性
③ 可以实现按需加载
(2) 模块化规范
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
好处:降低了沟通的成本,极大方便了各个模块之间的相互调用。
5.2 Node.js中的模块化
(1) Node.js中模块的分类
Node.js中根据模块来源不同,将模块分为3大类,分别是:
① 内置模块(内置模块是由Node.js官方提供的,例如fs、path、http等)
② 自定义模块 (用户创建的每一个.js文件,都是自定义模块)
③ 第三方模块(由第三方开发出来的模块,使用前需要先下载)
(2)加载模块
使用require()方法,可以加载需要的内置模块、自定义模块、第三方模块进行使用
//1. 加载内置的fs模块
const fs=require('fs')
//2. 加载用户的自定义模块
const cust=require(.cust.js)
//3.加载第三方模块
const moment=require('moment')
5.3 Node.js中的模块作用域
(1)什么是模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
(2)模块作用域的好处
防止全局变量污染的问题
const username='张三'
function sayHello() {
console.log('大家好,我是'+username);
}
const custom=require('./01.模块作用域')
console.log(custom);//{}
5.4 向外共享模块作用域中的成员
(1)module对象
在每一个.js自定义模块中都有module对象,它里面存储了和当前模块有关的信息
(2)module.exports对象
在自定义模块中,可以使用module.exports对象,将模块内的成员共享出去,供外界使用。外界使用require()方法导入自定义模块时,得到的就是module.exports所指向的对象
// 在一个自定义模块中,默认module.exports={}
// 向module.export对象上挂载username属性
module.exports.username='zs'
// 向module.exports对象上挂载sayHello方法
module.exports.sayHello=function () {
console.log('Hello!');
}
const age=20
module.exports.age=age
(3)共享成员时的注意点
使用require()方法导入模块时,导入的结果,永远以module.exports指向的对象为准。
module.exports.age=age
// 让module.exports指向一个全新的对象
module.exports={
nickname:'小黑',
sayHi(){
console.log('Hi!');
}
}
默认情况下,exports和module.qxports指向同一个对象。最终共享的结果还是以module.exports指向的对象为准。
(4)exports和module.exports的使用误区
require()模块时,得到的永远是module.exports指向的对象
exports={
username:'zs',
gender:'男'
}
module.exports=exports
module.exports.age='22'
//{username:'zs',gender:'男',age:'22'}
注意:为了防止混乱,建议不要在同一个模块中同时使用exports和module.exports
(5)Node.js中的模块化规范
Node.js遵循了CommonJS模块化规范,CommonJS规定了模块的特性和各模块之间如何相互依赖。
CommonJS规定:
①每个模块内部,module变量代表当前模块。
②module变量是一个对象,它的exports属性(即module.exports)是对外的接口。
③加载某个模块,其实是加载该模块的module.exports属性。require()方法用于加载 模块。
6.Express
6.1 初识Express
6.1.1 什么是Express
Express是基于Node.js平台,快速、开放、极简的Web开放框架。
Express的作用和Node.js内置的http模块类似,是专门用来创建Web服务器的。
Express的本质:就是一个npm上的第三方包,提供了快速创建Web服务器的便捷方法
npm i express@4.17.1
6.1.2 创建基本的Web服务器
const express=require('express')
// 2.创建Web服务器
const app=express()
// 4.监听客户端的FET和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('请求成功')
})
app.get('/',(req,res)=>{
// 通过req.query可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query是一个空对象
console.log(req.query);
res.send(req.query)
})
// 注意:这里的:id是一个动态的参数
app.get('/user/:id',(req,res)=>{
// req.params是动态匹配到的URL参数,默认也是一个空对象
console.log(req.params);
res.send(req.params)
})
// 3.启动web服务器
app.listen(8080,()=>{
console.log('express server running at http://127.0.0.1:8080');
})
6.1.3 托管静态资源
(1)express.static()
使用express.static(),可以非常方便地创建一个静态资源服务器。
注意:Express在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在URL中。
(2)托管多个静态资源目录
如果要托管多个静态资源目录,请多次调用express.static()函数:
访问静态资源文件时,express.static()函数会根据目录的添加顺序查询所需的文件。
(3)挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
6.1.4 nodemon
(1) 为什么要使用nodemon
在编写调试Node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close 掉,然后再重新启动,非常繁琐。
nodemon能够监听项目文件的变动,当代码被修改后,nodemon会自动帮我们重启项目,极大方便了开发和调试。
(2) 安装nodemon
在终端中,运行如下命令,即可将nodemon安装为全局可用的工具:
(3) 使用nodemon
在终端输入nodemon xxx.js
6.2 Express路由
6.2.1 Express中的路由
在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。
Express中的路由分3个部分,分别是请求的类型、请求的URL地址、处理函数。
6.2.2 路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数
路由匹配的注意点:
① 按照定义的先后顺序进行匹配
② 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
6.2.3 模块化路由
为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
① 创建路由模块对应的.js文件
② 调用express.Router()函数创建路由对象
③ 向路由对象上挂载具体的路由
④ 使用module.exports向往共享路由对象
⑤ 使用app.use()函数注册路由模块
// 这是路由模块
// 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('Post user list')
})
// 4.向外导出路由
module.exports=router
注册路由模块
6.3 Express中间件
6.3.1 中间件的概念
中间件,特指业务流程的中间处理环节。
Express的中间件,本质上就是一个Function处理函数。
注意:中间件函数的形参列表中,必须包含next参数,而路由处理函数中只包含req和res。
① next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
6.3.2 全局生效的中间件
客户发起的任何请求,到达服务器之后;都会触发中间件,叫做全局生效的中间件。
通过调用app.use(中间件函数),即可定义一个全局生效的中间件:
const express=require('express')
const app=express()
// 定义一个中间件函数
const mw=function(req,res,next) {
console.log("这是最简单的中间件函数");
// 把流转关系,转交给下一个中间件或路由
next()
}
// 将mw注册为全局生效的中间件函数
app.use(mw)
app.get('/',(req,res)=>{
res.send('Home page.');
})
app.get('/user',(req,res)=>{
res.send('User page.')
})
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080');
})
6.3.3 中间件的作用
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
const express=require('express')
const app=express()
app.use((req,res,next)=>{
// 获取到请求到达服务器的时间
const time=Date.now()
// 为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime=time
next()
})
app.get('/',(req,res)=>{
res.send('Home page.'+req.startTime);
})
app.get('/user',(req,res)=>{
res.send('User page.'+req.startTime)
})
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080');
})
6.3.3 局部生效的中间件
不使用app.use()定义的中间件,叫做局部生效的中间件。
const express=require('express')
const app=express()
const mw=(req,res,next)=>{
console.log('调用了局部生效的中间件');
next()
}
app.get('/',mw,(req,res)=>{
res.send('Home page.');
})
app.get('/user',(req,res)=>{
res.send('User page.')
})
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080');
})
6.3.4 中间件的分类
① 应用级别的中间件
通过app.use()或app.get()或app.post(),绑定到app实例上的中间件。
② 路由级别的中间件
绑定到express.Router()实例上的中间件。
// 这是路由模块
// 1.导入express模块
const express=require('express')
// 2.创建路由对象
const router=express.Router()
router.use(function(req,res,next){
console.log('Time:',Date.now())
next()
})
express.use('/',router)
// 3.挂载具体路由
router.get('/user/list',(req,res)=>{
res.send('Get user list.')
})
router.post('/user/add',(req,res)=>{
res.send('Post user list')
})
// 4.向外导出路由
module.exports=router
③ 错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃。
格式:必须有4个形参,分别是(err,req,res,next)。
注意:错误级别中间件必须注册在所有路由之后。
// 1.定义路由
app.get('/',(req,res)=>{
// 1.1 人为的制作错误
throw new Error('服务器内部发生了错误')
res.send('Home page.')
})
// 2.定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err,req,res,next)=>{
console.log('发生了错误!'+err.message);
res.send('Error:'+err.message)
})
④ Express内置的中间件
Express内置了3个常用的中间件:
- exprees.static 快速托管静态资源的内置中间件
- express.json 解析JSON格式的请求体数据
- express.urlencodeed 解析URL-encodeed格式的请求体数据
// 配置解析 application/json格式数据的内置中间件
app.use(express.json())
// 配置解析 application/x-www-form-urlencodeed 格式数据的内置中间件
app.use(express.urlencoded({extended:false}))
使用req.body接收客户端发送过来的数据,未经数据格式转换则值未undenfind。
6.3.5 自定义中间件
const express=require('express')
const app=express()
// 导入Node.js内置的querystring
const qs=require('querystring')
// 定义解析表单数据的中间件
app.use((req,res,next)=>{
// 定义中间件的具体业务逻辑
// 1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str=''
// 2.监听req的data事件
req.on('data',(chunk)=>{
str+=chunk
})
// 3.监听req的end事件
req.on('end',()=>{
// 在str中存放的是完整的请求体数据
// console.log(str);
// TODO:把字符串格式的请求体数据,解析成对象格式
const body=qs.parse(str)
req.body=body
next()
})
})
app.post('/user',(req,res)=>{
res.send(req.body)
})
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080');
})
6.4 使用Express写接口
6.4.1 创建API路由模块
const express=require('express')
const router=express.Router()
// 在这里挂载对应的路由
router.get('/get',(req,res)=>{
// 通过req.query 获取客户端通过查询字符串,发送到服务器的数据
const query=req.query
// 通过调用res.send()方法,向客户端响应处理的结果
res.send({
status:0,//0表示处理成功,1表示处理失败
msg:'GET请求成功!',//状态的描述
data:query//需要响应给客户端的数据
})
})
// 定义POST接口
router.post('/post',(req,res)=>{
// 通过req.body获取请求体中包含的url-encoded格式的数据
const body=req.body
// 调用res.send()方法,向客户端响应结果
res.send({
status:0,
msg:'POST请求成功!',
data:body
})
})
module.exports=router
6.4.2 CORS跨域资源共享
cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题。
使用步骤:
① 运行npm install cors 安装中间件
② 使用const cors=require('cors') 导入中间件
③ 在路由之前调用app.use(cors()) 配置中间件
CORS是由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
7. 数据库
7.1 数据库的基本概念
7.1.1 什么是数据库
数据库是用来组织、存储和管理数据的仓库。
7.2 使用MySql Workbench管理数据库
7.2.1 创建数据库和表
字段的特殊标识:
① PK(Primary Key)主键、唯一标识
② NN(Not Null)值不允许为空
③ UQ(Unique)值唯一
④ AI (Auto Increment)值自动增长
7.2.2 向表中写入数据
7.3 使用SQL管理数据库
7.3.1 SQL的select语法
(1)语法
select语句用于从表中查询数据。执行的结果被存储在一个结果表中,语法格式:
--这是注释
--从from指定的【表中】,查询出【所有的】数据。*表示【所有列】
select * from 表名称
--从from指定的【表中】,查询出指定列名称(字段)的数据。
select 列名称 from 表名称
注意:SQL语句中的关键字对大小写不敏感。
(2)select实际应用
-- 通过*把users表中所有的数据查询出来
-- select * from users
-- 从users表中把username和password对应的数据查询出来
select username,password from users
7.3.2 SQL的insert into语句
(1)语法
insert into语句用于向数据表中插入新的数据行,语法格式:
-- 语法解读:指定的表中,插入如下几列数据,列的值通过values指定
-- 注意:列和值要一一对应,多个列和多个值之间,使用英文的单号隔开
insert into table_name (列1,列2,...) values (值1,值2,...)
(2)insert into示例
-- 向users表中,插入新的数据,username的值为tony stark password的值为098123
insert into users (username,password) values ('tony stark','098123')
7.3.3 SQL的update语句
(1)语句
Update语句用于修改表中的数据,语法格式:
-- 语法解读:
-- 1.用update指定要更新哪个表中的数据
-- 2.用set指定列对应的新值
-- 3.用where指定更新的条件
update 表名称 set 列名称=新值 where 列名称=某值
(2)update示例
-- 将id为4的用户密码,更新为888888
update users set password='888888' where id=4
-- 更新id为2的用户,把用户密码更新为admin123 同时把用户的状态更新为1
update users set password='admin123',status=1 where id=2
7.3.4 SQL的delete语句
(1)语句
delete语句用于删除表中的行,语法格式:
-- 语法解读:
-- 从指定的表中,根据where条件,删除对应的数据行
delete from 表名称 where 列名称=值
(2)delete示例
-- 删除users表中,id为4的用户
delete from users where id=4
7.3.5 SQL的where子句
(1)语法
where子句用于限定选择的标准。在select、delete、update语句中,皆可使用where子句限定选择的标准。
-- 查询status为1的所有用户
select * from users where status=1
-- 查询id大于2的所有用户
select * from users where id>2
--查询username不等于admin的所有用户
select * from users where username<>'admin'
7.3.6 SQL的and和or运算符
(1)语法
and和or可在where子语句中把两个或多个条件结合起来。
and表示必须同时满足多个条件,相当于JavaScript中的&&运算符。
or表示只要满足任意一个条件即可,相当于JavaScript中的||运算符。
(2)示例
-- 使用and来显示所有状态为0且id小于3的用户
select * from users where status=0 and id<3
-- 使用or来显示所有status为1,或者username为zs的用户
select * from users where status=1 or username='zs'
7.3.7 SQL的order by子句
(1)语法
order by语句用于根据指定的列对结果集进行排序。
order by语句默认按照升序对记录进行排序。desc对记录进行降序。
-- 对users表中的数据,按照status字段进行升序排序
select * from users order by status
-- 按照id对结果进行降序排序 desc表示降序
select * from users order by id desc
7.3.8 SQL的count(*)函数
(1)语法
count(*)函数用于返回查询结果的总数据条数。语法格式:
select count(*) from 表名称
(2)示例
-- 查询users表中status为0的总数据条数
select count(*) from users where status=0
-- 使用as关键字给列起别名
select count(*) as total from users where status=0
select username as uname,password as upword from users
7.4 项目中操作MySql
7.4.1 在项目中操作数据库的步骤
① 安装操作MySQL数据库的第三方模块(mysql)
② 通过mysql模块连接到MySQL数据库
③ 通过mysql模块执行SQL语句
// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
host:'127.0.0.1',//数据库的IP地址
user:'root',//登录数据库的账号
password:'*********',//登录数据库的密码
database:'my_db_01'//指定要操作哪个数据库
})
// 测试mysql模块能否正常工作
db.query('select 1',(err,results)=>{
// mysql模块工作期间报错了
if(err) return console.log(err.message);
// 能够成功的执行SQL语句
console.log(results);
})
7.4.2 查询数据
// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
host:'127.0.0.1',//数据库的IP地址
user:'root',//登录数据库的账号
password:'********',//登录数据库的密码
database:'my_db_01'//指定要操作哪个数据库
})
// 查询users表中所有的数据
const sqlStr='select * from users'
db.query(sqlStr,(err,results)=>{
// 查询数据失败
if(err) return console.log(err.message);
// 查询数据成功
// 注意:如果执行的是select查询语句,则执行的结果是数组
console.log(results);
})
7.4.3 插入数据
// 1. 导入mysql模块
const mysql=require('mysql')
// 2. 建立与MySQL数据库的连接关系
const db=mysql.createPool({
host:'127.0.0.1',//数据库的IP地址
user:'root',//登录数据库的账号
password:'*****',//登录数据库的密码
database:'my_db_01'//指定要操作哪个数据库
})
// 向users表中,新增一条数据,其中username的值为Spider-Man,password的值为pcc123
const user={username:'Spider-name',password:'pcc123'}
// 定义待执行的SQL语句 ?表示占位符 防止SQL注入及提高可复用性
const sqlStr='insert into users (username,password) values (?,?)'
// 执行SQL语句
db.query(sqlStr,[user.username,user.password],(err,results)=>{
// 执行SQL语句失败
if(err) return console.log(err.message);
// 执行SQL语句成功
// 注意:如果执行的是insert into插入语句,则results是一个对象
// 可以通过affectedRows属性,来判断是否插入数据成功
if(results.affectedRows===1){console.log('插入数据成功');}
})
插入语句的便捷方法:
向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以:
const user={username:'Spider-Man2',password:'pcc4321'}
// 定义待执行的SQL语句
const sqlStr='insert into users set ?'
// 执行SQL语句
db.query(sqlStr,user,(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){console.log('插入数据成功');}
})
7.4.4 更新数据
// 更新用户信息
const user={id:6,username:'aaa',password:'000'}
// 定义SQL语句
const sqlStr='update users 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语句之后,执行的结果,也是一个对象,可以通过affectedRows判断是否更新成功
if(results.affectedRows===1){console.log('更新成功');}
})
更新表数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以:
const user={id:6,username:'aaaa',password:'0000'}
// 定义SQL语句
const sqlStr='update users set ? where id=?'
// 执行SQL语句
db.query(sqlStr,[user,user.id],(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){console.log('更新成功');}
})
7.4.5 删除数据
const sqlStr='delete from users where id=?'
db.query(sqlStr,6,(err,results)=>{
if(err) return console.log(err.message);
// 注意:执行delete语句之后,结果也是一个对象,也包含affectedRows属性
if(results.affectedRows===1){
console.log('删除数据成功');
}
})
使用delete语句,会真正的把数据从表中删除掉。为了保险起见,推荐使用标记删除的形式,来模拟删除的动作。
使用标记删除,就是在表中设置类似于status这样的状态字段,来标记当前数据是否被删除。
当用户执行删除的动作时,直接执行update语句,将数据对应的status字段标记为删除即可。
// 标记删除
const sqlStr='update users set status=? where id=?'
db.query(sqlStr,[1,7],(err,results)=>{
if(err) return console.log(err,message);
if(results.affectedRows===1){
console.log('标记删除成功');
}
})
8.前后端的身份认证
8.1 不同开发模式下的身份认证
① 服务端渲染推荐使用Session认证机制
② 前后端分离推荐使用JWT认证机制
8.2 Session认证机制
8.2.1 HTTP协议的无状态性
指的是客户端的每次HTTP请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
8.2.2 Cookie
Cookie是存储在用户浏览器中的一段不超过4KB的字符串。它由一个名称,一个值和其它几个用于控制Cookie有效期、安全期、使用范围的可选属性组成。(不具有安全性)
不同域名下的Cookie各自独立,每当客户端发送请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。
Cookie的几大特性:
① 自动发送
② 域名独立
③ 过期时限
④ 4KB限制
8.2.3 Session认证机制
8.2.4 在Express中使用Session认证
(1)安装express-session中间件,即npm install express-session。
(2)配置express-session中间件,通过app.use()来注册session中间件。
const session=require('express-session')
app.use(session({
secret:'itheima',//任意字符串
resave:false,//固定写法
saveUninitialized:true,//固定写法
}))
(3)向session中存数据
当express-session中间件配置成功之后,即可通过req.session来访问和使用session对象,从而存储用户的关键信息。
// 登录的 API 接口
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置了express-session这个中间件之后,才能够通过
req.session.user=req.body.username//用户的信息
req.session.islogin=true//用户的登录状态
res.send({ status: 0, msg: '登录成功' })
})
(4)从session中取数据
可以直接从req.session对象上获取之前存储的数据。
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({status:1,msg:'fail'})
}
res.send({
status:0,
msg:'success',
username:req.session.user
})
})
(5)清空session
调用req.session.destroy()函数,即可清空服务器保存的session信息。
// 退出登录的接口
app.post('/api/logout', (req, res) => {
// TODO_04:清空 Session 信息
req.session.destroy()
req.send({
status:0,
msg:'退出登录成功'
})
})
8.3 JWT认证机制
8.3.1 JWT的工作原理
用户的信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份。
8.3.2 JWT的组成部分
jwt通常由三部分组成,分别是Header(头部)、Payloac(有效荷载)、Signature(签名)。
三者之间使用.分隔,格式如下:
8.3.3 JWT的使用方式
客户端收到服务器返回的JWT后,会将它存储在localStorage或sessionStorage中。之后客户端每与服务器通信,都要带上JWT,从而进行身份认证。推荐把JWT放在HTTP请求头的Authorization字段中。格式如下:
8.3.4 在Express中使用JWT
(1)安装JWT相关包,npm install jsonwebtoken express-jwt。
① jsonwebtoken用于生成JWT字符串
② express-jwt用于将JWT字符串解析还原成JSON对象
(2)导入JWT相关的包,使用require()函数:
// 1.导入用于生成JWT字符串的包
const jwt=require('jsonwebtoken')
// 2.导入用于将客户端发送过来的JWT字符串,解析还原成JSON对象的包
const expressJWT=require('express-jwt')
(3)定义secret密钥
为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被别人破解,专门定义了一个用于加密和破解的secret密钥:
① 当生成JWT字符串时,需要使用secret密钥对用户信息进行加密,得到加密好的JWT字符串
② 当把JWT字符串解析还原成JSON对象时,需要使用secret密钥进行解密
//3.secret 密钥的本质:就是一个字符串
const seretKey='cqiq No1 ^_^'
(4)在登录成功后生成JWT字符串
调用jsonwebtoken包提供的sign()方法,将用户的信息加密成JWT字符串,响应给客户端:
// 登录接口
app.post('/api/login', function (req, res) {
// 将 req.body 请求体中的数据,转存为 userinfo 常量
const userinfo = req.body
// 登录失败
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登录失败!'
})
}
// 登录成功
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的密钥
// 参数3:配置对象,可以配置当前token的有效期
// 注意:不能把密码加密到token字符中
const tokenStr=jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})
res.send({
status: 200,
message: '登录成功!',
token: tokenStr // 要发送给客户端的 token 字符串
})
})
(5)将JWT字符串还原为JSON对象
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的AUthorization字段,将Token字符串发送到服务器进行身份认证。
服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象。(express-jwt版本最好为6.1.1否则会报错)
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// expressJWT({secret:secretKey})是用来解析Token的中间件
// .unless 用于配置不需要访问权限的接口
// 只要配置成功了express-jwt这个中间件,就可以把解析出来的用户信息,挂载到req.user属性上
app.use(expressJWT({secret:secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))
(6)使用req.user获取用户信息
当express-jwt配置成功后,即可在那些有权限的接口中,使用req.user对象,访问从JWT字符串中解析出来的用户信息。
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user);
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user // 要发送给客户端的用户信息
})
})
(7)捕获解析JWT失败后产生的错误
当使用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。可以通过Express的错误中间件,捕获错误并进行相关的处理。
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
// 错误是由token解析失败导致的
if(err.name==='UnauthorizedError'){
return res.send({
status:401,
message:'无效的token'
})
}
res.send({
status:500,
message:'未知的错误'
})
})