Node深入浅出

Node深入浅出

    • 一、Node是神
    • 二、NVM(node版本管理器)
    • 三、简单搭建node服务器
    • 四、http 超文本传输协议
    • 五、node常用网络模块示例
    • 六、node搭建http服务器
    • 七、node模块加载机制与npm使用剖析
    • 八、文件系统操作及同步异步方式的选择
      • 1. Node操纵文件系统是通过一个重要的原生模块来实现的: FileSystem(fs)
      • 2. fs.readFile 读取文件
      • 3. 对于fs中的绝大多数api来说,Node都提供了相同功能的两个版本: `同步版本与异步版本`
      • 4. 为什么node是如此提倡异步编程,但还要在文件系统中提供同步编程❓
          • 同步API和异步API的区别(获取返回值)
          • 那么异步[函数](https://so.csdn.net/so/search?q=%E5%87%BD%E6%95%B0&spm=1001.2101.3001.7020)怎么样才能拿到执行的结果呢,在这里我们使用回调函数(callback)
            • 案例一:回调地狱-callback函数一直嵌套解决(代码量多 如果有成千上万代码影响阅读性)
            • 案例二:使用`promise`依次执行函数
          • 到底该使用同步版本还是异步版本?
      • 5. fs.writeFile 写入文件
      • 6. `fs.open('test.txt', 'r+', (error, fd) =>{})`
      • 7. `fs.open`打开文件`fs.close`关闭文件
      • 8. `fs.unlink('hello.txt', (error) => {})` 删除文件
      • 9. `fs.rename(oldPath, newPath, callback)`重命名
      • 10. 文件与目录操纵详情
        • a. `fs.mkdir`创建目录,创建多层目录需要将`{ recursive: true }`变为`true`
        • b. `fs.readdir('./', (error, file) => {})`创建目录
        • c. `fs.access('./app0.js', (error) => {})`检测文件是否存在
        • d. `fs.realpath('app0.js', 'utf8', (error, resolvedPath) => {})` 查看目录
        • e. `fs.rmdir('mydir', { recursive: true }, (error) => {})` 删除目录
        • f. 读取流
        • g. 文件拷贝
    • 九、Buffer(缓存区)模块实例
    • 十、网络模块(http/tcp)
    • 十一、Node.js事件模型示例剖析
      • 自定义事件及响应式事件示例
      • 底层事件模型与异步操作示例
    • 十二、Node操纵数据库
      • Node操作mysql
      • Node操纵MongoDB
      • Node操纵Redis
        • 1. 验证登录示例
          • (1)首先用分层思想创建文件夹,结构如下图
          • (2)先写userRedisDao.js文件,代码如下
          • (3)userService.js
          • (4)userController.js
          • (5)最后编写app2.js代码创建node服务器与客户端建立连接并请求和响应数据
          • (6)运行结果
        • 2. 在登录的基础上编写登出的逻辑
        • 3. 主子进程之间的通信方式(这四种方法❣)
    • 十三、cluster模块与master和worker模式
      • cluster模式:
      • master模式:
    • 十四、koa与洋葱模型及中间件示例
      • koa官方链接:[https://koa.bootcss.com/#introduction](https://koa.bootcss.com/#introduction)
      • 洋葱模型
      • koa项目模块搭建示例

一、Node是神

  1. Node.js 是一个基于 V8 JavaScript 引擎构建的 JavaScript 运行时环境。
  2. 单线程(解决大量并发请求)
  3. Node深入浅出_第1张图片
  4. node.js中的npm(包管理器nodePackageManager)相当于java中的jdk/maven啥的

二、NVM(node版本管理器)

  1. 参考:https://github.com/nvm-sh/nvm
  2. 不支持window操作系统,但是可以使用nvm-windows
  3. command -v nvm 验证是否存在nvm
  4. nvm ls 查看本机的node版本
  5. nvm ls-remote查看远程的node版本

三、简单搭建node服务器

  1. 创建app.js
// 搭建服务器
let http = require('http')
// 创建服务器
let server = http.createServer(function(request, respose) {
    // 设置http响应头-普通文本
    respose.writeHead(200, { 'Content-Type' : 'text-plain'})
    // 将字符串发送给客户端
    respose.end('Hello Node.js')
})

//监听端口号
server.listen(3000, 'localhost')

//再打印个日志
console.log('Node Server started on port 3000');
  1. 再通过node app.js的命令运行此文件,效果如下

  2. Node深入浅出_第2张图片

  3. Node深入浅出_第3张图片

  4. node.js一般都是通过异步回调的方式

四、http 超文本传输协议

参考:http://nodejs.cn/api/http.html
使用 HTTP 服务器和客户端,则必须 require('http')
以下是我的示例代码

const http = require('http');
const server = http.createServer(function (request, response) {
    //捕获请求过来的数据 监听事件的发生
    request.on('data', function (chunk) {
        data += chunk
    })

    //当请求全部结束之后,后续不会有新的数据传递过来
    request.on('end', function () {
        let method = request.method;
        // headers本身是一个键值对的形式 用JSON.stringify把它转成Json
        let headers = JSON.stringify(request.headers);
        //获取http的版本号
        let httpVersion = request.httpVersion;
        // 获取请求的url
        let requestUrl = request.url;

        //设置响应的内容
        response.writeHead(200, {'Content-Type': 'text/html'})

        let responseData = method + ',' + headers + ',' + httpVersion + ',' + requestUrl;

        // 把数据发送出去
        response.end(responseData);
    })

    server.listen(3000, function() {
        console.log('Node Server started on port 3000');
    })
});

五、node常用网络模块示例

  1. 首先先简单搭建一个服务器端 用到上面app.js的代码
  2. 然后执行node app.js命令将app.js运行成功后 再执行node app4.js运行app4.js代码 如下
  3. 客户端通过请求服务器端响应得到数据Hello Node.js
// 搭建客户端
const http = require('http')

//let声明变量 const声明常量
let responseData = ''

// 客户端发起请求
http.request({
    'host': 'localhost',
    'port': '3000',
    'method': 'get'
}, function (response) {
    response.on('data', function(chunk) {
        responseData += chunk
    })
    response.on('end', function() {
        console.log(responseData);
    })
}).end()
  1. 运行效果 如下
  2. Node深入浅出_第4张图片
  3. Node深入浅出_第5张图片
  4. 解析字符串,把url字符串转换成对象
const url = require('url');

const urlString = 'http://www.test.com?orderId=12345';
// 解析字符串 把字符串转换成对象
const urlObject = url.parse(urlString);

console.log(urlObject);

Node深入浅出_第6张图片

  1. 把url对象转换成字符串
const url = require('url');

const urlObject = {
    protocol: 'http:',       
    slashes: true,
    auth: null,
    host: 'www.test.com',    
    port: null,
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.test.com',
    port: null,
    hostname: 'www.test.com',
    hash: null,
    search: '?orderId=12345',
    query: 'orderId=12345',
    pathname: '/',
    path: '/?orderId=12345',
    href: 'http://www.test.com/?orderId=12345'
}

let realUrl = url.format(urlObject)
console.log(realUrl);

Node深入浅出_第7张图片

  1. 通过查询字符串querystring.parse(str)把字符串转化为对象
  2. 通过查询字符串querystring.stringify(obj)把对象转化为字符串
  3. path.join('/hello', '/dssdf', '/safas') 拼接字符串为完整路径

六、node搭建http服务器

  1. 创建一个处理用户服务的userService.js
// 处理用户服务
class UserService {
    login(userName, password) {
        console.log(userName, password);
        return true
    }
}
//这是导出这个类
module.exports = new UserService()
  1. 搭建客户端
// 搭建客户端
const http = require('http')

//let声明变量 const声明常量
let responseData = ''

// 客户端发起请求
http.request({
    'host': 'localhost',
    'port': '3000',
    'method': 'get',
    'path': '/login?userName=zhangsan&password=123456'
}, function (response) {
    // 接收用户请求响应输出
    response.on('data', function(chunk) {
        responseData += chunk
    })
    response.on('end', function() {
        console.log(responseData);
    })
}).end()
  1. 搭建服务器
// 完成node服务器的启动 url的解析 请求参数的获取
const http = require('http')
const querystring = require('querystring')
const url = require('url')

const userService = require('./userService')

// 创建服务器
const server = http.createServer(function(request, response) {
    // 接收用户请求数据
    let data= ''
    request.on('data', function(chunk) {
        data += chunk
    })

    // 没有来源的数据(代码分层)
    request.on('end', function() {
        // 获取用户请求地址
        const requestUrl = request.url
        // 获取请求的方法
        const requestMethod = request.method
        if (requestUrl.includes('login') && requestMethod === 'GET') {
            const requestParams = url.parse(requestUrl)
            // 拿到用户名和密码对象
            const querystringParams = querystring.parse(requestParams.query)
            const result = userService.login(querystringParams.userName, querystringParams.password)
            console.log(result);
            // 服务器发送请求(返回数据给用户)
            response.writeHead(200, {'Content-Type': 'text/plain'})
            response.end('userName:', querystringParams.userName, 'password:', querystringParams.password)
        }
    })
    
})

// 启动服务器
server.listen(3000, function() {
    console.log('Server is listening 3000 port');
})

七、node模块加载机制与npm使用剖析

  1. 通过npm init创建package.json文件

  2. Node深入浅出_第8张图片

  3. mkdir node_modules 创建 node_modules文件

  4. 在这里插入图片描述

  5. npm install(或者是简写i) xx(下载插件) 局部安装

  6. 下载的依赖会自动放入dependencies中, ^表示每次下载时都会下载最新的版本,devDependencies表示在开发环境时才会用到的依赖,真正部署时是不需要的,从而也能减少生成体积包的大小

  7. npm i xx -g 表示全局安装 局部安装只有当前工程可用

  8. 删除依赖模块使用npm uninstall xx

  9. package-lock.json 锁定版本号

  10. rm -rf node_modules 可以删除node_modules,只要有package.json文件(依赖信息),执行npm i 就会自动下载里面所有依赖

八、文件系统操作及同步异步方式的选择

1. Node操纵文件系统是通过一个重要的原生模块来实现的: FileSystem(fs)

FileSystem模块用于对系统文件及目录进行读写操作。

2. fs.readFile 读取文件

Node深入浅出_第9张图片

3. 对于fs中的绝大多数api来说,Node都提供了相同功能的两个版本: 同步版本与异步版本

对于同步版本与异步版本来说,其在方法的命名上存在一个规则:
xxxx(异步) xxxxSync(同步)

4. 为什么node是如此提倡异步编程,但还要在文件系统中提供同步编程❓

  1. 同步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,等待写入成功后,再计算下一个数据,直到完成。
  2. 异步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,不等待写入成功与否的结果,立刻返回继续计算下一个数据,计算过程中可以收到之前写入是否成功的通知,直到完成。
同步API和异步API的区别(获取返回值)

同步代码可以从返回值中拿到执行结果,但是异步代码是不可以获取执行结果。

那么异步函数怎么样才能拿到执行的结果呢,在这里我们使用回调函数(callback)

Node深入浅出_第10张图片

案例一:回调地狱-callback函数一直嵌套解决(代码量多 如果有成千上万代码影响阅读性)
//异步回调地狱(通过callback获取返回值案例一)	
const fs = require('fs')
const path = require('path')
let path1 = path.join(__dirname, 'test.txt')
let path2 = path.join(__dirname, 'test2.txt')
let path3 = path.join(__dirname, 'test3.txt')
fs.readFile(path1, 'utf-8', function(error, data) {
    if (error) console.log('error1');
    console.log(data);
    fs.readFile(path2, 'utf-8', function(error, data) {
        if (error) console.log('error2');
        console.log(data);
        fs.readFile(path3, 'utf-8', function(error, data) {
            if (error) console.log('error3');
            console.log(data);
        })
    })
})

运行结果:
Node深入浅出_第11张图片

案例二:使用promise依次执行函数
  1. Promise是一个构造函数,有all(并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调)、race(只要有一个异步操作执行完毕,就立刻执行 then 回调)、reject(失败)、resolve(成功)这几个方法,原型上有then(链式调用)、catch(抛出错误)等方法
  2. 简单来说,Promise 就是用同步的方式写异步的代码,用来解决回调问题
//引入
const fs = require('fs')
const path = require('path')

let path1 = path.join(__dirname, 'test.txt')
let path2 = path.join(__dirname, 'test2.txt')
let path3 = path.join(__dirname, 'test3.txt')
function readFileTest(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf-8', function(error, data) {
            resolve(data)
        })
    })
}

//调用函数readFileTest
let p1 = readFileTest(path1)
p1.then(res => {
    console.log(res);
    return readFileTest(path2)
}).then(res2 => {
    console.log(res2);
    return readFileTest(path3)
}).then(res3 => {
    console.log(res3);
})

运行结果:
Node深入浅出_第12张图片

欧克,这样是不是看起来简单多了呢

到底该使用同步版本还是异步版本?
// Node操纵文件系统是通过一个重要的原生模块来实现的
// 对于fs中的绝大多数api来说,Node都提供了相同功能的两个版本: 同步版本与异步版本
const fs = require('fs')

// 读取文件(异步)
fs.readFile('test.txt', 'utf-8', function(error, data) {
    if (error) {
        console.log('error occured');
    } else {
        console.log(data);
    }
})

//读取文件(同步)对于同步版本没有回调
try {
    let data = fs.readFileSync('text.txt', 'utf-8')
    console.log(data);
} catch (error) {
    console.log(error);
}

当然是尽量最大可能去使用异步版本, 因为node.js是一个单线程的,如果方法执行时间比较长,它就会将node唯一的线程给阻塞住,一旦线程被阻塞住,node将无法去为其他任何的请求去服务,除非是万不得已,不然尽可能使用异步版本,因为它不会阻塞线程。

5. fs.writeFile 写入文件

Node深入浅出_第13张图片
Node深入浅出_第14张图片
Node深入浅出_第15张图片

{flag:'a'}表示追加 === fs.appendFile('info.txt', 'my info', 'utf8', (error) => { if(error) throw error } console.log('success'))

6. fs.open('test.txt', 'r+', (error, fd) =>{})

(r+)表示可读可写,fd(file descriptor) 表示文件的描述符数字是20

7. fs.open打开文件fs.close关闭文件

Node深入浅出_第16张图片

8. fs.unlink('hello.txt', (error) => {}) 删除文件

9. fs.rename(oldPath, newPath, callback)重命名

Node深入浅出_第17张图片

10. 文件与目录操纵详情

a. fs.mkdir创建目录,创建多层目录需要将{ recursive: true }变为true
const fs = require('fs')

//创建目录 { recursive: true }可创建多层目录 默认为false
fs.mkdir('mydir/hello/world', { recursive: true }, (error) => { 
    if(error) throw error
    console.log('success');
})
b. fs.readdir('./', (error, file) => {})创建目录
c. fs.access('./app0.js', (error) => {})检测文件是否存在
d. fs.realpath('app0.js', 'utf8', (error, resolvedPath) => {}) 查看目录
e. fs.rmdir('mydir', { recursive: true }, (error) => {}) 删除目录
f. 读取流
const fs = require('fs')

//文件读取流
const readStream = fs.createReadStream('./app12.js', {encoding: 'utf-8'})

// 监听文件打开 fd(file descriptor)表示文件的描述符数字是20
readStream.on('open', (fd) => {
    console.log(fd);
})

// 表示已经就绪
readStream.on('ready', () => {
    console.log('ready');
})

readStream.on('data', (data) => {
    console.log(data);
})

// 表示文件读取完毕
readStream.on('end', () => {
    console.log('end');
})

// 文件关闭
readStream.on('close', () => {
    console.log('close');
})

// 监听错误信息
readStream.on('error', (error) => {
    console.log(error);
})
g. 文件拷贝
const fs = require('fs')

// 读取流
const readStream = fs.createReadStream('./app12.js', {encoding: 'utf8'})

// 写入流(将读取到的流 app12.js写入到某个文件中)
const writeStream = fs.createWriteStream('myTest', {encoding: 'utf8'})

// 读取data数据 写入data数据
readStream.on('data', (data) => {
    writeStream.write(data, () => {
        console.log(data);
    })
})

然后就会有一个新的文件myTest文件出现了,里面的内容和app12的内容一样

九、Buffer(缓存区)模块实例

  1. Buffer无需引用 直接使用即可
// 创建一个Buffer的缓存区
// 分配内存
const buffer = Buffer.alloc(128)

// 写入的长度
const length = buffer.write('helloWorld大家', 'utf8')
// 字节长度(一个中文字符占3个字节)
console.log(length); // 16
  1. 比较Buffer的长度与字符串的长度(a转换到Buffer中形成二进制数据的时候 就会形成一个字节,如果加上一个中文,就会占3个字节,最后buffer的长度就为8,而字符串的长度是为6)
const str = 'abcde'
const buffer = Buffer.from(str)

// a转换到Buffer中形成二进制数据的时候 就会形成一个字节,如果加上一个中文,就会占3个字节,最后buffer的长度就为8,而字符串的长度是为6
console.log(str.length);
console.log(buffer.length);

console.log(buffer); // 依次对应‘abcde’的ASCII码

//如果要想将buffer的ASCII码显示为它本身 就用toString('utf8')
console.log(buffer.toString('utf8'));

Node深入浅出_第18张图片

  1. Buffer与字符串之间的相互转换
    1. Buffer -> 字符串 使用buffer.toString('utf8')
    2. 字符串 -> Buffer 使用Buffer.from(字符串)
  2. Buffer的拼接(使用Buffer.concat
const buffer1 = Buffer.from('hello')
const buffer2 = Buffer.from('hello')
const buffer3 = Buffer.from('hello')

const bufferArray = [buffer1, buffer2, buffer3]

//拼接
const bufferResult = Buffer.concat(bufferArray, buffer1.length + buffer2.length + buffer3.length)
// console.log(bufferResult);
console.log(bufferResult.length); //18
console.log(bufferResult.toString('utf8'));
  1. 判断类型
// 使用typeof判断类型
const str = '你好'
const buffer = Buffer.from('jjj')
const a = true
const obj = {}
const number = 0

console.log(typeof str); // string
console.log(typeof buffer);// object
console.log(typeof a);// boolean
console.log(typeof obj);// object
console.log(typeof number);// number

//通过isBuffer判断是否是Buffer
console.log(Buffer.isBuffer(buffer));//true   
console.log(Buffer.isBuffer(obj));//false  

十、网络模块(http/tcp)

node.js对http模块,tcp模块提供了很大的支持

HTTP是建立在TCP/IP之上的应用层协议

  1. 和http一样,tcp也是用createServer创建服务器,tcp模式与http很多都是一样的
const net = require('net')

// 和http一样,tcp也是用createServer创建服务器
const server = net.createServer((socket) => {
    console.log('client connected');
})

//监听
server.listen(8888, () => {
    // 监听成功
    console.log('server is listening');
})
  1. tcp的搭建客户端
// 搭建客户端
const net = require('net')
const clinet = new  net.Socket();

clinet.connect('8000', 'localhost', () => {
    console.log('connected to server');
})

十一、Node.js事件模型示例剖析

Nodejs的Events实现了一种观察者模式,其支持了Nodejs的核心机制,且http / fs / mongoose等都继承了Events,可以添加监听事件。这种设计模式在客户端的组件编程思想里经常会用到,我们先简单了解下该模式。

观察者模式就是为某一对象添加一监听事件,如on(‘request’, callback),由该对象在符合条件如request时自行触发,浏览器本身已经为dom实现了监听机制。

const http = require('http')

const httpServer = http.createServer()
//观察者模式
// addListener 和 on(别名) 是一样的用法
httpServer.addListener('request', (request, response) => {
    if (request.url === '/') {
        console.log('addListener');
        response.end('end')
    }
})

//once代表只执行一次 (当第二次执行的时候将被移除 不会执行)
// httpServer.once('request', (request, response) => {
//     if (request.url === '/') {
//         console.log('on');
//         response.end('end2')
//     }
// })

const listener = (request, response) => {
    if (request.url === '/') {
        console.log('addListener');
        response.end('end')
    }
}
// 将listener注册到httpServer上
httpServer.on('request', listener)
// 移除观察者removeListener对应的是off(别名)
// httpServer.removeListener('request', listener)
// httpServer.off('request', listener)

// removeAllListeners移除所有的监听器
httpServer.removeAllListeners('request')

httpServer.listen(3000, () => {
    console.log('listening to port 3000');
})

自定义事件及响应式事件示例

// 自定义事件
const EventEmitter = require('events')
const emitter = new EventEmitter()

// newListener表示只要有一个监听器对某个事件注册,就会被触发
emitter.once('newListener', (event, listener) => {
    if (event === 'myEvent') {
        emitter.on('myEvent', () => {
            console.log('hello');
        })
    }
})
// 注册事件
emitter.on('myEvent', () => {
    console.log('world');
})

// 发射
emitter.emit('myEvent')

底层事件模型与异步操作示例

Node本身能够做到如此高性能的根本原因在于事件(event)的使用,以及对事件监听者(listener, callback)的调用上

  • Node本身是基于事件循环机制的
  • 本质上当Node启动一个文件夹或者服务器后,node实际上是运行在一个死循环中的,如while(true) { ... }
  • 在这个死循环中 Node会不断监听事件、发射事件并且执行回调逻辑
  • 事件来源主要有两种:一是Node自身所发射出的事件,二是来自于node自身所运行的环境
  • 监听事件:回调都是要依附于相应的事件的
  • 执行回调逻辑:本质上都是由底层来执行的
  • 关于IO操作的异步执行逻辑: (同步的阻塞的模式进行文件的读取)
    1. 同步模式: 很好理解 顺序执行
    2. 异步模式: poll epoll(完全基于事件的驱动模式) 非阻塞
  • IOCP libuv 线程池
  • Node的单线程:所谓单线程,指的是Node的逻辑执行主线程是单线程的,既JavaScript代码运行所处的线程,这是个单线程,因为javascript本身只能执行在单线程当中
  • 具体的IO的操作都是在线程池中操作的
  • 当我们在程序中引入某个第三方模块时,那么整体的全部执行逻辑如下所示:
  • Node -> 第三方模块 -> 原生模块 -> 原生模块内部的实现 -> C++模块 -> libuv -> 线程池 -> 可用的线程 -> 执行底层的IO操作(涉及到操作系统调用)
  • 当Node在执行过程中,他会判断当前的操作系统类型:
    1. 启动Node运行时
    2. 检测是否有待处理的事件
    3. 如果没有,回到循环开始
    4. 如果有,那么从事件队列中取出一个事件
    5. 判断当前这个事件有没有与之关联的事件处理器(回调)
    6. 如果没有,回到循环开始
    7. 如果有,则执行事件的回调逻辑
    8. 回到循环开始,开始新一轮的事件检测流程
  • 整个Node的执行过程是由完整的事件循环机制 + 底层操作系统异步IO调用 + 线程池(底层库实现或有操作系统提供)共同配置来完成的
  • 对于单线程的Node来说,是否无法利用到多核的优势呢?
    • 从Node主线程来说,它只能运行在单个核心上
    • 对于底层的线程池来说,它们可以运行在多个核心上面,当然也可以同时运行,因此它们是完全可以利用多核的优势

十二、Node操纵数据库

Node操作mysql

  1. 非关系型数据库(二维表的格式)
  2. npm install mysql 下载mysql
  3. 在数据库中创建表
  4. Node深入浅出_第19张图片
    引入mysql,与本地数据库创建连接
// 引入相关的模块
const mysql = require('mysql')
// const uuid = require('uuid')

//创建连接
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'admin123',
    database: 'test'
})

// 像mysql数据库发送连接
connection.connect((error) => {
    if (error) {
        console.log(error);
        throw error
    } else {
        console.log('connection successful!');
    }
})

Node深入浅出_第20张图片

  1. npm install uuid 下载uuid(一般作为唯一值,是由机器硬件和时间等一系列生成的)
  2. 完整代码
// 引入相关的模块
const mysql = require('mysql')
const uuid = require('uuid')

//创建连接,开启连接池
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'admin123',
    database: 'test'
})

// 像mysql数据库发送连接
connection.connect((error) => {
    if (error) {
        console.log(error);
        throw error
    } else {
        console.log('connection successful!');
        //关闭mysql的连接(end()等待正在执行的事件完成后再关闭)
        // connection.end()

        // 对数据库表进行操作
        // 插入数据
        const userId = uuid.v1()
        const username = '二号男嘉宾'
        const realName = '伍祺峻'
        const age = 38
        const address = 'chengzhou'
        
        //(?)占位符
        connection.query('insert into users set ?', {
            id: userId,
            username: username,
            real_name: realName,
            age: age,
            address: address
        }, (error, result) => {
            if (error) {
                console.log('insert error' + error);
                throw error
            } else {
                console.log(result);
                // 执行对users表的查询
                connection.query('select * from users', (error, result) => {
                    if (error) {
                        console.log('select error' + error);
                        throw error
                    } else {
                        console.log(result);
                        connection.end(error => {
                            if (error) {
                                console.log('end error' + error);
                                throw error
                            }
                        })
                    }
                })
            }
        })
    }
})

Node操纵MongoDB

json格式的数据
下载:https://www.mongodb.com/try/download/community
正常安装完成后 访问localhost:27017 出现
Node深入浅出_第21张图片

就安装成功啦
npm i mongoose 下载mongoose
然后我们与mongoDB建立连接(数据库可以不用事先存在) 以下示例

//引入mongoose
const mongoose = require('mongoose')

// 与mongoDB建立连接(数据库可以不用事先存在)
const uri = 'mongodb://localhost:27017/mytest'

// useNewUrlParser(使用新的url解析器) useUnifiedTopology(统一的Top结构)
mongoose.connect(uri, {useNewUrlParser: true, useUnifiedTopology: true}, error => {
    if (error) {
        console.log(error);
        throw error
    } else {
        console.log('connect successful!');
    }
})

创建好数据库之后 对它执行一些操作示例:

const mongoose = require('mongoose')

// 与mongoDB建立连接(数据库可以不用事先存在)
const uri = 'mongodb://localhost:27017/mytest'

// useNewUrlParser(使用新的url解析器) useUnifiedTopology(统一的结构)
mongoose.connect(uri, {useNewUrlParser: true, useUnifiedTopology: true}, error => {
    if (error) {
        console.log(error);
        throw error
    } else {
        console.log('connect successful!');
        const parentSchema = new mongoose.Schema({
            name: String,
            age: Number,
            address: String
        })
        const studentSchema = new mongoose.Schema({
            name: String,
            age: Number,
            address: String,
            married: Boolean,
            parents: parentSchema
        })

        // 创建模式
        mongoose.model('student', studentSchema)
        const Student = mongoose.model('student')
        const student = new Student({
            // 想插入到数据库中的信息
            name: 'zhangsan',
            age: 20,
            address: 'tianj',
            married: false,
            parents: {
                name: 'lisa',
                age: 40,
                address: 'tianj'
            }
        })
        student.save((error) => {
            if (error) {
                console.log(error);
                throw error
            } else {
                console.log('save successful');
                // 查询所有文档
                student.find({}, (error, docs) => {
                    if (error) {
                        console.log(error);
                        throw error;
                    } else {
                        console.log(docs);
                        // 查询成功 关闭连接
                        // mongoose.connection.close()

                        // 删除某个文档
                        docs.forEach(element => {
                            element.remove();
                        });
                    }
                })
            }
        })
    }
})

Node操纵Redis

下载地址:https://github.com/tporadowski/redis/releases
安装教程:https://www.runoob.com/redis/redis-install.html
npm i ioredis 下载ioredis
async await 以同步的代码方式实现异步的功能

1. 验证登录示例
(1)首先用分层思想创建文件夹,结构如下图

Node深入浅出_第22张图片

(2)先写userRedisDao.js文件,代码如下
const Redis = require('ioredis')
const redisKeyPrefix = 'myRedis:info:user:'

class UserRedisDao {
    // 获取连接 客户端到服务端
    getRedisConnection() {
        return new Redis({
            host: 'localhost',
            port: 6379
        })
    }

    async storeUserId(userSessionId, userId) {
        // 获取redis连接
        const redis = this.getRedisConnection();
        redis.set(redisKeyPrefix + userSessionId, userId, 'ex', 1800, (error, result) => {
            redis.quit();
        })
    }

    async getUser(userSessionId) {
        const redis = this.getRedisConnection()
        return redis.get(redisKeyPrefix + userSessionId, (error, userId) => {
            redis.quit();
            return userId;
        })
    }

    // 重置信息
    async resetUser(userSessionId) {
        const redis = this.getRedisConnection()
        // 将指定的key指定的过期时间设置成指定的值
        redis.expire(redisKeyPrefix + userSessionId, 1800, (error, result) => {
            redis.quit();
        })
    }

    // 移除用户已经存在的sessionId
    async removeUserSessionId(userSessionId) {
        const redis = this.getRedisConnection()
        redis.del(redisKeyPrefix + userSessionId, (error, result) => {
            redis.quit()
        })
    }
}

// 导出
module.exports = new UserRedisDao();
(3)userService.js
const UserRedisDao = require('../db/redis/userRedisDao')

class UserService {
    // 调用底层dao的方法
    async storeUserId(userSessionId, userId) {
        // 在异步方法中等待异步的执行结果 返回之后才能往下走
        await UserRedisDao.storeUserId(userSessionId, userId);
    }

    async getUserSessionId(userSessionId) {
        return await UserRedisDao.getUser(userSessionId)
    }

    async resetUserSessionId(userSessionId) {
        await UserRedisDao.resetUser(userSessionId)
    }

    async removeUserSessionId(userSessionId) {
        await UserRedisDao.removeUser(userSessionId)
    }
}

module.exports = new UserService()
(4)userController.js
const userService = require('../service/userService')
const uuid = require('uuid')

class UserController {
    // 用户登录的方法
    async userLogin(username, password) {
        const userId = username;
        const userSessionId = uuid.v1();
        await userService.storeUserId(userSessionId, userId);
    }

    async userLogout(userSessionId) {
        await userService.userRemove(userSessionId)
    }

    async userOtherOperation(userSessionId) {
        const userId = await userService.getUserSessionId(userSessionId)
        console.log(userId, 'from userController');
        await userService.resetUserSessionId(userSessionId)
    }
}

module.exports = new UserController()
(5)最后编写app2.js代码创建node服务器与客户端建立连接并请求和响应数据
// 使用node服务器 解析用户传来的querystring
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const userController = require('./user/controller/userController')

const server = http.createServer((request, response) => {
    let data = ''
    request.on('data', (chunk) => {
        data += chunk
    })

    // 当所有的数据都传递完毕
    request.on('end', () => {
        const requestUrl = request.url;
        const requestMethod = request.method;
        console.log(requestUrl);

        if (requestUrl.includes('login') && requestMethod === 'GET') {
            const requestParams = url.parse(requestUrl)
            const queryObject = querystring.parse(requestParams.query)
            console.log(queryObject);
            userController.userLogin(queryObject.username, queryObject.passworld)
            response.writeHead(200, {'Content-Type' : 'text/plain'})
            response.end('username' + queryObject.username + ',passworld' + queryObject.passworld)
        }
    })
})

server.listen(3000, () => {
    console.log('listening to port 3000');
})
(6)运行结果

Node深入浅出_第23张图片

2. 在登录的基础上编写登出的逻辑

Node深入浅出_第24张图片

//部分代码
else if (requestUrl.includes('logout') && requestMethod === 'GET') {
    // 解析地址
    const requestPrams = url.parse(requestUrl)
    const queryObject = querystring.parse(requestParams.query)
    console.log(queryObject);

    // 调用登出方法
    userController.userLogout(queryObject.userSessionId)
    response.writeHead(200, {'Content-Type' : 'text/plain'})
    response.end('user logout')
}  else {
    // 验证其他两个方法(favicon.ico是网页标题左侧的图标)
    if (!requestUrl.includes('favicon.ico')) {
        // 解析地址
        const requestPrams = url.parse(requestUrl)
        const queryObject = querystring.parse(requestParams.query)
        console.log(queryObject);
        userController.userOtherOperation(queryObject.userSessionId)
        response.writeHead(200, {'Content-Type' : 'text/plain'})
        response.end('user userOtherOperation')
    }
}
3. 主子进程之间的通信方式(这四种方法❣)

Node深入浅出_第25张图片

十三、cluster模块与master和worker模式

cluster模式:

一个单个的node.js的实例,运行在单线程上,若想充分利用多核系统,用户需要手动启动node.js的cluater集群处理相关的负载。cluster是对child_process的一个封装,可以创建多个子进程帮助node.js主进程分担工作,使用它们协同完成相应的请求和处理,增强了主进程服务的效率,利用多核cpu的威力。

master模式:

简单的说,主从(Master-Slave)与进程-线程的关系类似,Master只有一台机器作为Master,其他机器作为Slave,这些机器同时运行组成了集群.Master作为任务调度者,给多个Slave分配计算任务,当所有的Slave将任务完成之后,最后由Master汇集结果,这个其实也是MapReduce思想所在。

const cluster = require('cluster');
const http = require('http')
const os = require('os')

const cpuCount = os.cpus().length;

// 如果是父进程
if (cluster.isMaster) {
    // fork出来对应的子进程
    for (let i = 0; i < cpuCount; ++i) {
        // 将当前文件从上至下执行
        cluster.fork()
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(worker.process.pid);
    })
} else {
    // 子进程 cpuCount次子进程执行这个httpServer
    const httpServer = http.createServer((request, response) => {
        let data = ''
        request.on('data', (chunk) => {
            data += chunk
        })

        request.on('end', () => {
            response.writeHead({'Content-Type' : 'text/plain'})
            response.end(`${process.pid}`)
        })
    })

    httpServer.listen(3000, () => {
        console.log('listening to port 3000');
    })
}

运行成功后的主进程id以及它监听的子进程:
Node深入浅出_第26张图片
在这里插入图片描述

十四、koa与洋葱模型及中间件示例

koa官方链接:https://koa.bootcss.com/#introduction

基于Node.js平台的下一代Web开发框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

洋葱模型

每一圈代表一个中间件,进入一层中间件访问并返回数据
Node深入浅出_第27张图片

示例代码:

const Koa = require('koa')
const app = new Koa()

// 中间件
app.use(async (ctx, next) => {
    console.log('myFuction start');
    // next()表示调用下一个中间件 等待next()都执行完后再执行后面的
    await next();
    console.log('myFuction finished');
})

// 中间件
app.use(async (ctx, next) => {
    console.log('myFuction2 start');
    // next()表示调用下一个中间件 等待next()都执行完后再执行后面的
    await next();
    console.log('myFuction2 finished');
})

// 中间件
app.use(async (ctx, next) => {
    console.log('myFuction3 start');
    // next()表示调用下一个中间件 等待next()都执行完后再执行后面的
    await next();
    console.log('myFuction3 finished');
})

// 中间件
app.use(async (ctx) => {
    ctx.response.type = 'text/html'
    // ctx.response.body ==== ctx.body
    ctx.response.body = 'Hello Koa'
    ctx.body = '

Hello Koa2

'
}) // 中间件 app.listen(3000, () => { console.log('app listening to port 3000'); })

运行结果:注意默认请求两次(一个是localhost,一个是favicon.ico,请求了一下ico图标)
Node深入浅出_第28张图片

koa项目模块搭建示例

  1. 建立目录

Node深入浅出_第29张图片
Node深入浅出_第30张图片
Node深入浅出_第31张图片
Node深入浅出_第32张图片
2. 下载第三方的中间件 npm i @kao/router
3. npm i koa-bodyparser (能够非常轻松的以Jason的形式拿到请求体)
4. npm i koa-static (对静态文件的处理)
5. npm i koa-compress(请求体压缩插件)
6. npm i koa-combine-routers(组合路由)
7. npm i axios(处理请求)
8. npm i jsonfile (在 Node.js 中轻松读/写 JSON 文件。注意:该模块不能在浏览器中使用。)
9. npm i qs (查询字符串的一个模块解析器)

const path = require('path')
const jsonfile = require('jsonfile')

// __dirname表示当前文件的路径
module.exports = jsonfile.readFileSync(path.join(__dirname, '../projectConfig.json'))
const path = require('path')
const jsonfile = require('jsonfile')

module.exports = jsonfile.readFileSync(path.join(__dirname, 'userServerUrlMapping.json'))

// 解析json文件的
const path = require('path')
const jsonfile = require('jsonfile')

module.exports = jsonfile.readFileSync(path.join(__dirname, 'userRequestUrlMapping.json'))
const axios = require('axios')
const projectConfig = require('../util/projectConfigResolver')

const hostBaseUrl = projectConfig.hostBaseUrl

exports.doHttpGetRequest = function (ctx, requestUrl, params) {
    return this.doHttpRequest(ctx, requestUrl, params, 'GET')
}
exports.doHttpPutRequest = function (ctx, requestUrl, params) {
    return this.doHttpRequest(ctx, requestUrl, params, 'PUT')
}
exports.doHttpPostRequest = function (ctx, requestUrl, params) {
    return this.doHttpRequest(ctx, requestUrl, params, 'POST')
}
exports.doHttpDeleteRequest = function (ctx, requestUrl, params) {
    return this.doHttpRequest(ctx, requestUrl, params, 'DELETE')
}

exports.doHttpRequest = function (ctx, requestUrl, params, method) {
    if ('GET' === method) {
        return axios({
            baseURL: hostBaseUrl,
            url: requestUrl,
            method: 'GET',
            params: params
        })
    } else if ('PUT' === method || 'POST' === method || 'DELETE' === method) {
        return axios({
            baseURL: hostBaseUrl,
            url: requestUrl,
            method: method,
            data: params 
        })
    }
}
const qs = require('qs')
const baseHttpClinet = require('../common/baseHttpClinet')
const userRequestUrlMappingResolver = require('../config/client/userRequestUrlMappingResolver')

class UserController {
    async login(ctx) {
        const requestUrl = userRequestUrlMappingResolver.login
        console.log(ctx.request.body);
        const response = await baseHttpClinet.doHttpPostRequest(ctx, requestUrl, JSON.stringify(ctx.request.body))
        const responseData = qs.parse(response.data)
        const responseDataCode = responseData.result.code;
        if (0 === responseDataCode) {
            // 请求成功
            ctx.body = responseData
        } else {
            ctx.body = responseData
        }
    }
}

module.exports = new UserController()
const Router = require('@koa/router')
const userRouter = new Router()
const userController = require('../controller/userController')
const userServerUrlMappingResolver = require('../config/server/userServerUrlMappingResolver')

userRouter.post(userServerUrlMappingResolver.login, userController.login)

module.exports = userRouter
const Koa = require('koa')
const path = require('path')
const combineRouters = require('koa-combine-routers')
const bodyParser = require('koa-bodyparser')
const koaStatic = require('koa-static')
const compress = require('koa-compress')

const app = new Koa()

const userRouter = require('../router/userRouter')

app.use(compress({
    threshold: 2048
}))

// 把请求体中的内容封装成一个一个的对象
app.use(bodyParser())

// 引入静态文件的处理
app.use(koaStatic(path.join(__dirname, '../dist')))

// 模块多 
const unifiedRouters = combineRouters(
    userRouter
)()
app.use(unifiedRouters)

module.exports = app
require('../app/app').listen(3000)

console.log('Koa2 Server local port 3000');

Node深入浅出_第33张图片

  1. 返回得数据
  2. Node深入浅出_第34张图片
// 用于真正实现登录请求的逻辑处理
const Koa = require('koa')

const app = new Koa()

app.use(async (ctx) => {
    ctx.response.type = 'application/json'
    const responseBody = {
        result: {
            code: 0,
            description: 'success'
        }, data: {
            username: 'zhangsan',
            address: 'taiyuan',
            age: 20
        }
    }
    ctx.body = JSON.stringify(responseBody)
})

app.listen(4000)
  1. 同步:方法名后面都会加上一个Sync,并且把读到的内容作为方法返回的结果
  2. 异步:方法返回的结果都通过回调函数来达成的

你可能感兴趣的:(学无止境,前端,javascript,服务器)