十三. Node.js Egg.js

399 阅读16分钟

1.基础

1.1 Node.js是什么?

  • Node.js诞生于2009年,由Joyent的员工 Ryan Dahl开发而成. Node.js不是一门语言也不是框架,它只是基于GooleV8引擎的JavaScript运行时环境,同时结合Libuv扩展了JavaScript功能,使之支持io fs 等语言才有的特性, 是的JavaScript能够同时具有Dom操作 和I/O 文件读写, 操作数据库等能力,是目前最简单的全栈式语言.
  • Node.js是一个开源和夸平台的JavaScript运行时环境.它几乎是任何类型项目的流行工具
  • Node.js在浏览器之外, 自己运行 V8 JavaScript引擎.这使得Node.js的性能非常好
  • Node.js应用程序在单个进程中运行,无需为每个请求创建新的线程.Node.js在其标准库中提供了一组异步的I/O原语,以防止JavaScript代码阻塞,通常Node.js中的库是使用非阻塞范式编写的,使得阻塞行为成为异常而不是常态.
  • 当Node.js执行I/O操作时(比如从网络读取,访问数据库或文件系统), Node.js将在响应返回时恢复操作(而不是阻塞线程和浪费CPU周期等待)
  • 这允许Node.js使用单个服务器处理数千个并发连接, 而不会引入管理线程并发(这可能是错误的重要来源)的负担.
  • Node.js具有独特的优势,因为数百万为浏览器编写JavaScript的前端开发者现在无需学习完全不同的语言,就可以编写除客户端代码之外的服务器端代码.
  • 在Node.js中, 可以毫无问题地使用新的ECMAScript标准,因为你不必等待所有用户更新他们的浏览器,你负责通过更改Node.js版本来决定使用哪个ECMAScript版本,你还可以通过运行带有标志的NOde.js来启用特定的实验性功能.
  • 大量的库: npm以其简单的结构帮助Node.js生态系统蓬勃发展,现在npm仓库托管了超过1百万个开源包,你可以自由使用. 7.png

1.2 简史

  • 2009

    • Node.js诞生
    • 第一版的npm被创建
  • 2010

    • Express诞生
    • Socket.io诞生
  • 2011

    • npm 发布 1.0 版本
    • 较大的公司(LinkedIn、Uber 等)开始采用 Node.js
    • hapi 诞生
  • 2012

    • 普及速度非常快
  • 2013

    • 第一个使用 Node.js 的大型博客平台:Ghost
    • Koa 诞生
  • 2014

    • 大分支:io.js 是 Node.js 的一个主要分支,目的是引入 ES6 支持并加快推进速度
  • 2015

    • Node.js 基金会 诞生
    • IO.js 被合并回 Node.js
    • npm 引入私有模块
    • Node.js 4(以前从未发布过 1、2 和 3 版本)
  • 2016

  • 2017

    • npm 更加注重安全性
    • Node.js 8
    • HTTP/2
    • V8 在其测试套件中引入了 Node.js,除了 Chrome 之外,Node.js 正式成为 JS 引擎的标杆
    • 每周 30 亿次 npm 下载
  • 2018

    • Node.js 10
    • ES 模块 .mjs 实验支持
    • Node.js 11
  • 2019

    • Node.js 12
    • Node.js 13
  • 2020

    • Node.js 14
    • Node.js 15
  • 2021

    • Node.js 16

1.3 Node.js特点

  • 事件驱动
  • 非阻塞I/O模型
  • 轻量和高效
  • 文件的读写, 进程的管理,网络通信

1.4 命令

  • nvm list 当前系统安装了哪些版本的nodejs
  • nvm use 16.x.x 来决定当前使用哪个版本
  • nvm alias default 16.x.xx 来决定默认使用哪个版本.

1.5 Node.js可以做什么

  • 基于Express, Koa2, 构建web应用
  • 基于Electron,构建桌面应用
  • restify, 构建api接口项目

1.6 怎么学Node

  • JavaScript基础语法 + Node.js内置API模块(fs,path,http等) + 第三方模块API(koa, MySQL)

2.模块化理解

2.1 什么是模块化

  • 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程. 对于整个系统来说,模块是可组合 分解和更换的单元.

2.2 Node.js 模块分类

  • 内置模块: 由Node.js官方提供的,例如fs,path,http等
  • 自定义模块: 用户创建的每个.js文件
  • 三方模块: 由第三方开发出来的

2.3 加载模块,导入导出

(1) 导入:
    * 加载内置模块 const fs =  require('fs')
    * 加载自定义模块 const custom = require('./custom.js')
    * 加载三方模块 const moment =  require('moment')

    * 使用require()时可以加省略后缀名.js
    *当使用require()方法加载看其他模块时, 被加载的模块会被执行.
    * 模块初始化: 导入模块仅在模块第一次使用时执行一次,并且在使用的过程中进行初始化, 之后缓存起来便于后续继续使用.
(2) module.exports 导出对象:
    * 在每一个.js自定义模块中都有一个module对象, 它里面存储了和当前模块有关的信息
    * module.exports默认值是一个空对象, 用来导出默认对象,没有指定对象名.
    常见于修改模块的原始导出对象, 通过module.exports来更改要导出的对象.
(3) exports别名 来导出对象:
    * 由于module.exports单词写起来比较复杂, 为了简化,Node提供了exports对象. 默认情况下,exportsmodule.exports指向同一个对象. 最终导出的结果还是以module.exports指向的对象为准.  相当于同一个人的两个名字.
(4) 模块作用域
        * 和函数作用域类似,在自定义模块中定义的变量, 方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域.
        * 好处: 防止全局变量污染.
    

2.4 Node.js模块化规范

(1) module 代表当前模块
(2) module变量是一个对象,它的exports属性 (即module.exports)是对外的接口.
(3) 加载某个模块,其实加载的是module.exports属性.

2.5 主模块

  • 通过命令行参数传递给NodeJS以启动程序的模块被称为主模块.主模块负责调度组成整个程序的其它模块完成工作. 例如通过以下命令启动程序时,main.js就是主模块.
终端: node main.js   // 运行main.js启动程序,main.js称为主模块
npm install -g cnpm --registry=https://registry.npm.taobao.org

Node.js中使用CommonJS模块化机制,通过npm下载的第三方包,我们在项目中引入第三方包都是: let xx = require('第三方包名').  require加载第三方包的原理机制是什么:
(1)优先在加载该包的模块的同级目录node_modules中查找第三方包.
(2) 找到该第三方包中的package.json文件,并且找到里面的main属性对应的入口模块,该入口模块即为加载的第三方模块.
(3)如果没有package.json或main属性,默认加载第三方包中的index.js文件.
(4)如果在同级目录没有找到node_modules文件,则会向父级目录查找node_modules文件夹
(5) 如果一直的磁盘跟目录, 没有找到的话就会报错.

2.6 npm与包

  • 什么是包: Node.js中的第三方模块又叫做包
  • 包的来源: 第三方提供 Node.js中的包都是免费且开源的, 不需要付费即可免费使用.
  • npm,inc: 国外一家公司, 旗下著名网站www.npmjs.com它是全球最大的包共享平台, 你可以从这个网站上搜索到任何你需要的包.
  • npm,inc公司还替公司一个地址为registry.npmjs.org的服务器, 来对外共享所有的包,我们可以从这个服务器上下载自己所需要的包.
(1)
    如何下包: 用npm包管理工具 (例如moment)
    包的语义化版本规范: 点分十进制 例如 2.34.11
    第一位数: 大版本
    第二位数: 功能版本
    第三位数: bug修复版本
    注: 只要前边的版本号增长了,后边就需归零.
    npm init -y命令: 创建package.json这个文件
(2)
    解决下包速度慢: 默认从国外下包,使用淘宝镜像.
    当前下包的默认镜像源: npm config get registry
    改变默认镜像源: npm config set registry=https://registry.npm.taobao.org/

    快速切换: nrm工具
    npm i nrm -g 
    nrm ls 查看所有镜像源
    nrm use taobao  切换镜像源
(3) 包的分类
    项目包: 
        * 开发依赖包 -D
        * 核心依赖包 -S
    全局包: -g  卸载时也用 -g
        * 只有工具性质的包,才被安装到全举报
        * i5ting_toc是一个可以把 md文档转为html页面的小工具
        * i5ting_toc -f 文件 -o  这个-o表示默认打开
    package.json中必须包含name,version,main这三个属性, 名 版本号 入口
(4) npm包管理常见的命令: 
npm -v
npm init
npm list
npm list -g
npm install 
npm --help
npm update
npm uninstall
npm config list
npm 命令 --help
npm info 指定包名
npm config set registry https://registry.npm.taobao.org
npm root   查看当前包的安装路径
npm roo -g  查看全局的包的安装路径
npm ls 包名   查看本地安装的指定包及版本信息,没有显示empty.
npm ls 包名 -g   查看全局安装的指定包及版本信息,没有显示empty
  • 开发属于自己的包
(1) 初始化包的结构, 新建文件夹mypack, 作为包的根目录
(2) mypack文件夹,新建三个文件:
    * package.json 包管理配置文件
    * index.js 包的入口文件
    * README.md 包的说明文档
(3)package.json 文件内容
    {
        "name": "pack",   // 真正包名, 不能重复
        "version": "1.0.0",   // 版本号
        "main": "index.js",   // 外界使用require来导入,默认导入main属性所指向的文件
        "description": "简短的描述信息",
        "keywords": ["mypack","format","escape"],   // 包的的关键字
        "license": "ISC",   // 开源许可协议
    }
(4)发布npm包
    * 注册npm账号.
    * 终端登录:npm login 用户名 密码 邮箱.
    * npm 下包服务器地址, 更改为npm的官方服务器.
    * 将终端切换到包根目录后, 运行npm publish命令,即可发布到npm网站上.
    * 删除已发布的包: npm unpublish 包名 --force命令, 即可从npm删除已发布的包. 
      只能删除72小时之内的包. 删除的包24小时之内不能重复发布.
      尽量不要往npm上发布没有意义的包.
    * 

2.7 模块的加载机制

  • 模块在第一次加载后会被缓存. 这也意味着多次调用require()不会导致模块内的代码被执行多次.
  • 注意: 不论是内置模块,用户模块,还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率.
  • 内置模块的加载机制: 内置模块是由Node.js官方提供的模块, 内置模块的加载优先级最高. 例如require('fs')始终返回内置的fs模块,既是再看node_modules目录下有名字相同的包也叫做fs.
  • 自定义模块的加载机制: 用require()时,必须指定以./ 或../ 开头. 如果没有指定./或../这样的路径,则node会把它当作内置模块或第三方模块进行加载.
  • 在使用require()时, 如果省略了文件的扩展名:
    • 首先按照确切的文件名进行加载
    • 补全.js进行加载
    • 补全.json进行加载
    • 补全.node进行加载
    • 都没, 则加载失败
  • 第三方模块的加载机制: 首先从当前的父目录开始从/node_modules文件夹中加载.如果没有找到对应的第三方模块,则会逐级向上各层的/node_modules中查找. 一直到根目录. 如果都没有则报错.
  • 目录作为模块: 使用require()加载目录时, 有三种加载方式:
    • 在被加载的目录查找一个叫做package.json的文件,并寻找main属性,作为加载入口.
    • 如果没有package.json 或 有package没main 或这俩都有没有main所指向的.js文件.会加载index.js文件
    • 都没有 此时会打印错误消息.

3.文件系统

3.1 介绍

  • node,读写文件也有同步和异步的接口,
  • fs模块时Node.js官方提供的,用来操作文件的模块.它提供了一系列的方法和属性,用来满足用户对文件的操作需求.
const fs = require('fs')
(1)读
fs.readFile(path,[options],callback) 读取参数
参数说明:路径, 编码格式, 回调函数 
fs.readFile('./index.txt', 'utf8', function(err,datastr) {}) 通过err是否为null来判断文件的读取结果.
(2)写
fs.writeFile(file,[options],callback) 向文件写入
参数说明: 第二个参数是 写入的内容

(3)路径动态拼接的问题:  使用完整路径, 不要提供./    ../等相对路径

4.path

4.1 基础

path.join() 方法,用来将多个路径片段拼接成一个完整的路径字符串
../  可以抵消一层路径
__dirname
以后凡是涉及到路径拼接的操作, 都要使用path.join()方法处理


path.basename() 方法, 用来从路径字符串中, 将文件名解析出来.
path.extname() 方法, 可以获取路径中的扩展名

5.http

5.1 基础

  • http模块是Node.js官方提供的,用来创建web服务器的模块.通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务.
  • 服务器和普通电脑的区别在于,服务器上安装了web服务器软件, 例如: IIS,Apache等. 通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器.
  • 在NOde.js中, 我们不需要使用IIS,Apache等这些第三方的web服务器软件.因为我们可以基于Node.js提供的http模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供web服务.

5.2 知识拓展

  • ip地址: 就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性.如果把 '个人电脑'比作 '一台电话', 那么"IP地址" 就相当于 "电话号码", 只有在知道对方的IP地址的前提下,才能与对应的电脑之间进行数据通信.

  • 互联网中每台web服务器,都有自己的IP地址, 例如: 大家可以在windows的终端中运行 ping www.baidu.com命令,即可查看到百度的IP地址.

  • 在开发期间, 自己的电脑既是一台服务器, 也是一台客户端,为了方便测试,可以在自己的浏览器中输入127.0.0.1这个IP地址,就能把自己的电脑当做一台服务器进行访问了.

  • 域名:Domain Name, IP和域名是一一对应的关系.. 127.0.0.1对应的域名就是localhost

  • 域名服务器:Domain name server

  • 端口号: 一台web服务器可以有多个端口号, 只有80端口可以被省略.

5.3 使用http

(1) 导入
const http= require("http")
(2) 创建web服务器的实例
const server = http.createServer()
(3) 为实例对象 绑定request事件
server.on('request', (req, res) =>
    console.log()   // req请求体   res是响应体
})

(4) 启动
server.listen(80, ()=> {
    console.log()
}
  • res.end(content)
  • res.setHeader('Content-Type','text/html',charset=utf-8')

5.4 根据不同的url来响应 不同的html内容

(1) 获取不同的url地址
(2) 置默认的响应内容
(3) 判断用户请求的是否为/  或/index.html 首页
(4) 判断用户请求的是否为/about.html关于页面
(5) 设置Content-Type 响应头, 防止中文乱码
(6) 使用res.end(content)把响应内容给客户端

6. EggJs 框架

Egg.js是阿里旗下为数不多的 ,让人放心使用的开源项目。Egg.js为企业级框架和应用而生的Node.js框架,Egg(简写)奉行【约定优于配置】的框架,按照一套同意的约定进行应用开发。适合团队开发,学习成本小,减少维护成本。

6.1 EggJs的特点

  • 提供基于Egg定制上层框架的能力
  • 高度可扩展的插件机制
  • 内置多进程管理(Node是单进程,无法使用多核CPU的能力)
  • 基于Koa开发,性能优异
  • 框架稳定,测试覆盖率高
  • 渐进式开发,逐步模块化模式

6.2 环境搭建

(1)创建项目
npm  install -g yarn
yarn create egg --type=simple  或者 npm init egg --type=simple
yarn install
yarn dev 
(2)

6.3 Egg.js与Koa/Express对比

  • Egg.js相对比Koa和Express框架的学习成本要高,但更适合企业级开发,有成熟的插件机制、扩展机制,还可以使用多进程管理。所以多付出一点学习成本是很划算的事情。我制作了一张图,对Egg.js和Express/Koa框架进行了对比。

Egg_01_01.jpg

6.4 Egg.js 项目结构介绍

- app                        - 项目开发的主目录,工作中的代码几乎都写在这里面
-- controller                -- 控制器目录,所有的控制器都写在这个里面
-- router.js                 -- 项目的路由文件
- config                     - 项目配置目录,比如插件相关的配置
-- config.default.js         -- 系统默认配置文件
-- plugin.js                 -- 插件配置文件
- logs                       -- 项目启动后的日志文件夹
- node_modules               - 项目的运行/开发依赖包,都会放到这个文件夹下面
- test                       - 项目测试/单元测试时使用的目录
- run                        - 项目启动后生成的临时文件,用于保证项目正确运行
- typings                    - TypeScript配置目录,说明项目可以使用TS开发
- .eslintignore              - ESLint配置文件
- .eslintrc                  - ESLint配置文件,语法规则的详细配置文件
- .gitignore                 - git相关配置文件,比如那些文件归于Git管理,那些不需要
- jsconfig.js                - js配置文件,可以对所在目录下的所有JS代码个性化支持
- package.json               - 项目管理文件,包含包管理文件和命令管理文件
- README.MD                  - 项目描述文件 

6.5 dev和start的区别

  • dev : 开发环境中使用,不用重启服务器,只要刷新。修改内容就会更改。
  • start:生产环境中使用,也就是开发完成,正式运营之后。以服务的方式运行。修改后要停止和重启后才会发生改变。

6.6 controller控制器的使用

6.6.1

  • HTML URL 渲染页面

  • RESTful Controller

  • 代理服务器

  • 直接响应数据或渲染模板

  • 接受用户的输入

  • 与路由建立对应关系

  • this.ctx 可以吃拿到上下文的对象

  • get请求的参数:

    • controller里 async index(){} 函数内, let query = this.ctx.request.query
    • router里 router.get('/fruits/:id', controller.fruits.getid), 然后在controller里 async getid(){} 函数内 let id = ctx.params
  • post请求: this.ctx.request.body里

    CSRF指跨站请求伪造,Egg中对post请求做了一些安全验证, 可以在config.default.js文件中,通过下面的设置验证.
config.security = {   // 提交post请求时, 会禁止你提交这个请求
    csrf: {
        enable: false,
    }
}

6.6.2 RESTful风格的定义

  • router.resoureces('fruits','/fruits',controller.fruits) // 一个方法同时定义增删改查

00.png

6.7 插件

6.7.1 egg-view-nunjucks


* 安装
* 引入: plugin.js, config.default.js
    nunjucks: {
        enable: true,
        package: 'egg-view-nunjucks',
    }

    config.view = {
        defaultViewEngine: 'nunjucks'
    }
* 使用
await this.ctx.render("index", { fruits: data })

6.7.2 egg-cors

* npm install --save egg-cors

* 在plugin.js文件中引入插件
    cors:{
        enable: true,
        package: 'egg-cors',
    }
    config.cors = {
        origin: "*",
        allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
    }

* 在config.default.js文件中配置 egg-cors插件
    

6.8 http 协议

* 请求: 
    get
    post
    delete
    put
    
* 响应
    状态: 200, 404, 500
    
* 数据: json格式, js对象,,

* http是无状态的协议
    登录,注册,购物车
    (1)使用cookie与session识别用户的.  cookie是存储在客户端的,session是存储在服务器端的. 这个cookie和session可以让用户保持一个登录状态的功能.
    (2)使用JWT(Json Web Token)识别用户. 也可以实现保持用户登录状态的功能.
        jwt: {
            enable: true,
            package: "egg-jwt"
        },
        config.jwt = {
            secret: 'username'
        }
    

6.9 中间件

6.9.1 定义

  • 创建目录middleware, 在内部创建.js文件
  • 在middleware中验证token和用户

6.10 数据持久化

6.10.1 介绍看

  • 应用程序的数据通常存储在数据库中.
  • 我们使用MySQL数据库实现数据的持久化.
  • 为了方便的操作mysql, 我们使用sequelize(ORM框架) 管理数据层的代码.

6.10.2 ORM框架

  • 对象关系映射(Object Relational Mapping)

  • sequelize是一个基于node的orm框架

  • 通过egg-sequelize, 可以直接使用sequelize提供的方法操作数据库,而不需要动手写SQL语句

  • 安装sequelize

    • egg-sequelize 和 mysql2
    • 在plugin.js引入插件
    • config.default.js 文件中配置数据库连接
    • 在app/model文件中创建数据模型
    • 添加app.js文件, 初始化数据库