乐趣区

关于前端:nodejs服务端开发

nodejs 服务端开发

1.1. nodejs 介绍
1.2. REPL 环境
1.3. 模块化
1.4. 异样解决
1.5. 核⼼模块 url、querystring

1. nodejs 介绍

http://nodejs.cn/learn/introd…

nodejs 在 2009 年诞⽣,到⽬前为⽌,也仅 12 年。Node.js 是⼀个开源与跨平台的
JavaScript 运⾏环境。所谓“运⾏环境”有两层意思:⾸先,JavaScript 语⾔通过 Node 在服务器运⾏,在这个意义上,Node 有点像 JavaScript 虚拟机;其次,Node 提供⼤量⼯具库,使得 JavaScript 语⾔与操作系统互动(⽐如读写⽂件、新建⼦过程),在这个意义上,Node ⼜是 JavaScript 的⼯具库。Node 外部采⽤ Google 公司的 V8 引擎,作为 JavaScript 语⾔解释器;通过⾃⾏开发的 libuv 库,调⽤操作系统资源。

2. REPL 环境

使⽤ node 命令能够执⾏⼀个 js ⽂件,如果 node 后参数缺省,则进⼊ REPL 环境

$ node

3. 模块化

3.1. package.json
是模块的清单⽂件,记录了以后模块的根本信息、依赖信息等

3.2. CommonJS 模块化

晚期的 Javascript(ECMAScript5)中是没有模块化的,nodeJS 推出后,使⽤的是

CommonJS 模块标准。起初 ES6 呈现后才呈现了 ECMAScript 模块化,在 node-v12 后能够使⽤ ECMAScript 模块化。
CommonJS 规定,每个⽂件就是⼀个模块,有⾃⼰的作⽤域。在⼀个⽂件⾥⾯定义的变量、函数、类,都是公有的,对其余⽂件不可⻅。每个模块外部,module 变量代表以后模块。这个变量是⼀个对象,它的 exports 属性(即 module.exports)是对外的接⼝。加载某个模块,其实是加载该模块的 module.exports 属性。require ⽅法⽤于加载模块。

var x = 5;
var addX = function (value) {return value + x;};
module.exports.x = x;
module.exports.addX = addX;
var example = require('./example.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
  1. 所有代码都运⾏在模块作⽤域,不会净化全局作⽤域。
  2. 模块能够屡次加载,然而只会在第⼀次加载时运⾏⼀次,而后运⾏后果就被缓存了,当前再加载,就间接读取缓存后果。要想让模块再次运⾏,必须革除缓存。
  3. 模块加载的程序,依照其在代码中呈现的程序。
  • module
    每个模块外部,都有⼀个 module 对象,代表以后模块。
  • require
    Node 使⽤ CommonJS 模块标准,内置的 require 命令⽤于加载模块⽂件。require 命令的基本功能是,读⼊并执⾏⼀个 JavaScript ⽂件,而后返回该模块的 exports 对象。如果没有发现指定模块,会报错。须要留神的是:
    1、如果参数字符串以“/”结尾,则示意加载的是⼀个位于绝对路径的模块⽂件。
    比方,require(‘/home/marco/foo.js’) 将加载 /home/marco/foo.js。
    2、如果参数字符串以“./”结尾,则示意加载的是⼀个位于相对路径(跟以后执⾏脚本的地位相⽐)的模块⽂件。⽐如,require(‘./circle’) 将加载以后脚本同⼀⽬录的
    circle.js。
    3、如果参数字符串不以“./“或”/“结尾,则示意加载的是⼀个默认提供的核⼼模块(位于 Node 的零碎装置⽬录中),或者⼀个位于各级 node_modules ⽬录的已装置模块(全局装置或部分装置)。

在 ES6 中每⼀个模块即是⼀个⽂件,在⽂件中定义的变量,函数,对象在内部是⽆法获取
的。如果你心愿内部能够读取模块当中的内容,就必须使⽤ export 来对其进⾏裸露(输入)。
 export
export 命令规定的是对外的接⼝,必须与模块外部的变量建⽴⼀⼀对应关系,也就是说内部接⼝须要⽤这个接⼝名来引⽤。

 var firstName = 'Michael';
 var lastName = 'vicky';
 export {firstName, lastName}; // 列表导出

 export {firstName as first, lastName as last}; // 重命名导出

 export var a = 3; // 导出单个属性
 export function multiply(x, y) {return x * y;}; // 导出单个属性

 // 默认导出,一个模块只能有一个默认导出,不能应用 var、let 或 const 用于导出默认值 ex
 export default {}
 export default function foo(){}
 var a = 1;
 export a; // 报错,因为没有提供对外的接口。应该 export var a = 1; 或者 export {a}

 import
动态的 import 语句⽤于导⼊由另⼀个模块导出的绑定。

 import * as person from './person.js' // 导入整个模块内容
 import {firstName,lastName} from './person.js' // 导入多个接口
 import {firstName as name} from './person.js' // 重命名
 import '/modules/my-module.js'; // 运行整个模块而不导入任何值
 import myDefault from './my-module.js'; // 导入应用 export default 导出的

3.4. 外围模块
 http 提供 HTTP 服务器性能。
 fs 与⽂件零碎交互。
 url 解析 url 字符串
 querystring 解析 URL 的查问字符串。
 util 提供⼀系列实⽤⼩⼯具。
 path 解决⽂件门路。
 crypto 提供加密和解密性能,基本上是对 OpenSSL 的包装。

4. 异样解决

Node 是单线程运⾏环境,⼀旦抛出的异样没有被捕捉,就会引起整个过程的解体。
所以,Node 的异样解决对于保证系统的稳固运⾏⾮常重要。⼀般来说,Node 有三种⽅法,流传⼀个谬误。

  • 使⽤ throw 语句抛出⼀个谬误对象,即抛出异样。
  • 将谬误对象传递给回调函数,由回调函数负责收回谬误。
  • 通过 EventEmitter 接⼝,收回⼀个 error 事件。
    4.1. try-catch
    ⼀般来说,Node 只在很少场合才⽤ try/catch 语句,⽐如使⽤ JSON.parse 解析 JSON ⽂
    本。这个构造⽆法捕捉异步运⾏的代码抛出的异样。

     // 能够捕捉
     try {console.log(a);
     } catch (err) {console.log("捕捉异样:",err);
     }
     // 无奈捕捉
     try {setTimeout(()=>{console.log(a);
     },0)
     } catch (err) {console.log("捕捉异样:",err);
     }

    4.2. 回调函数
    Node 采⽤的⽅法,是将谬误对象作为第⼀个参数,传⼊回调函数。这样就防止了捕捉代码
    与发⽣谬误的代码不在同⼀个时间段的问题。

     fs.readFile('/foo.txt', function(err, data) {if (err !== null) throw err;
     console.log(data);
     });

    4.3. EventEmitter 接⼝的 error 事件
    发⽣谬误的时候,也能够⽤ EventEmitter 接⼝抛出 error 事件。

     var EventEmitter = require('events').EventEmitter;
    
     var emitter = new EventEmitter();
     emitter.emit('error', new Error('something bad happened'));
     emitter.on('error', function(err) {console.error('出错:' + err.message);
     });
    5. url 模块

    提供解析 url 的⼯具,⼀个残缺的 href 构造如下:

    5.1. url.parse()
    将 url 字符串地址转换为 URL 对象

     let url = require('url')
     const myURL = url.parse('https://user:pass@sub.example.com:8080/p/a/t/h?que
    ry=string#hash');
     console.log(myURL);

    5.2. url.format()
    构建⼀个 URL 字符串

     const url = require('url');
     url.format({
     protocol: 'https',
     hostname: 'example.com',
     pathname: '/some/path',
     query: {
    page: 1,
    format: 'json'
     }
     });  // => 'https://example.com/some/path?page=1&format=json'

    5.3. url.resolve(from,to)
    合并 url 字符串

     const url = require('url');
     url.resolve('/one/two/three', 'four'); // '/one/two/four'
     url.resolve('http://example.com/', '/one'); // 'http://example.com/one'
     url.resolve('http://example.com/one', '/two'); // 'http://example.com/two'

    5.4. url.hash
    获取或设置 URL 中 hash 值

     const myURL = new URL('https://example.org/foo#bar');
     console.log(myURL.hash);// Prints #bar
     myURL.hash = 'baz';
     console.log(myURL.href);// Prints https://example.org/foo#baz

    5.5. url.host
    获取或者设置 URL 中的 host

     const myURL = new URL('https://example.org:81/foo');
     console.log(myURL.host); // Prints example.org:81
     myURL.host = 'example.com:82';
     console.log(myURL.href); // Prints https://example.com:82/foo

    5.6. url.hostname
    设置或获取 URL 中的 hostname,与 host 不同的是,hostname 不蕴含端⼝

     const myURL = new URL('https://example.org:81/foo');
     console.log(myURL.hostname);
     // Prints example.org

    5.7. url.origin
    获取 URL 中的 origin

     const myURL = new URL('https://example.org/foo/bar?baz');
     console.log(myURL.origin);
     // Prints https://example.org

    5.8. url.pathname
    获取或设置 URL 中的门路

     const myURL = new URL('https://example.org/abc/xyz?123');
     console.log(myURL.pathname);// Prints /abc/xyz

    5.9. url.port
    获取或设置 URL 中的端⼝号

     const myURL = new URL('https://example.org:8888');
     console.log(myURL.port);
     // Prints 8888

    5.10. url.protocol
    获取或设置 URL 的协定

     const myURL = new URL('https://example.org');
     console.log(myURL.protocol);// Prints https:
    
     myURL.protocol = 'ftp';
     console.log(myURL.href); // Prints ftp://example.org/

    5.11. url.search
    获取或设置 URL 的查问字符串

     const myURL = new URL('https://example.org/abc?123');
     console.log(myURL.search);
     // Prints ?123
    
     myURL.search = 'abc=xyz';
     console.log(myURL.href); // Prints https://example.org/abc?abc=xyz
  • querystring 模块
    7.1. querystring.parse(str[, sep[, eq[, options]]])
    将查问字符串解析为⼀个对象

     let querystring = require('querystring');
     let qs = "name=terry&age=12&gender=male"
     console.log(querystring.parse(qs));// {name: 'terry', age: '12', gender: 'male'}

    7.2. querystring.stringify(obj[, sep[, eq[, options]]])
    将⼀个对象序列化为查问字符串

      let querystring = require('querystring');
      let qs_obj = {name: 'terry', age: '12', gender: 'male'};
      console.log(querystring.stringify(qs_obj))
      name=terry&age=12&gender=male
     

    7.3. querystring.escape
    对查问字符串进⾏编码

     let querystring = require('querystring');
    
     console.log(querystring.escape('name= 张三 &age=12'));
     console.log(querystring.unescape('name%3D%E5%BC%A0%E4%B8%89%26age%3D12'));
    
     //name%3D%E5%BC%A0%E4%B8%89%26age%3D12
     //name= 张三 &age=12

    7.4. querystring.unescape
    对查问字符串进⾏解码

  • path 模块
    提供了⼀系列解析门路的⼯具
    8.1. path.basename(str[, ext]))
    返回门路中最初⼀局部

      let path = require('path')
      let name = '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare/3-path.js'
      console.log(path.basename(name));
      console.log(path.basename(name,'.js'));
      //3-path.js
      //3-path

8.2. path.dirname(path)
返回门路中代表⽂件夹的局部

1 let path = require('path')
2 let name = '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare/3-path.js'
3 console.log(path.dirname(name));
4 // /Users/lichunyu/Desktop/2021/web2102/nodejs/prepare

8.3. path.extname(path)
返回门路中⽂件的后缀名,即门路中最初⼀个 ’.’ 之后的局部。如果⼀个门路中并不蕴含 ’.’ 或
该门路只蕴含⼀个 ’.’ 且这个 ’.’ 为门路的第⼀个字符,则此命令返回空字符串。

1 let path = require('path')
2 let name = '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare/3-path.js'
3 console.log(path.extname(name));
4 // .js

8.4. path.format(pathObject)
从对象中返回门路字符串,和 path.parse 相同。

1 path.format({root: '/',name: 'file',ext: '.txt'});
2 // Returns: '/file.txt'
3 path.format({root: '/',base: 'file.txt',ext: 'ignored'});
4 // Returns: '/file.txt'
5 path.format({root: '/ignored',dir: '/home/user/dir',base: 'file.txt'});
6 // Returns: '/home/user/dir/file.txt'
7 path.format({dir: 'C:\\path\\dir',base: 'file.txt'});
8 // Returns: 'C:\\path\\dir\\file.txt'

8.5. path.isAbsolute(path)
判断参数 path 是否是绝对路径。

1 let path = require('path')
2 let name = '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare/3-path.js'
3 console.log(path.isAbsolute(name));
4 // true

8.6. path.join([…paths])
连贯多个地址

1 path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
2 // Returns: '/foo/bar/baz/asdf'

8.7. path.normalize(path)
标准化门路,能够辨认 ”.” “..”

1 path.normalize('/foo/bar//baz/asdf/quux/..');
2 // Returns: '/foo/bar/baz/asdf'

8.8. path.parse(path)
返回门路字符串的对象。

1 let path = require('path')
2 let name = '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare/3-path.js'
3 console.log(path.parse(name));
4 /*
5 {
6 root: '/',
7 dir: '/Users/lichunyu/Desktop/2021/web2102/nodejs/prepare',
8 base: '3-path.js',
9 ext: '.js',
10 name: '3-path'
11 }
12 */

8.9. path.relative(from, to)
⽤于将绝对路径转为相对路径,返回从 from 到 to 的相对路径(基于以后⼯作⽬录)

1 path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
2 // Returns: '../../impl/bbb'

8.10. path.resolve([…paths])
解析为绝对路径,给定的门路的序列是从右往左被解决的,后⾯每个 path 被顺次解析,直
到结构实现⼀个绝对路径。

1 path.resolve('/foo/bar', './baz');
2 // Returns: '/foo/bar/baz'
3 path.resolve('/foo/bar', '/tmp/file/');
4 // Returns: '/tmp/file'
5 path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
6 // If the current working directory is /home/myself/node,
7 // this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'

8.11. path.sep
平台的⽂件门路分隔符,’\’ 或 ‘/’。

1 'foo/bar/baz'.split(path.sep);
2 //linux 中 Returns: ['foo', 'bar', 'baz']

8.12. path.win32
提供蕴含上述 path 的⽅法的对象,不过总是以 win32 兼容的⽅式交互。
8.13. path.posix
提供蕴含上述 path 的⽅法的对象
8.14. path.delimiter
门路分隔符,linux 操作系统分隔符为 ”:”,windows 操作系统分隔符为 ”;”

1 let path = require('path')
2 console.log(path.delimiter);
3 //mac :
9. util 模块

util 模块提供了很多⼯具函数,具体可参照:
https://nodejs.org/dist/lates…
9.1. util.callbackify
将 async 异步函数(或者⼀个返回值为 Promise 的函数)转换成遵循异样优先的回调
⻛格的函数,例如将 (err, value) => … 回调作为最初⼀个参数。在回调函数中,第⼀个参数为回绝的起因(如果 Promise 解决,则为 null),第⼆个参数则是解决的值。

1 const util = require('util');
2 async function fn() {
3 return 'hello world';
4 }
5 const callbackFunction = util.callbackify(fn);
6 callbackFunction((err, ret) => {7 if (err) throw err;
8 console.log(ret);
9 });

1.1. nodejs 架构组成
1.2. 事件循环
1.3. 模仿事件
1.4. fs 模块

2. nodejs 架构组成

Node.js 是⼀个构建在 Chrome 浏览器 V8 引擎 上的 JavaScript 运⾏环境,使⽤ 单线程、事件驱动、非阻塞 I /O 的⽅式实现了⾼并发申请,libuv 为其提供了异步编程的能⼒。

Node.js 规范库由 Javascript 编写的,也就是咱们使⽤过程中间接能调⽤的 API,在源码中的 lib ⽬录下能够看到,诸如 http、fs、events 等常⽤核⼼模块

Node bindings 能够了解为是 javascript 与 C /C++ 库之间建⽴连贯的 桥,通过这个桥,底层实现的 C /C++ 库裸露给 javascript 环境,同时把 js 传入 V8 , 解析后交给 libuv 发动 非阻塞 I /O , 并期待 事件循环 调度;

V8:Google 推出的 Javascript 虚拟机,为 Javascript 提供了在⾮浏览器端运⾏的环境;libuv:为 Node.js 提供了跨平台,线程池,事件池,异步 I /O 等能⼒,是 Nodejs 之所以⾼效的次要起因;C-ares:提供了异步解决 DNS 相干的能⼒;http_parser、OpenSSL、zlib 等:提供包含 http 解析、SSL、数据压缩等能⼒;

2.1. 单线程
任务调度⼀般有两种⽅案: ⼀是 单线程串行执行,执⾏程序与编码程序⼀致,最⼤的问题是⽆法充沛利⽤多核 CPU,当并⾏极⼤的时候,单核 CPU 实践上计算能⼒是 100%; 另⼀种就是 多线程并行处理,长处是能够无效利⽤多核 CPU,毛病是创立与切换线程开销⼤,还波及到锁、状态同步等问题, CPU 常常会期待 I / O 完结,CPU 的性能就⽩⽩耗费。

通常为客户端连贯创立⼀个线程须要耗费 2M 内存,所以实践上⼀台 8G 的服务器,在 Java 应⽤中最多⽀持的并发数是 4000。⽽ Node.js 只使⽤⼀个线程,当有客户端连贯申请时,触发外部事件,通过⾮阻塞 I /O,事件驱动机制,让其看起来是并⾏的。实践上⼀台 8G 内存的服务器,能够同时包容 3 到 4 万⽤户的连贯。Node.js 采⽤单线程⽅案,免去锁、状态同步等繁冗问题, ⼜能提⾼ CPU 利⽤率。Node.js ⾼效的除了因为其单线程外,还必须配合下⾯要说的⾮阻塞 I /O。

2.2. ⾮阻塞 IO

发动 I / O 操作不等失去响应或者超时就⽴即返回,让过程持续执⾏其余操作,然而要通过轮询⽅式一直地去 check 数据是否已筹备好。Java 属于阻塞 IO,即在发动 I / O 操作之后会⼀直阻塞着过程,不执⾏其余操作, 直到失去响应或者超时为⽌;

Node.js 中采⽤了⾮阻塞型 I / O 机制,因而在执⾏了读数据的代码之后,将⽴即转⽽执⾏其后⾯的代码,把读数据返回后果的解决代码放在回调函数中,从⽽提⾼了程序的执⾏效率。当某个 I / O 执⾏结束时,将以事件的模式告诉执⾏ I / O 操作的线程,线程执⾏这个事件的回调函数。

例如:当你的 JavaScript 程序收回了⼀个 Ajax 申请(异步)去服务器获取数据,在回调函数中写了相干 response 的解决代码。JavaScript 引擎就会通知宿主环境:“嘿,我当初要暂停执⾏了,然而当你实现了这个⽹络申请,并且获取到数据的时候,请回来调⽤这个函数。“而后宿主环境(浏览器)设置对⽹络响应的监听,当返回时,它将会把回调函数插⼊到事件循环队列⾥而后执⾏。

3. 事件循环

3.1. 根本流程

  1. 每个 Node.js 过程只有⼀个主线程在执⾏程序代码,造成⼀个 执行栈(execution contextstack);
  2. 主线程之外,还保护⼀个 事件队列(Event queue),当⽤户的 网络申请或者其它的异步操作到来时,会先进⼊到事件队列中排队,并不会⽴即执⾏它,代码也不会被阻塞,持续往下⾛,直到主线程代码执⾏结束;
  3. 主线程代码执⾏结束实现后,而后通过 事件循环机制(Event Loop),查看队列中是否有要解决的事件,从队头取出第⼀个事件,从 线程池 调配⼀个线程来解决这个事件,而后是第⼆个,第三个,直到队列中所有事件都执⾏完了。当有事件执⾏结束后,会告诉主线程,主线程执⾏回调,并将线程归还给线程池。这个过程就叫 事件循环 (Event Loop);
  4. 一直反复上⾯的第三步;

3.2. 事件循环的 6 个阶段

 timers: 这个阶段执⾏定时器队列中的回调如 setTimeout() 和 setInterval()。

 I/O callbacks: 这个阶段执⾏⼏乎所有的回调。然而不包含 close 事件,定时器和
setImmediate() 的回调。

 idle, prepare: 这个阶段仅在外部使⽤,能够不用理睬。

 poll: 期待新的 I / O 事件,node 在⼀些非凡状况下会阻塞在这⾥。v8 引擎将 js 代码解析后传⼊ libuv 引擎后,循环⾸先进⼊ poll 阶段。poll 阶段的执⾏逻辑如下:先查看 poll queue 中是否有事件,有工作就按先进先出的程序顺次执⾏回调。当 queue 为空时,会查看是否有 setImmediate()的 callback,如果有就进⼊ check 阶段执⾏这些 callback。但同时也会查看是否有到期的 timer,如果有,就把这些到期的 timer 的 callback 依照调⽤程序放到 timerqueue 中,之后循环会进⼊ timer 阶段执⾏ queue 中的 callback。这两者的程序是不固定的,收到代码运⾏的环境的影响。如果两者的 queue 都是空的,那么 loop 会在 poll 阶段停留,直到有⼀个 i / o 事件返回,循环会进⼊ i /o callback 阶段并⽴即执⾏这个事件的 callback。

 check: setImmediate() 的回调会在这个阶段执⾏。

 close callbacks: 例如 socket.on(‘close’, …) 这种 close 事件的回调。

  • Event Loop 从工作队列获取工作,而后将工作增加到执⾏栈中(动静,依据函数调
    ⽤),JavaScript 引擎获取执⾏栈最顶层元素(即正在运⾏的执⾏高低⽂)进⾏运⾏!

3.3. 宏工作
Event Loop 有⼀个或多个工作(宏工作)队列,当 执⾏栈 为空时,会从工作队列⾥获取工作,加⼊到执⾏栈中,这⾥的工作就是 宏工作。如下都是宏工作:
 Events
 Parsing
 Callbacks
 Using a resource(I/O)
 Reacting to DOM manipulation
 script
 setTimeout/setInterval/setImmediate
 requestAnimationFrame

3.4. 微工作
每个 Event Loop 有⼀个微工作队列,同时有⼀个 microtask checkpoint,即每执⾏实现⼀个宏工作后,就会 check 微工作。所以,当某个 宏工作 执⾏实现后,会先执⾏ 微工作 队列,执⾏实现后,再次获取新的 宏工作。这⾥微工作相当于插队操作!!如下都是微工作:
 Process.nextTick
 Promise(aync/await)
 Object.observe(已过期)
 MutationObserver(监听 dom 树变动)

3.5. process.nextTick()
当将⼀个函数传给 process.nextTick() 时,则批示引擎在以后操作完结(在下⼀个事
件循环滴答开始之前)时调⽤此函数,意味着插队。所以,当要确保在下⼀个事件循环迭代中代码已被执⾏,则使⽤ nextTick().

3.6. setImmediate()
作为 setImmediate() 参数传⼊的任何函数都是在事件循环的下⼀个迭代中执⾏的回调。须要留神的是:传给 process.nextTick() 的函数会在事件循环的以后迭代中(以后操作完结之后)被执⾏。这意味着它会始终在 setTimeout 和 setImmediate 之前执⾏;提早 0 毫秒的 setTimeout() 回调与 setImmediate() ⾮常类似。执⾏程序取决于各种因素,然而它们都会在事件循环的下⼀个迭代中运⾏。

4. 模仿事件

Node.js 的⼤局部核⼼ API 都是围绕惯⽤的异步事件驱动架构构建的,在该架构中,某些类型的对象(称为 ” 触发器 ”)触发命名事件,使 Function 对象(” 监听器 ”)被调⽤。

1 const EventEmitter = require('events');
2 class MyEmitter extends EventEmitter {}
3 const myEmitter = new MyEmitter();
4 myEmitter.on('event', () => {5 console.log('an event occurred!');
6 });
7 myEmitter.emit('event');

4.1. API
 emit(eventName[,param,param])
事件发射器,eventName 示意模仿工夫名,param 为传递的参数。

 on(eventName, (param,param…)=>{}
事件注册

 once(eventName, (param,param…)=>{}
事件注册⼀次,该监听器最多为特定事件调⽤⼀次。⼀旦事件被触发,则监听器就会被注
销而后被调⽤。

 emitter.addListener(eventName, listener)

 emitter.eventNames()
返回列出触发器已为其注册监听器的事件的数组。数组中的值是字符串或 Symbol

 emitter.listeners(eventName)
返回名为 eventName 的事件的监听器数组的正本。

 emitter.off(eventName, listener)
事件解绑

 emitter.removeListener(eventName, listener)
事件解绑

 emitter.removeAllListeners([eventName])
事件全副解绑

4.2. 模仿事件机制

1 /**
2 * 事件机制
3 */
4 class EventEmitter{5 constructor(){6 this.listeners = {}; // 寄存事件监听函数{"event1": [f1,f2,f3],"eve
7 }
8 // 在 jq 中,一个事件源能够绑定多个事件处理函数,同一个事件能够绑定多个
9 addEventListener(eventName, handler){
10 let listeners = this.listeners;
11 // 先判断这个如果这个事件类型对应的值是一个数组,阐明其中曾经绑定过了事件处理
12 // 如果该事件处理函数未绑定过,进行绑定
13 if (listeners[eventName] instanceof Array) {14 if (listeners[eventName].indexOf(handler) === -1) {15 listeners[eventName].push(handler);
16 }
17 } else {
18 // 否则,实现该事件类型的首次绑定
19 listeners[eventName] = [].concat(handler);
20 }
21 }
22 // 移除事件处理函数
23 removeEventListener(eventName, handler){
24 let listeners = this.listeners;
25 let arr = listeners[eventName] || [];
26 // 找到该事件处理函数在数组中的地位
27 let i = arr.indexOf(handler);
28 // 如果存在,则删除
29 if (i >= 0) {30 listeners[eventName].splice(i, 1);
31 }
32 }
33 // 派发事件,实质上就是调用事件
34 dispatch(eventName, params){35 this.listeners[eventName].forEach(handler => {36 handler.apply(null, params);
37 });
38 }
39 }
5. fs 模块

fs 是 filesystem 的缩写,该模块提供本地⽂件的读写能⼒,这个模块⼏乎对所有操作
提供异步和同步两种操作⽅式,供开发者抉择。
5.1. readFile()
异步读取数据。该⽅法的第⼀个参数是⽂件的门路,能够是绝对路径,也能够是相对路径。留神,如果是相对路径,是绝对于以后过程所在的门路。第⼆个参数是读取实现后的回调函数。该函数的第⼀个参数是发⽣谬误时的谬误对象,第⼆个参数是代表⽂件内容的 Buffer 实例。

1 fs.readFile('./image.png', function (err, buffer) {2 if (err) throw err;
3
4 });
5

5.2. readFileSync()
⽤于同步读取⽂件,返回⼀个 Buffer 实例。法的第⼀个参数是⽂件门路,第⼆个参数能够是⼀个示意配置的对象,也能够是⼀个示意⽂本⽂件编码的字符串。默认的配置对象是 {
encoding: null, flag: ‘r’ },即⽂件编码默认为 null,读取模式默认为 r(只读)。

1 var text = fs.readFileSync(fileName, 'utf8');
2 // 将文件按行拆成数组
3 text.split(/\r?\n/).forEach(function (line) {
4 // ...
5 });

5.3. writeFile()
⽤于异步写⼊⽂件。该⽅法的第⼀个参数是写⼊的⽂件名,第⼆个参数是写⼊的字符串,第三个参数是回调函数。回调函数前⾯,还能够再加⼀个参数,示意写⼊字符串的编码(默认是 utf8)

1 fs.writeFile('message.txt', 'Hello Node.js', (err) => {2 if (err) throw err;
3 console.log('It\'s saved!');
4 });

5.4. writeFileSync()
该⽅法⽤于同步写⼊⽂件

1 fs.writeFileSync(fileName, str, 'utf8');
5.5. exists(path, callback)
5.6. mkdir()
5.7. mkdirSync()
5.8. readdir()
5.9. readdirSync()
5.10. stat()
5.11. createReadStream()
5.12. createWriteStream()

1.1. http 模块 api
1.2. http 协定
1.3. http 缓存机制
1.4. 三次握⼿四次挥⼿

2. Http 模块
1 var http = require('http');
2
3 http.createServer(function (request, response){4 response.writeHead(200, {'Content-Type': 'text/plain'});
5 response.write("Hello World");
6 response.end();
7 }).listen(8080, '127.0.0.1');

2.1. http
 http.METHODS

 http.STATUS_CODES

 http.createServer(options[, callback]
创立服务,callback 中的参数类型为 ClientRequest,ServerResponse

 http.request(options[, callback])
发送 requt 申请,callback 中的参数类型为 IncomingMessage

 http.get(url,options])
客户端发送申请,因为少数申请都是 get 申请,get 申请没有申请体,nodejs 提供了 http.get
这个简便⽅法。callback 中的参数类型为 IncomingMessage

2.2. http.Server
 事件: upgrate
 事件: connect
 事件: connection
 事件: request
 server.headersTimeout
 server.maxHeadersCount
 server.requestTimeout
 server.timeout
 server.keepAliveTimeout
 server.listen()
 server.close()
 server.setTimeout(msecs)

2.3. http.IncomingMessage
 data
当申请接管到数据⽚段

 事件:end
当申请接管到完数据

 事件:response
当申请实现获取响应对象

 message.url
获取申请的门路及参数

 message.method
获取申请⽅法

 message.statusCode
 message.statusMessage
 message.rawHeaders
 message.headers
 message.httpVersion
2.4. http.ClientRequest
客户端申请,该申请对象能够向后盾发送申请,后盾也能够承受前台发过来的申请对象。
http.request()返回⼀个 http.ClientRequest 对象,
 事件:response
当申请实现获取响应对象
 request.path
获取申请的门路及参数
 request.method
获取申请⽅法
 request.host
 request.protocol
 request.write(chunk, encoding)
向申请体中能够写⼊很多数据⽚段,encoding 示意编码,callback 为申请输⼊写⼊实现后
调⽤。
Returns true if the entire data was flushed successfully to the kernel buffer.
Returns false if all or part of the data was queued in user memory. ‘drain’ will be
emitted when the buffer is free again.
 request.end([data[, encoding]][, callback])
完结发送申请,如果 data 有数据,示意先 write 数据,再 end 申请
 request.writableEnded
申请是否写⼊实现
 request.removeHeader(name)
删除申请头
1 request.removeHeader(‘Content-Type’);
 request.setHeader(name, value)
设置申请头信息,value 能够为单值也能够为⼀个字符串数组
1 request.setHeader(‘Content-Type’, ‘application/json’);
2 request.setHeader(‘Cookie’, [‘type=ninja’, ‘language=javascript’]);
 request.getHeader(name)
获取申请头信息
 request.setTimeout(timeout[, callback])
设置超时工夫
 request.destroy([error])
销毁⼀个申请
 request.destroyed
判断⼀个申请是否被销毁
 request.flushHeaders()
刷新申请头信息到申请中
 request.getRawHeaderNames()
获取申请头 key 值数组
2.5. http.ServerResponse
该实例创立于服务器端,⽤于封装或封装了服务器端响应信息。
 事件:data
 response.write(chunk, encoding)
写⼊响应数据
 response.end([data[, encoding]][, callback])
示意以后 response 曾经实现响应头、响应体的发送
 response.flushHeaders()
刷新 response 的响应头
 response.writeHead(statusCode, statusMessage)
写⼊响应头
 response.getHeader(name)
获取响应头信息
 response.getHeaderNames()
获取响应头 key 值汇合
 response.getHeaders()
获取响应头
 response.hasHeader(name)
判断响应头信息中是否蕴含 name
 response.removeHeader(name)
从响应头信息中移除 key 为 name 的值
 response.socket
从响应头中获取 socket 信息
 response.statusCode
获取状态码
 response.statusMessage
获取状态信息

  1. http 协定
    HTTP 是⼀种可能获取如 HTML 这样的⽹络资源的 protocol(通信协定)。它是在 Web 上进
    ⾏数据交换的根底,是⼀种 client-server 协定,也就是说,申请通常是由像浏览器这样的承受
    ⽅发动的。⼀个残缺的 Web ⽂档通常是由不同的⼦⽂档拼接⽽成的,像是⽂本、布局形容、图
    ⽚、视频、脚本等等。
    3.1. 申请报⽂
    申请由以下元素组成:
     ⼀个 HTTP 的 method,常常是由⼀个动词像 GET , POST 或者⼀个名词像 OPTIONS,
    HEAD 来定义客户端的动作⾏为。通常客户端的操作都是获取资源(GET ⽅法)或者发送 H
    TML form 表单值(POST ⽅法),尽管在⼀些状况下也会有其余操作。
     要获取的资源的门路,通常是高低⽂中就很显著的元素资源的 URL,它没有 protocol
    (http://),domain(developer.mozilla.org),或是 TCP 的 port (en-US)
    (HTTP ⼀般在 80 端⼝)。
     HTTP 协定版本号。
     为服务端表白其余信息的可选头部 headers。
     对于⼀些像 POST 这样的⽅法,报⽂的 body 就蕴含了发送的资源,这与响应报⽂的 body 类
    似。
    3.2. 响应报⽂
    响应报⽂蕴含了下⾯的元素:
     HTTP 协定版本号。
     ⼀个状态码(status code),来告知对应申请执⾏胜利或失败,以及失败的起因。
     ⼀个状态信息,这个信息是⾮权威的状态码形容信息,能够由服务端⾃⾏设定。
     HTTP headers,与申请头部相似。
     可选项,⽐起申请报⽂,响应报⽂中更常⻅地蕴含获取的资源 body。
  2. http 缓存机制
    缓存的品种有很多, 其⼤致可归为两类:公有与共享缓存。共享缓存存储的响应可能被多个
    ⽤户使⽤。公有缓存只能⽤于独自⽤户。本⽂将次要介绍浏览器与代理缓存,除此之外还有⽹关
    缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存⽅式,为站点和 web 应⽤
    提供更好的稳定性、性能和扩展性。
    HTTP/1.1 定义的 Cache-Control 头⽤来辨别对缓存机制的⽀持状况,申请头和响应头
    都⽀持这个属性。通过它提供的不同的值来定义缓存策略。
  3. 没有缓存

  4. Cache-Control: no-store
  5. 缓存但须要从新验证:每次有申请收回时,缓存会将此申请发到服务器,服务器端会验证申请

    中所形容的缓存是否过期,若未过期(注:理论就是返回 304),则缓存才应用本地缓存正本。

  6. Cache-Control: no-cache
  7. 公共缓存,示意该响应能够被任何中间件(比方两头代理、CDN 等)缓存

  8. Cache-Control: public
  9. 公有缓存,示意该响应只能被浏览器缓存

  10. Cache-Control: private
    当客户端发动⼀个申请时,缓存检索到已有⼀个对应的古老资源(缓存正本),则缓存会先
    将此申请附加⼀个 If-None-Match 头,而后发给⽬标服务器,以此来查看该资源正本是否是依
    然还是算陈腐的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则
    示意此资源正本是陈腐的,这样⼀来,能够节俭⼀些带宽。若服务器通过 If-None-Match 或
    If-Modified-Since 判断后发现已过期,那么会带有该资源的实体内容返回。
    If-Modified-Since,和 Last-Modified ⼀样都是⽤于记录⻚⾯最初批改工夫的 HTTP 头
    信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,⽽ If-Modified-Since 则是
    由客户端往服务器发送的头。If-None-Match 的⼯作原理是在 HTTP Response 中增加 ETags 信
    息。
    浏览器服务器交互过程:
    1. 客户端申请⼀个⻚⾯(A)。
    2. 服务器返回⻚⾯ A,并在给 A 加上⼀个 ETag。
    3. 客户端展示该⻚⾯,并将⻚⾯连同 ETag ⼀起缓存。
    4. 客户再次申请⻚⾯ A,并将上次申请时服务器返回的 ETag ⼀起传递给服务器。
    5. 服务器查看该 ETag,并判断出该⻚⾯⾃上次客户端申请之后还未被批改,间接返回响应 304
    (未修改——Not Modified)和⼀个空的响应体。
  11. 示意资源可能被缓存(放弃陈腐)的最大工夫。

  12. Cache-Control: max-age=31536000
    4.1. http1.0、http1.1、http2.0
    在 HTTP1.0 中默认是短连贯:每次与服务器交互,都须要新开⼀个连贯!
    在 HTTP1.1 中默认就使⽤长久化连贯来解决:建⽴⼀次连贯,屡次申请均由这个连贯完
    成!(如果阻塞了,还是会开新的 TCP 连贯的)
    HTTP2 与 HTTP1.1 最重要的区别就是解决了线头阻塞的问题!其中最重要的改变是:多
    路复⽤ (Multiplexing)
  13. 三次握⼿
     ACK (Acknowledge character) 确认字符,示意接管到的字符⽆谬误
     SYN(Synchronize Sequence Numbers)同步序列编号
     FIN(Finish)完结编号
     MSL(Maximum Segment Lifetime) 示意“最⻓报⽂段寿命”,它是任何报⽂在⽹络上存
    在的最⻓工夫,超过这个工夫报⽂将被抛弃。
    进⾏三次握⼿的次要作⽤就是为了确认双⽅的接管能⼒和发送能⼒是否失常、指定⾃⼰的初
    始化序列号为后⾯的可靠性传送做筹备。
  14. 四次挥⼿
    TCP 的连贯的拆除须要发送四个包,因而称为四次挥⼿(Four-way handshake),客户端或
    服务器均可被动发动挥⼿动作。
    为什么要挥⼿四次?当服务端收到客户端的 SYN 连贯申请报⽂后,能够间接发送 SYN+ACK
    报⽂。其中 ACK 报⽂是⽤来应答的,SYN 报⽂是⽤来同步的。然而敞开连贯时,当服务端收到
    FIN 报⽂时,很可能并不会⽴即敞开 SOCKET,所以只能先回复⼀个 ACK 报⽂,通知客户
    端,“你发的 FIN 报⽂我收到了”。只有等到我服务端所有的报⽂都发送完了,我能力发送 FIN 报
    ⽂,因而不能⼀起发送。故须要四次挥⼿。
    day04
  15. 工作安顿
    1.1. express
    1.2. mysql DML
    1.3. 数据建模
  16. express
    express 是基于 Node.js 平台,疾速、凋谢、极简的 Web 开发框架。
    2.1. helloworld
     ⼿动创立⼯程
  17. $ mkdir myapp
  18. $ cd myapp
  19. $ npm init
  20. $ cnpm install express –save
     核⼼代码
  21. const express = require(‘express’)
  22. const app = express()
  23. const port = 3000
    4
  24. app.use(express.static(‘public’))
  25. app.get(‘/’, (req, res) => {
  26. res.send(‘Hello World!’)
  27. })
  28. app.listen(port, () => {
  29. console.log(Example app listening at http://localhost:${port})
  30. })
     路由定义
    定义了针对客户端申请的处理函数,不同 uri 对应不同的处理函数。通过 Express 的实例对象
    app 上的申请⽅法定义不同的路由,例如:get、post、delete、put、all…
  31. var express = require(‘express’);
  32. var router = express.Router();
    3
  33. / GET users listing. /
  34. router.get(‘/’, function(req, res, next) {
  35. res.send(‘respond with a resource’);
  36. });
    8
  37. module.exports = router;
  38. var usersRouter = require(‘./routes/users’);
  39. app.use(‘/users’, usersRouter);
     路由参数
     response ⽅法
     中间件
    body-parser
    cookie-parser
    cors
    serve-static
    2.2. 脚⼿架
    通过应⽤⽣成器⼯具 express-generator 能够疾速创立⼀个应⽤的⻣架。
  40. sql-ddl
    Route path: /users/:userId/books/:bookId
    Request URL: http://localhost:3000/users/3…
    req.params: {“userId”: “34”, “bookId”: “8989”}
    Route path: /users
    Request URL: http://localhost:3000/users?i…
    req.query: {“id”: “1”, “name”: “terry”}
    1
    2
    3
    1
    2
    3
    Method Description
    res.download() Prompt a file to be downloaded.
    res.end() End the response process.
    res.json() Send a JSON response.
    res.jsonp() Send a JSON response with JSONP support.
    res.redirect() Redirect a request.
    res.render() Render a view template.
    res.send() Send a response of various types.
    res.sendFile() Send a file as an octet stream.
    res.sendStatus() Set the response status code and send its string representation as the
    response body.
  41. $ mkdir cms
  42. $ cd cms
  43. $ npx express-generator
    3.1. 数据库定义
  44. mysql> CREATE DATABASE poll;
  45. mysql> USE poll
  46. Database changed
    3.2. 表定义
  47. create table < 表名 >(
  48. < 列名 >< 数据类型 >[列级残缺约束条件]
  49. [, < 列名 >< 数据类型 >[列级残缺约束条件]]
  50. [,< 表级完整性约束条件 >]
  51. );
    7
  52. DROP TABLE IF EXISTS tbl_exam_paper;
  53. CREATE TABLE tbl_exam_paper (
  54. id bigint(20) NOT NULL AUTO_INCREMENT,
  55. description varchar(255) DEFAULT NULL,
  56. title varchar(255) DEFAULT NULL,
  57. department_id bigint(20) DEFAULT NULL,
  58. user_id bigint(20) DEFAULT NULL,
  59. PRIMARY KEY (id),
  60. UNIQUE KEY id (id),
  61. KEY FK92534C8DF2C32590 (department_id),
  62. CONSTRAINT FK92534C8DF2C32590 FOREIGN KEY (department_id) REFERENCES
  63. )
    3.3. 表批改
  64. 增加列

  65. mysql> alter table tableName add columnName dataType
  66. 删除列

  67. mysql> alter table tableName drop columnName dataType
  68. 批改列

  69. mysql> alter table tableName modify columnName dataType
  70. 删除数据库表

  71. mysql> drop table tableName
  72. 清空数据库表内容

  73. mysql> truncate table s_item
  74. sql-dml
    ⽤于拜访和解决数据库的规范的计算机语⾔。
    4.1. 查问语句
     简略查问
  75. mysql> select [all|distinct] < 指标列表达式 >
  76. -> from < 表或视图 >[, < 表或视图 >
  77. -> [where < 条件表达式 >]
  78. -> [group by < 列名 1 >
  79. -> [having< 条件表达式 >]]
  80. -> [order by < 列名 2 >[asc|desc]];
  81. select name
  82. from tbl_student
  83. where gender = ‘male’;
     连贯查问
  84. select s.name,c.name
  85. from tbl_student s, tbl_student_course sc ,tbl_course c
  86. where s.id = sc.student_id
  87. and c.id = sc.course_id;
    4.2. 插⼊语句
  88. insert into tbl_student(id,name,gender)
  89. values (null,’terry’,’gender’)
    4.3. 批改语句
  90. update tbl_student
  91. set name = ‘ 王五 ’
  92. where id = 1;
    4.4. 删除语句
  93. delete from tbl_user
  94. where id = 1;
  95. 数据库建模
    关系型数据库中常⻅的关系有⼀对⼀、⼀对多、多对多;其中⼀对⼀是⼀对多的⼀种特例;
    多对多能够拆分成两个⼀对多。所以⼀对多⾄关重要。
     ⼀对多
    ⼀对多关系是项⽬开发过程中最常⻅的关联关系。例如,⼀个⽼师能够在任教多⻔课程。在
    ⼀对多关系中,外键通常保护在多的⼀⽅。
     ⼀对⼀
    ⼀对⼀关系是⼀对多关系的⼀种特例(为外键增加唯⼀束缚)。例如,⼀个⼈只能对应⼀个
    身份证。在⼀对⼀关系中,外键能够保护在任意⼀⽅
     多对多
    多对多关系是也能够了解为是⼀对多关系的⼀种特例(两个⼀对多)。例如,学⽣和课程的
    关系,⼀个学⽣能够选修多⻔课程,⼀个⻔课程也能够被多个学⽣选修。在多对多关系中,外键
    保护在桥表中
  96. 数据类型
     整数
     ⼩数
     字符类型
     ⽇期类型
  97. mysql 模块
    mysql 模块提供了 nodejs 操作 mysql 数据库的 api
    https://www.npmjs.com/package…
    day05
  98. 工作安顿
    1.1. mysql 模块
    1.2. 学⽣选课零碎综合演练
  99. mysql 模块
    mysql 模块提供了 nodejs 操作 mysql 数据库的 apihttps://www.npmjs.com/package…
    2.1. 装置
    2.2. 应⽤
    每次进⾏ sql 操作的时候都创立⼀个连贯对象,使⽤实现后将连贯对象敞开,敞开后的连贯
    对象⽆法再次使⽤。这不利于咱们进⾏代码封装。
    $ cnpm install mysql –save
    var mysql = require(‘mysql’);
    float 单精度,使⽤ 4 个字节示意,float(m,d)
    double 双精度,使⽤ 8 个字节示意,double(m,d) m 示意位数,d 示意⼩数位数
    char(n) 定⻓字符,n 的取值为 0~255,示意存储字符的最⼤数⽬
    varchar(n) 变⻓字符,n 的取值为 0~65,535
    blob 寄存字节对象,⽐如视频,⾳频信息
    text 寄存字符对象,⽐如⼤型⽂本信息。
    date 寄存⽇期,格局为:year-month-day,例如 98-09-04;year 如果为 70-99 示意 1970-
    199;year 如果为 00-69 示意 2000-2069.
    time 格局为:hh:mm:ss,例如 09:00:00
    datetime 示意⽇期与工夫,格局为:year-month-day hh:mm:ss,例如 91-03-14 09:00:00
    1
    1
  100. var connection = mysql.createConnection({
  101. host : ‘121.199.29.84’,
  102. user : ‘briup’,
  103. password : ‘briup’,
  104. database : ‘briup-sc’
  105. });
  106. connection.connect();
    9
  107. connection.query(‘SELECT * FROM tbl_student’, function (error, results, fiel
  108. if (error) throw error;
  109. console.log(‘The solution is: ‘, results);
  110. });
    14
  111. connection.end();
    2.3. 连接池
    连接池技术能够创立多个连贯放到连接池中,咱们能够将连接池的代码进⾏封装,每次须要
    连贯的的时候,通过连接池获取⼀个连贯对象即可,使⽤实现后将该连贯对象开释。
  112. var mysql = require(‘mysql’);
  113. var pool = mysql.createPool({
  114. connectionLimit : 10,
  115. host : ‘121.199.29.84’,
  116. user : ‘briup’,
  117. password : ‘briup’,
  118. database : ‘briup-sc’
  119. });
  120. // 获取连贯
  121. pool.getConnection((err,connection)=>{
  122. if(err) throw err;
  123. // 查问
  124. connection.query(sql, function (error, results, fields) {
  125. if (error) throw error;
  126. // 开释
  127. connection.release();
  128. resp.send(Message.success(results));
  129. });
  130. })
  131. })
  132. 综合演练
    截⽌⽬录,咱们曾经实现了 nodejs 根底语法、核⼼模块、http 服务器编程、数据库编程的学
    习,接下来通过这些技术实现后端服务的开发。
    3.1. 数据建模
    在学⽣选课业务中,⼀个学⽣能够选多⻔课程,⼀⻔课程能够被多个⼈来选,⼀个课程只能
    由⼀个老师来负责。
    3.2. 初始化⼯程
  133. $ mkdir sc-server
  134. $ cd sc-server
  135. $ npx express-generator
  136. $ cnpm install
  137. $ cnpm install mysql –save
  138. $ cnpm install cors –save
  139. $ npm start
    通过上述操作,咱们会创立⼀个基于 express 的 sc-server 的⼯程,该⼯程中默认蕴含了
    cookie-parser、debug、express、jade、morgan、body-parser(内置)、serve-static 依赖,
    咱们还须要⼿动装置 cors 和 mysql,npm start 命令会启动该⼯程,默认占据 3000 端⼝,须要注
    意的是,如果后盾接⼝代码更新,请务必重启该⼯程。
    3.3. Message 封装
    咱们冀望,后端提供的所有端⼝都具备统⼀的标准,例如:
  140. {
  141. status:200,
  142. message:””,
  143. data:null,
  144. timestamp:1630492833755
  145. }
    这样,⽅便前端统⼀解决。如下是封装代码。
  146. class Message {
  147. constructor(status,message,data){
  148. this.status = status;
  149. this.message = message;
  150. this.data = data;
  151. this.timestamp = new Date().getTime();
  152. }
    8
  153. static success(param){
  154. if(typeof param == ‘string’){
  155. return new Message(200,param,null)
  156. } else if (typeof param == ‘object’){
  157. return new Message(200,’success’,param)
  158. }
  159. }
    16
  160. static error(message){
  161. return new Message(500,message,null);
  162. }
  163. }
    21
  164. module.exports = Message;
    3.4. 接⼝编写
    接⼝开发的时候留神要分模块开发,即每个模块创立⼀个新的 router,每个接⼝沿着 获取
    参数 -> 业务逻辑解决 -> 数据库操作 -> 后果返回来进⾏。如下是实例代码:
  165. const express = require(‘express’)
  166. const Message = require(‘../utils/Message’)
  167. const pool = require(‘../utils/Connection’)
    4
  168. const router = express.Router();
  169. // 1. 查问
  170. router.get(‘/findAll’,(req,resp)=>{
  171. let sql = “select * from tbl_student”
  172. // 获取连贯
  173. pool.getConnection((err,connection)=>{
  174. if(err) throw err;
  175. // 查问
  176. connection.query(sql, function (error, results, fields) {
  177. if (error) throw error;
  178. // 开释
  179. connection.release();
  180. resp.send(Message.success(results));
  181. });
  182. })
  183. })
  184. // 2. 删除
  185. router.delete(‘/deleteById’,(req,resp)=>{
  186. let id = req.query.id;
  187. let sql = “delete from tbl_student where id = “+ id;
  188. pool.getConnection((err,connection)=>{
  189. if(err) throw err;
  190. connection.query(sql,(error,results)=>{
  191. if(error) throw error;
  192. connection.release();
  193. resp.send(Message.success(‘ 删除胜利 ’))
  194. })
  195. })
  196. })
  197. // 3. 保留或更新
  198. router.post(‘/saveOrUpdate’,(req,resp)=>{
  199. let stu = req.body;
    37
  200. let sql = “insert into tbl_student(id,name,gender,birth) values(null,'”+st
  201. if(stu.id){
  202. sql = “update tbl_student set name = ‘”+stu.name+”‘,gender='”+stu.gender
  203. }
    42
  204. pool.getConnection((err,connection)=>{
  205. if(err) throw err;
  206. connection.query(sql,(error,results)=>{
  207. if(error) throw error;
  208. connection.release();
  209. resp.send(Message.success(‘ 操作胜利!’))
  210. })
  211. })
  212. })
    3.5. 前端开发
    前端开发仍旧使⽤ vue + axios + elementui 来进⾏
  213. <!DOCTYPE html>
  214. <html lang=”en”>
  215. <head>
  216. <meta charset=”UTF-8″>
  217. <meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
  218. <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
  219. <title> 学生选课 </title>
  220. <!– axios –>
  221. <script src=”https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js”>
  222. <!– vue –>
  223. <script src=”https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js”></sc
  224. <!– elementui –>
  225. <link href=”https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.3/theme-chal
  226. <script src=”https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.3/index.min
  227. </head>
  228. <body>
  229. <div id=”app”>
  230. <h2> {{name}}</h2>
  231. <el-button @click=”loadStudents” size=”small” type=”primary”> 刷新 </el-bu
  232. <el-button @click=”toSave” size=”small” type=”primary”> 录入 </el-button>
  233. <!– 表格 –>
  234. <el-table :data=”students” size=”small”>
  235. <el-table-column prop=”name” label=” 姓名 ”></el-table-column>
  236. <el-table-column prop=”gender” label=” 性别 ”></el-table-column>
  237. <el-table-column prop=”birth” label=” 生日 ”></el-table-column>
  238. <el-table-column label=” 操作 ” width=”100″ align=”center”>
  239. <template v-slot=”scope”>
  240. <el-button type=”text” size=”mini” @click=”toDelHandler(scope.row)
  241. <el-button type=”text” size=”mini” @click=”toEditHandler(scope.row
  242. </template>
  243. </el-table-column>
  244. </el-table>
  245. <!– 表格 –>
  246. <!– 模态框 –>
  247. <el-dialog
  248. :title=”title”
  249. :visible.sync=”visible”
  250. width=”50%”>
  251. <el-form :model=”form” size=”small” label-width=”80px”>
  252. <el-form-item label=” 姓名 ”>
  253. <el-input v-model=”form.name”></el-input>
  254. </el-form-item>
  255. <el-form-item label=” 性别 ”>
  256. <el-radio-group v-model=”form.gender”>
  257. <el-radio label=” 男 ” value=” 男 ”></el-radio>
  258. <el-radio label=” 女 ” value=” 女 ”></el-radio>
  259. </el-radio-group>
  260. </el-form-item>
  261. <el-form-item label=” 出生日期 ”>
  262. <el-date-picker
  263. v-model=”form.birth”
  264. value-format=’timestamp’
  265. type=”date”
  266. placeholder=” 抉择日期 ”>
  267. </el-form-item>
  268. </el-form>
  269. <span slot=”footer” class=”dialog-footer”>
  270. <el-button @click=”visible = false” size=”small”> 取 消 </el-button>
  271. <el-button type=”primary” @click=”submitHandler” size=”small”> 确 定 <
  272. </span>
  273. </el-dialog>
  274. <!– –>
  275. </div>
  276. <script>
    65
  277. new Vue({
  278. el:”#app”,
  279. data:{
  280. name:” 学生治理 ”,
  281. students:[],
  282. visible:false,
  283. form:{},
  284. title:” 录入学生信息 ”
  285. },
  286. created(){
  287. this.loadStudents();
  288. },
  289. methods:{
  290. toDelHandler(row){
  291. this.$confirm(‘ 此操作将永恒删除该数据, 是否持续?’, ‘ 提醒 ’, {
  292. confirmButtonText: ‘ 确定 ’,
  293. cancelButtonText: ‘ 勾销 ’,
  294. type: ‘warning’
  295. }).then(() => {
  296. let url = “http://localhost:3000/student/deleteById”
  297. axios.delete(url,{
  298. params:{id:row.id}
  299. }).then((resp)=>{
  300. this.$message({type: ‘success’, message: resp.data.message})
  301. this.loadStudents();
  302. })
  303. })
  304. },
  305. toEditHandler(row){
  306. this.title = “ 批改学生信息 ”
  307. this.form = {…row}
  308. this.visible = true;
  309. },
  310. submitHandler(){
  311. let url = “http://localhost:3000/student/saveOrUpdate”;
  312. axios.post(url,this.form).then(resp => {
  313. this.$message({type:’success’,message:resp.data.message})
  314. this.loadStudents();
  315. this.visible = false;
  316. })
  317. },
  318. toSave(){
  319. this.title = “ 录入学生信息 ”
  320. this.form = {}
  321. this.visible = true;
  322. },
  323. loadStudents(){
  324. let url = “http://localhost:3000/student/findAll”
  325. axios.get(url).then(resp => {
  326. this.students = resp.data.data;
  327. })
  328. }
  329. }
  330. })
  331. </script>
  332. </body>
  333. </html>
    day06
  334. 工作安顿
    1.1. egg ⼊⻔
    1.2. egg 路由
    1.3. egg 控制器
    1.1. egg 服务
    1.2. egg 中间件
  335. egg ⼊⻔
    2.1. 初始化
    2.2. ⽬录构造
    2.3. 内置对象
    这⾥介绍⼀下框架中内置的⼀些根底对象,包含从 Koa 继承⽽来的 4 个对象
    (Application, Context, Request, Response) 以及框架扩大的⼀些对象(Controller, Service,
    Helper, Config, Logger)
     Application
    全局应⽤对象,在⼀个应⽤中,只会实例化⼀个,它继承⾃ Koa.Application,在它上⾯我
    们能够挂载⼀些全局的⽅法和对象。
    $ mkdir app && cd app
    $ npm init egg –type=simple
    $ cnpm install
    $ npm start

    $ npm stop
    /*
    能够通过 ctx.app 拜访到 Application 对象
    在继承于 Controller, Service 基类的实例中,能够通过 this.app 拜访到 Applicatio
    */
    // app/controller/user.js
    class UserController extends Controller {
    async fetch() {
    1
    2
    3
    4
    5
    6
    7
    1
    2
    3
    4
    5
    6
    7
     Context
    Context 是⼀个申请级别的对象,继承⾃ Koa.Context。在每⼀次收到⽤户申请时,框架
    会实例化⼀个 Context 对象,这个对象封装了这次⽤户申请的信息,并提供了许多便捷的⽅法
    来获取申请参数或者设置响应信息。框架会将所有的 Service 挂载到 Context 实例上,⼀些插
    件也会将⼀些其余的⽅法和对象挂载到它上⾯
     Request
    Request 是⼀个申请级别的对象,继承⾃ Koa.Request。封装了 Node.js 原⽣的 HTTP
    Request 对象,提供了⼀系列辅助⽅法获取 HTTP 申请常⽤参数
     Response
    Response 是⼀个申请级别的对象,继承⾃ Koa.Response。封装了 Node.js 原⽣的
    HTTP Response 对象,提供了⼀系列辅助⽅法设置 HTTP 响应。
     Controller
    框架提供了⼀个 Controller 基类,并举荐所有的 Controller 都继承于该基类实现。这个
    Controller 基类有下列属性:
     ctx – 以后申请的 Context 实例。
     app – 应⽤的 Application 实例。
     config – 应⽤的配置。
     service – 应⽤所有的 service。
     logger – 为以后 controller 封装的 logger 对象。
     Service
    框架提供了⼀个 Service 基类,并举荐所有的 Service 都继承于该基类实现。
     Helper
    Helper ⽤来提供⼀些实⽤的 utility 函数。它的作⽤在于咱们能够将⼀些常⽤的动作抽离在
    helper.js ⾥⾯成为⼀个独⽴的函数,这样能够⽤ JavaScript 来写简单的逻辑,防止逻辑扩散各
    处,同时能够更好的编写测试⽤例。Helper ⾃身是⼀个类,有和 Controller 基类⼀样的属性,
    它也会在每次申请时进⾏实例化,因而 Helper 上的所有函数也能获取到以后申请相干的高低⽂
    信息。
    this.ctx.body = this.ctx.app.cache.get(this.ctx.query.id);
    }
    }
    // app/controller/user.js
    class UserController extends Controller {
    async fetch() {
    this.ctx.body = “”;
    }
    }
    8
    9
    10
    1
    2
    3
    4
    5
    6
    // 能够在 Context 的实例上获取到以后申请的 Request(ctx.request) 和 Response(ct
    x.response) 实例。
    1
  336. // app/controller/user.js
  337. class UserController extends Controller {
  338. async fetch() {
  339. const {app, ctx} = this;
  340. const id = ctx.request.query.id;
  341. ctx.response.body = app.cache.get(id);
  342. }
  343. }
     Config
    咱们举荐应⽤开发遵循配置和代码拆散的准则,将⼀些须要硬编码的业务配置都放到配置⽂
    件中,同时配置⽂件⽀持各个不同的运⾏环境使⽤不同的配置,使⽤起来也⾮常⽅便,所有框
    架、插件和应⽤级别的配置都能够通过 Config 对象获取到。咱们能够通过 app.config 从
    Application 实例上获取到 config 对象,也能够在 Controller, Service, Helper 的实例上通过
    this.config 获取到 config 对象。
     Logger
    框架内置了性能强⼤的⽇志性能,能够⾮常⽅便的打印各种级别的⽇志到对应的⽇志⽂件
    中,每⼀个 logger 对象都提供了 4 个级别的⽅法:
     logger.debug()
     logger.info()
     logger.warn()
     logger.error()
  344. egg 路由
    Router 次要⽤来形容申请 URL 和具体承当执⾏动作的 Controller 的对应关系,框架约定
    了 app/router.js ⽂件⽤于统⼀所有路由规定。通过统⼀的配置,咱们能够防止路由规定逻
    辑散落在多个地⽅,从⽽呈现未知的抵触,集中在⼀起咱们能够更⽅便的来查看全局的路由规
  345. egg 控制器
    // app/controller/user.js
    class UserController extends Controller {
    async fetch() {
    const {app, ctx} = this;
    const id = ctx.query.id;
    const user = app.cache.get(id);
    ctx.body = ctx.helper.formatUser(user);
    }
    }
    // app/extend/helper.js
    module.exports = {
    formatUser(user) {
    return only(user, [ ‘name’, ‘phone’]);
    }
    };
    ‘use strict’;
    module.exports = app => {
    const {router, controller} = app;
    router.get(‘/’, controller.home.index);
    router.get(‘/student/findById’, controller.student.findById);
    router.post(‘/student/saveOrUpdate’, controller.student.saveOrUpdate);
    router.delete(‘/student/deleteById’, controller.student.deleteById);
    router.post(‘/student/pageQuery’, controller.student.pageQuery);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Controller 负责解析⽤户的输⼊,解决后返回相应的后果。框架举荐 Controller 层次要对
    ⽤户的申请参数进⾏解决(校验、转换),而后调⽤对应的 service ⽅法解决业务,失去业务结
    果后封装并返回。
    4.1. 获取申请参数
     query、queries
  346. const query = this.ctx.query;
  347. const queries = this.ctx.queries
     routerParams
  348. const params = this.ctx.params;
     body
  349. const data = this.ctx.request.body;
     header
    ctx.headers,ctx.header,ctx.request.headers,ctx.request.header:这⼏个⽅法是等价
    的,都是获取整个 header 对象。ctx.get(name) ctx.request.get(name):获取申请 header 中
    的⼀个字段的值,如果这个字段不存在,会返回空字符串。咱们倡议⽤ ctx.get(name) ⽽不是
    ctx.headers[‘name’],因为前者会⾃动解决⼤⼩写。
  350. ctx.host
  351. ctx.protocol
  352. ctx.ips
  353. ctx.ip
     cookie
  354. let count = ctx.cookies.get(‘count’);
  355. count = count ? Number(count) : 0;
  356. ctx.cookies.set(‘count’, ++count);
     session
    通过 Cookie,咱们能够给每⼀个⽤户设置⼀个 Session,⽤来存储⽤户身份相干的信息,
    这份信息会加密后存储在 Cookie 中,实现跨申请的⽤户身份放弃。
  357. const ctx = this.ctx;
  358. // 获取 Session 上的内容
  359. const userId = ctx.session.userId;
  360. const posts = await ctx.service.post.fetch(userId);
    4.2. 调⽤ Service
    咱们并不想在 Controller 中实现太多业务逻辑,所以提供了⼀个 Service 层进⾏业务逻辑
    的封装,这不仅能提⾼代码的复⽤性,同时能够让咱们的业务逻辑更好测试。在 Controller 中
    能够调⽤任何⼀个 Service 上的任何⽅法,同时 Service 是懒加载的,只有当拜访到它的时候
    框架才会去实例化它。
  361. const res = await ctx.service.post.create(req);
    4.3. 发送 HTTP 响应
     status
  362. this.ctx.status = 201;
     body
    绝⼤少数的数据都是通过 body 发送给申请⽅的,和申请中的 body ⼀样,在响应中发送
    的 body,也须要有配套的 Content-Type 告知客户端如何对数据进⾏解析。
     作为⼀个 RESTful 的 API 接⼝ controller,咱们通常会返回 Content-Type 为
    application/json 格局的 body,内容是⼀个 JSON 字符串。
     作为⼀个 html ⻚⾯的 controller,咱们通常会返回 Content-Type 为 text/html 格局
    的 body,内容是 html 代码段。
  363. this.ctx.body = {
  364. name: ‘egg’,
  365. category: ‘framework’,
  366. language: ‘Node.js’,
  367. };
    6
  368. this.ctx.body = ‘<html><h1>Hello</h1></html>’;
     重定向
  369. ctx.redirect(url)
  370. egg 服务
    简略来说,Service 就是在简单业务场景下⽤于做业务逻辑封装的⼀个形象层,提供这个抽
    象有以下⼏个益处:
     放弃 Controller 中的逻辑更加简洁。
     放弃业务逻辑的独⽴性,形象进去的 Service 能够被多个 Controller 反复调⽤。
     将逻辑和展示拆散,更容易编写测试⽤例,
  371. const {Service} = require(‘egg’)
    2
  372. class StudentService extends Service {
  373. async findById(id){
  374. let sql = “select * from tbl_student where id = ?”
  375. let student =await this.app.mysql.query(sql,[id]);
  376. return student;
  377. }
    9
  378. // student {id,name,gender,birth}
  379. async saveOrUpdate(student){
  380. let result = null;
  381. if(student.id){
  382. result = await this.app.mysql.update(‘tbl_student’,student)
  383. } else {
  384. result = await this.app.mysql.insert(‘tbl_student’,student)
  385. }
  386. return result;
  387. }
    20
  388. async deleteById(id){
  389. let result = await this.app.mysql.delete(‘tbl_student’,{id})
  390. }
  391. }
  392. module.exports = StudentService;
  393. egg-mysql
    6.1. 装置
  394. $ cnpm install –save egg-mysql
    6.2. 配置
  395. // config/plugin.js
  396. ‘use strict’;
    3
  397. /* @type Egg.EggPlugin /
  398. module.exports = {
  399. mysql :{
  400. enable: true,
  401. package: ‘egg-mysql’,
  402. }
  403. };
    11
  404. // config.default.js
  405. //…
  406. const userConfig = {
  407. // myAppName: ‘egg’,
  408. mysql : {
  409. // 单数据库信息配置
  410. client: {
  411. // host
  412. host : ‘121.199.29.84’,
  413. user : ‘briup’,
  414. password : ‘briup’,
  415. database : ‘briup-sc’,
  416. port: ‘3306’,
  417. },
  418. // 是否加载到 app 上,默认开启
  419. app: true,
  420. // 是否加载到 agent 上,默认敞开
  421. agent: false,
  422. }
  423. };
  424. //…
    6.3. 根本使⽤
  425. // service/student.js
  426. const {Service} = require(‘egg’)
  427. class StudentService extends Service {
  428. async findById(id){
  429. let student = await this.app.mysql.query(‘select * from tbl_student wh
    ere id = ?’,[id])
  430. //let student = await this.app.mysql.get(‘tbl_student’,{id})
  431. return student;
  432. }
  433. }
    10
  434. module.exports = StudentService;
    6.4. 快捷⽅法
     insert(table,{})
    table 为表名,{} 为插⼊的字段与值的映射对象
  435. mysql.insert(‘tbl_student’,{name:”terry”,gender:” 男 ”})
    2
  436. // insert into tbl_student (name,gender) values(“terry”,” 男 ”)
     get(table,{})
    table 为表名,{}为 where 条件
  437. mysql.get(‘tbl_student’,{id:1})
    2
  438. // select * from tbl_student where id = 1;
     select(table,{})
    table 为表名,{} 为对象,对象中蕴含了 wher、columns、orders、limit、offset 等属性
  439. mysql.select(‘tbl_student’,{
  440. where:{gender:” 男 ”},
  441. columns: [‘name’, ‘gender’], // 要查问的表字段
  442. orders: [[‘id’,’desc’], [‘name’,’desc’]], // 排序形式
  443. limit: 10, // 返回数据量
  444. offset: 0,
  445. })
    8
  446. /*
  447. select name,gender
  448. from tbl_student
  449. where gender=” 男 ”
  450. order by id desc, name desc
  451. limit 0,10;
  452. */
     update(table,{}[,options])
    table 为表名,{}为更新后的值,默认依据 id 来更新
  453. mysql.update(tbl_student,{id:1,name:”terry”,gender:’ 女 ’})
  454. mysql.update(tbl_student,{id:1,name:”terry”,gender:’ 女 ’},{
  455. where:{id:1}
  456. })
  457. /*
  458. update tbl_student
  459. set name = ‘terry’,gender = ‘ 女 ’
  460. where id = 1;
  461. */
     delete(table,{})
    table 为表名,{}为删除条件
  462. mysql.delete(tbl_student, {id:1})
    2
  463. //delete from tbl_student where id = 1;
    day07
  464. 工作安顿
    1.1. 中间件
    1.2. jwt
  465. 中间件
    2.1. 中间件
    Egg 是基于 Koa 实现的,所以 Egg 的中间件模式和 Koa 的中间件模式是⼀样的,都是基
    于洋葱圈模型。每次咱们编写⼀个中间件,就相当于在洋葱外⾯包了⼀层。
  466. module.exports = options => {
  467. return async function gzip(ctx, next) {
  468. await next();
  469. //—-
  470. }
  471. }
    2.2. 统⼀异样解决
    通过同步⽅式编写异步代码带来的另外⼀个⾮常⼤的益处就是异样解决⾮常⾃然,使⽤
    try catch 就能够将依照标准编写的代码中的所有谬误都捕捉到。这样咱们能够很便捷的编
    写⼀个⾃定义的错误处理中间件。
  472. // error_handler.js
    2
  473. let Message = require(‘../utils/Message’);
  474. module.exports = () => {
  475. return async function errorHandler(ctx, next) {
  476. try {
  477. await next();
  478. } catch (err) {
  479. // 所有的异样都在 app 上触发一个 error 事件,框架会记录一条谬误日志
  480. ctx.app.emit(‘error’, err, ctx);
    11
  481. const status = err.status || 500;
  482. // 生产环境时 500 谬误的具体谬误内容不返回给客户端,因为可能蕴含敏感信息
  483. const error = status === 500 ? ‘Internal Server Error’ : err.message;
    15
  484. // 从 error 对象上读出各个属性,设置到响应中
  485. ctx.body = Message.error(error);
  486. if (status === 422) {
  487. ctx.body.data = err.errors;
  488. }
  489. ctx.status = status;
  490. }
  491. };
  492. };
    25
  493. // config.default.js
  494. config.middleware = [‘errorHandler’];
  495. jwt
    https://www.jianshu.com/p/576…
    使⽤ jwt 进⾏⽤户身份认证⾮常⽅便。其语法格局如下:
    jwt.sign(payload, secretOrPrivateKey, [options, callback])
    jwt.verify(token, secretOrPublicKey, [options, callback])
     payload
    为⼀个对象,前期能够依据 token 解析出这个对象的信息
     secretOrPrivateKey
    秘钥
     options
    algorithms:HS256
    exp : Math.floor(Date.now() / 1000) + (60 * 60),
  496. $ cnpm install egg-jwt –save
    2
  497. // plugins.js
  498. jwt : {
  499. enable: true,
  500. package: ‘egg-jwt’,
  501. },
  502. // config.default.js
  503. jwt:{
  504. secret:”888888″
  505. },
    controller 中解决登录
  506. const {Controller} = require(‘egg’);
  507. const Message = require(‘../utils/Message’)
  508. class UserController extends Controller{
    4
  509. // 验证 token,申请时在 header 配置 Authorization=Bearer ${token}
  510. async login() {
  511. const {ctx, app} = this;
  512. const data = ctx.request.body;
  513. // 验证用户信息代码 ….
  514. const token = app.jwt.sign({username: data.username,}, app.config.jwt.
  515. expiresIn: ’60s’,
  516. });
    13
  517. ctx.body = Message.success({token}) ;
  518. }
  519. }
    17
  520. module.exports = UserController;
    参考
    http://javascript.ruanyifeng….
    https://nodejs.org/dist/lates…
退出移动版