Skip to content

Latest commit

 

History

History
1234 lines (1102 loc) · 45.7 KB

05.nodeJs.md

File metadata and controls

1234 lines (1102 loc) · 45.7 KB

Node.js

什么是Nodejs

  • Node.js 是一个基于 Chrome 谷歌V8 引擎的 JavaScript 运行环境

优缺点

  • 优点
    • 高并发
    • 异步非阻塞的 I/O (I/O 线程池)
    • 特别适用于 I/O 密集型应用
    • 事件循环机制
    • 单线程
    • 可以跨平台
  • 缺点
    • 回调函数嵌套太多、太深 产生回调地狱问题
    • 单线程 处理不好 cpu 密集型任务
    • 只支持单核CPU,不能充分利用CPU
    • 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃(原因:单进程,单线程)

注意

  • 浏览器是 javascript 的前端运行环境
  • nodejs 是 javascript 的后端运行环境
  • nodejs 中无法调用 dom和bom等浏览器内置 api

nodejs 可以干什么

优缺点

  • 优点
    • 异步非阻塞的 I/O (I/O 线程池)
    • 特别适用于 I/O 密集型应用
    • 事件循环机制
    • 单线程
    • 可以跨平台
  • 缺点
    • 回调函数嵌套太多、太深 产生回调地狱问题
    • 单线程 处理不好 cpu 密集型任务

Nodejs的安装

  • 官网:https://nodejs.org/en/
    • LTS版本为稳定版本 Current为最新版本 (尽量安装稳定版)
  • 标准化安装(傻瓜式)一路next即可
  • 打开终端检测Nodejs 是否安装成功 输入:node -v 返回当前nodejs版本号

fs 文件系统(File System) 通过node来操作系统中的文件

  • 不带sync的是异步 带sync的是同步 异步都带 callback回调函数

文件的读写

  • fs.readFile() 方法 用来读取指定文件中的内容
      // 首先必须导入 fs 模块
      const fs = require('fs')
    
      // 以 utf8的编码格式 读取指定的内容 并打印 err和 dataStr
      // 参数1 读取文件的存放路径
      // 参数2 读取文件时 采用的编码格式 一般默认指定为 utf8
      // 参数3 回调函数,拿到读取失败和成功的结果 err dataStr
      fs.readFile('./files/1.txt', 'utf8', function (err, dataStr) {
        // 当读取成功 err 返回的值为 null
        // 当读取失败 err 返回的值为 错误对象 dataStr的值为 undefined
        console.log(err); //err
        console.log('--------');
        console.log(dataStr); //输出文本
      })
  • fs.writeFile() 方法 用来指向指定的文件中写入内容
    • 注意点1 只能创建文件 不能创建路径
    • 注意点2 重复调用fs.writeFile 写入一个文件 新写入的会覆盖之前的内容
      // 首先必须导入 fs 模块
      const fs = require('fs')
    
      // 参数1 指定一个文件路径的字符串
      // 参数2 表示要写入的内容
      // 参数3 可选参数 表示以什么格式写入文件内容 默认值 utf8
      // 参数4 回调函数
      fs.writeFile('./files/2.txt', 'hello XBG', 'utf8', function (err) {
      // 写入成功 err 返回值为 null
      // 写入错误 err 返回错误对象 (将盘符写为电脑没有的就可以报错 'V50:/files/2.txt')
        console.log(err);
      })
  • 读写demo
      // 需求 将 成绩.txt 文件中 文本 ‘小红=99 小白=100 小黄=70 小黑=66 小绿=88’
      // 处理为 如下形式并写入到成绩ok.txt 新文件中
      /* 
      小红:99
      小白:100
      小黄:70
      小黑:66
      小绿:88
      */
    
      const fs = require('fs')
    
      fs.readFile('./demo/成绩.txt', 'utf8', function (err, value) {
      
        if (err == null) {
          let str = value.replaceAll("=", ':').split(' ').join('\r\n')
          console.log(str);
          fs.writeFile('./demo/成绩ok.txt', str, 'utf8', function (err) {
            if (err == null) console.log('写入成功');
          })
        }
      })

fs 处理路径问题

  • 在使用fs 模块操作文件时(读写都会),如果提供的操作路径是以./或./开头的相对路径时,很容易出现路径动态拼接错误的问题
  • 原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
      // 问题复现 文件结构 D盘:father文件 
      - demo文件夹
        - files 文件夹
          - 01.txt 文件
        - app.js 执行js 文件
      
      //使用如下代码去 获取 01.txt的文本内容
      const fs = require('fs')
      fs.readFile('./files/01.txt', 'utf8', function (err, dataStr) {
        console.log(err); //err
        console.log('--------');
        console.log(dataStr); //输出文本
      })
    
      // 重点在于 在那个文件下执行  一般情况下会在 demo 路径下执行 app.js 文件
      // 例如 demo文件夹的上一级目录为 father 然后在father 目录下进行执行
      // 执行命令为 node  demo/app.js (最终报错 找不到)
      // 实际执行的路径为 xxx盘:father/files/01.txt  (使用了当前位于目录位置路径+方法读取或写入的路劲进行了拼接) 最终导致 中间层的文件夹路径不存在引发了报错
      
      
  • 解决方式1 将相对路径改为 绝对路径 这种方式 移植特别差不利于维护
      // // node 02.path/01.路径问题.js
      let url = 'D:/father/demo/files/01.txt'
      const fs = require('fs')
      fs.readFile(url, 'utf8', function (err, dataStr) {
        console.log(err); //err
        console.log('--------');
        console.log(dataStr); //输出文本
      })
    
      //了解
      // 如果使用如下路径 :执行时注意符号 node .\02.path\01.路径问题.js
      // 在js 中 \ 为转义符 所以需要 使用两个 \\ 才能表示 \
      //let url = 'D:\\father\\demo\\files\\01.txt'
  • 解决方式2 __dirname (推荐)
  • __dirname+ 需要处理的文件 相对路径
  • 使用 __dirname 后可以在任意的 目录下去执行文件
       const fs = require('fs')
       console.log(__dirname);
       fs.readFile(__dirname + '/files/01.txt', 'utf8', function (err, dataStr) {
         console.log(err); //err
         console.log('--------');
         console.log(dataStr); //输出文本
       })

path 模块

  • path.join() 将多个路径片段拼接为完整的路径字符串
    • 可以有效的避免使用 + 号拼接路径造成的问题 (使用加号拼接时 ./ 会出问题)
      const path = require('path')
      const fs = require('fs')
      let url = path.join('/a', 'b/c', '../', './d', 'e')
      console.log(url); // 输出 \a\b\d\e  
      //  ../ 会抵消前一个文件路径
    
      //demo
      fs.readFile(path.join(__dirname,'./files/01.txt'), 'utf8', function (err, dataStr) {
      console.log(err); //err
      console.log('--------');
      console.log(dataStr); //输出文本
      }) 
  • path.basename(path,[’ext‘]) 获取路径中的文件名
    • 可以获取路径中的最后一部分 经常通过这个方法获取路径中的文件名
      //path.basename(path,[’ext‘])
      // path 必填 表示一个路径的字符串
      // ext 可选 表示文件的扩展名 字符串类型
    
      let path = require('path')
      let url = './files/01.txt'
      console.log(path.basename(url)); //返回 01.txt  将整个文件名返回
      console.log(path.basename(url, '.txt')); //返回 01  返回了 文件名 不带后缀
  • path.extname('path') 获取路径中的文件扩展名
      let path = require('path')
      let url = './files/01.txt'
      console.log(path.extname(url)); //.txt

http内置模块

  • http 模块是 Node,js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
  • 如果要希望使用 http 模块创建 Web 服务器,则需要先导入它: const http = require( "http' )
  • 创建第一个 简单的服务器
      // 1. 导入 http 模块
      const http = require('http')
    
      // 2. 创建 web 服务器实例
      const serve = http.createServer()
    
      // 3. 为服务器实例绑定 request 事件 ,监听客户端请求
      serve.on('request', (req, res) => {
        console.log('成功响应');
        // req 是请求对象,它包含了与客户端相关的数据和属性
        // req.url 是客户端请求的 URL 地址 (端口号后面的地址)
        // req.method 是客户端的 method 请求类型
        // console.log(req);
        // console.log(req.url, req.method);
    
        // res 是响应对象,它包含了与服务器相关的数据和属性
        console.log(res);
        // res.end() 方法的作用:向客户端发送指定的内容,并结束这次请求的处理过程
        // 当调用 res.end0 方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式
        // 为了防止中文显示乱码问题 需要设置响应头 Content-Type 值  text/html; charset=utf-8
        res.setHeader('Content-Type', 'text/html; charset=utf-8')
        // 进行响应
        res.end(`小八嘎:url:${req.url}, method:${req.method}`)
    
      })
    
      // 4. 启动服务
      // 调用服务器实例 .listen() 方法 既可启动当前的web服务器实例
    
      serve.listen(8080, function () {
        console.log('127.0.0.1:8080');
      })
    
      //终端使用 `node  被执行文件.js `  进行执行
      //或者安装 nodemon  可以让执行文件一直保持着执行状态
    
    
    
      // test----------------------------------
       // 引用 http模块
      let http = require('http'); //内置模块 http
      // console.log(http);
    
      // 创建服务器  第一个参数req 浏览器请求  第二个参数res服务器进行响应
      // 一次请求一次响应
      let server = http.createServer(function (req, res) {
          // 设定请求头支持万国码
          // writeHead 书写服务器请求头
          // 200 状态码 请求成功
          res.writeHead(200, {
              "Content-type": "text/html;charset=utf-8"
          })
          // 服务器发送响应
          // 服务器一旦不进行响应 页面进入假死状态
          res.end('服务器启动了123')
      })
      // 监听端口  3000为端口号 一个应用一个端口号
      server.listen(3000)
    
      // 开启服务 node 文件
      // strl+c 关闭服务器
  • createServer((req,res)=>{}) 创建服务器
  • writeHead 书写服务器请求头
  • end() 服务器输出响应
  • listen(3000) 端口号
  • 一次请求 一次响应 浏览器刷新一次就请求一次 服务器打开修改后需要重启

根据不同url 响应不同的html

  // 1. 导入 http 模块
  const http = require('http')

  // 2. 创建 web 服务器实例
  const serve = http.createServer()

  // 3. 为服务器实例绑定 request 事件 ,监听客户端请求
  serve.on('request', (req, res) => {
    console.log('成功响应');
    const url = req.url
    // 定义返回的内容
    let content = '<h1>404 NOt found!</h1>'

    // 判断请求的url 根据不同url 设置不同返回的内容
    if (url === '/' || url === '/index.html') {
      content = '<h1>首页</h1>'
    } else if (url === '/about.html') {
      content = '<h1>关于页面</h1>'
    }
    // 为了防止中文显示乱码问题 需要设置响应头 Content-Type 值  text/html; charset=utf-8
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    // 进行响应
    res.end(content)

  })

  // 4. 启动服务
  // 调用服务器实例 .listen() 方法 既可启动当前的web服务器实例

  serve.listen(8080, function () {
    console.log('http://127.0.0.1:8080');
  })

模块化 CommonJS 是一套模块化规范

  • 模块化指的就是将一个大的功能拆分为一个一个小的模块,通过不同的模块的组合来实现一个大功能。
  • 模块化带来的好处有:
    • 提高代码的复用性
    • 提高代码的可维护性
    • 可以实现按需加载
  • 模块化规范
    • 使用什么样的语法格式来引用模块
    • 在模块中使用什么样的语法格式向外暴露成员
    • 模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己
  • nodejs 中的模块作用域
    • 模块作用域和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
    • 可以防止全局变量污染问题

向外共享模块作用域中的成员

module 对象

  • 在每个 .js 自定义模块中都有一个 module 对象并存储了和当前模块有关的信息 console.log(module)
  • module.exports 对象
    • 在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用
    • 外界用 require() 方法导入自定义模块时,得到的就是 moduleexports 所指向的对象
    • 注意:使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准
      • 例如前面单独导出了 module.exports.username='张三' 但后面又 module.exports={} 最后返回的是所指向的对象
      // 01.js  导入
      // 在外界使用 require 导入一个自定义模块的时候 得到的成员就是 那个模块中
      // 通过 module.exports 指向的那个对象
      const test = require('./02.js')
      console.log(test); //{ username: '张三', sayHello: [Function (anonymous)] }
    
      //02.js  导出
      // 在一个自定义模块中 默认情况下 module.exports={}
      // 向 module.exports 对象上挂载 username 属性
      module.exports.username = '张三'
      // 向 module.exports 对象上挂载 sayHello 对象
      module.exports.sayHello = function () {
        console.log('Hello');
      }

exports 对象

exports 和 module.exports 的使用误区

  • 时刻谨记 使用 require() 模块时 得到的永远是 module.exports 指向的对象
  • 为了防止混乱 建议不要在同一个模块中同时使用 exports 和 module.exports
  • 由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了exports 对象。
  • 默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。
    //03.js
      console.log(module.exports);
      console.log(exports);
      console.log(
        module.exports === exports
      ); // true
    
      let test = require('./04.js')
      console.log(test);
    
      //04.js
      module.exports.username = '张三'
      exports.age = 18

Node.js 中的模块化规范

  • Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性各模块之间如何相互依赖
  • CommonJS规定:
    • 每个模块内部,module 变量代表当前模块
    • module 变量是一个对象,它的 exports 属性(即 module.exports) 是对外的接口.
    • 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块.

npm与包

  • Nodejs 中的第三方模块又叫做包,第三方模块和包指的是同一个概念,只不过叫法不同
  • 为什么需要包
    • 由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低
    • 包是基于内置横块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率
  • 初次装包完成后,在项目文件夹下多一个叫做 node modules 的文件夹和 package-lockjson 的配置文件
    • node modules 文件夹用来存放所有已安装到项目中的包。require0 导入第三方包时,就是从这个目录中查找并加载包
    • package-lock;json 配置文件用来记录 node modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地等
  • 安装指定版本的包 使用 包名@版本号 举例:[email protected]

包管理配置文件

  • devDependencies 节点
    • 如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中 -D | -dev
    • 与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中 -S | -save

express

  • express 简介
    • express (node插件) Express就是运行在node中的用来搭建服务器的模块
    • Express 是基于 Node,js 平台,快速、开放、极简的 Web 开发框架
    • 通俗的理解: Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的

express 的安装

  • npm init 初始化包管理
  • npm i express --save --dev 下载
  • npm express -v 查看当前版本

express 搭建一个简单的服务器

```js
// 1. 导入express
const express = require('express')

// 2. 创建 web 服务器
const app = express()

// 4.监听客户端 的 get post 请求,并向客户端响应具体内容
// req 请求对象 包含了与请求相关的属性与方法
// res 响应对象 包含了与响应相关的属性与方法
app.get('/user', (req, res) => res.send({
  name: '张三',
  age: 18,
  city: '洛圣都'
}))
app.post('/user', (req, res) => res.send('这是一个post请求的响应'))

// 3. 调用 app.listen (端口号,启动成功的回调函数)

app.listen(3000, () => {
  console.log('http://localhost:3000/user');
})
```

req.query 获取url中携带的查询参数

  • 通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数 (默认是一个空对象)
const express = require('express')
const app = express()
// 获取url中携带的查询参数
app.get('/', (req, res) => {
  // req.query 默认是一个空对象
  console.log(req.query);
  res.send(req.query)
})
app.listen(3000, () => {
  console.log('http://localhost:3000/user');
})

req.params 获取URL中的 动态参数

  • 通过 req.params 对象,可以访问到 URL 中,通过:匹配到的动态参数' (默认是一个空对象)
  • 发送请求示例: http://localhost:3000/user/10
  • 注意
    • 只有 : 符号是固定的 后面的参数名称是任意的
    • 动态参数也不是唯一 可以写多个 例如'/user/:id/:name' 发送请求时 http://localhost:3000/user/10/张三 即可
const express = require('express')
const app = express()
app.get('/user/:id', (req, res) => {
  // req.params 是动态匹配到的 url参数,默认是一个空对象
  console.log(req.params);
  res.send(req.params)
})

app.listen(3000, () => {
  console.log('http://localhost:3000/user');
})

express.static(文件路径) 托管静态资源

  • express.static(),通过它,我们可以非常方便地创建一个静态资源服务器
  • 静态资源管理 图片 、css 文件、 html文件
  • 注意:Express在指定的静态目录中查找文件,并对外提供资源的访问路径因此,存放静态文件的目录名不会出现在 URL 中 (也就是 public 文件夹
  • 多个静态资源需要引用多次中间件调用 app.use()
  • 注意 当引用多个静态资源文件时 两个静态资源文件中 例如都存在 img.png 那么在访问该资源的时候会优先展示 先托管的静态资源目录中的文件
  • 托管静态资源案例
      //  托管静态资源
      const express = require('express')
    
      const app = express()
    
      // 调用 express.static() 方法,快速的对外提供静态资源
    
      app.use(express.static('./public'))
    
      // 托管多个静态资源目录 多次调用 express.static() 函数即可
      app.use(express.static('./files'))
    
      // 托管后访问静态资源
      // 例如:http://localhost:3000/img.jpg
    
      app.listen(3000, () => {
        console.log('http://localhost:3000');
      })
  • 给静态资源添加访问前缀
    • 在访问时 http://localhost:3000/public/img.jpg
    const express = require('express')
    const app = express();
    // 静态资源添加前缀
    app.use('/public',express.static('./public'))
    
    app.listen(3000, (err) => {
        if (!err) console.log('服务启动成功');
        else {
            console.log(err);
        }
    })

express.Router() 路由

  • 在 Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系
  • 路径和请求方法合起来一般被称为 API 端点(Endpoint)。而服务器根据客户端访问的端点选择相应处理逻辑的机制就叫做路由
  • 相同域名下,访问不同的地址
  • '/' -为根路由 '/xxx'-一级路由 '/xxx/xxx'-二级路由 以此类推 路由层级不超过5级 否则嵌套过深

模块化路由

  • 为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块
    • 1.创建路由模块对应的 js 文件
    • 2.调用express.Router() 函数创建路由对象
    • 3.向路由对象上挂载具体的路由
    • 4.使用 module.exports 向外共享路由对象
    • 5.使用 app.use() 函数注册路由模块
    // router.js
    // 引入exoress
    const express = require('express')
    // 添加路由对象
    let router = express.Router();
    // 挂载get路由
    router.get('/', (req, res) => {
        res.send('get首页')
    })
    // 挂载post路由
    router.post('/', (req, res) => {
        res.send('post首页')
    })
    // 将路由对象暴露
    module.exports = router;
    
    
    // app.js
    // 引入exoress
    const express = require('express')
    // 创建服务对象
    const app = express();
    // 导入路由模块
    const router = require('./router');
    // app.use是express 调用中间件的方法
    app.use(router);
    // 为路由模块添加前缀
    // app.use('/api', router)
    // 访问时 http://localhost:3000/api
    app.listen(3000, (err) => {
        if (!err) console.log('服务启动成功');
        else {
            console.log(err);
        }
    })

动态路由

  • demo
      const express = require('express')
    
      const app = express();
      // :冒号表示动态路由 :冒号后面表示动态参数
      app.get('/meishi/:abc', function (req, res) {
          // params 获取页面上 get的动态路由
          let {
              abc
          } = req.params;
          res.send(`欢迎进入${abc}`)
      })
      app.post('/', function (req, res) {
          res.send('post请求')
      })
      app.listen(3000, (err) => {
          if (!err) console.log('服务启动成功');
          else {
              console.log(err);
          }
      })

express中间件 (Middleware 特指业务流程的中间处理环节)

  • 什么是中间键
    • 中间件就是能够适用多个应用场景、可复用性良好的代码
    • 中间件是按顺序执行的,因此在配置中间件时顺序非常重要,不能弄错
    • 中间件在执行内部逻辑的时候可以选择将请求传递给下一个中间件,也可以直接返回用户响应
  • demo
    const express = require('express');
    const app = express();
    // 定义中间件
    const a = function (req, res, next) {
        console.log('中间件');
        next()
    }
    // 将中间件挂载为全局中间件
    app.use(a);
    
    //当调用任何一个 接口时 中间件 a 都会被执行一次 
    app.get('/', (req, res) => {
        console.log('get log 首页1');
        res.send('首页2')
    
    })
    
    app.listen(3000, (err) => {
        if (!err) console.log("服务启动成功");
    })
  • 中间件的作用
    • 多个中间件之间 共用一份req,res 这样就可以利用上一个中间件 统一为req,res对象 添加 自定义的属性、方法 给 下一个中间件使用
  • 注意
    • 例如在单个接口上:多个中间件的调用 使用逗号分隔 或者使用数组嵌套(x1,x2 | [x1,x2])
    • 所有中间件 本质上 是一个function 处理函数
    • next() 多个中间件连续调用的关键 表示将流转关系 转交 给 下一个中间/路由
    • 所有中间件中 必须优先调用 通过use方法调用的中间件 ,如果不是优先调用 之前调用的中间件必须 next()
    • 必须在 路由之前 注册中间件
    • 可以使用连续的多个中间件进行处理请求,不要忘记调用 next(),会按照中间件定义的先后顺序依次进行调用
    • 最好不要 在next() 后 再书写代码 容易造成代码逻辑混乱
    • 连续调用多个中间件的时候,中间件可以共享 req,res 对象

全局中间件 和 局部中间件

  • 全局中间件 调用方式: app.use(中间件函数)
    • 客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局中间件
      const express = require('express');
      const app = express();
      app.use((req, res, next) => {
          const time = Date.now();
          console.log(time);
          req.t = time;
          next();
      })
      app.get('/', (req, res, next) => {
          res.send('当前时间' + req.t);
          next();
      })
      app.listen(3000, (err) => {
          if (!err) console.log("服务启动成功");
      })
  • 局部中间件 使用 声明接收
    • 不需要使用 app.use() 调用的中间件,统称为 局部中间件
      const express = require('express');
      const app = express();
      
      const a = (req, res, next) => {
          console.log('a中间件');
          next();
      }
      const b = (req, res, next) => {
          console.log('b中间件');
          next();
      }
      app.get('/', [a, b], (req, res, next) => {
          res.send('首页');
      })
      app.get('/news', (req, res, next) => {
          res.send('新闻页');
      })
      
      app.listen(3000, (err) => {
          if (!err) console.log("服务启动成功");
      })

中间件的分类

  • 1.应用级别的中间件 通过app.use/get/post 绑定在app实例上的中间件
  • 2.路由级别的中间 绑定到 express.Router()上的中间件
      const express = require('express')
      const app = express()
      const port = 3000
      const router = express.Router()
    
      router.use((req, res, next) => {
        console.log('阿姨不洗铁路');
        const xbg = '小八嘎'
        req.xbgs = xbg
        next()
      })
      app.use('/', router)
    
      app.get('/', (req, res) => {
        res.send('Home page' + req.xbgs)
      })
      app.post('/user', (req, res) => {
        res.send('User page')
      })
    
    
      app.listen(port, () => console.log(`Example app listening on port ${port}!`))
  • 3.错误级别的中间件 专门捕获异常错误,从而防止程序因异常崩溃的问题
    • 方法:必须 四个参数 (err,req,res,next)
    • 重点:错误级别中间件 必须注册在 所有路由之后 再去调用
    • demo
      const express = require('express');
      const app = express();
      const port = 3000;
      app.get('/', (req, res, next) => {
          throw new Error('一个错误信息')
          res.send('首页');
      })
      app.use((err, req, res, next) => {
          console.log(err.message);
          res.send(err.message)
      })
      app.listen(port, (err) => {
          if (!err) console.log("服务启动成功")
          else {
              console.log(`Example app listening on port ${port}!`);
          }
      })
  • 4.内置中间件
    • express.static() 托管静态资源的中间件
    • express.json() 解析json格式的请求数据,兼容性问题4.16,语法:app.use(express.json())
    • express.urlencoded() 解析url-urlencoded格式的请求体数据 兼容性问题4.16+ 语法:app.use(express.urlencoded({extended:false}))
    • express.json() 和 express.urlencoded() 案例
      const express = require('express')
      const app = express()
      const port = 3000
      
      // 通过 express.json() 解析表单中的  json 格式的数据
      app.use(express.json())
      
      // 通过 express.urlencoded() 解析表单中的  url-encoded 格式的数据
      app.use(express.urlencoded({
        extended: false
      }))
      
      /* 
      访问 :http://localhost:3000/ post 接口时 发送 json数据
      {"name":"zs","age":18}
      */
      app.post('/', (req, res) => {
        console.log(req.body);
        res.send('ok')
      })
      
      // 发送请求的方式为  body 中的 x-www-form-urlencoded
      app.post('/2', (req, res) => {
        console.log(req.body);
        res.send('ok')
      })
      
      
      app.listen(port, () => console.log(`Example app listening on port ${port}!`))
  • 5.第三方中间件 非express内置
    • 下载第三方中间件 npm i body-parser
    • 通过require() 导入中间件 用use方法进行注册
    • express 内置的 urlencoded 中间件 就是基于 body-parser 第三方中间件进一步封装出来的 所以效果是一样的
    • demo
      const express = require('express');
      const app = express();
      const port = 3000;
      //导入中间件
      const bodyParser = require('body-parser');
      //用use方法进行注册
      app.use(bodyParser.urlencoded({
          express: false
      }))
      app.post('/name', (req, res) => {
          console.log(req.body);
          res.send(req.body);
      })
      app.listen(port, (err) => {
          if (!err) console.log("服务启动成功")
          else {
              console.log(`Example app listening on port ${port}!`);
          }
      })

自定义中间件

  • demo
    // 自定义中间件
      <!-- express.js -->
      const express = require('express');
      const app = express()
      const port = 3000;
      const parse = require('./parse')
      app.use(parse);
      app.post('/user', (req, res) => {
          res.send(req.body)
      })
      app.listen(port, (err) => {
          if (!err) console.log('服务启动成功');
      })
    
    
      <!-- parse.js -->
      // 使用 querystring模块解析请求体数据
      const qs = require('querystring'); //解析字符串
      const Parse = (req, res, next) => {
          let str = ''
          // req.data 获取客户端发送给服务器的数据
          // 监听req.data 事件
          req.on('data', (sj) => {
              str += sj
          })
          req.on('end', () => {
              // 将字符串转换成 对象格式
              const body = qs.parse(str);
    
              req.body = body;
              console.log(body);
              next()
          })
      }
      module.exports = Parse;
  • 自定义中间件 封装
    • demo
      // 1.挂载到服务器上
      <!-- 文件1 -->
      const express = require("express");
      const app = express()
      const port = 3000
      app.use(express.urlencoded({
          extended: false
      }))
      
      const router = require('./jiekou2')
      app.use('/api', router)
      
      app.listen(port, () => console.log('启动成功'))
      
      
      <!-- 文件2 -->
      const express = require("express");
      const router = express.Router()
      // 2. 创建路由模块 保证对应的数据在对应的路由上
      
      // 通过路由暴露数据
      router.get('/get', (req, res) => {
          const query = req.query; //浏览器的参数
          res.send({
              status: 0, //0 表示成功 1表示失败
              msg: 'success', //状态的描述信息
              data: query // 相应给客户端的数据
          })
      })
      router.post('/post', (req, res) => {
          const body = req.body;
          res.send({
              status: 0,
              msg: 'success',
              data: body
          })
      })
      module.exports = router;

解决跨域

jsonp 方式只支持解决 get 形式的跨域 不推荐使用,了解即可

使用 cors 中间件 (cors是express的一个第三方中间件)

  • cors 安装与使用
    • 1.安装 npm i cors
    • 2.导入 const cors = require('cors')
    • 3.在路由之前调用 app.use( cors() )
  • 特别注意 一定要在所有路由之前调用 cors 否则无效

cors 响应头

  • Access-Control-Allow-Origin
    • Access-Control-Allow-Origin: <origin> | * ,origin 参数的值指定了允许访问该资源的外域URL
    • res.setHeader('Access-Control-Allow-Origin','http://xxx.com') 表示 只允许来自http://xxx.com域名下 的访问
    • * 表示 通配符 允许任何域的请求
  • 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 三者之-)
    • 发送额外的请求头需要在服务器端通过 Access-Control-Allow-Headers 对额外请求头进行声明,否则会失败
    • 例如:res.setHeader('Access-Control-Allow-Headers','Content-Type,x-Custom-Header')
      • 多个请求头之间使用逗号分隔
  • Access-Control-Allow-Methods
    • 默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求
    • 如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法
    • 指定:res.setHeader('Access-Control-Allow-Methods','GET、POST、PUT、DELETE')
    • 任何请求方式:res.setHeader('Access-Control-Allow-Methods','*')

简单请求和预检请求 (了解 测试时使用火狐浏览器)

  • 简单请求
    • 请求方式为 GET、POST、HEAD
  • 预检请求
    • 在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据.
    • 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
    • 请求头中包含自定义头部字段
    • 向服务器发送了 application/json 格式的数据
  • 二者区别
    • 简单请求的特点: 客户端与服务器之间只会发生一次请求
    • 预检请求的特点: 客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求

在项目中操作 MySql

配置 mysql 模块

  • 1.安装依赖并导入 npm i mysql -s
  • 2.创建连接池
    • mysql.createConnection({})
    • mysql.createPool({})
  • demo
      //导入 mysql 模块
      const mysql = require('mysql');
      //创建连接
      let db = mysql.createPool({
          host: "127.0.0.1",      //数据库 ip 地址
          user: 'root',           //登录数据库的账号
          password: 'xxx',        //登录数据库的密码
          database: 'xxx',        //指定要操作的数据库名
      })
    
      // 测试 mysql 是否连接成功
      db.query('select 1', (err, res) => {
          if (err) return console.log(err.message);
          console.log(res);
      })

JWT 认证机制

  • 什么是JWT
    • JWT(英文全称: JSON Web Token) 是目前最流行的跨域认证解决方案
    • jwt原理 :用户的信息通过 Token 字符用的形式,保存在客户端测览器中。服务器通过还原 Token 字符串的形式来认证用户的身份
  • Session 认证的局限性
    • Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端按口的时候,需要做很多额外的配置,才能实现跨域 Session 认证
  • Session 和 JWT 如何选择
    • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
    • 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用JWT 认证机制

JWT 的使用

  • 安装 npm i jsonwebtoken express-jwt jsonwebtoken 生成jwt字符串 express-jwt将jwt字符串解析还原成json对象

将 JWT 字符串还原为 JSON对象

终端以及命令

  • 打开 PowerShell 终端:shift+鼠标右键 在当前目录打开 windows PowerShell
  • 建议尽量使用 git 终端

windows 终端命令

  • 上键 快速定位到上一次执行的命令
  • 文件或文件夹 首字+Tab键 快速补全路径
  • esc键 快速清空当前已输入的命令
  • cls 清空终端
  • ......

git 终端命令

  • cd xxx 进入xxx文件或文件夹
  • cd.. 返回上一级目录
  • mkdir xxx 创建一个空文件夹
  • ls 列出当前文件夹中所有文件
  • clear 清屏
  • pwd 显示当前目录的路径
  • cat xxx 查看文件内容
  • vi 文件名.后缀 创建一个文件
    • 自动进入了文件 输入i 进行编辑模式
    • esc+ : + wq +enter 保存并退出文本编辑
    • esc+ : + q! +enter 不保存并退出文本编辑
  • ......

文件读写进阶

  • 同步异步写入

    • fs.openSync(path,flags,[mode]) 打开
      • path 需要打开文件的路径
      • flags 打开文件要做的操作类型 r- 只读的 w-可写的
      • [mde]可选 设置文件的操作权限 一般省略
    • fs.writeSync(fd, string, [position, [encoding]]) 写入
    • ff 文件的描述符 需要传递要写入的文件的描述符
      • string 写入的内容
      • position 写入的起始位置
      • 写入的编码格式
    • fs.closeSync(ff) 关闭文件
    • demo
      let fs = require('fs')
      let ff = fs.openSync('hello.txt', 'w')
      console.log(ff); //3
      fs.writeSync(ff, '今天是糟糕的一天')
      fs.closeSync(ff)
    • fs.open(path,flags,[mode],callback)
      • path 需要打开文件的路径
      • flags 打开文件要做的操作类型 r- 只读的 w-可写的
      • [mde]可选 设置文件的操作权限 一般省略
      • callback
        • 异步调用的方法 结果都是通过回调函数参数返回的 回调函数的两个参数
        • err错误对象 如果没有错误为null
        • fd文件的描述符
    • demo
      let fs = require('fs')
      fs.open('hello2.txt', 'w', function (err, fd) {
      // console.log(123);
      // console.log(arguments); //[Arguments] { '0': null, '1': 3 }
      // 判断是否出错
        if (!err) {
          // 没有出错对文本进行操作
          fs.write(fd, '异步写入的糟糕', function (err) {
              if (!err) {
                  console.log('写入成功');
              }
      
              // 关闭文件
              fs.close(fd, function (err) {
                  if (!err) {
                      console.log('文件已关闭');
                  }
              })
                })
                console.log(fd); //3
            } else {
                // 如果报错 输出
                console.log(err);
            }
        })  
  • 简单文件写入

    • fs.writeFile(file,data,[options],callback) 异步
    • fs.writeFileSync(file,data,[options]) 同步
      • file 操作的文件路劲
      • fata 要吸入的数据
      • options 选项 可以对写入进行一些设置
      • callback 当写入完成以后执行的函数
    • demo
      // 异步写入
      let fs = require('fs');
      fs.writeFile('hello3.txt', '这是通过writeFile写入的糟糕', {
          flag: 'a'
      }, function (err) {
          if (!err) {
              console.log('写入成功');
          }
      })
      
      
      // 同步简单写入
      // 当写路径使用的是反斜杠 表示为转义 所以需要写两个 或者使用正斜杠
      // C:\\Users\\Administrator\\Desktop\\hello4.txt
      // C:/Users/Administrator/Desktop/hello4.txt
      
      let fs = require('fs');
      fs.writeFileSync('C:/Users/Administrator/Desktop/hello4.txt', '这   是通过writeFile写入的糟糕', {
          flag: "w"
      })
  • 流式文件写入 同步异步 简单文件写入 都不适合大文件的写入 性能较差 容易导致内存溢出

  • fs.createWriteStream(path, [option]) //可写 创建读写流

    • path 文件路劲
    • option 配置参数
    • fs.write 写入
    • demo
      let fs = require('fs')
      // 创建一个可写流
      // fs.createWriteStream(path, [option]) //可写
      // path 文件路劲
      // option 配置参数
      
      let ws = fs.createWriteStream('hello4.txt')
      // 通过监听的流的open和close事件监听流的打开和关闭
      // open是一次性事件 on绑定的长期有的事件 once绑定一次性的事件
      ws.once("open", function () {
          console.log('流打开了');
      })
      ws.once("close", function () {
          console.log('流关闭了');
      })
      
      // 写入 可以一直写入
      ws.write('这是通过流式文件写入的糟糕1');
      ws.write('这是通过流式文件写入的糟糕2');
      ws.write('这是通过流式文件写入的糟糕3');
      
      // ws.close();
      ws.end()
  • 其他一些操作

    • fs.existsSync() 检查一个文件是否存在
    • fs.statSync() 获取文件信息
      • demo
        fs.stat('one.jpg', function (err, stat) {
        console.log(arguments);
        stat.size 文件大小
        stat.isFile() 是否是个文件
        stat.isDirectory() 是否是一个文件夹
        console.log(stat);
        }) 
    • fs.unlink(path,callback) 删除文件
      • /fs.unlinkSync(path)
    • fs.readdir(path,[options],callback) 列出文件
      • / fs.readdirSync(path,[options])
    • fs.mkdir(path, [mode], callback) 建立目录
      • /fs.mkdirSync(path, [mode])
    • fs.rmdir(path, callback) 删除目录
      • /fs.rmdirSync(path)
    • fs.rename(oldPath, newPath,callback) 重命名文件夹 和文件
      • /fs.renameSync(oldPath, newPath)
      • oldPath -旧的 newPath -新的
    • fs.watchFile(filename[Option],listener) 监视文件的修改
      • demo
        // filename要监视的文件名
        // Option配置{interval: 1000}响应速度
        // listener 回调函数 当文件发生变化时 回调函数会执行
        // curr当前文件的状态
        // prev修改前的文件状态
        // 这两个对象都是stats的对象
        
        
        fs.watchFile('hello4.txt', {
            interval: 1000
        }, function (curr, prev) {
            console.log('文件发生变化了');
            console.log('修改前文件大小' + prev.size);
            console.log('修改后文件大小' + curr.size);
        })
    • 配置
      • w+ --- 读写 创建(不存在) 覆写(存在)
      • r --- 读取 不存在报错
      • r+ --- 读写 不存在报错
      • a --- 追加 创建(不存在)
      • a+ --- 读取追加 创建(不存在)

模块

内部外部模块 (内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)

```js
let a = require('./a') // 外部模块
let b = require('math') // 内部模块  node自带的模块 引用node自带的模块不需要写路劲
```
  • 在node中一个js文件就是一个模块

  • 在node中每一个模块中的代码都是运行在一个独立的函数中的

    • 进行验证console.log(arguments.callee.toString());
      • 返回的是function (exports, require, module, __filename, __dirname){}
  • 模块的引用 通过 require() 函数来引入外部的模块

    • demo
      <!-- a.js文件 -->
      // require('.b/b.js') 引用模块的概念  a模块调用 b模块 
      var b = require('./b/b')   / require('./b/b.js') 可以省略后缀
      console.log(b); //我是b.js文件
      
      <!-- b.js文件 -->
      console.log('我是b.js文件')
  • 模块的暴露 通过 exports 导出变量 导出的是一个对象

  • 默认情况下模块内部代码对于外部来说都是不可见的,可以通过三种方式向外部暴露变量和函数

    • exports.xxx自定义=xxx值导出的是一个对象
      • var a = 123; exports.abc = a;
    • module.exports ={} 模块导出 多个导出 常用方式
      • let a = 123; module.exports = {a,b}
      • 多个变量的导出
      • demo
        <!-- a文件 -->
          var b = require('./b');
          console.log(b);  //{ b: 123, c: [Function: c] }
        
        <!-- b文件 -->
        // 多个的导出 modlue.exports={}
        module.exports = {
            b,
            c
        }
        const b = 123;
        const c = function () {
        console.log(2);
             }
    • global全局 将变量定义到全局中 只能单个导出
      • node中没有window对象 因为运行环境在服务端中
      • demo
      <!--  a文件 -->
        var b = require('./b');
        console.log(b); //输出{}
        // global全局 导出
        console.log(abc);
      
        <!-- b文件 -->
        const b = 123;
        global.abc = b;
  • __filename 返回当前模块的文件名 当前文件的自身绝对路径

  • __dirname; 返回当前模块的目录名 当前文件所在目录绝对路径

    • 使用__dirname与相对路径 进行拼接调用 在拼接相对路径前 多写一个 /
      • demo
        //a文件中的a 调用 b文件中的b
        <!-- a文件中的a.js -->
        let v = require(__dirname + '/../b/b')
        console.log(v);
        
        <!-- b文件中的b.js -->
        let b = 456;
        module.exports = b; 

自定义模块 (用户创建的每个 .js 文件,都是自定义模块)

第三方模块 (由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)

通过 NVM 管理 nodejs版本

nvm常用命令

nvm -v								             // 查看nvm版本
nvm ls  						 	             // 查看目前已经安装的 nodejs 版本
nvm current 						           // 显示当前 nodejs 的版本
nvm list available 					       // 显示可下载 nodejs 版本的部分列表
nvm install xxx.xxx.xxx	 			     // 安装指定的版本的 nodejs
nvm install stable                 // 安装最新稳定版 nodejs
nvm use xxx.xxx.xxx			 		       // 设置(切换)指定版本的 nodejs
nvm alias default                  // 设置默认版本的 nodejs
nvm uninstall xxx.xxx.xxx			     // 删除已安装的指定版本
nvm alias 							           // 给不同的版本号添加别名
nvm unalias					 		           // 删除已定义的别名
nvm reinstall-packages <version> 	 // 在当前版本 node 环境下,重新全局安装指定版本号的npm包
nvm proxy 	                       // 查看设置与代理
nvm on 	                           //打开nodejs控制
nvm off 		                        //关闭nodejs控制