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); // 5console.log(example.addX(1)); // 6
- 所有代码都运⾏在模块作⽤域,不会净化全局作⽤域。
- 模块能够屡次加载,然而只会在第⼀次加载时运⾏⼀次,而后运⾏后果就被缓存了,当前再加载,就间接读取缓存后果。要想让模块再次运⾏,必须革除缓存。
- 模块加载的程序,依照其在代码中呈现的程序。
- 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?query=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中的hostconst 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中的originconst 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. 根本流程
- 每个Node.js过程只有⼀个主线程在执⾏程序代码,造成⼀个 执行栈 (execution contextstack);
- 主线程之外,还保护⼀个 事件队列 (Event queue),当⽤户的 网络申请或者其它的异步操作到来时,会先进⼊到事件队列中排队,并不会⽴即执⾏它,代码也不会被阻塞,持续往下⾛,直到主线程代码执⾏结束;
- 主线程代码执⾏结束实现后,而后通过 事件循环机制 (Event Loop),查看队列中是否有要解决的事件,从队头取出第⼀个事件,从 线程池 调配⼀个线程来解决这个事件,而后是第⼆个,第三个,直到队列中所有事件都执⾏完了。当有事件执⾏结束后,会告诉主线程,主线程执⾏回调,并将线程归还给线程池。这个过程就叫 事件循环 (Event Loop);
- 一直反复上⾯的第三步;
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], "eve7 }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;34 });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');23 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
获取状态信息
- 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。 - http缓存机制
缓存的品种有很多,其⼤致可归为两类:公有与共享缓存。共享缓存存储的响应可能被多个
⽤户使⽤。公有缓存只能⽤于独自⽤户。本⽂将次要介绍浏览器与代理缓存,除此之外还有⽹关
缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存⽅式,为站点和 web 应⽤
提供更好的稳定性、性能和扩展性。
HTTP/1.1定义的 Cache-Control 头⽤来辨别对缓存机制的⽀持状况, 申请头和响应头
都⽀持这个属性。通过它提供的不同的值来定义缓存策略。 没有缓存
- Cache-Control: no-store
缓存但须要从新验证:每次有申请收回时,缓存会将此申请发到服务器,服务器端会验证申请
中所形容的缓存是否过期,若未过期(注:理论就是返回304),则缓存才应用本地缓存正本。
- Cache-Control: no-cache
公共缓存,示意该响应能够被任何中间件(比方两头代理、CDN等)缓存
- Cache-Control: public
公有缓存,示意该响应只能被浏览器缓存
- 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)和⼀个空的响应体。 示意资源可能被缓存(放弃陈腐)的最大工夫。
- Cache-Control: max-age=31536000
4.1. http1.0、http1.1、http2.0
在HTTP1.0中默认是短连贯:每次与服务器交互,都须要新开⼀个连贯!
在HTTP1.1中默认就使⽤长久化连贯来解决:建⽴⼀次连贯,屡次申请均由这个连贯完
成!(如果阻塞了,还是会开新的TCP连贯的)
HTTP2与HTTP1.1最重要的区别就是解决了线头阻塞的问题!其中最重要的改变是:多
路复⽤ (Multiplexing) - 三次握⼿
ACK (Acknowledge character)确认字符,示意接管到的字符⽆谬误
SYN(Synchronize Sequence Numbers)同步序列编号
FIN(Finish)完结编号
MSL( Maximum Segment Lifetime) 示意“最⻓报⽂段寿命”,它是任何报⽂在⽹络上存
在的最⻓工夫,超过这个工夫报⽂将被抛弃。
进⾏三次握⼿的次要作⽤就是为了确认双⽅的接管能⼒和发送能⼒是否失常、指定⾃⼰的初
始化序列号为后⾯的可靠性传送做筹备。 - 四次挥⼿
TCP 的连贯的拆除须要发送四个包,因而称为四次挥⼿(Four-way handshake),客户端或
服务器均可被动发动挥⼿动作。
为什么要挥⼿四次?当服务端收到客户端的SYN连贯申请报⽂后,能够间接发送SYN+ACK
报⽂。其中ACK报⽂是⽤来应答的,SYN报⽂是⽤来同步的。然而敞开连贯时,当服务端收到
FIN报⽂时,很可能并不会⽴即敞开SOCKET,所以只能先回复⼀个ACK报⽂,通知客户
端,“你发的FIN报⽂我收到了”。只有等到我服务端所有的报⽂都发送完了,我能力发送FIN报
⽂,因而不能⼀起发送。故须要四次挥⼿。
day04 - 工作安顿
1.1. express
1.2. mysql DML
1.3. 数据建模 - express
express是基于 Node.js 平台,疾速、凋谢、极简的 Web 开发框架。
2.1. helloworld
⼿动创立⼯程 - $ mkdir myapp
- $ cd myapp
- $ npm init
- $ cnpm install express --save
核⼼代码 - const express = require('express')
- const app = express()
- const port = 3000
4 - app.use(express.static('public'))
- app.get('/', (req, res) => {
- res.send('Hello World!')
- })
- app.listen(port, () => {
- console.log(
Example app listening at http://localhost:${port}
) - })
路由定义
定义了针对客户端申请的处理函数,不同uri对应不同的处理函数。通过Express的实例对象
app上的申请⽅法定义不同的路由,例如:get、post、delete、put、all... - var express = require('express');
- var router = express.Router();
3 - / GET users listing. /
- router.get('/', function(req, res, next) {
- res.send('respond with a resource');
- });
8 - module.exports = router;
- var usersRouter = require('./routes/users');
- app.use('/users', usersRouter);
路由参数
response ⽅法
中间件
body-parser
cookie-parser
cors
serve-static
2.2. 脚⼿架
通过应⽤⽣成器⼯具 express-generator 能够疾速创立⼀个应⽤的⻣架。 - 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. - $ mkdir cms
- $ cd cms
- $ npx express-generator
3.1. 数据库定义 - mysql> CREATE DATABASE poll;
- mysql> USE poll
- Database changed
3.2. 表定义 - create table <表名>(
- <列名><数据类型>[列级残缺约束条件]
- [, <列名><数据类型>[列级残缺约束条件]]
- ...
- [,<表级完整性约束条件>]
- );
7 - DROP TABLE IF EXISTS
tbl_exam_paper
; - CREATE TABLE
tbl_exam_paper
( id
bigint(20) NOT NULL AUTO_INCREMENT,description
varchar(255) DEFAULT NULL,title
varchar(255) DEFAULT NULL,department_id
bigint(20) DEFAULT NULL,user_id
bigint(20) DEFAULT NULL,- PRIMARY KEY (
id
), - UNIQUE KEY
id
(id
), - KEY
FK92534C8DF2C32590
(department_id
), - CONSTRAINT
FK92534C8DF2C32590
FOREIGN KEY (department_id
) REFERENCES - )
3.3. 表批改 增加列
- mysql> alter table tableName add columnName dataType
删除列
- mysql> alter table tableName drop columnName dataType
批改列
- mysql> alter table tableName modify columnName dataType
删除数据库表
- mysql> drop table tableName
清空数据库表内容
- mysql> truncate table s_item
- sql-dml
⽤于拜访和解决数据库的规范的计算机语⾔。
4.1. 查问语句
简略查问 - mysql> select [all|distinct] <指标列表达式>
- -> from <表或视图>[, <表或视图>
- -> [where <条件表达式>]
- -> [group by <列名1>
- -> [having<条件表达式>]]
- -> [order by <列名2>[asc|desc]];
- select name
- from tbl_student
- where gender = 'male';
连贯查问 - select s.name,c.name
- from tbl_student s, tbl_student_course sc ,tbl_course c
- where s.id = sc.student_id
- and c.id = sc.course_id;
4.2. 插⼊语句 - insert into tbl_student(id,name,gender)
- values (null,'terry','gender')
4.3. 批改语句 - update tbl_student
- set name = '王五'
- where id = 1;
4.4. 删除语句 - delete from tbl_user
- where id = 1;
- 数据库建模
关系型数据库中常⻅的关系有⼀对⼀、⼀对多、多对多;其中⼀对⼀是⼀对多的⼀种特例;
多对多能够拆分成两个⼀对多。所以⼀对多⾄关重要。
⼀对多
⼀对多关系是项⽬开发过程中最常⻅的关联关系。例如,⼀个⽼师能够在任教多⻔课程。在
⼀对多关系中,外键通常保护在多的⼀⽅。
⼀对⼀
⼀对⼀关系是⼀对多关系的⼀种特例(为外键增加唯⼀束缚)。例如,⼀个⼈只能对应⼀个
身份证。在⼀对⼀关系中,外键能够保护在任意⼀⽅
多对多
多对多关系是也能够了解为是⼀对多关系的⼀种特例(两个⼀对多)。例如,学⽣和课程的
关系,⼀个学⽣能够选修多⻔课程,⼀个⻔课程也能够被多个学⽣选修。在多对多关系中,外键
保护在桥表中 - 数据类型
整数
⼩数
字符类型
⽇期类型 - mysql模块
mysql模块提供了nodejs操作mysql数据库的api
https://www.npmjs.com/package...
day05 - 工作安顿
1.1. mysql模块
1.2. 学⽣选课零碎综合演练 - 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 - var connection = mysql.createConnection({
- host : '121.199.29.84',
- user : 'briup',
- password : 'briup',
- database : 'briup-sc'
- });
- connection.connect();
9 - connection.query('SELECT * FROM tbl_student', function (error, results, fiel
- if (error) throw error;
- console.log('The solution is: ', results);
- });
14 - connection.end();
2.3. 连接池
连接池技术能够创立多个连贯放到连接池中,咱们能够将连接池的代码进⾏封装,每次须要
连贯的的时候,通过连接池获取⼀个连贯对象即可,使⽤实现后将该连贯对象开释。 - var mysql = require('mysql');
- var pool = mysql.createPool({
- connectionLimit : 10,
- host : '121.199.29.84',
- user : 'briup',
- password : 'briup',
- database : 'briup-sc'
- });
- // 获取连贯
- pool.getConnection((err,connection)=>{
- if(err) throw err;
- // 查问
- connection.query(sql, function (error, results, fields) {
- if (error) throw error;
- // 开释
- connection.release();
- resp.send(Message.success(results));
- });
- })
- })
- 综合演练
截⽌⽬录,咱们曾经实现了nodejs根底语法、核⼼模块、http服务器编程、数据库编程的学
习,接下来通过这些技术实现后端服务的开发。
3.1. 数据建模
在学⽣选课业务中,⼀个学⽣能够选多⻔课程,⼀⻔课程能够被多个⼈来选,⼀个课程只能
由⼀个老师来负责。
3.2. 初始化⼯程 - $ mkdir sc-server
- $ cd sc-server
- $ npx express-generator
- $ cnpm install
- $ cnpm install mysql --save
- $ cnpm install cors --save
- $ npm start
通过上述操作,咱们会创立⼀个基于express的sc-server的⼯程,该⼯程中默认蕴含了
cookie-parser、debug、express、jade、morgan、body-parser(内置)、serve-static依赖,
咱们还须要⼿动装置cors和mysql,npm start命令会启动该⼯程,默认占据3000端⼝,须要注
意的是,如果后盾接⼝代码更新,请务必重启该⼯程。
3.3. Message封装
咱们冀望,后端提供的所有端⼝都具备统⼀的标准,例如: - {
- status:200,
- message:"",
- data:null,
- timestamp:1630492833755
- }
这样,⽅便前端统⼀解决。如下是封装代码。 - class Message {
- constructor(status,message,data){
- this.status = status;
- this.message = message;
- this.data = data;
- this.timestamp = new Date().getTime();
- }
8 - static success(param){
- if(typeof param == 'string'){
- return new Message(200,param,null)
- } else if (typeof param == 'object'){
- return new Message(200,'success',param)
- }
- }
16 - static error(message){
- return new Message(500,message,null);
- }
- }
21 - module.exports = Message;
3.4. 接⼝编写
接⼝开发的时候留神要分模块开发,即每个模块创立⼀个新的router,每个接⼝沿着 获取
参数-> 业务逻辑解决 -> 数据库操作 -> 后果返回来进⾏。如下是实例代码: - const express = require('express')
- const Message = require('../utils/Message')
- const pool = require('../utils/Connection')
4 - const router = express.Router();
- // 1. 查问
- router.get('/findAll',(req,resp)=>{
- let sql = "select * from tbl_student"
- // 获取连贯
- pool.getConnection((err,connection)=>{
- if(err) throw err;
- // 查问
- connection.query(sql, function (error, results, fields) {
- if (error) throw error;
- // 开释
- connection.release();
- resp.send(Message.success(results));
- });
- })
- })
- // 2. 删除
- router.delete('/deleteById',(req,resp)=>{
- let id = req.query.id;
- let sql = "delete from tbl_student where id = "+ id;
- pool.getConnection((err,connection)=>{
- if(err) throw err;
- connection.query(sql,(error,results)=>{
- if(error) throw error;
- connection.release();
- resp.send(Message.success('删除胜利'))
- })
- })
- })
- // 3. 保留或更新
- router.post('/saveOrUpdate',(req,resp)=>{
- let stu = req.body;
37 - let sql = "insert into tbl_student(id,name,gender,birth) values(null,'"+st
- if(stu.id){
- sql = "update tbl_student set name = '"+stu.name+"',gender='"+stu.gender
- }
42 - pool.getConnection((err,connection)=>{
- if(err) throw err;
- connection.query(sql,(error,results)=>{
- if(error) throw error;
- connection.release();
- resp.send(Message.success('操作胜利!'))
- })
- })
- })
3.5. 前端开发
前端开发仍旧使⽤vue + axios + elementui来进⾏ - <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>学生选课</title>
- <!-- axios -->
- <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js">
- <!-- vue -->
- <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></sc
- <!-- elementui -->
- <link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.3/theme-chal
- <script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.3/index.min
- </head>
- <body>
- <div id="app">
- <h2> {{name}}</h2>
- <el-button @click="loadStudents" size="small" type="primary">刷新</el-bu
- <el-button @click="toSave" size="small" type="primary">录入</el-button>
- <!-- 表格 -->
- <el-table :data="students" size="small">
- <el-table-column prop="name" label="姓名"></el-table-column>
- <el-table-column prop="gender" label="性别"></el-table-column>
- <el-table-column prop="birth" label="生日"></el-table-column>
- <el-table-column label="操作" width="100" align="center">
- <template v-slot="scope">
- <el-button type="text" size="mini" @click="toDelHandler(scope.row)
- <el-button type="text" size="mini" @click="toEditHandler(scope.row
- </template>
- </el-table-column>
- </el-table>
- <!-- 表格 -->
- <!-- 模态框 -->
- <el-dialog
- :title="title"
- :visible.sync="visible"
- width="50%">
- <el-form :model="form" size="small" label-width="80px">
- <el-form-item label="姓名">
- <el-input v-model="form.name"></el-input>
- </el-form-item>
- <el-form-item label="性别">
- <el-radio-group v-model="form.gender">
- <el-radio label="男" value="男"></el-radio>
- <el-radio label="女" value="女"></el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="出生日期">
- <el-date-picker
- v-model="form.birth"
- value-format='timestamp'
- type="date"
- placeholder="抉择日期">
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button @click="visible = false" size="small">取 消</el-button>
- <el-button type="primary" @click="submitHandler" size="small">确 定<
- </span>
- </el-dialog>
- <!-- -->
- </div>
- <script>
65 - new Vue({
- el:"#app",
- data:{
- name:"学生治理",
- students:[],
- visible:false,
- form:{},
- title:"录入学生信息"
- },
- created(){
- this.loadStudents();
- },
- methods:{
- toDelHandler(row){
- this.$confirm('此操作将永恒删除该数据, 是否持续?', '提醒', {
- confirmButtonText: '确定',
- cancelButtonText: '勾销',
- type: 'warning'
- }).then(() => {
- let url = "http://localhost:3000/student/deleteById"
- axios.delete(url,{
- params:{id:row.id}
- }).then((resp)=>{
- this.$message({ type: 'success', message: resp.data.message })
- this.loadStudents();
- })
- })
- },
- toEditHandler(row){
- this.title = "批改学生信息"
- this.form = {...row}
- this.visible = true;
- },
- submitHandler(){
- let url = "http://localhost:3000/student/saveOrUpdate";
- axios.post(url,this.form).then(resp => {
- this.$message({type:'success',message:resp.data.message})
- this.loadStudents();
- this.visible = false;
- })
- },
- toSave(){
- this.title = "录入学生信息"
- this.form = {}
- this.visible = true;
- },
- loadStudents(){
- let url = "http://localhost:3000/student/findAll"
- axios.get(url).then(resp => {
- this.students = resp.data.data;
- })
- }
- }
- })
- </script>
- </body>
- </html>
day06 - 工作安顿
1.1. egg⼊⻔
1.2. egg路由
1.3. egg控制器
1.1. egg服务
1.2. egg中间件 - 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 - // app/controller/user.js
- class UserController extends Controller {
- async fetch() {
- const { app, ctx } = this;
- const id = ctx.request.query.id;
- ctx.response.body = app.cache.get(id);
- }
- }
Config
咱们举荐应⽤开发遵循配置和代码拆散的准则,将⼀些须要硬编码的业务配置都放到配置⽂
件中,同时配置⽂件⽀持各个不同的运⾏环境使⽤不同的配置,使⽤起来也⾮常⽅便,所有框
架、插件和应⽤级别的配置都能够通过 Config 对象获取到。咱们能够通过 app.config 从
Application 实例上获取到 config 对象,也能够在 Controller, Service, Helper 的实例上通过
this.config 获取到 config 对象。
Logger
框架内置了性能强⼤的⽇志性能,能够⾮常⽅便的打印各种级别的⽇志到对应的⽇志⽂件
中,每⼀个 logger 对象都提供了 4 个级别的⽅法:
logger.debug()
logger.info()
logger.warn()
logger.error() - egg路由
Router 次要⽤来形容申请 URL 和具体承当执⾏动作的 Controller 的对应关系, 框架约定
了 app/router.js ⽂件⽤于统⼀所有路由规定。通过统⼀的配置,咱们能够防止路由规定逻
辑散落在多个地⽅,从⽽呈现未知的抵触,集中在⼀起咱们能够更⽅便的来查看全局的路由规
则 - 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 - const query = this.ctx.query;
- const queries = this.ctx.queries
routerParams - const params = this.ctx.params;
body - 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'],因为前者会⾃动解决⼤⼩写。 - ctx.host
- ctx.protocol
- ctx.ips
- ctx.ip
cookie - let count = ctx.cookies.get('count');
- count = count ? Number(count) : 0;
- ctx.cookies.set('count', ++count);
session
通过 Cookie,咱们能够给每⼀个⽤户设置⼀个 Session,⽤来存储⽤户身份相干的信息,
这份信息会加密后存储在 Cookie 中,实现跨申请的⽤户身份放弃。 - const ctx = this.ctx;
- // 获取 Session 上的内容
- const userId = ctx.session.userId;
- const posts = await ctx.service.post.fetch(userId);
4.2. 调⽤Service
咱们并不想在 Controller 中实现太多业务逻辑,所以提供了⼀个 Service 层进⾏业务逻辑
的封装,这不仅能提⾼代码的复⽤性,同时能够让咱们的业务逻辑更好测试。在 Controller 中
能够调⽤任何⼀个 Service 上的任何⽅法,同时 Service 是懒加载的,只有当拜访到它的时候
框架才会去实例化它。 - const res = await ctx.service.post.create(req);
4.3. 发送HTTP响应
status - 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 代码段。 - this.ctx.body = {
- name: 'egg',
- category: 'framework',
- language: 'Node.js',
- };
6 - this.ctx.body = '<html><h1>Hello</h1></html>';
重定向 - ctx.redirect(url)
- egg服务
简略来说,Service 就是在简单业务场景下⽤于做业务逻辑封装的⼀个形象层,提供这个抽
象有以下⼏个益处:
放弃 Controller 中的逻辑更加简洁。
放弃业务逻辑的独⽴性,形象进去的 Service 能够被多个 Controller 反复调⽤。
将逻辑和展示拆散,更容易编写测试⽤例, - const {Service} = require('egg')
2 - class StudentService extends Service {
- async findById(id){
- let sql = "select * from tbl_student where id = ?"
- let student =await this.app.mysql.query(sql,[id]);
- return student;
- }
9 - // student {id,name,gender,birth}
- async saveOrUpdate(student){
- let result = null;
- if(student.id){
- result = await this.app.mysql.update('tbl_student',student)
- } else {
- result = await this.app.mysql.insert('tbl_student',student)
- }
- return result;
- }
20 - async deleteById(id){
- let result = await this.app.mysql.delete('tbl_student',{id})
- }
- }
- module.exports = StudentService;
- egg-mysql
6.1. 装置 - $ cnpm install --save egg-mysql
6.2. 配置 - // config/plugin.js
- 'use strict';
3 - /* @type Egg.EggPlugin /
- module.exports = {
- mysql :{
- enable: true,
- package: 'egg-mysql',
- }
- };
11 - // config.default.js
- //...
- const userConfig = {
- // myAppName: 'egg',
- mysql : {
- // 单数据库信息配置
- client: {
- // host
- host : '121.199.29.84',
- user : 'briup',
- password : 'briup',
- database : 'briup-sc',
- port: '3306',
- },
- // 是否加载到 app 上,默认开启
- app: true,
- // 是否加载到 agent 上,默认敞开
- agent: false,
- }
- };
- //...
6.3. 根本使⽤ - // service/student.js
- const {Service} = require('egg')
- class StudentService extends Service {
- async findById(id){
- let student = await this.app.mysql.query('select * from tbl_student wh
ere id = ?',[id]) - //let student = await this.app.mysql.get('tbl_student',{id})
- return student;
- }
- }
10 - module.exports = StudentService;
6.4. 快捷⽅法
insert(table,{})
table为表名,{} 为插⼊的字段与值的映射对象 - mysql.insert('tbl_student',{name:"terry",gender:"男"})
2 - // insert into tbl_student (name,gender) values("terry","男")
get(table,{})
table为表名,{}为where条件 - mysql.get('tbl_student',{id:1})
2 - // select * from tbl_student where id = 1;
select(table,{})
table为表名,{} 为对象,对象中蕴含了wher、columns、orders、limit、offset等属性 - mysql.select('tbl_student',{
- where:{gender:"男"},
- columns: ['name', 'gender'], // 要查问的表字段
- orders: [['id','desc'], ['name','desc']], // 排序形式
- limit: 10, // 返回数据量
- offset: 0,
- })
8 - /*
- select name,gender
- from tbl_student
- where gender="男"
- order by id desc, name desc
- limit 0,10;
- */
update(table,{}[,options])
table为表名,{}为更新后的值,默认依据id来更新 - mysql.update(tbl_student,{id:1,name:"terry",gender:'女'})
- mysql.update(tbl_student,{id:1,name:"terry",gender:'女'},{
- where:{id:1}
- })
- /*
- update tbl_student
- set name = 'terry',gender = '女'
- where id = 1;
- */
delete(table,{})
table为表名,{}为删除条件 - mysql.delete(tbl_student, {id:1})
2 - //delete from tbl_student where id = 1;
day07 - 工作安顿
1.1. 中间件
1.2. jwt - 中间件
2.1. 中间件
Egg 是基于 Koa 实现的,所以 Egg 的中间件模式和 Koa 的中间件模式是⼀样的,都是基
于洋葱圈模型。每次咱们编写⼀个中间件,就相当于在洋葱外⾯包了⼀层。 - module.exports = options => {
- return async function gzip(ctx, next) {
- await next();
- //----
- }
- }
2.2. 统⼀异样解决
通过同步⽅式编写异步代码带来的另外⼀个⾮常⼤的益处就是异样解决⾮常⾃然,使⽤
try catch 就能够将依照标准编写的代码中的所有谬误都捕捉到。这样咱们能够很便捷的编
写⼀个⾃定义的错误处理中间件。 - // error_handler.js
2 - let Message = require('../utils/Message');
- module.exports = () => {
- return async function errorHandler(ctx, next) {
- try {
- await next();
- } catch (err) {
- // 所有的异样都在 app 上触发一个 error 事件,框架会记录一条谬误日志
- ctx.app.emit('error', err, ctx);
11 - const status = err.status || 500;
- // 生产环境时 500 谬误的具体谬误内容不返回给客户端,因为可能蕴含敏感信息
- const error = status === 500 ? 'Internal Server Error' : err.message;
15 - // 从 error 对象上读出各个属性,设置到响应中
- ctx.body = Message.error(error);
- if (status === 422) {
- ctx.body.data = err.errors;
- }
- ctx.status = status;
- }
- };
- };
25 - // config.default.js
- config.middleware = ['errorHandler'];
- 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), - $ cnpm install egg-jwt --save
2 - // plugins.js
- jwt : {
- enable: true,
- package: 'egg-jwt',
- },
- // config.default.js
- jwt:{
- secret:"888888"
- },
controller中解决登录 - const {Controller} = require('egg');
- const Message = require('../utils/Message')
- class UserController extends Controller{
4 - // 验证token,申请时在header配置 Authorization=
Bearer ${token}
- async login() {
- const { ctx, app } = this;
- const data = ctx.request.body;
- // 验证用户信息代码....
- const token = app.jwt.sign({ username: data.username, }, app.config.jwt.
- expiresIn: '60s',
- });
13 - ctx.body = Message.success({token}) ;
- }
- }
17 - module.exports = UserController;
参考
http://javascript.ruanyifeng....
https://nodejs.org/dist/lates...