目标:
什么是Node.js
Node.js可以做什么
Node.js中的JavaScript的组成部分有哪些
能使用fs模块处理路径
能使用path模块处理路径
能使用http模块写一个基本的web服务器
Node.js® 是一个基于Chrome V8 引擎解析js源码的、开源、跨平台的 JavaScript 运行环境。
基于Express框架构建Web应用/ 基于Electron框架构建跨平台的桌面应用/基于restify框架构建API接口项目/读写和操作数据库…
cls: 清屏 | dir:显示当前文件目录
Node.js官方提供用来操作文件的模块,提供了一些属性和方法,对文件进行操作
//在js代码中导入
const fs = require('fs')
fs.readFile(), 读取文件的中的内容
fs.readFile(path[, options], callback)
// path: 文件路径 字符串 相对路径会出现路径动态拼接问题
// options: 指定以什么格式读取 文件 utf8等 字符串类型("utf8")
// callback, 读取后的回调函数, 前两个形参 分别表示 读取失败 的值(对象)(成功则为 null), 和读取成功的值(失败则为undefined)
例子:
//1.引入fs文件系统模块
const fs = require('fs')
fs.readFile('./files/1.txt', 'utf8', function(err, haha){
//失败的结果
console.log(err)
console.log("--------")
//成功的结果
console.log(haha)
})
//判断文件是否读取成功 方法
fs.readFile('./files/1.txt', 'utf8', function(err, haha){
//判断是否读取成功
if(err){
return console.log('文件读取失败'+ err.message)
}
else {
return console.log('文件读取成功'+ haha)
}
1.打印:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5wLi6gt6-1686537415345)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424194850170.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aNcKnjHs-1686537415346)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424200012749.png)]
fs.writeFile(), 向文件写内容
fs.writeFile(file, data[, options], callback) //覆盖方式 自动创建文件
// file: 文件路径 字符串 相对路径会出现路径动态拼接问题
// data: 写入数据
// options:指定以什么格式读取 文件 utf8等 字符串
// 回调函数: 第一个参数为写入失败的对象,成功为 null
例子:
fs.writeFile('./files/1.txt', ",世界!", 'utf8', function(err){
console.log(err)
})
//判断文件是否写入成功
fs.writeFile('./files/1.txt', ",世界!", 'utf8', function(err){
//判断是否写入成功
if(err){
return console.log('文件写入失败'+ err.message)
}
else {
return console.log('文件写入成功')
}
})
打印:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ClyLI2C-1686537415346)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424200810206.png)]
动态路径拼接问题:
以上方法的 都需要提供 一个 文件路径 此时如果是一个相对路径 如 ./ …/ 等, 如果此时我在根目录的其他目录执行 node 路径 js文件, 如下图讲解:
这是我放 js 文件的目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNJ6hst0-1686537415347)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424211449614.png)]
,其中js内容为:
fs.readFile('./files/1.txt', 'utf8', function(err, haha){
//判断是否读取成功
if(err){
return console.log('文件读取失败'+ err.message)
}
else {
return console.log('文件读取成功'+ haha)
}
这时候在这个目录执行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pw1k2qR6-1686537415347)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424211531506.png)]
可以看到, 正确执行了,并且读取了文件 1.txt的内容
此时我往上一个文件夹,通过node + 路径 + js文件执行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dkmKE9HW-1686537415347)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230424211751726.png)]
由上图绿色部分可知:readFile() 中的文件路径被 拼接为了 当前路径加相对路径,自然就 no such file or directory 了。 此时的解决办法就是 将相对路径换成绝对路径,虽然可行,但是路径过于臃肿 可移植性差。 解决方案如下:
__dirname: 表示当前文件所处的目录
修改后的代码:
//将读取文件路径和js文件的路径拼接即可
fs.readFile( __dirname + '/files/1.txt', 'utf8', function(err, haha){
//判断是否读取成功
if(err){
return console.log('文件读取失败'+ err.message)
}
else {
return console.log('文件读取成功'+ haha)
}
Node.js官方提供用来处理路径的模块。
//使用前需要导入
let path = require('path')
path.join(): 将多个路径拼接成一个完整的路径字符串
path.join([....paths]) //传入多个 路径 片段 ,返回一个(string)路径字符串
例子:
#!/usr/bin/node
//引入path模块
const path = require('path')
//拼接路径片段
let path1 = path.join('/a','/b/c','../','/d')
console.log(path1)
let path2 = path.join(__dirname + '/files/1.txt')
console.log(path2)
打印:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnw5hIbK-1686537415347)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230425222941059.png)] 发现问题,/c没有被拼接,这是因为拼接了一个 …/
path.basename(), 从路径字符串中,将文件名解析出来。
path.basename(path[, ext])
//ext: 扩展名
例子:
#!/usr/bin/node
//引入path模块
const path = require('path')
//创建一个路径
const path3 = '/a/b/c/d/e/f/g/fuck.txt'
//不带ext
console.log(path.basename(path3))
//带ext
console.log(path.basename(path3, '.txt'))
打印:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMCrVImp-1686537415347)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230425224750030.png)] 带 ext 参数,将省略后缀
Node.js提供的,用来创建Web服务器的模块。将普通客户端变成Web服务器, 并提供资源
const http = require('http')
http.createServer()
例子:
//1.导入https模块
//2.创建web实例
//3.为服务器绑定request事件,监听客户端请求
//4.启动服务器
//导入http模块
const http = require('http')
//创建服务器
const server = http.createServer()
//绑定request事件, 如果有客户端请求服务器,会触发request事件
server.on('request',function (req, res) {
console.log(req.url + "请求了这个服务器,使用了" + req.method + '请求')
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end("你请求了这个服务器")
})
//req(请求对象): 包含了 请求端 相关的数据和属性
//req.url :请求端 的 url地址 端口后后面的 地址
//req.method: 请求端的 请求类型
//res(响应对象): 包含了 服务器 相关的数据和属性
//res.end(): 向 请求端 发送指定内容 并结束这次请求
//为了防止中文显示乱码问题,需要设置响应头,Content-Type的值为text/html; charset=utf-8
//res.setHeader('Content-Type', 'text/html; charset=utf-8'):
//启动服务器,和启动成功的回调函数
server.listen(80, function () {
console.log('服务器运行在 http://localhost')
})
例子2: 根据不同的url 响应不同的html内容:
const http = require('http')
const server = http.createServer()
server.on('request', function (req, res) {
let content = "404 Not Found"
res.setHeader('Content-Type', 'text/html; charset=utf-8')
if(req.url === '/index.html')
{
res.end('首页
')
}
else if(req.url === '/about.html'){
res.end('baidu')
}
else {
res.end(content)
}
})
server.listen(80, function(){
console.log('服务器以运行在80端口')
})
小贴士:
假设res.end()返回一个网页字符串(html),html里面有一些样式 和 js 文件的外联样式, 外联样式 的 href 属性也会向服务器发起请求,此时需要注意这些子请求的路径问题。
目标:
说出模块化的好处
CommonJS规定了哪些内容
Node.js中模块的三大分类是什么
能使用npm管理包
了解什么是规范的包结构
了解模块的加载机制
模块化就 遵照一定的规则 将大模块 划分为 小模块
好处:提高代码复用 , 按需调用模块, 维护方便
模块化规范:
如: 使用什么样的格式 引入模块 / 如何向外部模块 暴露模块中的成员 供其使用等
遵循模块化规范,降低了人员的沟通成本,提升了开发效率。
分类:
内置模块:(fs , path, http 等) 自定义模块:由用户创建的 js文件 第三方模块:需要从外部下载引入的模块如: Mysql, Express等。
加载模块:
//加载内置模块
const fs = require('fs')
//加载自定义模块
const custom = require('./mymodle.js') //可以省略 .js 后缀
//加载第三方模块
const Mysql = require('Mysql')
注意:加载自定义模块会执行自定义模块中的代码
好处:默认情况下在模块内定义的变量或方法,外面的模块无法访问,防止变量污染。
每一个模块中都有一个module对象,存储了当前模块的各种信息,其中exports对象控制着 成员暴露 窗口,如下:
console.log(module)
输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xa50geog-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230426154255957.png)]
作用:向外共享模块内部成员 默认为 { }
//如果在自定义模块中的module.exports没有做出有关定义的情况下,使用require方法加载自定义对象,得到的值是一个空对象,由此可见,require()所指向的对象就是exports指向的对象。
const my = require('自定义模块')
共享成员:
自定义模块代码:
let name = 'zhang'
//向外共享 属性
module.exports.age = 18
//向外共享 方法
module.exports.sayHelloWorld = function () {
console.log('Hello World!')
}
//共享 模块内 属性
module.exports.name = name
外部模块引入自定义模块:
//加载自定义模块
const my = require('./自定义成员')
console.log(my)
my.sayHelloWorld()
console.log(my.name, my.age)
打印:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mqj55eDc-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230426160617404.png)]
exports对象:
module.exports 和 exports 指向同一个对象, require()模块时**,永远以module.exports对象为准。**
Node.js 遵循 CommonJS规范,规范了模块如何相互依赖和模块的特性:
规定如下:
每个模块内部,module变量代表当前模块
module.exports对象时对外的接口
加载某个模块,其实是加载该模块的module.exports对象,require()用于加载模块
是什么: 他妈的就是 第三方模块
为什么需要包: Node.js的内置模块无法满足日常项目开发需求,或者实现起来很麻烦,所以有了包的出现。
包是基于内置模块封装的,但效率更高,使用方便简单。
Where is download?
官网:https://www.npmjs.com/
服务器:https://registry.npmjs.org
包的使用步骤:
1.使用包管理工具,在项目中安装包
$ npm install 包名称 / $ npm i 包名称 //cmd linux终端执行 最新版本
$ npm install 包名称@版本号 //安装指定版本的包
2.使用require()导入包
3.参考包文档进行使用
包的版本以 “点分十进制” 定义, 总共有三位数 如 2.24.1
其中每位含义如下:
第一位:大版本, 一般需要重构底层代码
第二位:功能版本, 对当前大版本的功能进行拓展,不会修改底层代码
第三位:Bug修复版本,修复一个Bug + 1
版本号的提升规则:只要前面的版本号增长了,后面的全部 归零
npm规定,在项目根目录中,必须提供一个叫做package.json的包管理配置文件,用来记录与项目有关的配置信息。例如:
项目的名称 版本号 描述等
项目中都用到了哪些包
哪些包只在开发期间会用到
哪些包在开发和部署时都需要用到
可以在项目根目录中,创建一个叫做package.json的配置文件,用来记录项目中安装了哪些包。从而剔除node_modules目录之后,在团队成员之间共享项目的源代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sXqntGgC-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230426192808251.png)]
注意:在今后的项目开发中,一定要把node_mudules文件夹,添加到.gitignore忽略文件中。
快速创建package.json:
$ npm init -y
package.json 中的 dependencies 节点用于记录 安装了哪些包
一次性安装剔除后的包:
npm install // npm 包管理工具包会去package.json文件读取dependencies节点的 包数据 ,从而一次性安装他们。
卸载包:
npm uninstall 包名
//卸载后,会自动将包名从 dependencies 节点中剔除。
devDependencies 节点:用于记录只在 项目开发中会用到的包 项目上线后不会用到的包, 而denpendencise到会用到
npm install -D 包名 //将包记录到devDependencies中
切换NPM镜像源:
npm config get registry //查看当前的镜像源
npm config set registry=镜像源地址
包的分类:
项目包: 在node_module中的包
开发依赖包 记录到 devDependencise节点中
核心依赖包 记录到 dependencise节点中
全局包:
npm i 包名 -g //安装
npm uninstall 包名 -g //卸载
//只有工具性质的包,才有全局安装的必要性,提供一些好用的终端命令
全局包 i5ting_toc: 将md 文件转化成 html页面
npm install i5ting_toc -g //安装
i5ting_toc -f md文件路径 -o //-o 选项在默认浏览器中打开
规范的包结构:
1.必须以单独的目录存在
2.包顶级目录必须包含 package.json文件
3.package.json文件必须包含 name, version, main 分别对应 包名, 包版本, 包入口
如何写自己的包:
1.创建一个目录,包含index.js, pakage.json, README.md文件
2.将功能写到index.js中,暴露成员,接受测试。
3.在包的根目录创建一个src文件夹,同时把每个功能对应一个js文件(小模块),一一添加到src文件夹,这一步叫做模块 化拆分。
4.将每个小模块进行成员暴露,在大模块中引入小模块,同时使用如下方式暴露成员。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1phIldIf-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230426214353471.png)] 其中date和escape就是小模块对象
5.编写包的说明文档 README.md 只要把作用,用法,注意事项说清楚即可
6.初始化package.json文件
npm init -y
7.登录你的npm账号(需要去npm官网创建)
npm login
8.发布你的包,处于包的根目录
npm publish
9.如果你不想要了,删除你的包
npm unpublish 包名 --force //从npm删除你以发布的包
//只能删除72小时内发布的包
//删除了24小时不能重复发布包
//不要发布没有意义的包
优先从缓存中加载,如加载一个自定义模块后,再次加载,没有执行自定义模块的内容。
内置模块的加载优先级最高
自定义模块的加载必须以./ 或者 …/开头, 不然会当作内置模块和第三方模块加载。如过自定义模块没有添加后缀,则按照以下方式查找: .js -> .json -> .node
const name = require('./haha/text.js')
第三方模块加载机制:如果require 路径中 判断不是内置模块/ ./ 或…/开头的自定义模块,则会在当前目录查找node_module文件夹里对应的模块,如果当前目录没有node_module文件夹,会往上层目录查找,如果一直没有,会一直查找到 磁盘根目录
目录做为模块加载
当把目录做为模块标识符,传递给require()进行加载的时候的加载过程:
直接寻找目录下 package.json文件,查找main属性,做为require()加载的入口
没有package.json文件,或main入口不存在或无法解析,会去加载目录下加载index.js文件,如果以上都失败了
就会打印 Error: Cannot find module ‘xxx’
目标:
1.能使用express.static()快速托管静态资源
2.能使用express路由精简项目结构
3.能够使用常见的expree中间件
4.能使用express创建API接口
5.能在express中启用cors跨域资源共享
是一个基于Node.js的http内置模块基础上封装的Web开发框架
可以创建Web网页服务器/API接口服务器
//加载express模块
const express = require('express')
//创建一个web服务器
const server = express()
//启动服务器,并监听80端口
server.listen(80, ()=>{
console.log('Web服务器已在 http://localhost:80 运行')
})
服务器名.get(URL, callback(req, res){})
//其中URL表示 客户端 请求的 url路径
服务器名.post(URL, callback(req, res){})
//同get请求一致
res.send(响应内容)
例子:
//加载express模块
const express = require('express')
//创建一个web服务器
const server = express()
//监听客户端的get请求
server.get('/', (req, res)=>{
res.send({name: 'zhang', age: 20})
})
//监听客户端的post请求
server.post('/dade', (req, res)=>{
res.send('谁叫你请求POST')
})
//启动服务器,并监听80端口
server.listen(80, ()=>{
console.log('Web服务器已在 http://localhost:80 运行')
})
req.query(默认是一个空对象) 获取 客户端 请求url后的 查询字符串:如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bka2CAua-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427140028914.png)]
通过res.send(req.query)输出为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HevTmx8I-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427140211876.png)]
req.params对象(默认空对象)可以访问到客户端请求URL地址中,通过 : 匹配到的 动态参数:
例子:
//监听客户端的get请求
server.get('/user/:name', (req, res)=>{ // 也可以匹配多个 动态参数 如 /user/:name/:age/:gender
res.send(req.params)
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akdx7Dii-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427140930269.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vYAmlCv-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427140946892.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXf2eNfj-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427141013165.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UfcOnXmG-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427141026909.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17eaIBBY-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427143413062.png)]
注意点: 在浏览器或者测试中需要访问 静态资源 目录的资源时 将需要**省略掉 静态资源根目录(bootstrap TV)**才能访问
如果非要添加根目录或者别名来访问资源,则应该挂载路径前缀:
webServer.use('/bootstrap TV', express.static('./bootstrap TV')) //设置和资源根目录一样的前缀
webServer.use('/fuck', express.static('./bootstrap TV')) //设置这个别名也是可以的
托管多个静态服务器资源:多调用一次static()即可
const express = require('express')
const webServer = express()
//指定静态服务器 资源路径(目录)
webServer.use(express.static('./bootstrap TV'))
webServer.use(express.static('./other'))
webServer.listen(80, ()=> {
console.log("The server running at http://localhost")
})
注意:如果两个静态服务器资源目录有一个相同路径相同名称的文件,以先设置的静态资源目录的那个为准。
nodemon 就是一个npm工具包,作用就是,监听到项目文件发生更改,会自动重启服务器,这样我们就不用去Ctrl +C 然后启动服务器了。
安装:
npm i nodemon -g
使用:
node ~.js 替换为 nodemon ~.js 即可
路由解决一个数据包到目的地址的最佳路径问题。
什么是express里的路由:
webServer.请求方法(请求路径,回调函数) //对应了 路由的 三个组成部分
//如下:
app.get('/user', (req,res)=>{})
app.post('/haah', (req,res)=>{})
路由的匹配过程:需要请求类型和地址一致才能成功匹配分发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndetD5ih-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427155632320.png)]
最简单的路由用法(不推荐):
app.get('/', function (req,res) {
res.send('haha')
})
app.post('/', (req, res)=>{
res.send('post')
})
模块化路由(推荐):
创建路由模块:
//1.导入express模块
const express = require('express')
//2.创建路由对象
const router = express.Router()
//3.定义路由
router.get('/zhang', (req, res)=>{
res.send('you are get')
})
router.post('/xiao',(req, res)=>{
res.send('you are post')
})
//4.暴露路由对象
module.exports = router
注册路由模块(使用):
const express = require('express')
const app = express()
//1. 导入路由模块
const router = require('./router')
//2.注册路由模块
app.use(router)
app.listen(80, ()=>{
console.log('server running at http://localhost');
})
为路由模块添加访问前缀:
app.use('/api', router) // '/api' 就是访问前缀
此时需要使用
http://localhost/api/zhang 来get
http://localhost/api/xiao 来post
特指 业务流程 的 中间处理环节
作用:多个中间件之间, 共享req和res,我们可以在上游的中间件中添加自定义方法或属性,这样下游中间件和路由也能使用了。
中间件的调用流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RynxH50-1686537415349)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427172137834.png)]
当一个请求到达Express服务器后,连续调用多个中间件,对这次请求进行预处理,上一个中间件的输出,做为下一个中间件的输入。
中间件的格式:
中间件本质上就是一个 function函数,只是形参列表略有不同:
app.get('/', function(req, res, next){next()},function(req, res){})
//其中 中间件处理函数 形参 必须有next这个函数。 只有路由函数不包含
//next函数的作用: 是连续调用多个中间件的关键, 用于流转到下一个中间件 或 路由
全局中间件:
客户端发出的 任何请求 ,到达服务器之后 都会触发的中间件, 叫做全局生效的中间件
通过调用 :
app.use(中间件名称)
即可定义一个全局中间件。
例子:
const express = require('express')
const app = express()
const router = require('./router')
//1.定义一个中间件
const sayHello = function (req, res, next) {
console.log("Hello, 这是一个中间件")
next() //流转到下一个中间件 或者 router
}
//2.定义第二个中间件
const sayHey = (req, res, next) => {
console.log('Hey, 这是第二个中间件')
next() //流转到下一个中间件 或者 router
}
//3.将中间件设置为全局中间件
app.use('/api',sayHello,sayHey,router)
app.listen(80, ()=>{
console.log('server running at http://localhost');
})
终端后台输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IL84UVae-1686537415350)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427173832213.png)]
全局中间件的简化写法:
//中间件的 简化写法
app.use(
'/api'
,sayHello
,(req, res, next) => {
console.log('Hey, 这是第二个中间件')
next() //流转到下一个中间件 或者 router
} //sayhey中间件 简化写法
,router)
注册多个全局中间件:
const express = require('express')
const webServer = express()
const router = require('./router')
//1.第一个全局中间件
webServer.use((req, res, next)=>{
console.log('中间件1');
next()
})
//2.第二个全局中间件
webServer.use((req, res, next)=>{
console.log('中间件2')
next()
})
//3.注册全局路由
webServer.use('/api', router)
webServer.listen(80, ()=> {
console.log('The server is running at http://localhost')
})
局部中间件:
不在use()函数中的中间件都叫局部中间件
定义多个局部中间件:
const express = require('express')
const webServer = express()
const router = require('./router')
//1.局部中间件1
const mf1 = (req, res, next)=> {
console.log('局部中间件1')
next()
}
//2.局部中间件2
const mf2 = (req, res, next)=> {
console.log('局部中间件2')
next()
}
webServer.get('/api', mf1, mf2)
webServer.post('/api', [mf1, mf2])
//这两种方式等效
webServer.use('/api', router)
webServer.listen(80,()=>{
console.log('server run')
})
中间件注意事项:
1.必须注册在路由之前(程序由上至下执行)
2.next() 别忘写
3.next()后不要写额外代码
4.中间件共享req和res
中间件分类:
应用级中间件:凡是注册到 express服务器(webServer | app 等)上的中间件 叫做应用级中间件
路由级中间件:凡是注册到router上的中间件,叫做路由级中间件
错误级别中间件:用于捕获整个项目发生的异常错误,从而防止项目异常崩溃问题。 必须有四个形参
(err, req, res, next); 必须注册在所有路由之后
例子:
const express = require('express')
const webServer = express()
const router = require('./router')
//1.定义一个路由,抛出错误
webServer.get('/', (req, res)=>{
throw new Error('人为错误')
res.send('你好吗')
})
webServer.use('/api', router)
//2.注册一个错误级别的中间件
webServer.use((err, req, res, next)=> {
console.log('发生了:' + err.message)
res.send(err.message)
})
webServer.listen(80,()=>{
console.log('server run')
})
express内置中间件:
express.static(), 快速托管静态资源的,如html,图片,css样式等
express.json(), 解析json格式的请求体数据
例子:
const express = require('express')
const webServer = express()
const router = require('./router')
//注册json内置中间件,解析json格式请求体
webServer.use(express.json())
webServer.post('/', (req, res)=> {
// req.body 可以拿到 客户端的请求体
res.send(req.body)
})
webServer.listen(80,()=>{
console.log('server run')
})
express.urlencoded(),解析URL-encoded格式的请求体数据,解析表单数据
例子:
const express = require('express')
const webServer = express()
const router = require('./router')
//注册json内置中间件,解析json格式请求体
webServer.use(express.json())
//注册urlencoded()中间件,解析URL-encoded格式的请求体数据
webServer.use(express.urlencoded({extended: false}))
webServer.post('/', (req, res)=> {
// req.body 可以拿到 客户端的请求体
res.send(req.body)
})
webServer.listen(80,()=>{
console.log('server run')
})
第三方中间件:
body-parse中间件: 用来解析请求体数据
使用步骤:
安装中间件 npm install body-parser
使用 require 导入中间件
调用express服务器.use()注册使用中间件
const express = require('express')
//1.导入中间件
const body_parser = require('body-parser')
const webServer = express()
const router = require('./router')
//2.注册中间件
webServer.use(body_parser.urlencoded({extended: false}), body_parser.json())
//这他妈和express的内置中间件一样,原来是内置的还是基于三方的封装出来的
webServer.post('/', (req, res)=> {
// req.body 可以拿到 客户端的请求体
res.send(req.body)
})
webServer.listen(80,()=>{
console.log('server run')
})
自定义中间件:
需求: 定义一个像 express.urlencoded()用来解析客户端发送的请求体的自定义中间件
步骤:
定义中间件
监听req的 data事件
2.1 如果数据量较大,无法一次性发送,客户端会将数据拆分了发送。所以data事件可能会被触发多次,每一次触发data事件,获取到的数据只是完整数据的一部分,需要手动对接受的数据进行拼接。
2.2 客户端向服务器发送请求体会触发data事件
监听req的 end 事件
3.1 客户端请求体数据接收完成后,会触发end事件
使用内置的querystring模块解析请求体数据
4.1 Node.js 内置了一个querystring模块, 专门处理查询字符串, 通过模块提供的parse()函数,可以把查询字符串,解析成对象的格式。
将解析出来的数据对象挂在为req.body
将自定义中间件封装为模块
封装中间模块部分:
//封装的自定义中间件模块 部分
const qs = require('querystring')
//1.定义功能
function body_parser(req, res, next){
//2.监听req的data事件
//2.1 定义变量用于存放客户端发到服务器的数据
let str = ''
//2.2 监听data事件
req.on('data', (dataPart)=>{
//2.3 拼接请求体数据,隐式转换为字符串
str += dataPart
})
//3.监听req的end事件
req.on('end', ()=>{
//4. 使用querystring模块,解析 查询字符串
const body = qs.parse(str)
//5.将解析的body挂载到req.body
req.body = body
next()
})
}
//2.暴露成员
module.exports = body_parser
主程序调用部分:
//express主程序部分
const express = require('express')
const webServer = express()
//1.导入自定义中间件模块
const bq = require('./1ziDingYiZhongJJ')
//2.使用自定义中间模块
webServer.use(bq)
webServer.post('/', (req, res)=>{
res.send(req.body)
})
webServer.listen(80, ()=>{
console.log('The server running at http://localhost')
})
不支持跨域:
expressServer.js文件:
const express = require('express')
const apiServer = express()
//导入routerAPI模块
const router = require('./routerAPI.js')
//将路由模块注册到apiServer上
apiServer.use(router)
apiServer.listen(80,()=>{
console.log('Express server running at http://localhost/')
})
routerAPI.js文件:
const express = require('express')
const router = express.Router()
//挂载对应的模块
router.get('/get', (req, res)=>{
let queryStr = req.query
res.send({
status: 0, //0 成功, 1 失败
msg: 'GET 请求成功', //状态的描述
data: queryStr //响应给客户端的数据
})
})
//解析表单数据的中间件
router.use(express.urlencoded({extended: false}))
router.post('/post', (req, res)=>{
let body = req.body
res.send({
status: 0,
msg: 'POST 请求成功',
data: body
})
})
module.exports = router
接口的跨域问题:
使用Jsonp解决跨域:只支持GET请求,淘汰
使用cors中间件解决跨域问题:
步骤:
$ npm install cors
const cors = require('cors')
apiServer.use(cors()) //在路由之前
说实话:这玩意很爽
cors的概念:
CORS(Cross-Origin Resource Sharing, 跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定
浏览器是否阻止前端JS代码跨域获取资源。 浏览器的同源安全策略默认会阻止网页‘跨域’ 获取资源。但如果配置了
CORS相关的HTTP响应头, 就可以解除浏览器的跨域访问限制。
CORS会在请求头加上Origin字段,该字段包含了客户端的 协议, 域名, 端口
CORS会在响应头包含以下信息,从而解除同源策略限制:
//Access-Control-Allow-Origin: 允许跨域的域名 | *(所有)
//设置响应头
res.setHeader("Access-Control-Allow-Origin", 'http://haha.com') //表示只允许 http://haha.com的请求
res.setHeader("Access-Control-Allow-Origin", '*') //表示允许任何的请求
//----------------------------------------------------------------------------------------------
//Access-Control-Allow-Headers
// CORS仅支持客户端向服务器发送如下的9个 请求头
// Accept, Accept-Language, Content-Language, DPR, Downlink, Save-Data, Viewport-Width, Width,
// Content-Type (值限于:text/plain, multipart/form-data, application/x-www-form-urlencoded三者之一)
// 如果客户端向服务器发送了额外的请求头消息,则需要在服务器, 通过以下对额外请求头进行声明,否则请求失败
res.setHeader("Access-Control-Allow-Headers", 'Content-Type, X-Custom-Header')
//---------------------------------------------------------------------------------------------
//Access-Control-Allow-Methods
//默认情况下CORS仅支持客户端发起GET, POST, HEAD请求
//如果客户端希望通过PUT,DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Allow-Methods
//来知名实际请求所允许使用的HTTP方法
res.setHeader("Access-Control-Allow-Methods", 'POST, GET, DELETE, HEAD') //只允许使用这些请求
res.setHeader("Access-Control-Allow-Methods", '*') //允许使用任何请求
Access-Control-Allow-Credentials: true
具体原理参照:https://www.cnblogs.com/yblue/p/14092303.html
简单请求:
请求方式:GET, POST,HEAD
请求头部字段:基础 9 个请求头, 参照 上面 的代码片段。
预检请求:
请求方式:GET, POST,HEAD 之外的请求
请求头部:包含自定义头部字段
向服务器发送了 application/json格式的数据
在浏览器与服务器正式通信前,浏览器会先发送OPTION请求进行预检, 已获知服务器是否允许该实际请求, 所以这一次的OPTION请求称为‘预检请求’。 服务器成功响应遇见请求后,浏览器才会发送真正的请求,并携带真实数据。
简单请求和预检请求的区别:
简单:客户端和服务器只会发生一次请求
预检: 客户端和服务器会发生两次请求,option预检请求成功之后,才会发起真正的请求。
没有使用CORS之前,服务器的响应消息会被浏览器的同源策略拦截
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwVApvZ8-1686537415350)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230427224323552.png)]
解决跨域后的api项目:
expressServer.js文件:
const express = require('express')
//cors解决跨域问题
const cors = require('cors')
const apiServer = express()
// 配置cors中间件
apiServer.use(cors())
//导入routerAPI模块
const router = require('./routerAPI.js')
//将路由模块注册到apiServer上
apiServer.use(router)
apiServer.listen(80,()=>{
console.log('Express server running at http://localhost/')
})
目标:
知道如何配置数据库
能使用常见的SQL语句操作数据库(增删改查)
能够在Express中操作MySQL数据库
能够了解Session的实现原理
了解JWT的实现原理
概念:数据库(database) 用来组织 存储 和管理数据的仓库。为了方便管理数据 就有了数据库管理系统的概念,可以对数据进行增删改查的操作。
常见的数据库和分类:
Mysql, Oracle ,SQL server (三个都是传统型数据库, 又叫关系型数据库或SQL数据库)
Mongodb(新型数据库, 非关系型数据库或NoSQL数据库),一定程度上弥补的传统型数据库的缺陷
数据组织结构: 数据库 -> 数据表table-> 数据行row->字段field 4部分组成
SQL:结构化查询语言, 一门专门访问和处理数据库的 编程语言
目标: 查(select) , 增(insert into), 改(update), 删(delete)
语法: where 条件, and和or运算符, order by 排序, count(*)函数
查:
-- 从指定数据库的数据表获得所有列
select * from my_db01.users;
-- 从一个数据库的数据表得到 指定字段的数据
select username from my_db01.users;
select username, password from my_db01.users;
增:
-- 向users表中插入新数据, 字段名和值需要一一对应
insert into users(username, password) value ('zhangmazi', '123456');
select * from my_db01.users
改:
-- 把users表 id为3的数据行的 密码 更新为 888888
update users set password='888888' where id=3;
select * from users;
-- 一次性更新多个列
update users set password='admin123', status=1 where id=2;
select * from users;
删:
-- 从users表中,删除id=4的用户
delete from users where id=4;
select * from users;
where子句:限定选择的条件:用于select, update, delete语句中
在where的运算符:和JS一样, 新增 between(范围) 和 like(模糊)
select * from users where id > 1;
select * from users where id != 2;
select * from users where id <> 2; -- 这也是不等于
select * from users where password = 888888
and 和 or 运算符:可以结合多个条件:
-- 这个变量为了数据库的安全禁用的更新,需要设置为0或者off才能更新
set sql_safe_updates=0;
update users set password=123 where id =1 or username ="ls";
select * from users;
order by: 根据指定的列对结果集进行排序。
默认升序, DESC关键字可设置为降序排序, ASC升序
-- 对users表中的列按照id的降序排序
select * from users order by id DESC;
-- 对users表中的列按照id的升序排序
select * from users order by id ASC;
-- 多重排序 先按照status 进行降序排序,再按照username字母的顺序,进行升序排序
select * from users order by status desc, username ASC;
count(*):返回查询结果的总条数
-- 查询users表中的status=0的条数
select count(*) from users where status = 0
使用AS关键字给字段设置别名:
-- 查询users表中的status=0的条数
select count(*) as '总数' from users where status = 0
select username as '用户名', password as '密码' from my_db01.users;
查询结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KlliTSlf-1686537415350)(D:\前端\我的笔记\Node.js.assets\image-20230428143235341.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdx1eebT-1686537415350)(D:\前端\我的笔记\Node.js.assets\image-20230428143459569.png)]
步骤:
安装 和 配置 三方模块 mysql
//1. 安装
$npm i mysql
//2. 配置
//2.1.导入mysql模块
const mysql = require('mysql')
//2.2.配置数据连接信息
const db = mysql.createPool({
host: '127.0.0.1', //数据库的ip地址
user: 'root', //登录数据库的账号
password:'admin123', //登录数据库的密码
database: 'my_db01' //指定要操作的数据库
})
//2.3 测试是否连接成功
//定义一条select语句
const q = 'select * from users'
db.query(q, (err, results)=>{
if(err) return console.log(err.message)
console.log(results)
})
//err 为空则为成功, results为执行语句后的结果。
select语句的得到的results为一个数组。
insert into :插入数据:返回result是一个对象
//新用户对象
const user = {username: 'iro-Man', password:'hash'}
//定义一条select语句, ? 代表占位符------------------------------------------------
const insert = 'insert into users(username, password) value (?,?)'
//insert 后面的数组 可以 依次 填充占位符数据
db.query(insert,[user.username, user.password], (err, results)=>{
if(err) return console.log(err.message)
//affectedRows 属性 是表 改变的行数 改变了一行就是新增成功了
if(results.affectedRows === 1) console.log('新增成功')
})
插入数据便捷方式:
const user = {username: 'greenMan', password:'hash'}
const insert = 'insert into users SET ?'
//数据对象的每个属性和数据表的字段必须一一对应
//insert 后面的user对象 可以 依次 填充占位符数据
db.query(insert,user, (err, results)=>{
if(err) return console.log(err.message)
//affectedRows 属性 是表的有一行改变了 就是新增成功了
if(results.affectedRows === 1) console.log('新增成功')
})
id唯一性:就算是删除了记录,id仍然会隐式保留
更新数据:
//需要更新的对象信息和条件
const user = {username:'hiteMan', password:'888', id:7}
//语句
const sqlStr = 'update users set username=?, password=? where id=?'
db.query(sqlStr, [user.username, user.password, user.id], (err, results)=>{
if(err) console.log(err.message)
if(results.affectedRows === 1) {
console.log('修改成功!');
}
})
更新数据便捷方式:同样是一一对应
//需要更新的对象信息和条件
const user = {username:'hiteMan', password:'888', id:7}
//语句
const sqlStr = 'update users set ? where id=?'
//注意这个写法
db.query(sqlStr, [user, user.id], (err, results)=>{
if(err) console.log(err.message)
if(results.affectedRows === 1) {
console.log('修改成功!');
}
})
删除数据:推荐用ID的唯一标识来删除数据(results 是一个对象)
//删除id为5的用户
const sqlStr = 'delete from users where id=?'
// 如果只有一个占位符,可以不要数组
db.query(sqlStr, 5, (err, results)=>{
if(err) return console.log(err.message);
if(results.affectedRows === 1) {
console.log("删除成功")
}
})
标记删除:
delete会真正的把数据从表中删除。 建议改变(Update)status值来标记删除的数据
//标记删除id为1的用户
const sqlStr = 'update users set status = 1 where id = ?'
// 如果只有一个占位符,可以不要数组
db.query(sqlStr, 1, (err, results)=>{
if(err) return console.log(err.message);
if(results.affectedRows === 1) {
console.log("删除成功")
}
})
概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面数据。
优点:
前端耗时少, 有利于SEO
缺点:占用服务器端资源, 不利于前后端分离,开发效率低
概念:前后端分离的开发模式,依赖于 Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是 后端 只负责提供API接口,前端使用Ajax的开发模式。
优点:
开发体验好。前端专注于UI界面的开发,后端专注于api的开发,且前端有更多的选择性。
用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。
**减轻了服务器端的渲染压力。**因为页面最终是在用户的浏览器中生成的。
缺点:
1. 不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫无法爬取页面有效信息。(解决方案:利用Vue, React等前端框架的SSR(server side render)技术能够很好的解决SEO问题!
概念: (Authentication),又称 身份验证 , 鉴权,通过一定的手段,完成对用户身份的确认。
概念: 指客户端的 每次HTTP请求都是独立的, 连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。
概念: 是存储在用户浏览器的一段不超过4kb的字符串。 有一对键(name),值(value)和 其他几个用于控制Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的Cookie各自独立,每当客户端发起请求时,会 自动把当前域名下的所有未过期的Cookie一同发送到服务器。
Cookie的几大特性:1. 自动发送 2. 每个域名的Cookie相互独立 3. 都有过期时限 4.大小有4kb限制
Cookie在身份认证中的作用: 客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器中。随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器即可验明客户端的身份
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4f452bMG-1686537415350)(D:\前端\我的笔记\Node.js.assets\image-20230428184917875.png)]
Cookie不具有安全性: 由于Cookie时存储在浏览器中的,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器。不要使用Cookie存储隐私的数据!!!
为了应对Cookie没有安全性可言,我们的Session认证机制提供在在服务端验证Cookie的真实合法性的服务。
Session的工作原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e3o2Ub88-1686537415350)(D:\前端\我的笔记\Node.js.assets\image-20230428192824591.png)]
安装配置Session中间件:
$ npm i express-session
//------------------------------------------------------------
const express = require('express')
//1.导入session中间件
const session = require('express-session')
const webServer = express()
//2.配置Session中间件
webServer.use(session({
secret:'zhangyuzhangyu', // secret属性的值可以为任何字符串
resave: false, // 固定写法
saveUninitialized: true // 固定写法
}))
webServer.listen(80,()=>{
console.log('This server is running at http://localhost')
})
向session中存数据:
当express-session中间件配置成功后,req.session这个对象才存在,可以在访问和使用session对象存储用户的关键信息:
webServer.post('/login', (req, res)=>{
if(req.body.username !== 'zhang' && req.body.password !=='123456'){
res.send('登陆失败')
}
//在没有导入session中间件是req没有session这个对象的
//存储用户信息
req.session.userInfo = req.body
//存储用户登录状态
req.session.isLogin = true
res.send('登录成功')
})
向session中取数据:
//从session获取用户名
webServer.get('/get/username', (req, res)=>{
//判断是否登录
if(!req.session.isLogin){
return res.send({status: 1, msg:'fail'})
}
res.send({
status: 0,
msg:'success',
username: req.session.userInfo.username
})
})
清空session:
调用**req.session.destroy()**函数,即可清空服务器保存的session信息
//退出登录接口, 清空session
webServer.post('/logout', (req, res)=>{
req.session.destroy()
res.send('退出成功')
})
session认证的局限性:
Session认证机制需要配合Cookie才能实现。由于Cookie默认不支持跨域访问,所以当涉及到前端跨域请求后端接口的时候,需要做很多额外配置,才能实现跨域Session认证。
注意:当前端请求接口**不存在跨域问题的时候****,推荐使用Session** 身份认证机制
当前端需要跨域请求后端接口的时候,推荐使用JWT认证机制。
全称:JSON Web token ,目前最流行的跨域认证解决方案
由 Header , Payload, Signature 三部分组成。
格式为:
Header.Payload.Signature
//实际格式
sadfasdfjasdoghoi213j4j2i3hu34651354kj1h54126k25j46l2j654j563.e4234l141251435345125413423434315436jlsdjfsd98723r8y9234oiyr8723h42hfqy3894rhehfoi138y9151035hqleghofh8r13whfd83hedo129012103912031283012.123u10ufjqwhasifiahsf0asfiasjhfipahsfpiahsfiahsfph1320fhahfiahsf
Payload 部分才是真正用户信息,他是用户信息经过加密之后生成的字符串。 Header和Signature只是安全性相关的部分,只为了保证JWT的安全性。
JWT的使用方式:
客户端请求服务器时,需要将JWT放在请求头的 Authorization 字段中,格式如下:
Authorization: Bearer <token>
安装JWT相关的包:
$ npm install jsonwebtoken //用于生成token加密字符串
$ npm install express-jwt //用于把token字符串解析还原为json对象
//=================================================================
const jwt = require('jsonwebtoken')
const { expressjwt: expressJWT } = require("express-jwt"); //由于版本问题,导入express-jwt中间件必须以这样的方式导入
// const expressJWT = require("express-jwt"); 无效
定义secret密钥:
为了保证JWT字符串的安全性。放置在传输过程中被别个破解,需要定义一个用于加密和解密的secret密钥。
1.需要secre密钥t对用户信息进行加密,获得加密好的JWT字符串
2.想把JWT字符串还原成JSON对象时,需要使用secret密钥解密
//secre就是一字符串
const secretKey = '6666 fuckyou holy shit give your pop, so big ass,hahaha'
生成加密Token:
const token = jwt.sign(
{username: req.body.username}, //用户信息对象 , 不能将密码进行token的加密,这样很危险
secretKey, //加密密钥
{expiresIn: '30s'} //配置对象: 设置token的有效期
)
Token字符串还原为用户信息JSON对象:
客户端每次在访问哪些有权限接口的时候,都需要主动通过 请求头中的 Authoriazation字段,将Token 字符串发送到服务器进行身份认证。
此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象。
//使用use()注册中间件
//expressJWT({secret: 密钥}).unless({path:[/^\/api\//]})
//其中 unless 用来指定哪些接口不需要访问权限 如登录 / 注册等
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken')
const { expressjwt: expressJWT } = require("express-jwt"); //由于版本问题,导入express-jwt中间件必须以这样的方式导入
// const expressJWT = require("express-jwt"); 无效
const cors = require('cors')
app.use(cors())
// 解析表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
// 定义加密解密的密钥
const secretKey = 'lalala5678@hahaha'
// 配置解析token的中间件
// expressJWT({ secret: secretKey }) 就是用来解析token的中间件
// unless({ path: [/^\api\//] }) 配置哪些接口不需要访问权限
app.use(expressJWT({secret:config.jwtSecretKey,algorithms:['HS256']}).unless({path:[/^\/api/]}))
// 加密生成token
app.post('/api/login', (req, res) => {
const userinfo = req.body
if (userinfo.username != 'admin' || userinfo.password != '123456') {
return res.send({
status: 400,
msg: 'fail'
})
}
// 登陆成功后 生成JWT字符串
jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '20s' }, (err, token) => {
if (err) throw err;
res.json({ success: true, token: "Bearer " + token });
})
})
// expressJWT配置成功后 可以访问从JWT字符串中解析出来的用户信息
app.get('/admin/getInfo', (req, res) => {
// 解析出来的信息会自动挂载到req.auth上
res.send({
status: 200,
msg: 'success',
userInfo:req.auth
})
})
app.listen(80, () => {
console.log('express serve running...');
})
通过全局错误中间件,捕获token 不合法/ 失效等 JWT 问题
app.use((err, req, res, next)=>{
//这个错误是由token解析失败导致的
if(err.name === 'UnauthorizedError'){
return res.send({
status: 401,
message: "invalid token"
})
}
res.send({
status: '500',
message: 'Unknow Error'
})
})
1.不建议在数据库中保存明文密码,应使用加密npm包,加密密码后存入数据库。
bcryptjs 模块:对用户密码进行加密
加密后的密码:无法被你想破解
同意明文多次加密,得到的加密结果各不相同,保证了安全性
//先对密码进行加密,然后保存到数据库
userInfo.password = bcryptjs.hashSync(userInfo.password, 10)
//第一个参数是需要加密的值:
//第二个参数是一个 加盐值
表单验证的原则:前端验证为辅, 后端验证为主, 后端永远不要相信前端提交过来的任何内容
后端单纯的使用if …else…形式来对数据合法性进行验证,效率低下,出错率高,维护性差,推荐使用第三方验证模块,降低出错率,提高验证效率和可维护性,
让后端程序员把更多的精力放在核心业务逻辑的处理上
为表单中携带的每个数据项,定义验证规则
/scheme/user.js 文件下
// 导入定义验证规则的包
const joi = require('joi')
//定义用户名和密码的验证规则
//string() 值必须是字符串
//alphanum() 值只能是包含a-zA-Z0-9的字符串
//min(length) 最小长度
//max(length) 最大长度
//required() 值是必填项,不能为undefined
//pattern(正则表达式), 值必须符合正则表达式规则
//用户名的验证规则
const username = joi.string().alphanum().min(1).max(10).required()
//密码的验证规则
const password = joi.string().pattern(/^[\S]{6,12}$/).required()
//定义验证注册和登录表单数据的规则对象
exports.reg_login_schema = {
body:{
username,
password,
}
}
来实现自动对表单数据进行验证的功能
/router/user.js
//inport express module
const express = require('express')
//import userFunctions
const userHeaderler = require('../router_handler/user')
//create a Router obh
const Router = express.Router()
//导入验证数据的中间件
const expressJoi = require('@escook/express-joi')
//导入需要验证规则对象
const { reg_login_schema } = require('../schema/user')
//routers-------------------------------------
//login
Router.post('/login', userHeaderler.login)
//register
Router.post('/register', expressJoi(reg_login_schema), userHeaderler.regUser) //***********************************
//export
module.exports = Router
app.js
//import express module
const express = require('express')
//import cors mid to resolve cross-domain problem
const cors = require('cors')
//import userRouter
const userRouter = require('./router/user')
const Joi = require('joi')
//create a express instance
const app = express()
//configure cors
app.use(cors())
// analysis formdata mid
app.use(express.urlencoded({extended: false}))
// 封装响应失败状态 status=0 的 send()
app.use((req, res, next)=>{
res.cc = (err, status = 1)=>{
res.send({
status,
//判断是错误对象还是,字符串
message: err instanceof Error ? err.message : err
})
}
next()
})
// analysis userRouter
app.use('/api', userRouter)
// 错误级别中间件
app.use(function (err, req, res, next) {
//Joi 参数校验失败
if (err instanceof Joi.ValidationError) { //*************************************************************************************
return res.send({
status: 1,
message: err.message
})
}
//未知错误
res.send({
status: 1,
message: err.message
})
})
//Lisen 80 at app
app.listen(3007,()=>{
console.log('This server is running at http://127.0.0.1:3007')
})
作用: 用于解析form-data格式的请求体数据
安装:
$ npm install multer
导入并初始化:
// 导入multer 和 path
const multer = require('multer')
const path = require('path')
// 创建multer实例, 解析到的 “文件” 会保存到 ../uploads文件夹中
const upload = multer({dest: path.join(__dirname, "../uploads")})
//发布文章路由
//upload.single() 是一个局部生效的中间件,用来解析FormData格式的表单数据
//将文件类型的数据,解析并挂载到req.file属性中
//将文本类型的数据,解析并挂载到req.body属性中
Router.post('/add',upload.single('cover_img'), article_handler.addArticle)
验证数据的中间件
const expressJoi = require(‘@escook/express-joi’)
//导入需要验证规则对象
const { reg_login_schema } = require(‘…/schema/user’)
//routers-------------------------------------
//login
Router.post(‘/login’, userHeaderler.login)
//register
Router.post(‘/register’, expressJoi(reg_login_schema), userHeaderler.regUser) //***********************************
//export
module.exports = Router
~~~js
app.js
//import express module
const express = require('express')
//import cors mid to resolve cross-domain problem
const cors = require('cors')
//import userRouter
const userRouter = require('./router/user')
const Joi = require('joi')
//create a express instance
const app = express()
//configure cors
app.use(cors())
// analysis formdata mid
app.use(express.urlencoded({extended: false}))
// 封装响应失败状态 status=0 的 send()
app.use((req, res, next)=>{
res.cc = (err, status = 1)=>{
res.send({
status,
//判断是错误对象还是,字符串
message: err instanceof Error ? err.message : err
})
}
next()
})
// analysis userRouter
app.use('/api', userRouter)
// 错误级别中间件
app.use(function (err, req, res, next) {
//Joi 参数校验失败
if (err instanceof Joi.ValidationError) { //*************************************************************************************
return res.send({
status: 1,
message: err.message
})
}
//未知错误
res.send({
status: 1,
message: err.message
})
})
//Lisen 80 at app
app.listen(3007,()=>{
console.log('This server is running at http://127.0.0.1:3007')
})
作用: 用于解析form-data格式的请求体数据
安装:
$ npm install multer
导入并初始化:
// 导入multer 和 path
const multer = require('multer')
const path = require('path')
// 创建multer实例, 解析到的 “文件” 会保存到 ../uploads文件夹中
const upload = multer({dest: path.join(__dirname, "../uploads")})
//发布文章路由
//upload.single() 是一个局部生效的中间件,用来解析FormData格式的表单数据
//将文件类型的数据,解析并挂载到req.file属性中
//将文本类型的数据,解析并挂载到req.body属性中
Router.post('/add',upload.single('cover_img'), article_handler.addArticle)