前情了解

Node.js 是什么? 把 JavaScript 运行在操作系统上的运行环境(将 V8 引擎拿到其他的运行平台)。

浏览器是如何把 JS 代码跑起来的呢? 每个浏览器中有处理 JS 代码的引擎,像是翻译工具,将 JS 代码编译成二进制语言让 CPU 去处理后展现在我们的面前。(如著名 V8 引擎)。

那么像浏览器中一些交换(如弹窗)是如何运作的呢? 我们在书写代码的时候去调用浏览器提供的 Api 实现特定效果。如浏览器操作类:弹窗、跳转、历史记录。DOM 节点操作类:节点获取、节点删除。网络操作类:Ajax、websocket。

Node.js 与 JavaScript 是什么关系? 可以把 Node.js 当成运行环境,Node.js 可以解析它后,可以让它 JS 跑在操作系统上。

Node.js 能做什么

  • 通过网络与读写操作来实现 web 服务器开发

    • 操作数据库

    • 发送网络请求

  • 通过操作网络来实现网络爬虫开发

  • 通过读写功能完成一套脚手架工具

Node.js 发展历史? 08 年 V8 引擎问世,带来了能操作操作系统的 Api,进而实现了 Node.js(09 年 Ryan Dahl 发布)

下载

官网:Node.js

如何使用 Node.js 把 JavaScript 跑起来

如在桌面创建一个 JS 文件,如 index.js

使用命令行执行 (cmd) 进到桌面

运行 node index.js

就跑起来了!

文件操作

文件操作主要是引用 Node.js 提供的 fs 模块来处理

1
2
3
4
5
6
7
// 读文件
var fs = require('fs');
fs.readFile('./file.txt', 'utf-8', (err, data) => {
if (!err) {
console.log(data);
}
});
1
2
3
4
5
6
7
8
// 写入文件内容
var fs = require('fs');

fs.writeFile('./file.txt', 'asdfasdfasdfasdfasdfsdfsadfasdfasfd', 'utf-8', (err, data) => {
if (!err) {
console.log(err);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
// 在文件中追加内容
var fs = require('fs');
fs.readFile('./file.txt', 'utf-8', (err, data) => {
if (!err) {
data = data + '8888';
fs.writeFile('./file.txt', data, err => {
if (!err) {
console.log('追加成功');
}
});
}
});

模块化编程概念

require(“fs”) 是什么意思? 使用 require() 去引用 Node.js 提供的 fs 模块

我们为什么需要模块化? 拆分代码,相互独立(可以解耦),导入导出

JavaScript 有哪些模块化规范? Common.js、ES Module

模块化开发规范

ES Module 导入导出

首先注意 Node.js 本身不默认支持,所以需要在 package.json 中声明 { “type” : “module” } 或者使用 .mjs 后缀 。

分别导入 or 导出

  • export { num } :将需要导出的模块变量名放入 {} 中。

  • import { num } from “文件路径”:分别导入

  • export { num as num1 }:别名导出,导入是 num1 变量而不是 num。

  • import { num as number }:别名导入,后续代码需要用 number 变量才能取到值。

默认导入 or 导入

  • export default {} 默认导出,此 JS 默认导出一个

  • import num form “文件名”:与分别导入区别是不需要加括号,因为只有一个模块或变量被暴露。

Common Module

导入导出:Node.js 默认支持,在 JS 中 Module 是全局的。

导入

1
2
3
4
5
6
7
8
// 导出模块 m1.js
var val = 'this is commonjs';
var foo = 'foo';

module.export.val = val; //给 export 对象上绑定了一个 val 属性并赋值
module.export.foo = foo;

// 或使用不带 module ,直接 export 也可以导出

导入

1
2
3
// 导入模块 同根目录下的 m1.js
var m1 = require('./m1.js');
console.log(m1); //{ val:"this is commonjs",foo:"foo" }**

npm 包管理器

是什么?是 npm 第三方包进行管理的工具,你可以自行上传你写好的模块等工具或使用 npm 下载他人写好的工具包。

初始化 npm 项目 npm init 生成 package.json 文件

配置 package.json 文件

自定义脚手架

什么是脚手架?全局命令行工具、创建项目初始化代码文件及目录

脚手架的基本能力?全局命令行执行能力,命令行交互能力

如何自定义一个脚手架?创建自定义全局命令,命令参数接收处理,终端交互,下载远程项目代码,项目初始化完成提示。

npm 辅助库

commander:负责参数解析为选项和命令参数

inquirer:负责提供用户界面和查询会话流程

download-git-repo:从远程仓库下载代码

ora:命令行等待工具

chalk:终端字符串样式

代码参考

需求:创建一个可以下载拉 git 仓库代码的脚手架,目前需求是能下 express、koa、egg 三个框架自定义初始化项目。日常使用可以自己修改需要的框架 list 即可。

image-20221018152646059

代码仓库:GitHub - wu-honghao/h-cli

发布

参考:npm 包发布

Node 实现服务器逻辑

推荐安装:nodemon 可以热更新服务器 npm i nodemon -g

  1. 使用 Node 创建一个 HTTP 的服务器,并能够接收到客户端发来的请求

  2. 获取到客户端具体的请求数据,并根据不同的请求数据进行处理

  3. 将处理之后的结果,响应回客户端,并断开本次链接

需求:处理返回一个 html 及其内容、处理表单 post 提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// server.js
var http = require('http');
var router = require('./router');
// 创建 server 对象
var server = http.createServer();
// 监听 8080 端口
server.listen(8080, () => {
console.log('http://127.0.0.1:8080');
});

server.on('request', (req, res) => {
// 根据路由路劲去响应数据
router(req, res);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// router.js
var fs = require('fs');
var url = require('url');
var controller = require('./controller');

module.exports = (req, res) => {
// 设置响应体的数据类型为文本类型
// res.setHeader('Content-type', 'text/plain;charset=utf-8');
// 写入响应体
// res.write('您好');

// 设置响应体的数据类型为 html
res.setHeader('Content-type', 'text/html;charset=utf-8');
// 读取服务端 index.html 和 图片
if (req.method == 'GET') {
// 使用 url 库解析 req.url,true的意思是将解析成对象
console.log(url.parse(req.url, true));
if (req.url === '/') {
controller.index(res);
} else {
fs.readFile('./1.jpg', (err, data) => {
res.write(data);
// 断开本次链接
res.end();
});
}
} else if (req.method == 'POST') {
console.log('POST');
var data = '';
// req 封装了 net 模块,因此可以监听 data 和 end 去完成数据的接收
req.on('data', function (d) {
data += d;
});
req.on('end', function (d) {
controller.user(require('querystring').parse(data), res);
});
res.end();
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// controller.js
var fs = require('fs');

module.exports = {
index(res) {
fs.readFile('./index.html', 'utf-8', (err, data) => {
res.write(data);
res.end();
});
},
user(postData, res) {
console.log(postData);
res.write('123123');
},
};

Express

是什么?高度包容、快速而极简的  Node.js Web 框架

应用场景:Web 应用程序(网站)、API 接口服务器、服务器渲染中间键,开发辅助工具,自定义集成框架

特性:上手简单,具有丰富的 API 支持,强大的路由功能,灵活的中间键及丰富的第三方中间支持,性能接近原生 Node

用户增删改查

需求:对用户的信息进行增删改查

案例:GitHub - wu-honghao/express-video-server 版本号:0e68ee4

HTTP 响应状态码

1xx : 信息性

2xx : 成功

3xx : 重定向

4xx : 客户端错误

5xx : 服务端错误

MongoDb 使用

下载 MongoDB 非关系型数据库 & Navicat 数据库图形化界面工具

MongoDB 软件:https://www.mongodb.com/try/download/community2 v5.0.13

MongoDB 安装教程:https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows/

Navicat:https://www.navicat.com.cn/download/navicat-premium

破解 Navicat:https://learnku.com/articles/67706

Navicat 建立 MongoDB 连接

Navicat 中新建数据库和集合表

1
2
use mytest // 切换数据库。不存在的也允许切换,但只是在内存中存在,只有库中有数据才能保留下来
db.cc.insert({username:'lisi',age:18}) // 往数据库名为 cc 集合中添加一条数据,不存在的集合在插入后就可以存在了。

增删改查

1
2
3
4
5
6
7
8
9
//db.user.insertOne({username:'lisi',age:12}) // 插入一条数据
//db.user.insertMany([{username:'lisi1',age:13},{username:'lisi2',age:14}]) // 插入多条数据
//db.user.find({username:'lisi'}) // 查询名字为 lisi 的
//db.user.find({age:{$gt:15}}) // 查询年龄为 15 以上的
//db.user.findOne({age:{$gt:15}}) // 查询一条年龄为 15 以上的
//db.user.updateOne({username:'hh'},{$set:{age:30}}) // 修改 username 为 hh 的一条数据的年龄为 30
//db.user.updateMany({age:{$gt:15}}, {$set:{username:'lisi'}}) // 修改 age 大于 15 的多条数据的 username 为 30
//db.user.deleteOne({age:{$gt:15}}) // 删除年龄大于 15 的第一条找到的数据
//db.user.deleteMany({age:{$gt:13}}) // 删除年龄大于 15 的多条数据

Node.js 项目中使用 mongodb

在项目中安装 mongodb 使用依赖的库:npm install mongodb

记得现在 Navicat 中连接 Mongodb 执行生成数据库 video 和 user 集合后再跑这个案例,不然会找不到数据库的。

1
2
use video // 切换数据库。不存在的也允许切换,但只是在内存中存在,只有库中有数据才能保留下来
db.user.insert({username:'lisi',age:18}) // 往数据库名为 user 集合中添加一条数据,不存在的集合在插入后就可以存在了。

下面是使用的一个小案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// index.js
// 导入 MongoClient 对象
import { MongoClient } from 'mongodb';
// 创建连接对象
const client = new MongoClient('mongodb://127.0.0.1:27017');

// 将可操作的集合实例对象返回
const clientFun = async function (c) {
// 建立连接
await client.connect();
// 拿到数据库实例对象
const db = client.db('video');
// 拿到集合实例对象
return db.collection(c);
};

const main = async () => {
let userCollection = await clientFun('user');
// 查找数据
// let user = await userCollection.find();
// 数据多条返回,记得调用 toArray 方法将数据拿到
// console.log(await user.toArray());

// 向 userCollection 新增一条数据
// let result = await userCollection.insertOne({ username: 'hh', age: 18 });
// console.log(result);

// 向 userCollection 新增多条数据
// let result = await userCollection.insertMany([
// { username: 'asdfsdf', age: 18 },
// { username: 'asdf', age: 16 },
// ]);
// console.log(result);

// 查询 userCollection 中的一条数据
// let result = await userCollection.findOne({ age: { $gt: 15 } });
// console.log(result);

// 更新一条数据
// let result = await userCollection.updateOne({ age: 12 }, { $set: { username: 'lisiu' } });
// console.log(result);

// 更新多条数据
// let result = await userCollection.updateMany({ age: { $gt: 16 } }, { $set: { username: 'lisisiss' } });
// console.log(result);

// 删除一条数据
// let result = await userCollection.deleteOne({ age: { $lt: 13 } });
// console.log(result);

// 删除多条数据
// let result = await userCollection.deleteMany({ age: { $lt: 18 } });
// console.log(result);
};

// 当方法跑完后执行 finally 关闭数据库连接
main().finally(() => {
client.close();
});

Express 中间件分类

  1. 应用程序级别的中间件
  2. 路由级别的中间件
  3. 错误处理中间件
  4. 内置中间件
  5. 第三方中间件

应用级别中间件

基本中间件

1
2
3
app.use((req, res, next) => {
//...
});

限定请求方法中间件

1
2
3
app.get('/user', (req, res, next) => {
//...
});

多个处理函数针对一个路由的匹配规则

1
2
3
4
5
6
7
8
9
app.get(
'/user',
(req, res, next) => {
//...
},
(req, res, next) => {
next();
}
);

小项目 express video

实现登录注册,用户列表的返回

登录

首先在路由中添加一个中间件 router.get("/logins",校验,具体的登录业务的回调) ,后续客户端在访问这个路径后就能调用对应的业务代码

校验逻辑

下面的校验方式使用的是函数调用验证方式而非直接编写规则在中间件上,此处理解的不深,具体看官方文档:https://express-validator.github.io/docs/running-imperatively.html

1
2
3
4
5
6
7
// middleware/validate/userValidate.js
const validate = require('./errorBack');

module.exports.login = validate([
body('email').notEmpty().withMessage('邮箱不为空').bail().isEmail().withMessage('邮箱格式不正确'),
body('password').notEmpty().withMessage('密码不为空'),
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
// middleware/validate/errorBack.js
const { validationResult } = require('express-validator');

module.exports = validator => {
return async (req, res, next) => {
await Promise.all(validator.map(validate => validate.run(req)));
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(401).json({ error: errors.array() });
}
next();
};
};
1
2
// 路由中编写规则
routerUser.get('/logins', validator.login, login);

业务

封装数据库查询的方法后去调用 mongoose

文件上传

使用到的库: multer (npm)

设置路径拿到 upload 方法

1
const upload = multer({dest:'public/'})

post 请求中设置只读 headimg 的上传内容

1
// .post token 认证后加上 upload.single('headimg')

处理文件改名 fs.rename,同步就用 promisify()

支持静态资源访问,后续直接在服务器端口后直接拼接访问即可

1
2
3
4
// 在 app.js 中处理
app.use(express.static('public'));

// http://localhost:3000/7854da8584c4c1238b31c9f7d238fabb.png

阿里云视频点播(VOD)