Node.js 基础学习笔记

Node.js

目标:

​ 什么是Node.js

​ Node.js可以做什么

​ Node.js中的JavaScript的组成部分有哪些

​ 能使用fs模块处理路径

​ 能使用path模块处理路径

​ 能使用http模块写一个基本的web服务器

基础

1.什么是Node.js

Node.js® 是一个基于Chrome V8 引擎解析js源码的、开源、跨平台的 JavaScript 运行环境。

2.可以做什么

基于Express框架构建Web应用/ 基于Electron框架构建跨平台的桌面应用/基于restify框架构建API接口项目/读写和操作数据库…

3.常见的DOS命令

cls: 清屏 | dir:显示当前文件目录

fs文件系统模块

是什么如何使用

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)
  }

Path路径模块

是什么如何使用

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 参数,将省略后缀

http模块

是什么如何使用

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管理包

​ 了解什么是规范的包结构

​ 了解模块的加载机制

什么是模块化和好处:

模块化就 遵照一定的规则 将大模块 划分为 小模块

好处:提高代码复用 , 按需调用模块, 维护方便

模块化规范:

如: 使用什么样的格式 引入模块 / 如何向外部模块 暴露模块中的成员 供其使用等

遵循模块化规范,降低了人员的沟通成本,提升了开发效率。

Node.js中的模块化

分类:

内置模块:(fs , path, http 等) 自定义模块:由用户创建的 js文件 第三方模块:需要从外部下载引入的模块如: Mysql, Express等。

加载模块:

//加载内置模块
const fs = require('fs')
//加载自定义模块
const custom = require('./mymodle.js') //可以省略 .js 后缀
//加载第三方模块
const Mysql = require('Mysql')

注意:加载自定义模块会执行自定义模块中的代码

模块作用域

好处:默认情况下在模块内定义的变量或方法,外面的模块无法访问,防止变量污染。

module对象

每一个模块中都有一个module对象,存储了当前模块的各种信息,其中exports对象控制着 成员暴露 窗口,如下:

console.log(module)

输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xa50geog-1686537415348)(C:\Users\zxy24\AppData\Roaming\Typora\typora-user-images\image-20230426154255957.png)]

module.exports 对象

作用:向外共享模块内部成员 默认为 { }

//如果在自定义模块中的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中的模块化规范

Node.js 遵循 CommonJS规范,规范了模块如何相互依赖和模块的特性:

规定如下:

  1. 每个模块内部,module变量代表当前模块

  2. module.exports对象时对外的接口

  3. 加载某个模块,其实是加载该模块的module.exports对象,require()用于加载模块


npm与包

是什么: 他妈的就是 第三方模块

为什么需要包: 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’


Express模块

目标:

1.能使用express.static()快速托管静态资源

2.能使用express路由精简项目结构

3.能够使用常见的expree中间件

4.能使用express创建API接口

5.能在express中启用cors跨域资源共享

是什么 能做什么

是一个基于Node.js的http内置模块基础上封装的Web开发框架

可以创建Web网页服务器/API接口服务器

基本使用

创建基本的Web服务器
//加载express模块
const express = require('express')

//创建一个web服务器
const server = express()

//启动服务器,并监听80端口
server.listen(80, ()=>{
    console.log('Web服务器已在 http://localhost:80 运行')
})


监听客户端的 get 请求
服务器名.get(URL, callback(req, res){})
//其中URL表示 客户端 请求的 url路径
监听客户端的 post请求
服务器名.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)]

express.static(URL)设置静态服务器资源路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 自动重启服务器

nodemon 就是一个npm工具包,作用就是,监听到项目文件发生更改,会自动重启服务器,这样我们就不用去Ctrl +C 然后启动服务器了。

安装:

npm i nodemon -g

使用:

node ~.js 替换为 nodemon ~.js 即可

express路由

路由解决一个数据包到目的地址的最佳路径问题。

什么是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
中间件(Middleware)

特指 业务流程 的 中间处理环节

作用:多个中间件之间, 共享reqres,我们可以在上游的中间件中添加自定义方法或属性,这样下游中间件和路由也能使用了。

中间件的调用流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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中间件: 用来解析请求体数据

​ 使用步骤:

  1. 安装中间件 npm install body-parser

  2. 使用 require 导入中间件

  3. 调用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()用来解析客户端发送的请求体的自定义中间件

​ 步骤:

  1. 定义中间件

  2. 监听req的 data事件

    2.1 如果数据量较大,无法一次性发送,客户端会将数据拆分了发送。所以data事件可能会被触发多次,每一次触发data事件,获取到的数据只是完整数据的一部分,需要手动对接受的数据进行拼接。

    2.2 客户端向服务器发送请求体会触发data事件

  3. 监听req的 end 事件

    3.1 客户端请求体数据接收完成后,会触发end事件

  4. 使用内置的querystring模块解析请求体数据

    4.1 Node.js 内置了一个querystring模块, 专门处理查询字符串, 通过模块提供的parse()函数,可以把查询字符串,解析成对象的格式。

  5. 将解析出来的数据对象挂在为req.body

  6. 将自定义中间件封装为模块

封装中间模块部分:

//封装的自定义中间件模块 部分
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')
})

express写接口

不支持跨域:

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中间件解决跨域问题:

​ 步骤:

  1. $ npm install cors 
    
  2. const cors = require('cors')
    
  3. 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. 安装 和 配置 三方模块 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为执行语句后的结果。
    
    1. 通过mysql模块执行SQL语句

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("删除成功")
    }
})

Web 开发模式

分类

基于服务器渲染的传统Web开发模式

概念:服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面数据。

优点:

前端耗时少, 有利于SEO

缺点:占用服务器端资源, 不利于前后端分离,开发效率低

基于前后端分离的新型Web开发模式

概念:前后端分离的开发模式,依赖于 Ajax技术的广泛应用。简而言之,前后端分离的Web开发模式,就是 后端 只负责提供API接口,前端使用Ajax的开发模式。

优点:

  1. 开发体验好。前端专注于UI界面的开发,后端专注于api的开发,且前端有更多的选择性。

  2. 用户体验好。Ajax技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。

  3. **减轻了服务器端的渲染压力。**因为页面最终是在用户的浏览器中生成的。

缺点:

​ 1. 不利于SEO。因为完整的HTML页面需要在客户端动态拼接完成,所以爬虫无法爬取页面有效信息。(解决方案:利用Vue, React等前端框架的SSR(server side render)技术能够很好的解决SEO问题!

身份认证

概念: (Authentication),又称 身份验证 , 鉴权,通过一定的手段,完成对用户身份的确认。

Session认证机制

HTTP协议的无状态性

概念: 指客户端的 每次HTTP请求都是独立的, 连续多个请求之间没有直接的关系,服务器不会主动保留每次HTTP请求的状态。

使用cookie 突破HTTP协议的无状态性
什么是cookie

概念: 是存储在用户浏览器的一段不超过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)]

在Express中使用Session认证

安装配置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认证机制。

JWT 认证机制:

全称:JSON Web token ,目前最流行的跨域认证解决方案

工作原理
image-20230429124653607
JWT组成部分

由 Header , Payload, Signature 三部分组成。

格式为:

Header.Payload.Signature
//实际格式 
sadfasdfjasdoghoi213j4j2i3hu34651354kj1h54126k25j46l2j654j563.e4234l141251435345125413423434315436jlsdjfsd98723r8y9234oiyr8723h42hfqy3894rhehfoi138y9151035hqleghofh8r13whfd83hedo129012103912031283012.123u10ufjqwhasifiahsf0asfiasjhfipahsfpiahsfiahsfph1320fhahfiahsf

Payload 部分才是真正用户信息,他是用户信息经过加密之后生成的字符串。 Header和Signature只是安全性相关的部分,只为了保证JWT的安全性。

image-20230429125753553

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…形式来对数据合法性进行验证,效率低下,出错率高,维护性差,推荐使用第三方验证模块,降低出错率,提高验证效率和可维护性,

让后端程序员把更多的精力放在核心业务逻辑的处理上

joi 包

​ 为表单中携带的每个数据项,定义验证规则

/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,
    }
}
@escook/express-joi 中间件,

​ 来实现自动对表单数据进行验证的功能

/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')
})
multer 模块

作用: 用于解析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')
})
multer 模块

作用: 用于解析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)

你可能感兴趣的:(node.js,学习,笔记)