Skip to main content

Node.js 架构及使用示例

一、什么是 Node.js

Node.js® 是一个开源、跨平台的 JavaScript 运行时环境。

二、Node.js 的架构

Node.js 由 Libuv、Chrome V8、一些核心 API 构成,如图:

  • Node Standard Library:Node.js 标准库,对外提供的 JavaScript 接口,例如模块 http、buffer、fs、stream 等;
  • Node bindings:JavaScript 与 C++ 连接的桥梁,对下层模块进行封装,向上层提供基础的 API 接口;
    • V8:Google 开源的高性能 JavaScript 引擎,使用 C++ 开发,负责把 JavaScript 代码转换成 C++,然后去跑这层 C++ 代码;
    • Libuv:是使用 C 和 C++ 为 Node.js 开发的一个跨平台的支持事件驱动的 I/O 库,同时也是 I/O 操作的核心部分,例如读取文件和 OS 交互,Node 中的 Event Loop 就是由 libuv 来初始化的;

三、Node.js 特点/适用场景

Node.js 利用单线程,远离多线程死锁、状态同步等问题;而且利用异步 I/O,让单线程远离阻塞,以更好地使用 CPU。

由于 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效,避免了等待输入输出(数据库、文件系统、Web服务器...)响应而造成的 CPU 时间损失。所以 Node.js 适合运用在高并发、I/O 密集、少量业务逻辑的场景。

对应到业务上,如果只是简单的数据库增删改查、流量不大的项目,Server 端可以完全使用 Node.js 实现;而流量较大,复杂度高的项目,可以用 Node.js 作为接入层,再由后端同学负责实现服务。如图:

四、Node.js 的示例

1、简单示例

新建文件 server.js

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

运行以下命令:

node server.js

打开 http://127.0.0.1:3000/

控制台可以看到:

可通过 Postwoman 插件调试

2、发送 GET/POST 请求

const http = require('node:http');
const querystring = require('node:querystring')

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
const method = req.method
const url = req.url
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1])

// 设置返回格式为 JSON
res.setHeader('Content-type', 'application/json')

// 返回的数据
const resData = {
method,
url,
path,
query
}

// 返回
if (method === 'GET') {
res.end(
JSON.stringify(resData)
)
}
if (method === 'POST') {
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
resData.postData = postData
res.end(
JSON.stringify(resData)
)
})
}
});

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

GET 调试结果:

POST 调试结果:

3、Node.js 项目构建

以下是原生 Node.js 的接口开发示例,点击查看 Express 项目示例

3-1、项目初始化

  1. 初始化 package.json
  2. 安装 nodemon 监测文件变化,自动重启 node;
  3. 安装 cross-env 跨平台运行脚本,设置环境变量,兼容 mac、linux 和 windows;
  4. 添加入口文件 bin/www.js,在这里配置 Server;
  5. 将业务代码分离至 app.js,在入口文件引入。
package.json
{
"name": "node-web-server-blog",
"version": "1.0.0",
"description": "",
"main": "bin/www.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js",
"prd": "cross-env NODE_ENV=production nodemon ./bin/www.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"cross-env": "^7.0.3",
"nodemon": "^2.0.20"
}
}

运行 yarn devyarn prd,即可启动开发环境或生产环境的服务。

3-2、路由接口开发

以一个博客项目为例,在 app.js 中引入以下接口:

博客接口:

  • 获取博客列表(GET)
  • 获取博客详情(GET)
  • 新建一篇博客(POST)
  • 更新一篇博客(POST)
  • 删除一篇博客(POST)

用户接口:

  • 用户登录(POST)
app.js
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')

const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader('Content-type', 'application/json')

// 统一处理 path
const url = req.url
req.path = url.split('?')[0]

// 处理 blog 路由
const blogData = handleBlogRouter(req, res)
if (blogData) {
res.end(
JSON.stringify(blogData)
)
return
}

// 处理 user 路由
const userData = handleUserRouter(req, res)
if (userData) {
res.end(
JSON.stringify(userData)
)
return
}

// 未命中路由,返回 404
res.writeHead(404, { "Content-type": "text/plain" })
res.write("404 not Found\n")
res.end()
}

module.exports = serverHandle

3-3、数据模型建立/数据处理分离

  1. src/model 下建立统一的接口返回模型;
  2. router 路由与其中的数据处理分离;

新建统一的返回模型:

src/model/resModel.js
class BaseModel {
constructor(data, message) {
if (typeof data === 'string') {
this.message = data
data = null
message = null
}
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}

class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.status = 0
}
}

class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.status = -1
}
}

module.exports = {
SuccessModel,
ErrorModel
}

点击查看此步骤的完整代码

3-4、连接配置 MySQL 数据库

点击学习 Node.js 连接操作 MySQL 数据库详情

yarn add mysql2 -D
src/config/db.js
const env = process.env.NODE_ENV  // 环境参数

// 配置
let MYSQL_CONFIG

if (env === 'dev') {
MYSQL_CONFIG = {
host: 'localhost',
user: 'root',
password: 'xxx',
port: 3306,
database: 'myblog'
}
}

if (env === 'production') {
// 这里应为线上配置,暂时用本地替代
MYSQL_CONFIG = {
// 同上
}
}

module.exports = {
MYSQL_CONFIG
}

点击查看此步骤的完整代码

3-5、接口对接 MySQL 数据库

src/controller/blog.js
// 获取博客列表接口的数据处理函数
const { exec } = require('../db/mysql')

// 根据 author 和 type 查询博客列表
const getList = (author, type) => {
let sql = `select * from blogs where 1=1 `
if (author) {
sql += `and author = '${author}'`
}
if (type) {
sql += `and type = '${type}'`
}
sql += `order by createtime desc`

// 返回 promise
return exec(sql)
}

运行:

yarn dev

访问 http://127.0.0.1:7676/api/blog/list,结果如下:

点击查看此步骤的完整代码

五、Node.js 调试

v8 Inspector Protocol 是 nodejs v6.3 新加入的调试协议,通过 websocket与 Client/IDE 交互,同时基于 Chrome/Chromium 浏览器的 devtools 提供了图形化的调试界面。Node.js 可通过 Inspector Protocol 配合 Chrome 开发者工具进行调试,具体步骤如下:

{
"scripts": {
"dev": "cross-env NODE_ENV=dev ./node_modules/.bin/nodemon bin/www",
"dev": "cross-env NODE_ENV=dev ./node_modules/.bin/nodemon --inspect=9222 bin/www",
},
}

运行项目后,直接访问监听的端口页,打开 Chrome 开发者工具,可看到调试按钮,点开即可进入调试页:

可访问 chrome://inspect/#devices 查看当前浏览器监听的所有 inspect:

点开 Configure 可修改或新增监听端口。