node.js

本文介绍了Node.js,它基于Chrome V8引擎,适用于I/O密集型应用。阐述了其事件循环机制、宏任务与微任务。还介绍了自定义、全局、系统和第三方模块。此外,讲解了Express、Koa、Sequalize等相关框架,以及Elasticsearch开发入门知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. node.js简介

node.js是一个基于Chrome V8引擎的javascript运行环境

  1. 优点:

    1. 异步非阻塞的I/O(I/O线程池),适合用于I/O密集型应用
    2. 事件驱动
    3. *单线程
    4. 跨平台
  2. 缺点:

    1. 回调地狱问题
    2. 单线程,处理不好CPU密集型任务

    名词解释:

    1. I/O操作:比如文件的读写,数据库的读写操作等
    2. 异步非阻塞的I/O:文件或数据库进行读写操作时,仍能进行其他操作
    3. 事件驱动:需要执行某个异步操作的同时发射一个事件并写好这个事件的处理函数,当异步操作完成后触发这个事件并执行相关的处理函数,内部的实现实际上是一个观察者模式

node服务端与java服务端的区别

  • java服务端:多线程并发,一个线程只处理一个请求,直到请求-响应结束为止,因此适用于CPU密集型操作
  • node服务端:单线程,使用回调的方式依次处理所有请求,因此适用于I/O密集型操作

2. 事件循环【node的事件轮询机制】

node端没有浏览器的管理模块用于处理异步操作,由libuv这个库提供事件循环和线程池

Node.js启动的时候会初始化由libuv提供的事件循环,每次的事件循环都包含6个阶段:

  1. 第一阶段:timer(定时器阶段------setTimeout,setInterval)
    1.1. 如果有定时器开始计时
    1.2. 执行满足执行条件的定时器的回调
  2. 第二阶段:pending callbacks(系统阶段)
  3. 第三阶段:idle /ˈaɪdl/ ,prepare(准备阶段):仅node内部使用
  4. 第四阶段:poll(轮询阶段)
    4.1 如果回调队列不为空,从回调队列中依次取出回调函数并执行,直到回调队列为空。
    4.2 如果回调队列为空
           4.2.1 如果有设置过setImmediate【setImmediate是立即执行函数】:进入下一个阶段check阶段。
           4.2.2 如果未设置setImmediate:在此阶段停留等待回调函数被插入到回调队列【这里会有个超时时间设置防止一直等待下去】。如果有需要执行定时器的回调函数了,则进入下一个阶段,为了下次循环到定时器阶段来执行定时器回调【可以理解为定时器回调不加入到回调队列中】。
  5. 第五阶段:check
    5.1 专门用于执行setImmediate所设置的回调函数
  6. 第六阶段:close callbacks(关闭回调)

宏任务与微任务

Node端事件循环中的异步队列也是这两种:macro(宏任务)队列和 micro(微任务)队列。
常见的macro-task:setTimeout、setInterval、script(整体代码)、 I/O操作、 setImmediate等。
常见的micro-task:new Promise().then(回调)等。

注意:

  1. 事件循环的六个阶段,每个阶段都有一个先入先出的(FIFO)的用于执行回调的队列,这六个基本队列都属于宏队列。事件循环运行到每个阶段,都会从对应的回调队列中取出回调函数去执行,直到队列当中的内容耗尽,或者执行的回调数量达到了最大。然后事件循环就会进入下一个阶段,然后又从下一个阶段对应的队列中取出回调函数执行,这样反复直到事件循环的最后一个阶段。
  2. node10及其之前版本:要等到宏队列当中的所有宏任务全部执行完毕才会去执行微队列当中的微任务
  3. node11版本以后:跟浏览器端运行一致。一旦要执行一个阶段里对应宏队列当中的一个宏任务时,先遍历微任务队列,执行完微队列当中的所有微任务再回到刚才的宏队列执行宏任务。
  4. process.nextTick()这个函数其实是独立于EventLoop之外的,它有一个自己的队列。process.nextTick在下一次循环中优先执行。
    ** 执行原理:Node每一次循环都是一个tick,每次tick,Chrome V8都会从事件队列当中取所有事件依次处理。遇到nextTick事件,将其加入事件队尾,等待下一次tick到来的时候首先执行。
console.log(1)
Promise.resolve().then(() => {
  console.log('promise one'))
})
process.nextTick(() => {
  console.log('nextTick one')
})

setTimeout(() => {
  process.nextTick(() => {
    console.log('nextTick two')
  })
  console.log(3)
  Promise.resolve().then(()=> {
    console.log('promise two')
  })
  console.log(4)
}, 3);

// 执行结果:
/**
 * 1
 * nextTick one
 * promise one
 * 3
 * 4
 * nextTick two
 * promise two
 */

3. node模块

自定义模块
全局模块:全局对象,无须引用就可以直接使用的对象
系统模块:内置模块,需要require,但不需要下载
第三方模块:需要下载,需要require

1. 自定义模块

1. CommonJS规范

前端模块化

2. node外层函数
  • node中任何一个模块(js文件)都被一个外层函数所包裹,该外层函数不可见,由node底层调用

    //1. 在js文件中输入console.log(arguments.callee.toString());语句
    //2. 控制台执行命令行【node  js文件名】控制台可输出该不可见的外层函数
    //形如:
    function(exports,require,module,_filename,_dirname){
    //js文件中的内容
    console.log(arguments.callee.toString());
    }
    

    function(exports,require,module,_filename,_dirname){//js文件中的内容}

    • 外层函数形参:
    1. exports:用于支持CommonJS模块化规范的暴露语法
    2. require:用于支持CommonJS模块化规范的引入语法
    3. module:用于支持CommonJS模块化规范的暴露语法
    4. _filename:当前运行js文件的绝对路径
    5. _dirname:当前运行js文件所在文件夹的绝对路径
  • 为什么要设计这个外层函数?
    1). 用于支持模块化语法
    2). 隐藏服务器内部实现

  • 深入理解模块

2. 全局模块

全局模块包括以下几种:

  1. 全局对象global
    global对象主要用来扩展变量和方法,类似于浏览器的window
  2. 为模块包装而提供的全局对象----模块外层函数的5个形参
    exports, require, module, _filename, _dirname
  3. 控制台Console模块

    console.log()
    console.info()
    console.table()
    console.time(label),console.timeEnd(label),相同的label就会启动一个定时器与停止一个定时器并计时。

  4. EventLoop相关API

    setTimeout()设置延时定时器
    clearTimeout()取消延时定时器
    setInterval()设置循环定时器
    clearInterval()取消循环定时器
    setImmediate()设置立即执行函数
    clearImmediate()取消立即执行函数
    queueMicrotask

  5. process对象
    在Node.js中每个应用程序都是一个进程类的实例对象。process对象代表应用程序,这是一个全局对象,可以通过它来获取Node.js应用程序以及运行该程序的用户、环境等各种信息的属性、方法和事件。
  6. Buffer缓冲器
Buffer缓冲器
1. Buffer是什么
  1. Buffer是一个和数组类似的对象,用于存储数据。Buffer是专门用来保存二进制数据的(和数组,对象不同是,数组和对象保存的是基本数据类型和引用数据类型)。
  2. Buffer的效率高,存储和读取很快,他是直接对计算机的内存进行操作
  3. Buffer的大小一旦确定了,不可修改
  4. 每个元素占用内存大小为1字节
  5. Buffer是node中非常核心的模块,无需下载,直接可以使用
2. Buffer的使用
  1. 创建Buffer

    1.创建一个指定size大小的Buffer实例-----被废弃(效率很低)
    方式一:性能最差----1.在堆里随便开辟空间(包括曾被别人用过后弃用,但尚未被垃圾处理机制回收的部分) 2. 清理空间
    let buf = new Buffer(size);
    方式二:性能稍好----在堆里从来没被用过的部分开辟一块空间
    let buf = Buffer.alloc(size);
    方式三:性能最好 ---- 在堆里随便开辟空间(包括曾被别人用过后弃用,但尚未被垃圾处理机制回收的部分) ,因此不安全
    let buf = Buffer.allocUnsafe(size);

    let buf = Buffer.allocUnsafe(size);
    buf.fill(0) //将可能出现的敏感数据用0全部填充
    // 相当于Buffer.alloc(size);

  2. 将数据存入一个Buffer实例
    let buf = Buffer.from(数据)
    数据(包括文件、图片等)都是以二进制存储,但是自动转化为十六进制输出,buf.toString()可以转化为字符串输出

3. 系统模块

  • fs:⽂件系统
  • path:路径系统,能够处理路径之间的问题
  • events
  • http: 设置⼀个 http 服务器,发送 http 请求,监听响应等等
  • net
  • child_process
  • vm: ⼀个专⻔处理沙箱的虚拟机模块,底层主要来调⽤ v8 相关 api 进⾏代码解析
  • v8
  • dns:处理 dns 相关内容,例如我们可以设置 dns 服务器等等
  • zlib压缩
  • crypto:加密相关模块,能够以标准的加密⽅式对我们的内容进⾏加解密
1. fs模块

node中的文件系统:
在Node中有一个文件系统,可以对计算机中的文件进行增删改查的一些列操作。Node给我们提供了一个内置的模块,叫做fs模块(文件系统),专门用于操作文件,fs模块是Node的核心内置模块,无需下载,直接引入。

const fs = require(‘fs’)

1. 简单文件写入
  • 异步文件写入
    fs.writeFile(file,data[,options],callback)方法

    参数介绍:

    1. file:要写入的文件路径+文件名.后缀
    2. data:要写入文件的数据
    3. options:可选参数,配置对象
      (1). encoding:设置文件的编码格式,默认值utf-8
      (2). mode:设置文件的操作权限。默认值0o666=0o222+0o444
      0o111:文件可执行权限,几乎不用
      0o222:文件可被写入的权限
      0o444:文件可被读取的权限
      (3). flag:打开文件要执行的操作,默认是‘w’.
      ‘a’:追加
      ‘w’:写
    4. callback:回调函数
      ($). err:回调函数的参数,错误对象
    //demo
    //引入fs模块
    let fs = require('fs')
    //调用writeFile()方法
    fs.writeFile('./test.txt','今天天气好晴朗',(err)=>{
    	if(err){
    	console.log('文件写入失败',err)
    	}else{
    	console.log('文件写入成功')
    	}
    })
    
2. 流式文件写入

fs.createWriteStream(path[,options])

参数描述:

  1. path:要写入文件的路径+文件名.后缀
  2. options:配置对象(可选参数)
    (1). flags:打开文件要执行的操作,默认是‘w’.
    ‘a’:追加
    ‘w’:写
    (2). encoding:设置文件的编码格式,默认值utf-8
    (3). fd:文件统一标识符。linux下文件标识符
    (4). mode:设置文件的操作权限。默认值0o666=0o222+0o444
    0o111:文件可执行权限,几乎不用
    0o222:文件可被写入的权限
    0o444:文件可被读取的权限
    (5). autoClose:自动关闭,关闭文件
    (6). emitClose:强制关闭,关闭文件
    (7). start:写入文件的起始位置
//使用步骤:
//0.引入fs模块
let fs = require('fs');
//1.创建一个可写流用于写入文件,该可写流处于打开状态
let ws = fs.createWriteStream('./demo.txt');
//监听流的状态
ws.on('open',()=>{
console.log('流打开了');
})
ws.on('close',()=>{
console.log('流关闭了了');
})
//2. 使用可写流写入数据
ws.write('今天天气好晴朗');
//3. 关闭流
//在node的8版本中使用以下方式关闭流会导致数据丢失
ws.close();
//在node的8版本中使用下面的方法关闭流
ws.end();
3. 简单文件读取
  • 异步简单读取文件
    fs.readFile(path[,options],callback)方法

    参数介绍:

    1. path:要读取的文件路径+文件名.后缀
    2. options:可选参数,配置对象
    3. callback:回调函数
      ($). err:回调函数的参数,错误对象
      ($). data:读取出来的数据

    简单文件写入和简单文件读取,都是一次性把所有要写入或者要读取的内容加载到内存中,容易造成内存泄漏。

    //demo
    //引入fs模块
    let fs = require('fs')
    //调用writeFile()方法
    fs.readFile('./test.txt',(err,data)=>{
    	if(err){
    	console.log('文件读取失败',err)
    	}else{
    	console.log(data)
    	}
    })
    
4. 流式文件读取

fs.createReadStream(path[,options])

参数描述:

  1. path:要读取文件的路径+文件名.后缀
  2. options:配置对象(可选参数)
    (1). flags
    (2). encoding
    (3). fd
    (4). mode
    (5). autoClose
    (6). emitClose
    (7). start:起始偏移量
    (8). end:结束偏移量
    (9). highWaterMark:控制每次读取数据的大小,默认64*1024 = 65536
//使用步骤:
//0.引入fs模块
let fs = require('fs');
//1.创建一个可读流用于写入文件
let rs = fs.createReadStream('./demo.txt');
//监听流的状态
rs.on('open',()=>{
console.log('流打开了');
})
rs.on('close',()=>{
console.log('流关闭了了');
})
rs.on('data',(data)=>{
console.log(data);//data.length==65535,每次读取64KB的内容
})
2. path模块

const path = require(“path”);

/*
1、path.join([path1][, path2][, ...]):连接路径,主要用途在于,会正确使用当前系统的路径分隔符
2、path.resolve([from ...], to):用于返回绝对路径,类似于多次cd处理
3、path.dirname(p):返回文件夹
4、path.extname(p):返回后缀名
5、path.basename(p):返回路径中的最后一部分
*/
const path = require("path");

// 连接路径:/test/test1/2slashes/1slash
console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));

// 转换为绝对路径:/Users/jian/workspace/cjs/main.js
const mainPath = path.resolve('main.js')
console.log('resolve : ' + mainPath);

// 路径中文件的后缀名:.js
console.log('ext name : ' + path.extname(mainPath));

// 路径最后名字:main.js
console.log('ext name : ' + path.basename(mainPath));
3. events模块

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它,包括 fs、 http 、net在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

EventEmitter 会按照监听器注册的顺序同步地调用所有监听器,所以必须确保事件的排序正确,且避免竞态条件。

常见api

addListener(event, listener)注册监听器
removeListener(event, listener)移除监听器
removeAllListeners([event])移除所有监听器
on(event, listener)注册监听器
off(eventName, listener)移除监听器
once(event, listener)为指定事件注册一个单次监听器
emit(event, [arg1], [arg2], […])按监听器的顺序执行执行每个监听器,如果事件有注册监听返回true,否则返回 false

错误处理:当 EventEmitter 实例出错时,应该触发 ‘error’ 事件。 这些在 Node 中被视为特殊情况。如果没有为 ‘error’ 事件注册监听器,则当 ‘error’ 事件触发时,会抛出错误、打印堆栈跟踪、并退出 Node.js 进程。作为最佳,应该始终为 ‘error’ 事件注册监听器。

const EventEmitter = require('events').EventEmitter;
var eventEmitter = new EventEmitter();
eventEmitter.on('event', function(a, b) {
  console.log(a, b, this, this === eventEmitter);
  // 打印: 普通函数,this指向eventEmitter实例
  //   a b MyEmitter {
  //     domain: null,
  //     _events: { event: [Function] },
  //     _eventsCount: 1,
  //     _maxListeners: undefined } true
});
eventEmitter.emit('event', 'a', 'b');

eventEmitter.on('event', (a, b) => {
  //箭头函数,this指向module.exports
  console.log(a, b, this, this === module.exports);
  // 打印: a b {} true
});
eventEmitter.emit('event', 'a', 'b');

面试题:事件模型设计(使用:发布订阅模式)

class EventEmitter{
  constructor(maxListeners){
    this.events = {}
    this.maxListeners = maxListners || Infinity
  }
  
  on(event, listener){
    if(!this.events[event]){
      this.events[event] = []
    }
    if(this.maxListener != Infinity 
       && this.events[event].length > this.maxListeners){
      console.warn(`${event} has reached max listeners.`)
      return this
    }
    this.events[event].push(listener)
    return this
  }
  
  off(event, listener){
    if(!listener){
      this.events[event] = null //无listener全部移除
    }else{
      this.events[event] = this.events[event].filter(item => item !== listener)
    }
    return this//链式调用
  }
  
  once(event, listener){
    const func = (...args) => {
      this.off(event, listener)
      listener.apply(this, args)
    }
    this.on(event, func)
  }
  
  emit(event, ...args){
    const listeners = this.events[event]
    if(!listeners){
      console.log("no listeners")
      return this
    }
    listeners.forEach(cb => {
      cb.apply(this, args)
    })
    return this
  }
}
4. http模块
//引入http模块
let http = require('http');
//引入处理字符串的依赖
let qs=require('querystring');

//1. 创建服务对象
let server = http.createServer((request,response)=>{
    /**
     * request:请求对象。里面包含了客户端给服务器的东西。
     * response:响应对象。里面包含了服务器给客户端的东西
     * GET请求的数据保存在"request.url"里面;
     * POST请求的数据保存在body里面、比较大,服务端监听data事件来接收分段传来的数据,同时服务端监听end事件来得知数据传输是否结束。
     */
     
    //GET请求
    //例:
    /* 
    let params = request.url.split('?')[1];
    let {name,age} = qs.parse(params);
    response.setHeader('content-type','text/html;charset=utf8');
    response.end(`<h1>HelloWorld</h1><br>使用者:${name},年龄:${age}`);
    */

    //POST请求
    //例:
    let str='';
    request.on('data', data=>{
        str+=data;
    });
    request.on('end', ()=>{
        //处理请求数据
        let {name,age}=qs.parse(str);
        response.setHeader('content-type','text/html;charset=utf8');
        response.end(`<h1>HelloWorld</h1><br>使用者:${name},年龄:${age}`);
    });
});

//2. 指定服务端口号,并做端口监听。第一个参数为开发人员指定的该服务器的端口号
server.listen(3000,(err)=>{
    if(!err){
        console.log('服务启动成功');
    }else{
        console.log(err);
    }
})
5. net模块
6. child_process模块
1. 进程和线程

进程:进程是程序执行过程中资源分配和管理的基本单位,也是资源分配的最小单位。进程可以理解为一个应用程序的执行过程,应用程序一旦执行,就是一个进程。每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。

线程:线程是CPU调度的最小单位,同时也是程序执行的最小单位。

进程和线程之间的区别和联系
进程是线程的容器。一个线程只能隶属于一个进程,但是一个进程是可以拥有多个线程的。
进程间不会相互影响,不同进程间数据很难共享,同一进程下不同线程间数据很易共享。
线程没有地址空间,线程包含在进程的地址空间中。所有的线程共享进程的内存和资源。但是每个线程拥有自己的栈段,寄存器的内容。

Js的执行是单线程的,js主要用来与用户的交互,以及操作DOM,所以js的执行必须是单线程的。

2. Node中线程和进程

在单核CPU系统之上node采用单进程+单线程的模式来开发。在多核CPU系统之上,可以通过child_process.fork开启多个进程(Node.js 在 v0.8 版本之后新增了Cluster来实现多进程架构) ,即多进程+单线程模式。注意:开启多进程不是为了解决高并发,主要是解决了单进程模式下 Node.js CPU 利用率不足的情况,充分利用多核 CPU 的性能。

3. process全局对象

Node.js中的进程process是一个全局对象,无需require直接使用,可用来获取当前进程中的相关信息。

  1. process.env:环境变量,例如通过 process.env.NODE_ENV获取不同环境项目配置信息
  2. process.nextTick
  3. process.pid:获取当前进程id
  4. process.ppid:当前进程对应的父进程
  5. process.cwd:获取当前进程工作目录
  6. process.platform:获取当前进程运行的操作系统平台
  7. process.uptime:当前进程已运行时间
  • 进程事件 :
  1. process.on(‘uncaughtException’, cb):捕获异常信息
  2. process.on(‘exit’, , cb):进程推出监听
  • 三个标准流:
  1. process.stdout: 标准输出
  2. process.stdin: 标准输入
  3. process.stderr: 标准错误输出
总结:
  1. Javascript是单线程,但是做为宿主环境的Node.js并非是单线程的。
  2. Node.js和Nginx均采用事件驱动方式,避免了多线程的线程创建、线程上下文切换的开销。如果你的业务大多是基于I/O操作,那么你可以选择Node.js来开发。
4. Node.js创建进程

Node.js提供了child_process内置模块,可以用来创建子进程,有下面四种方式:

  1. child_process.spawn():适用于返回大量数据,例如图像处理,二进制数据处理。

  2. child_process.exec() :适用于小量数据,maxBuffer默认值为 200 * 1024 超出这个默认值将会导致程序崩溃,数据量过大可采用spawn。

  3. child_process.execFile():类似child_process.exec(),区别是不能通过shell来执行,不支持像I/O重定向和文件查找这样的行为

  4. child_process.fork():衍生新的进程,并调用一个指定的模块,该模块已建立了 IPC 通信通道,允许在父进程与子进程之间发送消息。进程之间是相互独立的,每个进程都有自己的V8实例、内存,系统资源是有限的,不建议衍生太多的子进程出来,通长根据系统CPU核心数设置。。

1. child_process.spawn
2. child_process.exec
3. child_process.execFile
4. child_process.fork

参考资料

node的process以及child_process
Node.js child_process模块解读
深入理解Node.js 中的进程与线程

4. 第三方模块

1. 包与包管理器

包实际上就是一个压缩文件,解压以后还原为目录。符合CommonJS规范的目录,一个标准的包,应该包含如下文件:

  1. package.json 描述文件(包的 “说明书”,必须要有!!!)
  2. bin 可执行二进制文件
  3. lib 经过编译后的js代码
  4. doc 文档(说明文档、bug修复文档、版本变更记录文档)
  5. test 单元测试(一个测试报告)

如何让一个普通文件夹变成一个包?

  1. 让这个文件夹拥有一个:package.json文件即可,且package.json里面的内容要合法。
  2. 执行命令:npm init
  3. 包名的要求:不能有中文、不能有大写字母、不能与npm仓库上其他包同名。
NPM

全称:Node Package Manager , 是Node的包管理器,专门用于管理包
NPM的常用指令:

  1. npm -v 查看npm的版本
  2. npm init 初始化项目的package.json文件
  3. 【搜索】:
    1. npm search xxxxx
    2. 通过网址搜索:www.npmjs.com
  4. 【安装】:(安装之前必须保证文件夹内有package.json,且里面的内容格式合法)
    1. npm install / i 包名(安装指定的包)
    2. npm install xxxxx --save 或 npm i xxxx -S 或 npm i xxxx
      备注:
      (1).安装完的第三方包,放在node_modules这里
      (2).会自动产生一个package-lock.json(npm版本在5.x.x以后才有该文件),里面缓存的是每个下载过的包的地址,目的是为了下次安装时速度快一些。
      (3).当安装完一个包,该包的名字会自动写入到package.json中的【dependencies(生产依赖)】里。
    3. npm install xxxxx --save-dev 或 npm i xxxx -D 安装包并将该包写入到【devDependencies(开发依赖中)】
      备注:什么是生产依赖与开发依赖?
      (1).只在开发(写代码时)时需要依赖的库,就是开发依赖 ----- 例如:语法检查库、压缩代码、扩展css前缀的库。
      (2).生产环境中必不可少的库,就是生产依赖 ------ 例如:jquery。
      所谓生产就是:项目开发完毕,要部署到服务器上运行。
      (3).某些库即属于开发依赖,又属于生产依赖 -------例如:jquery。
    4. npm i xxxx -g 全局安装xxxx包(一般来说,带有指令集的包要进行全局安装,例如:browserify、babel等)
      查看全局安装的位置:npm root -g
    5. npm i xxx@yyy :安装xxx包的yyy版本
    6. npm i :安装package.json中所有声明的包//安装项目中的所有依赖
  5. 【移除】:
    npm remove xxxxx 移除xxxx包,并且删除该包在package.json中的声明
  6. 【其他命令】:
    1. npm aduit fix :检测项目依赖中的一些漏洞,并且尝试着修复。
    2. npm view xxxxx versions :查看npm仓库中xxxx包的所有版本信息
    3. npm view xxxxx version :查看npm仓库中xxxx包的最新版本
    4. npm ls xxxx :查看我们所安装的xxxx包的版本
2. node定时任务模块

node定时任务

5. node相关框架

1. express

express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架。简单来说express就是运行在node中的用来搭建服务器的模块。

//安装依赖
//npm install express

//0. 引入express模块
var express = require('express')

//1. 创建应用对象
var app = express()

//2. 配置路由
//例:
app.get('/',(request,response)=>{
    response.send(`HelloWorld`);
})

//3. 开启服务器,监听3000端口
app.listen(3000,(err)=>{
    if(err){
        console.log(err);
    }else{
        console.log('服务器请求成功');
    }
})
1. 路由(Route)

express中提供了一系列函数,可以让我们很方便的实现路由:
语法:app.< method>(path,callback)
语法解析:
method指的是HTTP请求方法,比如:
app.get()
app.post()
path指要通过回调函数来处理的URL地址
callback参数是应该处理该请求并把响应发回客户端的请求处理程序。参数:request对象、response对象

  • request对象
    request对象是路由回调函数中的第一个参数,存储了用户发送给服务器的请求信息

    request对象的属性和方法:

    1. request.query 获取get请求的query参数,拿到的是一个对象
    2. request.params 获取get请求参数路由的参数,拿到的是一个对象
    3. request.body 获取post请求体,拿到的是一个对象(要借助一个中间件,解析post请求的请求体中所携带的urlencoded编码格式的参数为一个对象,然后在挂载到request对象中,才能通过request.body获取数据)
    4. request.get(xxxx) 获取请求头中指定key对应的value
  • response对象
    response对象是路由回调函数中的第二个参数,存储了服务器发送给用户的响应信息。

    response对象的属性和方法:

    1. response.send() 给浏览器做出一个响应(会自动追加响应头)
    2. response.end() 给浏览器做出一个响应(不会自动追加响应头)
    3. response.download() 告诉浏览器下载一个文件
    4. response.sendFile() 给浏览器发送一个文件
    5. response.redirect() 重定向到一个新的地址(url)
    6. response.set(key,value) 自定义响应头内容 ,需要在服务器做出响应之前
    7. response.get(key) 获取响应头指定key对应的value, ,需要在服务器做出响应之后
    8. res.status(code) 设置响应状态码
2. 中间件

express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。
中间件(Middleware) 本质上是一个函数,他有三个参数:request、response、next 。它可以访问请求对象(request), 响应对象(response), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。

  • 中间件功能 :

    1. 执行任何代码。
    2. 修改请求和响应对象。
    3. 终结请求-响应循环。(让一次请求得到一次响应)
    4. 调用堆栈中的下一个中间件。如果中间件内部没有调用next函数,那么执行权就不会传递下去。
  • 中间件的分类

    【var app = express()】

    1. 应用(全局)级中间件(过滤非法的请求,例如防盗链)
      ----第一种写法:app.use((request,response,next)=>{})
      【定义在所有路由之前时,是所有请求的第一扇门,所有请求都必须经过该处理】
      ----第二种写法:使用函数定义
      function XXX(request,response,next){}
      调用方式:
      app.< method>(path,XXX,callback)
      【在需要进行某种处理的路由中使用,更加灵活】
    2. 第三方中间件(通过npm下载的中间件,例如使用body-parser解析post请求的请求体中所携带的参数)
    3. 内置中间件(express内部封装好的中间件)
      ----app.use(express.urlencoded({extended:true}))【解析post请求的请求体中所携带参数】
      ----app.use(express.static(‘public’)) 【暴露静态资源】
    4. 路由器中间件 (Router)
  • 备注:

    • 在express中,定义路由和中间件的时候,根据定义的顺序(代码顺序),将定义的每一个中间件或路由放在一个数组的容器中。当请求过来时,依次从容器中取除中间件和路由(先进先出),进行匹配,如果匹配成功,则将请求交给该路由或中间件处理。
    • 对于服务器来说,一次请求,只有一个响应。即只有一个请求对象和一个响应对象,其他任何形式的请求对象request和响应对象response都是对二者的引用。
  • 应用级中间件简单使用

    //应用级中间件简单使用
    const express = require('express')
    
    const app = express();
    
    /* 
    ********应用级中间件*************
    //****方式一:
    app.use((request,response,next)=>{
        //【定义在所有路由之前时,是所有请求的第一扇门,所有请求都必须经过该处理】
       request.aaa = 'aaa'
       next();
    })
    
    app.get('/',(request,response)=>{
        console.log(request.aaa);//aaa
        response.send(`HelloWorld`);
    })
    
    app.get('/demo',(request,response)=>{
        console.log(request.aaa);//aaa
        response.send(`HelloWorld`);
    }) 
    */
    
    //*****方式二:
     function pre(request,response,next){
        request.aaa = 'aaa';
        next();
    }
    
    app.get('/',pre,(request,response)=>{
        console.log(request.aaa);//aaa
        response.send(`HelloWorld`);
    })
    
    app.get('/demo',(request,response)=>{
        console.log(request.aaa);//undefined
        response.send(`HelloWorld`);
    })
    
    app.listen(3000,(err)=>{
        if(err){
            console.log(err);
        }else{
            console.log('服务器请求成功');
        }
    })
    
  • 第三方中间件简单使用

    //第三方中间件body-parser的简单使用
    const express = require('express');
    const bodyParser = require('body-parser')
    
    const app = express();
    
    //使用第三方中间件body-parser
    //解析post请求的请求体中所携带的urlencoded编码格式的参数为一个对象,随后挂载到request对象上,才能通过request.body获取post请求的数据
    app.use(bodyParser.urlencoded({ extended: false }));
    
    app.post('/',(request,response)=>{
        console.log(request.body);//[Object: null prototype] { name: 'chi', age: '18' }
        response.send('ok');
    })
    
    app.listen(3000,(err)=>{
        if(err){
            console.log(err);
        }else{
            console.log('服务器请求成功');
        }
    })
    
  • 内置中间件简单使用

    //内置中间件简单使用
    const express = require('express');
    
    const app = express();
    
    //解析post请求的请求体中所携带参数
    app.use(express.urlencoded({extended:true}));
    //暴露静态资源
    //可以访问到当前项目路径下public文件下所有静态资源
    app.use(express.static(__dirname+'/public'));
    
    app.post('/',(request,response)=>{
        console.log(request.body);//{ name: 'chi', age: '18' }
        response.send('ok');
    })
    
    app.listen(3000,(err)=>{
        if(err){
            console.log(err);
        }else{
            console.log('服务器请求成功');
        }
    })
    
3. 路由器(Router)

Router 是一个完整的中间件和路由系统,也可以看做是一个小型的app对象。用来更好的分类管理route

//router.js文件
const express = require("express");
var myrouter =express.Router(); // 建立了一个路由的中间件,处理各种请求
myrouter.get("/one",(req,res)=>{
    res.send("one...");
})
myrouter.get("/two",(req,res)=>{
    res.send("two...");
})
module.exports = function(){
	return myrouter;
};   // 导出,以便使用

//index.js文件
const express = require("express");
const myrouter = require("./router");  // 引入路由的中间件
var app = express();
//用staic 中间件处理静态资源
app.use(express.static("./public"));
app.use((req,res,next)=>{   // 拦截所有的请求
   console.log("hello");
   next();   //调用下一个中间件,切换到了app.use(myurouter)
});
app.use(myrouter());   // 使用路由器中间件
app.listen(3000,()=>{
   console.log("listen 3000....");
});
4. 中间件执行流程和原理
  • 线性模型
  • 异步模式:下一个中间件有异步处理,当前中间件不会等待下一个中间件处理完成
// 路由中间件
app.get('/test', (req, res, next) => {
 console.log('1-1')
 next()
 console.log('1-2')
}, (req, res, next) => {
 console.log('2-1')
 next()
 console.log('2-2')
}, (req, res, next) => {
 console.log('3-1')
 next()
 console.log('3-2')
})
app.get('/test', (req, res) => {
 console.log('test')
 res.send('end')
})
//1-1 2-1 3-1 test 3-2 2-2 1-2

app.get('/test', (req, res, next) => {
 console.log('1-1')
 next()
 console.log('1-2')
}, (req, res, next) => {
 console.log('2-1')
 next()
 console.log('2-2')
}, (req, res, next) => {
 console.log('3-1')
 await sleep(3)
 //后面函数会延迟执行
 next()
 console.log('3-2')
})
app.get('/test', (req, res) => {
 console.log('test')
 res.send('end')
})
//1-1 2-1 3-1 2-2 1-2 test 3-2

app.get('/test', (req, res, next) => {
 console.log('1-1')
 next()
 console.log('1-2')
}, async (req, res, next) => {//即使下一个中间件有异步处理,当前中间件不会等待下一个中间件处理完成
 console.log('2-1')
 await next() //不会等待中间件的异步执行完成 Promise.resolve().then(()=> console.log('2-2'))
 console.log('2-2')
}, (req, res, next) => {
 console.log('3-1')
 await sleep(3)
 //后面函数会延迟执行
 next()
 console.log('3-2')
})
app.get('/test', (req, res) => {
 console.log('test')
 res.send('end')
})
//1-1 2-1 3-1 1-2 2-2 test 3-2
5. 错误处理

错误处理是在⼀个特殊签名的中间件中完成的,⼀般被添加到链的最后

  • 可以通过next传递错误

  • 可以是代码运⾏错误

  • 使用最后的中间件捕获

    // 可以通过next传递错误
    app.get('/test', (req, res, next) => {
       console.log('test')
       next(new Error('500 can only greet "world"'))
       // res.send('end')
    })
    // 可以是代码运⾏错误
    app.use((req, res) => {
       if (req.query.greet !== 'world') {
          throw new Error('can only greet "world"')
       }
       res.status(200)
       res.send(`Hello ${req.query.greet} from Express`)
    })
    // 会被最后的中间件捕获,4个参数即可不必放置在最后
    app.use((err, req, res, next) => {
       if (!err) {
          next()
          return
       }
       console.log('Error handler:', err.message)
       res.status(500)
       res.send('Uh-oh: ' + err.message)
    })
    

2. Koa

Koa是由Express 原班人马打造的新生代的基于Node.js的一款Web框架
它的特点是优雅、简洁、表达力强、自由度高。本身代码只有1000多行,所有功能都通过插件实现,很符合 Unix 哲学。凭借精巧的“洋葱模型”和对 Promise 以及 async/await 异步编程的完全支持,Koa 框架自从诞生以来就吸引了无数 Node 爱好者。

let Koa = require('koa'); //引入koa
let app = new Koa();    //声明一个实例app
// 对于任何请求,app将调用该异步函数处理请求:
// 参数ctx【Context 对象】是由koa传入的封装了request和response的变量,我们可以通过它访问request和response。
// 参数next是koa传入的将要处理的下一个异步函数。
app.use(async (ctx,next)=>{   
    ctx.body = "hello"
});
app.listen("3000");  //监听端口
1. Context 上下文对象

Koa的Context 对象将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。
每个请求都将创建一个 Context对象,并在中间件中作为接收器引用,或者 ctx 标识符,如以下代码片段所示:

app.use(async ctx => {
  ctx; 			// 这是 Context
  ctx.req: 		// 这是 node原生 Request对象.
  ctx.res; 		// 这是 node原生 response 对象.
  ctx.request;  // 这是 koa Request对象.
  ctx.response; // 这是 koa Response对象.
  ctx.state;	// 推荐的命名空间,用于通过中间件传递信息和你的前端视图。例如:ctx.state.user = await User.find(id);
  ctx.app;		// 应用程序实例引用
  ctx.app.emit;	// Koa 内部 EventEmitter。ctx.app.emit 发出一个类型由第一个参数定义的事件。对于每个事件,您可以连接 "listeners",这是在发出事件时调用的函数。
  ctx.cookies.get(name, [options]);	// 通过 options 获取 cookie name
  ctx.cookies.set(name, value, [options]); // 通过 options 设置 cookie name 的 value
  ctx.throw([status], [msg], [properties]); // 用来抛出一个包含 .status 属性错误的帮助方法,其默认值为 500。这样 Koa 就可以做出适当地响应。
  /* 
  例如 ctx.throw(400, 'name required') 等效于:
	const err = new Error('name required');
	err.status = 400;
	err.expose = true;
	throw err;
	*/
	ctx.assert(value, [status], [msg], [properties])// 当 !value 时抛出一个类似 .throw 错误的帮助方法。这与 node 的 assert() 方法类似.
	ctx.respond; // 为了绕过 Koa 的内置 response 处理,你可以显式设置 ctx.respond = false;。 如果想要写入原始的 res 对象而不是 Koa 封装的 response,可以使用此参数。请注意,Koa 不支持使用此功能。这可能会破坏 Koa 中间件和 Koa 本身的预期功能。使用这个属性被认为是一个 hack,只是便于那些希望在 Koa 中使用传统的 fn(req, res) 功能和中间件的人。
	。。。
});

Koa Request 对象 & Response对象 是在 node 的 原生请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。

2、路由
  1. 原生路由
    通过ctx.request.path可以获取用户请求的路径,由此实现简单的路由。

    const main = ctx => {
      if (ctx.request.path !== '/') {
        ctx.response.type = 'html';
        ctx.response.body = '<a href="/">Index Page</a>';
      } else {
        ctx.response.body = 'Hello World';
      }
    };
    
  2. koa-route 模块
    原生路由用起来不太方便,可以使用封装好的koa-route模块。

    const route = require('koa-route');
    
    const about = ctx => {
      ctx.response.type = 'html';
      ctx.response.body = '<a href="/">Index Page</a>';
    };
    
    const main = ctx => {
      ctx.response.body = 'Hello World';
    };
    
    app.use(route.get('/', main));
    app.use(route.get('/about', about));
    
  3. 静态资源----koa-static模块
    如果网站提供静态资源(图片、字体、样式表、脚本…),为它们一个个写路由就很麻烦,也没必要。koa-static模块封装了这部分的请求。

    const path = require('path');
    const serve = require('koa-static');
    
    const main = serve(path.join(__dirname));
    app.use(main);
    
  4. 重定向
    有些场合,服务器需要重定向(redirect)访问请求。比如,用户登陆以后,将他重定向到登陆前的页面。ctx.response.redirect()方法可以发出一个302跳转,将用户导向另一个路由。

    const redirect = ctx => {
      ctx.response.redirect('/');
      ctx.response.body = '<a href="/">Index Page</a>';
    };
    app.use(route.get('/redirect', redirect));
    
3、中间件
1. 洋葱模型

洋葱模型

中间件的执行很像一个洋葱,但并不是一层一层的执行,而是以next为分界,先执行本层中next以前的部分,当下一层中间件执行完后,再执行本层next以后的部分。
一个洋葱结构,从上往下一层一层进来,再从下往上一层一层回去。
在这里插入图片描述

let Koa = require('koa');
let app = new Koa();
// 使用app.use,回调函数只能传一个,多个需多次注册
app.use(async (ctx,next)=>{
    console.log(1);
   await next();
    console.log(2);
});
app.use(async (ctx,next)=>{
    console.log(3);
   await next();
    console.log(4);
});
app.listen("3000");

// 输出顺序:
// 1
// 3
// 4
// 2
2. express和koa做对比

kkkk

3. 常用中间件
  • koa-compose:中间件的合成
  • koa-router:路由
  • koa-mounut:子路由
  • koa-static:静态资源
  • koa-body:body解析
  • koa-parameter:参数校验
1. koa-compose

koa-compose模块可以将多个中件合成为一个。

const compose = require('koa-compose');

const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}

const main = ctx => {
  ctx.response.body = 'Hello World';
};

const middlewares = compose([logger, main]);
app.use(middlewares);
2. koa-router
  1. koa-router基础写法

    let Koa = require('koa');
    let app = new Koa();
    let Router = require('koa-router');
    let router = new Router();
    router.get('/',async (ctx,next)=>{
        ctx.body = 'hello people';
        await next()
    });
    router.get('/list',async (ctx,next)=>{
        ctx.body = 'list';
    });
    app.use(router.routes()); // 挂载
    app.use(router.allowedMethods());//当请求数据的方法与设置的方法不一致,会报错。比如默认get请求获取,用post发请求会报错
    app.listen(3000);
    
  2. koa-router中嵌套路由写法
    假如 /home是首页,再次基础上有/home/list 首页列表页 /home/todo 首页todo页。这时就需要用到嵌套路由

    const Koa = require('koa');
    const app = new Koa();
    const Router = require('koa-router');
    //home的路由
    let home = new Router();
    home.get('/list',async(ctx)=>{
        ctx.body="Home list";
    }).get('/todo',async(ctx)=>{
        ctx.body ='Home ToDo';
    });
    //装载所有子路由
    let router = new Router();
    router.use('/home',home.routes(),home.allowedMethods());
    //加载路由中间件
    app.use(router.routes()).use(router.allowedMethods());
    app.listen(3000);
    
  3. koa-router参数的传递

    1、通过/arcicle/:id/:name传参

    let Koa = require('koa');
    let app = new Koa();
    let Router = require('koa-router');
    let router = new Router();
    //实现  /arcicle/id/name形式的传参
    router.get('/acticle/:id/:name',(ctx,next)=>{
        ctx.body = ctx.params.id +"-"+ ctx.params.name;
    });
    app.use(router.routes());
    app.listen(3000);
    

    2、通过/arcicle?id=1&name=cgp传参

    const Koa = require('koa');
    const Router = require('koa-router');
    const app = new Koa();
    const router = new Router();
    router.get('/article', function (ctx, next) {
        ctx.body=ctx.query; //query方法实现json形式
    });
    app.use(router.routes())
    app.listen(3000,()=>{
        console.log('starting at port 3000');
    });
    
3. koa-static
let Koa = require('koa');
let server = require('koa-static');
let app = new Koa();
app.use(server(__dirname +'/public'));
app.listen(3000);
4. koa-body

用来解析请求体的中间件,比如获取post提交的表单数据,通过koa-body解析后就能获取到数据。

let Koa = require('koa');
let bodyParser = require('koa-body')
let app = new Koa();
app.use(bodyParser()); // 解析请求体的中间件
app.use(async (ctx, next) => {
    if (ctx.path === '/' && ctx.method === 'GET') {
        ctx.set('Content-Type', 'text/html;charset=utf8');
        ctx.body = `
        <form action="/" method="post">
            <input type="text" name="username" >
            <input type="text" name="password" >
            <input type="submit" >
        </form>
        `
    }
});
app.listen(3000);
5. koa-parameter

参数校验

const parameter = require('koa-parameter');
parameter(app);
router.post('/', async ctx => {
   //接收⼀个对象
   ctx.verifyParams({
     // 校验的简写形式
     username: 'string',
     password: { type: 'string', required: true } // 或者也可以这样
   })
   ctx.body = { code: 1 }
})
4. 错误处理
  1. 500 错误
    如果代码运行过程中发生错误,我们需要把错误信息返回给用户。HTTP 协定约定这时要返回500状态码。Koa 提供了ctx.throw()方法,用来抛出错误,ctx.throw(500)就是抛出500错误。

    const main = ctx => {
      ctx.throw(500);
    };
    
  2. 404错误
    如果将ctx.response.status设置成404,就相当于ctx.throw(404),返回404错误。

    const main = ctx => {
      ctx.response.status = 404;
      ctx.response.body = 'Page Not Found';
    };
    
  3. 处理错误的中间件
    为了方便处理错误,最好使用try…catch将其捕获。但是为每个中间件都写try…catch太麻烦,可以让最外层的中间件,负责所有中间件的错误处理。

    const handler = async (ctx, next) => {
      try {
        await next();
      } catch (err) {
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.body = {
          message: err.message
        };
      }
    };
    
    const main = ctx => {
      ctx.throw(500);
    };
    
    app.use(handler);
    app.use(main);
    
  4. error 事件的监听
    运行过程中一旦出错,Koa 会触发一个error事件。监听这个事件,也可以处理错误

    const main = ctx => {
      ctx.throw(500);
    };
    
    app.on('error', (err, ctx) =>
      console.error('server error', err);
    );
    
  5. 释放 error 事件
    需要注意的是,如果错误被try…catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。

    const handler = async (ctx, next) => {
      try {
        await next();
      } catch (err) {
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.type = 'html';
        ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
        ctx.app.emit('error', err, ctx);
      }
    };
    
    const main = ctx => {
      ctx.throw(500);
    };
    
    app.on('error', function(err) {
      console.log('logging error ', err.message);
      console.log(err);
    });
    

3. Sequalize

orm框架(对象关系映射)

  • 安装

    npm install --save sequelize
    根据具体使⽤的数据库选择安装驱动程序
    npm install --save pg pg-hstore # Postgres
    npm install --save mysql2
    npm install --save mariadb
    npm install --save sqlite3
    npm install --save tedious # Microsoft SQL Server

1. 连接数据库
// config.js
var mysql = {
    database: 'production',
    username: 'www',
    password: 'secret-password',
    host: '192.168.1.199'
};

module.exports = mysql;
// ======================================================
// db.js
const { Sequelize } = require('sequelize');
// 配置数据库连接
const sequelize = new Sequelize({
 	dialect: 'mysql',
    host: mysql.host,
    database: mysql.db,
    username: mysql.username,
    password: mysql.password,
    port: mysql.port,
});
module.exports = sequelize;
// ===============================================================
// index.js
const sequelize = require("./db");
(async function(){
  try {
    await sequelize.authenticate();
    console.log('Connection has been established successfully.');
  } catch (error) {
    console.error('Unable to connect to the database:', error);
  }
})();
3.关闭连接
// 默认情况下,Sequelize 将保持连接打开状态,并对所有查询使用相同的连接. 
// 如果需要关闭连接,可以调用 sequelize.close()【这是异步的并返回一个 Promise】
2. model ---- ⽤来描述数据库表的结构

模型model是 Sequelize 的本质. 模型是代表数据库中表的抽象

该模型告诉 Sequelize 有关它代表的实体的几件事,例如数据库中表的名称以及它具有的列(及其数据类型)。使用 sequelize.define。

Model存放的文件夹必须在models内,并且以Model名字命名,例如:Pet.js,User.js等等。

每个Model必须遵守一套规范:

  • 统一主键,名称必须是id,类型必须是STRING(50);
  • 主键可以自己指定,也可以由框架自动生成(如果为null或undefined);
    所有字段默认为NOT NULL,除非显式指定;
  • 统一timestamp机制,每个Model必须有createdAt、updatedAt和version,分别记录创建时间、修改时间和版本号。其中,createdAt和updatedAt以BIGINT存储时间戳,最大的好处是无需处理时区,排序方便。version每次修改时自增。
const User = sequelize.define('User', {
   // Model attributes
   id: {
     type: DataTypes.INTEGER(11),
     primaryKey: true, // 主键
     autoIncrement: true, 
   },
   firstName: {
     type: DataTypes.STRING,
     allowNull: false
   },
   lastName: {
     type: DataTypes.STRING
     // allowNull 默认为 true
   },
   age: {
   		type: DataTypes.INTEGER
   }
}, {
  // 在数据库中发现User表已经同步完成
  createdAt: false, // 不增加创建时间列
  updatedAt: false, // 不增加修改时间列
  paranoid: true //该属性决定该表的数据不会真正的删除,而是增加一列deletedAt,记录删除的时间
});
module.exports = User
3. 增删改查
// 1、增加
// service
async function addUser(User, userInfo) {
   console.log('addUser', User, userInfo)
   const user = await User.create(userInfo);
   return user
}
// controler
app.post('/user', async (req, res) => {
   const result = await services.User.addUser(req.body)
   res.send(result)
})

// 2、修改
// service
function updateUser(User, id, userInfo) {
  console.log('updateUser', User, userInfo)
  return User.update(
   {
   ...userInfo,
   },
   {
     where: {
        id, //查询条件
     }
    }
  )
}
// controller
app.put('/user/:id', async (req, res) => {
   console.log(req.params.id)
   const result = await services.User.updateUser(req.params.id, req.body)
   res.send(result)
})

// 3、查找
// service
function getAllUsers(User) {
   return User.findAll(
   // {
   // attributes: ['id', 'firstName', 'lastName']
   // }
   )
}
function getUserById(User, id) {
   // return User.findOne({
   // where: {
   // id,
   // }
   // });
   return User.findByPk(id)
}
// controller
// 获取列表
app.get('/user', async (req, res) => {
   const result = await services.User.getAllUsers({})
   res.send(result)
})
// 获取⼀个
app.get('/user/:id', async (req, res) => {
   const userId = req.params.id
   if(!userId) {
     res.send('error id not found')
     return;
   }
   const result = await services.User.getUserById(userId)
   res.send(result)
})

//4、删除
// service
async function deleteUserById(User, id) {
  try {
	const result = await User.destroy({
      where: { //where是指定查询条件
        id,
      } 
	})
	return result
  } catch (e) {
	return Promise.reject(e)
  }
}
// 删除
// controller
app.delete('/user/:id', async (req, res) => {
  try {
	const result = await services.User.deleteUserById(req.params.id)
    if(result === 1) {
      res.send('delete success')
    } else {
      res.send('delete fail')
    } 
  } catch(e) {
	res.send(e)
  }
})
4. mysql

4. Elasticsearch 开发入门 - Nodejs

全文搜索属于最常见的需求,开源的 Elasticsearch (简称 Elastic)是目前全文搜索引擎的首选。Elasticsearch 是一个高度可扩展的开源全文本搜索和分析引擎。 它可以快速,近乎实时地存储,搜索和分析海量数据。 通常用作支持具有复杂搜索功能和要求的应用程序的基础引擎/技术。

Elastic 的底层是开源库 Lucene。但是没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

1. 连接到 Elasticsearch

导入我们的 Elasticsearch 模块并创建一个针对 Elasticsearch 主机的客户端对象。 最后使用 ping 方法验证 Elasticsearch 连接是否成功。 如果成功,它将返回 true。

// index.js
const es = require('elasticsearch');
const client = es.Client({ host: 'http://localhost:9200' });

client.ping()
  .then(res => console.log('connection success', res))
  .catch(err => console.error('wrong connection', err));
2. 创建索引

Elasticsearch 中的索引是文档 document 的集合。 术语文档 document 广泛用于表示 Elasticsearch 中的数据。 例如,创建一个名为 student 的索引来存储学生列表。[索引名称必须小写]

3. 插入文档 ---- .index()
// 尝试插入一些学生文档。
const es = require('elasticsearch');
const client = es.Client({ host: 'http://localhost:9200' });

client.ping()
  .then(res => console.log('connection success', res))
  .catch(err => console.error('wrong connection', err));

 client.index({
  index: 'students',
  body: {
    name: 'John Doe',
    age: 17,
    hobby: 'basketball'
  }
})
.catch(err => console.error(err));
4. 获取 / 搜索文档 ---- .search()
// 要从项目中获取学生文档,可以使用搜索方法。
const es = require('elasticsearch');
const client = es.Client({ host: 'http://localhost:9200' });

client.ping()
  .then(res => console.log('connection success', res))
  .catch(err => console.error('wrong connection', err));

client.search({
  index: 'students',
  body: {
    query: {
      // 获得名字叫做 John 的文档。
      match: { name: 'John' }
    }
  }
})
.then(res => console.log(JSON.stringify(res)))
.catch(err => console.error(err));

在 Elasticsearch 中的搜索中,有两类搜索:

  • queries

  • aggregations

    它们之间的区别在于:
    query 可以帮我们进行全文搜索,而 aggregation 可以帮我们对数据进行统计及分析。我们有时也可以结合query 及 aggregation一起使用,比如我们可以先对文档进行搜索然后在进行 聚合

    // 要从项目中获取学生文档,可以使用搜索方法。
    const es = require('elasticsearch');
    const client = es.Client({ host: 'http://localhost:9200' });
    
    client.ping()
      .then(res => console.log('connection success', res))
      .catch(err => console.error('wrong connection', err));
    
    client.search({
      index: 'students',
      body: {
        query: {
          match: {
            title: "community"
          }
        },
        // aggregation:聚合
        aggs: {
          top_authors: {
            terms: {
              field: "author"
            }
          }
        }
      }
    })
    .then(res => console.log(JSON.stringify(res)))
    .catch(err => console.error(err));
    

ElasticSearch搜索语法学习(term,filter,bool,terms,range)

5. 更新文档 ---- .update()
假设要更新 ID1 的学生的爱好。
const es = require('elasticsearch');
const client = es.Client({ host: 'http://localhost:9200' });
client.ping()
  .then(res => console.log('connection success', res))
  .catch(err => console.error('wrong connection', err));

client.update({
  index: 'students',
  id: '1',
  body: {
    doc: {
      hobby: 'swimming'
    }
  }
})
.then(res => console.log(JSON.stringify(res)))
.catch(err => console.error(err)); 
6. 删除一个文档 ---- .delete()
// 删除id为1的文档
const es = require('elasticsearch');
const client = es.Client({ host: 'http://localhost:9200' });

client.ping()
  .then(res => console.log('connection success', res))
  .catch(err => console.error('wrong connection', err));

client.delete({
  index: 'students',
  id: '1'  
})
.then(res => console.log(JSON.stringify(res)))
.catch(err => console.error(err));

扩展

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值