NodeJS

发布于 2022-06-20  101 次阅读


node.js

js

image-20220607230908585

js 为什么在浏览器中可以执行

待执行的 js 代码 通过 JavaScript 解析引擎 执行

不同的浏览器使用不同的 JavaScript 解析引擎:

Chrome 浏览器 => V8

Firefox 浏览器 => OdinMonkey(奥丁猴)

Safri 浏览器 => JSCore

IE 浏览器 => Chakra(查克拉)

etc...

其中,Chrome 浏览器的 V8 解析引擎性能最好!

为什么 JavaScript 可以操作 DOM 和 BOM

image-20220608093435043

每个浏览器都内置了 DOM、BOM 这样的 API 函数,因此,浏览器中的 JavaScript 才可以调用它们

运行环境是指代码正常运行所需的必要环境

image-20220608093534033

  • V8 引擎负责解析和执行 JavaScript 代码。

  • 内置 API 是由运行环境提供的特殊接口,只能在所属的运行环境中被调用。

node.js

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

image-20220608094042498

注:

  • 浏览器 是js的 前端运行环境
  • node 是js的 后台运行环境
  • node 中 无法调用 DOM 和 BOM 等 浏览器内置API

node 可以做什么

Node.js 作为一个 JavaScript 的运行环境,仅仅提供了基础的功能和 API。然而,基于 Node.js 提供的这些基础能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了 Node.js ,可以让前端程序员胜任更多的工作和岗位:

基于 Express 框架(http://www.expressjs.com.cn/),可以快速构建 Web 应用

基于 Electron 框架(https://electronjs.org/),可以构建跨平台的桌面应用

基于 restify 框架(http://restify.com/),可以快速构建 API 接口项目

读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…

总之:Node.js 是大前端时代的“大宝剑”,有了 Node.js 这个超级 buff 的加持,前端程序员的行业竞争力会越来越强!

node 安装

LTS : 长期稳定版

Current : 新特性测试版

查看安装版本

node -v

在 node 中执行 js

  • win + r
  • 输入 node 要执行的 js 文件路径
console.log("Hello nodejs");
node 1.js
//  Hello nodejs

fs 文件系统模块

fs 模块 用来 操作文件 的模块

  • fs.readFile() 读取文件
  • fs.writeFile() 写入文件

image-20220608104004610

js 中 导入

const fs = require('fs');

读写

const fs = require('fs');

// Read file
fs.readFile('../flies/1.md', 'utf8', (err, data) => {
    if (err) {
        console.log(err);
    }
    console.log(data);
});

// Write file
fs.writeFile('../flies/1.md', 'Hello Node.js', (err) => {
    if (err) {
        console.log(err);
    }
    console.log('File written successfully');
});

__dirname : 当前文件目录

注:

fs.writeFlie() 只能创建文件 不能创建目录

fs.writeFlie() 重复执行新的内容会覆盖旧内容

path 路径模块

path 模块 用来 处理路径 的模块

  • path.join() 用来将多个路径片段拼接成一个完整的路径字
  • path.basename() 用来从路径字符串中,将文件名解析出来

导入

const path = require('path');

练习 path.join()

const fs = require('fs');
const path = require('path');

console.log(__dirname);
// Write file
fs.writeFile(path.join(__dirname, '../flies/1.md'), 'Hello World', (err) => {
    if (err) throw err;
    console.log('File created');
});

// Read file
fs.readFile(path.join(__dirname, '../flies/1.md'), 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});

读取文件不在使用 + 号拼接 使用 path.join()

path.basename()

path.extname()

const fpath = "./flies/1.md";
console.log(path.basename(fpath));    // 1.md

// 移除扩展名 
console.log(path.basename(fpath,'.md'));    // 1

// 获取文件后缀名
console.log(path.extname(fpath));  // .md

console.log(path.basename(fpath, path.extname(fpath)));   // 1

练习

将 ./flies/index.html 拆分成 ./shi.css ./shi.js ./shi.html

// 将 ./flies/index.html 拆分成 ./shi.css   ./shi.js   ./shi.html
const path = require('path');
const fs = require('fs');

// 定义正则表达式匹配 <style></style> 标签
const reg = /<style>[\s\S]*<\/style>/;
// 定义正则表达式匹配 <script></script> 标签
const reg2 = /<script>[\s\S]*<\/script>/;

// 读取文件
fs.readFile(path.join(__dirname, '../flies/index.html'), 'utf8', (err, data) => {
    if (err) {
        console.log("读取文件失败:" + err);
    }
    // 调用方法读取 css 文件
    resolveCss(data);
    // 调用方法读取 js 文件
    resolveJs(data);
    // 调用方法读取 html 文件
    resolveHtml(data);
});

// 处理 css 文件
function resolveCss(htmlStr) {
    // 使用正则表达式匹配 <style></style> 标签
    let cssStr = reg.exec(htmlStr);
    // 如果匹配到了 <style></style> 标签
    if (cssStr) {
        // 将匹配到的 <style></style> 标签替换成空字符串
        cssStr = cssStr[0].replace('<style>','').replace('</style>','');
    }
    // 将 cssStr 写入到 ./css/shi.css 文件中
    fs.writeFile(path.join(__dirname, '../css/shi.css'), cssStr, (err) => {
        if (err) {
            console.log("写入文件失败:" + err);
        }
        console.log("写入文件成功");
    });
}

// 处理 js 文件
function resolveJs(htmlStr) {
    // 使用正则表达式匹配 <script></script> 标签
    let jsStr = reg2.exec(htmlStr);
    // 如果匹配到了 <script></script> 标签
    if (jsStr) {
        // 将匹配到的 <script></script> 标签替换成空字符串
        jsStr = jsStr[0].replace('<script>','').replace('</script>','');
    }
    // 将 jsStr 写入到 ./js/shi.js 文件中
    fs.writeFile(path.join(__dirname, '../js/shi.js'), jsStr, (err) => {
        if (err) {
            console.log("写入文件失败:" + err);
        }
        console.log("写入文件成功");
    });
}

// 处理 html 文件
function resolveHtml(htmlStr) {
    // 将 <style></style> 标签替换成 <link rel="stylesheet" href="./css/shi.css">
    htmlStr = htmlStr.replace(reg, '<link rel="stylesheet" href="./css/shi.css">');
    // 将 <script></script> 标签替换成 <script src="./js/shi.js"></script>
    htmlStr = htmlStr.replace(reg2, '<script src="./js/shi.js"></script>');

    // 将 替换后的文件 写入到 ./shi.html 文件中
    fs.writeFile(path.join(__dirname, '../shi.html'), htmlStr, (err) => {
        if (err) {
            console.log("写入文件失败:" + err);
        }
        console.log("写入文件成功");
    });
}

http 模块

什么是 客户端 什么是 服务器?

在网络节点中 负责消费资源的电脑,叫做客户端

负责对外提供网络资源的电脑,叫服务器

http 模块 用来创建web服务器 的模块

http.createServer() 把一台普通电脑 变成 Web 服务器

导入

const http = require('http');

在 node.js 中 不需要 IIS,Apache 等 第三方服务器软件

通过几行简单的代码,就能轻松手写一个服务器软件

const http = require('http');

// 创建 web 服务器    为服务器添加监听事件
http.createServer().on('request', (req, res) => {
      // 设置响应头
      res.writeHead(200, {
          'Content-Type': 'text/html;charset=utf-8'
      });
      // 设置响应体
      const url = req.url;
      // 判断不同的请求路径,返回不同的响应体
      if (url === '/') {
          res.end('<h1>Hello World</h1>');
      } else if (url === '/index.html') {
          res.end('<h1>Hello World index</h1>');
      } else {
          res.end('<h1>404 Not found!</h1>');
      }
      // 打印客户端请求的 url 和 method
      console.log(req.url);
      console.log(req.method);
// 启动服务器
}).listen(8080, () => {
     console.log('Server is running at http://localhost:8080');
}).on('error', (err) => {
     console.log(err);
// 关闭服务器
}).on('close', () => {
     console.log('Server is closed');
});

只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数

req 请求

req.url 是客户端请求的 URL 地址

req.method() 是客户端 请求的 method 请求类型

res 响应

res.end() 向客户端响应数据

模块化

模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元

把一个 大文件 拆分成 独立并且互相依赖的多个模块

  • 提高代码复用性
  • 提高代码可维护性
  • 可以实现按需加载

模块划分

  • 内置模块 由 nodejs 官方提供 如 fs, path, http 等
  • 自定义模块 创建的每个 js 文件 都是自定义模块
  • 第三方模块 由第三方开发出来的模块 使用前需要下载

加载自定义模块 可以省略 .js 后缀

// 加载自定义的模块
const m1 = require('./2.js');

模块作用域

在自定义模块中定义的变量,方法 只能在当前模块内被访问

防止全局作用域污染问题

模块作用域.js

const username = '张三';

function sayHello() {
    console.log('Hello ' + username);
}

test.js

const custom = require('./模块作用域');

console.log(custom);

只能在当前模块访问

PS E:\project\front\node\nodeStudy\js> node .\test.js
{}

向外共享模块作用域中的成员

model.exports()

在 模块作用域.js 添加

module.exports = {username, sayHello};
PS E:\project\front\node\nodeStudy\js> node .\test.js
{ username: '张三', sayHello: [Function: sayHello] }
exports()

model.exports() 和 exports() 指向同一个对象

exports.username = username;
exports.sayHello = sayHello;
PS E:\project\front\node\nodeStudy\js> node .\test.js
{ username: '张三', sayHello: [Function: sayHello] }

最终都是以 model.exports() 指定的对象为准

image-20220608152232425

nodejs 中的 第三方模块 就是

包是基于内置模块封装出来的 提供了更加高级,更加方便的 API ,极大的提高了开发效率

下载包

下载地址

可以通过 npm 直接下载

npm

语法

npm install 包名
npm i 包名     # 简写
npm i 包名 -g     # 全局安装
npm i moment@2.22.2   # 安装指定版本的包

卸载包

npm uninstall 包名

通过 淘宝npm镜像 提高 npm 下载速度

# 查看当前的下包镜像源
npm config get registry
# 将镜像源切换为淘宝镜像源
npm config get registry=https://registry.npm.taobao.org/

nrm 更方便切换下包镜像源

# 通过 npm 包管理器 将 nrm 安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源
nrm ls
# 切换淘宝镜像
nrm use taobao

i5ting_toc 一个可以将 md 文档转为 html 页面的小工具

npm i -g i5ting_toc
# md 转 html
i5ting_toc -f 1.md -o

image-20220608164546294

结果如下

image-20220608164627204

格式化时间

image-20220608154009782

const moment = require('moment');

let dt = moment().format('YYYY-MM-DD HH:mm:ss');
console.log(dt);
PS E:\project\front\node\nodeStudy\js> node .\time.js
2022-06-08 15:45:22

npm 执行后项目会多一个 node_modulespackage-lock.json

node_modules : 存放所有已经安装到项目的包

package-lock.json : 记录 node_modules 目录下的每一个包的下载信息

image-20220608155409186

对人协作开发问题

image-20220608155645341

package.json 中记录了项目中安装了那些包 共享时只要把 package.json 共享即可

"dependencies": {
    "jquery": "^3.6.0",
    "moment": "^2.29.3"
 }

在项目开发中 应该把 node_modules 文件夹 添加到 .gitignore 忽略文件中

接收到别人共享文件后

执行 npm i 就可以一次性安装所有的依赖性

npm i 会读取 package.json 中的所有依赖包 一次性下载

dependencies 和 devDependencies

devDependencies 包只在项目开发阶段使用,在上线后不使用

dependencies 包在项目开发阶段和上线后都要使用

npm i 包名 -D  # 记录到 devDependencies 节点中
npm i 包名 --save-dev  # 完整写法
{
  "dependencies": {
    "jquery": "^3.6.0",
    "moment": "^2.29.3"
  },
  "devDependencies": {
    "webpack": "^5.73.0"
  }
}

创建属于自己的开发包

  • 初始化包的基本结构

    image-20220608165542499

  • 初始化 package.json
{
    "name" : "xiaotao-tools",
    "version" : "0.0.1",
    "description" : "提供了格式化时间的工具",
    "author" : "xiaotao",
    "license" : "ISC",
    "main" : "index.js",
    "keywords" : ["time", "format" ,"xiaotao"],
    "scripts" : {
        "test" : "echo \"Error: no test specified\" && exit 1"
    }
}
  • 在 index.js 中写 格式化时间的逻辑代码
// 包的入口文件

// 定义格式化时间的函数
function formatDate(date, fmt) {
    const dt = new Date(date);

    const y = dt.getFullYear();
    const m = padZero(dt.getMonth() + 1);
    const d = padZero(dt.getDate());
    const h = padZero(dt.getHours());
    const i = padZero(dt.getMinutes());
    const s = padZero(dt.getSeconds());
    const ms = padZero(dt.getMilliseconds());
    const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    const weekDay = week[dt.getDay()];
    const time = `${h}:${i}:${s}`;
    const dateTime = `${y}-${m}-${d} ${time}`;
    const dateTimeWithWeek = `${y}-${m}-${d} ${time} ${weekDay}`;
    const dateTimeWithWeekAndMs = `${y}-${m}-${d} ${time} ${weekDay} ${ms}`;

    return `${y}-${m}-${d} ${h}:${i}:${s} ${weekDay}`;
}

// 定义一个补零的函数
function padZero(num) {
    return num < 10 ? `0${num}` : num;
}

// 向外暴露一个格式化时间的函数
module.exports = {
    formatDate
}
  • 在 index.js 中定义转义及还原 html 的方法
// 定义转义html字符的函数
function escapeHtml(html) {
    return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '$quot;');
}
// 定义还原html字符的函数
function unescapeHtml(html) {
    return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
  • 将不同的功能进行模块化拆分
    • 格式化时间 dataFormat.js
    • 处理 HTML 字符串的功能 htmlEscape.js

image-20220608173607152

dateFormat.js

// 定义格式化时间的函数
function formatDate(date, fmt) {
    const dt = new Date(date);

    const y = dt.getFullYear();
    const m = padZero(dt.getMonth() + 1);
    const d = padZero(dt.getDate());
    const h = padZero(dt.getHours());
    const i = padZero(dt.getMinutes());
    const s = padZero(dt.getSeconds());
    const ms = padZero(dt.getMilliseconds());
    const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    const weekDay = week[dt.getDay()];
    const time = `${h}:${i}:${s}`;
    const dateTime = `${y}-${m}-${d} ${time}`;
    const dateTimeWithWeek = `${y}-${m}-${d} ${time} ${weekDay}`;
    const dateTimeWithWeekAndMs = `${y}-${m}-${d} ${time} ${weekDay} ${ms}`;

    return `${y}-${m}-${d} ${h}:${i}:${s} ${weekDay}`;
}

// 定义一个补零的函数
function padZero(num) {
    return num < 10 ? `0${num}` : num;
}

module.exports = {
    formatDate
}

escapeHtml.js

// 定义转义html字符的函数
function escapeHtml(html) {
    return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '$quot;');
}

// 定义还原html字符的函数
function unescapeHtml(html) {
    return html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}

module.exports = {
    escapeHtml, unescapeHtml
}

index.js

// 包的入口文件
const formatDate = require('./src/dateFormat');
const escapeHtml = require('./src/escapeHtml');

// 向外暴露
module.exports = {
    ...formatDate,    // ... 将里面的属性展开
    ...escapeHtml
}
  • 编写包的说明文档 README.md
安装
npm install xiaotao-tools
导入
const xiaotao = require('./xiaotao-tools');
格式化时间
let dt = xiaotao.formatDate(new Date());
console.log(dt);
// 结果  2022-06-08 17:35:39 星期三
转义 HTML 中的特殊字符
let html = xiaotao.escapeHtml('<h1>你好</h1>');
console.log(html);
// 结果   <h1>你好</h1>
还原 HTML 中的特殊字符
console.log(xiaotao.unescapeHtml(html));
// 结果  <h1>你好</h1>
  • 发布

https://www.npmjs.com/ 注册一个账号

  • 在终端登录
npm login 
依次输入   用户名  密码   邮箱

登录之前必须切换为 官方镜像

image-20220608180458300

发布 切换到包的根目录xiaotao-tools

npm publish

image-20220608181131015

查看官网

image-20220608181344990

image-20220608181407889

删除已经发布的包
npm unpublish 包名 --force 
  • 只能删除 72 小时内的包
  • 24 小时内不能重复发送

模块的加载机制

模块在第一次加载后会被缓存 多长调用 require() 不会重复执行

三种模块 都会 优先从缓存中加载 提高模块的加载效率

内置模块的优先级最高

Express

Express 是基于 Nodejs 平台,快速 开放 极简 的 Web 开发框架

Express 的作用和nodejs 内置的 http 模块类型

本质: 就是一个 npm 第三方包 用于 快速创建 web 服务器

内置http 模块用起来复杂,开发效率低 Express 是基于 http 模块进一步封装出来的,能够极大提高开发效率

安装

npm i express

创建 web 服务器

const express = require('express');
// 创建 web 服务器    为服务器添加监听事件
const app = express();

// // 监听 get post 请求
app.get('/', (req, res) => {
    res.send('Hello World');
}).get('/index.html', (req, res) => {
    res.send('Hello World index');
}).get('/404', (req, res) => {
    res.send('404 Not found!');
});

app.post('/', (req, res) => {
    res.send('Hello World');
}).post('/index.html', (req, res) => {
    res.send('Hello World index');
}).post('/404', (req, res) => {
    res.send('404 Not found!');
});

// 设置响应头
app.listen(80, () => {
    console.log('Server is running at http://localhost');
}).on('error', (err) => {
    console.log(err);
}).on('close', () => {
    console.log('Server is closed');
});

req.query

获取客户端发送过来的查询参数 默认是 一个 空对象

app.get('/', (req, res) => {
    console.log(req.query);
    res.send(req.query);
})

image-20220608225526232

image-20220608225634233

req.params

获取 url 中的 动态参数

app.post('/:id', (req, res) => {
    res.send(req.params);
})

image-20220608230035020

托管静态资源

express.static()

可以创建一个 静态资源服务器

// 将 public 下的 css js img 对外开放访问
app.use(express.static('public'))
const express = require('express');
const app = express();

// 将 public 下的 文件 对外开放访问
app.use(express.static('./public'))

app.listen(80, () => {
    console.log('Server is running at http://localhost');
});

托管多个

app.use(express.static('./public'))
app.use(express.static('./files'))

在访问之前挂载前缀

app.use('/static', express.static('../files'))

nodemon

热部署工具

安装

npm i -g nodemon

使用

node app.js
# 将上面的 node 替换为 nodemon
nodemon app.js

image-20220608233201109

路由

Express 中的路由

客户端的请求服务器处理函数 之间的映射关系

组成

app.METHOD(PATH, HANDER)
       // 请求类型   url      函数
app.get('/index.html', (req, res) => {
    res.send('Hello World index');
})

image-20220608234005415

模块化路由

为了方便对路由管理 express 不建议直接挂载到 app 上 而是推荐将路由抽离为单独的模块

步骤:

  1. 创建路由模块对应的 .js 文件
  2. 调用 express.Router() 函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 使用 module.exports 向外共享路由对象
  5. 使用 app.use() 函数注册路由模块

app.js

const express = require('express');
const app = express();

// 导入路由模块
const router = require('./router');
// 将路由模块挂载到 app 中
app.use(router);

app.listen(80, () => {
    console.log('Server is running at http://localhost');
});

router.js

const express = require('express');
// 创建路由模块
const router = express.Router();

// 挂载具体路由
router.get('/user/list', (req, res) => {
    res.send('get user list');
});

router.post('/user/add', (req, res) => {
    res.send('add user');
});

// 向外导出路由模块
module.exports = router;

image-20220608235940876

// 添加统一访问前缀
app.use('/api', router);

中间件

app.use('router') 就是一个中间件

作用: 对请求进行 预处理

image-20220609000639874

中间件格式

中间件函数的形参列表中, 必须包含 next 参数

路由处理函数中只包含 req 和 res

image-20220609000940896

next 函数作用

多个中间件连续调用的关键 把流转关系交给下一个 中间件 或 路由

image-20220609001157780

const express = require('express');
const app = express();

// 定义 中间件 函数
const logger = (req, res, next) => {
    console.log('logger');
    // 调用 next 函数,放行下一个中间件
    next();
}

app.listen(80, () => {
    console.log('Server is running at http://localhost');
});

全局生效的中间件

客户端发起的请求,到达服务器之后,都会执行的中间件

通过调用 app.use(中间件函数) 即可定义一个 全局中间件函数

// 全局中间件
app.use(logger);

可以做一些 类似于 日志的 处理

作用

多个中间件之间 共享 同一份 req 和 res 我们可以在 上游 的中间件中,统一 为 req res 添加自定义的 属性 和 方法 ,供 下游 使用

image-20220609002805503

const logger = (req, res, next) => {
    console.log('logger');
    req.a = 10;
    // 调用 next 函数,放行下一个中间件
    next();
}

app.use('logger')

app.get('/', (req, res) => {
    console.log(req.a);
    res.send('Hello World' + req.a);
});

多个全局中间件

// 定义 中间件 函数
const logger = (req, res, next) => {
    console.log('logger');
    req.a = 10;
    // 调用 next 函数,放行下一个中间件
    next();
}

// 多个全局中间件
app.use(logger).use((req, res, next) => {
    console.log('use');
    next();
}); 

app.get('/', (req, res) => {
    console.log(req.a);
    res.send('Hello World' + req.a);
});

局部中间件

不使用 app.use() 定义的中间件

// 定义局部中间件
const mw = (req, res, next) => {
    console.log("这是局部中间件");
    next();
}

// 该中间件只有在调用过的路由生效
// 可以同时调用多个中间件
app.get("/user", mw, mw2, (req, res) => {
    res.send("调用了局部中间件:" + mw);
})

中间件注意事项

  • 中间件必须在路由之前注册
  • 执行完中间件函数后 必须写 next()
  • 一个路由可以同时调用多个中间件
  • 只要调用 next() , 后面就不要写代码
  • 连续调用多个中间件 共享同一个 req 和 res

中间件分类

五大类

应用级中间件

通过 app.use() app.get() app.post() 绑定到 app 实例上的中间件

// 全局中间件
app.use((req, res, next) => {
    next();
}); 

// 局部中间件
app.get("/user", mw, (req, res) => {
    res.send("user" );
})

路由级中间件

绑定到 router 实例上的中间件

router.use(function (req, res, next) {
    console.log("user");
    next();
})

错误级中间件

专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题

格式:

function(err, req, res, next) 必须有四个形参

app.get('/user/list', (req, res) => {
    // 自定义一个错误
    throw new Error("服务器发生错误");
    // 导致后面代码不能正常执行
    res.send('get user list');
});

// 定义一个错误级中间件
app.use((err, req, res, next) => {
    if (err.status === 500) {
        console.log('发生了错误' + err.message);
        res.send('Error:' + err.message);
    } else {
        next();
    }
});
  • 错误级中间件 必须放在路由之后

Express 中间件

express.static

快速托管静态资源

express.json

解析 json 格式的请求数据 (仅在 4.16.0+ 版本有用)

app.use(express.json())
express.urlencoded

解析 URL-encoded 格式的请求数据 (仅在 4.16.0+ 版本有用)

app.use(express.urlencoded({ extended: false }))

req.body 接收客户端发送过来的请求数据

第三方中间件

自定义中间件

处理 客户端表单数据

diy.js

const qs = require('querystring'); 

// 解析表单数据中间件
const bodyParser = (req, res, next) => {
    // 定义一个 str 字符串 储存客户端发送过来的请求体数据
    let str = '';
    // 监听 req 的 data 事件
    req.on('data', (chunk) => {
        // 每次接收到客户端发送过来的数据,都会触发 data 事件
        // chunk 是一个二进制数据流,需要转换成字符串
        str += chunk;
    });
    // 监听 req 的 end 事件
    req.on('end', () => {
        // 当客户端发送完请求体数据后,会触发 end 事件
        // 将请求体数据转换成对象
        req.body = qs.parse(str);
        console.log(req.body);
        // 调用 next 函数,放行下一个中间件
        next();
    });
}

module.exports = bodyParser;
// 导入 express 模块
const express = require('express');
const app = express();
const bodyParser = require('./diy');

app.use(bodyParser);
app.post('/user', (req, res) => {
    res.send(req.body);
})

// 启动服务器
app.listen(80, () => {
    console.log('Server is running at http://localhost');
});

完整请求

app.js

const express = require('express');
const app = express();

app.use(express.json())
app.use(express.urlencoded({ extended: false }))

// 导入路由模块
const router = require("./router");
// 将路由模块挂载到 app 中
app.use(router);

app.listen(80, () => {
    console.log('Server is running at http://localhost');
});

router.js

const express = require('express');
// 创建路由模块
const router = express.Router();

// get 请求
router.get('/user/list', (req, res) => {
    // 获取客户端发送到服务器的数据
    const query = req.query;
    res.send({
        status: 0,
        msg: 'success',
        data: query
    });
});

// post 请求
router.post('/user/add', (req, res) => {
    // 获取客户端发送到服务器的数据
    const body = req.body;
    res.send({
        status: 0,
        msg: 'success',
        data: body
    });
});

router.use(function (req, res, next) {
    console.log("user");
    next();
})

// 向外导出路由模块
module.exports = router

CORS 跨域

安装

npm i cors

在路由之前配置 cors 解决接口跨域问题

const cors = require('cors')
app.use(cors())

cors 响应头部

Access-Control-Allow-Origin

res.setHeader('Access-Control-Allow-Origin','*')    // 允许任何域请求
res.setHeader('Access-Control-Allow-Origin','www.xiaotao.cloud')    // 允许指定域请求

Access-Control-Allow-Headers

默认情况下,CORS 仅支持客户端向服务器发送 9 个请求头

如果需要额外的请求头信息,则需要设置

res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');

Access-Control-Allow-Methods

默认情况下 cors 仅支持 get post head 请求

// 只允许某些请求
res.setHeader('Access-Control-Allow-Methods', 'post, get, delete, put');
// 允许所有
res.setHeader('Access-Control-Allow-Methods', '*');

连接MySQL

安装 MySQL 模块

npm i mysql

CRUD

const mysql = require('mysql');
// 建立与 mysql 的连接
const db = mysql.createPool({
    host: '127.0.0.1',
    user: 'root',
    password: '123456',
    database: 'blog'
});

// 查询所有
db.query(`SELECT * FROM blog_article`, (err, result) => {
    if (err) console.log(err);
    console.log(result);
});

// 添加
db.query(`INSERT INTO blog_tag VALUES ('java', 'http://www.xiaotao.cloud/java')`, (err, result) => {
    if (err) console.log(err);
    if (result.affectedRows > 0) {
        console.log('添加成功');
    } else {
        console.log('添加失败');
    }
});

// 添加 简写
const blog_tag = { tag_name : 'spring', tag_url : 'http://www.xiaotao.cloud/spring' };
db.query(`INSERT INTO blog_tag SET ?`, blog_tag, (err, result) => {
    if (err) console.log(err);
    if (result.affectedRows > 0) {
        console.log('添加成功');
    } else {
        console.log('添加失败');
    }
});

// 更新
db.query(`UPDATE blog_tag SET tag_name = 'JavaScript' WHERE tag_id = 1`, (err, result) => {
    if (err) console.log(err);
    if (result.affectedRows > 0) {
        console.log('更新成功');
    } else {
        console.log('更新失败');
    }
});

// 更新 简写
const blog_tags = { tag_id : 1, tag_name : 'spring', tag_url : 'http://www.xiaotao.cloud/spring' };
db.query(`UPDATE blog_tag SET ? WHERE tag_id = ?`, [blog_tags, tag_id], (err, result) => {
    if (err) console.log(err);
    if (result.affectedRows > 0) {
        console.log('更新成功');
    } else {
        console.log('更新失败');
    }
});

// 删除
db.query(`DELETE FROM blog_tag WHERE tag_id = 2`, (err, result) => {
    if (err) console.log(err);
    if (result.affectedRows > 0) {
        console.log('删除成功');
    } else {
        console.log('删除失败');
    }
});

前后台身份验证

JWT

安装

npm i jsonwebtoken express-jwt

jsonwebtoken 生成 jwt 字符串

express-jwt 将 jwt 字符串解析还原成 json 对象

登录鉴权

const express = require('express');
const app = express();

// 导入 jwt 相关
const jwt = require('jsonwebtoken');
const expressJWT = require('express-jwt');

// 允许跨域
const cors = require('cors');
app.use(cors());

// 解析 post 表单数据中间件
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));

// 定义 secret 密钥 用以生成 token 对 jwt 加密 解密
const secretKey = '123456 ^_^';

// 将 JWT 字符串解析还原成 JSON 对象的中间件   以 /api 访问的 不需要拦截
// 这里会自动添加一个 req.user 挂载用户信息
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))

// 登录接口
app.post('/api/login', function (req, res) {
    // 将 req.body 请求体中的数据,转存为 userinfo 常量
    const userinfo = req.body
    // 登录失败
    if (userinfo.username !== 'admin' || userinfo.password !== 'root') {
      return res.send({
        status: 400,
        message: '登录失败!',
      })
    }
    // 登录成功
    // 在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
    // 参数1:用户的信息对象
    // 参数2:加密的秘钥
    // 参数3:配置对象,可以配置当前 token 的有效期
    // 不要把密码加密到 token 字符中
    const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
    res.send({
      status: 200,
      message: '登录成功!',
      token: tokenStr, // 要发送给客户端的 token 字符串
    })
})

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
    // 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
    console.log(req.user)
    res.send({
      status: 200,
      message: '获取用户信息成功!',
      data: req.user, // 要发送给客户端的用户信息
    })
})

// 全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
    // 这次错误是由 token 解析失败导致的
    if (err.name === 'UnauthorizedError') {
      return res.send({
        status: 401,
        message: '无效的token',
      })
    }
    res.send({
      status: 500,
      message: '未知的错误',
    })
})

// 启动web服务器
app.listen(80, function () {
    console.log('Express server running at http://127.0.0.1')
})

本当の声を響かせてよ