HTTPS 协议 && node_EventLoop && others
文章目录
HTTPS 原理
保证数据在传输过程中不被窃取和篡改,从而保证 传输
安全。默认端口 443
加密
-
对称加密: 产生一个密钥,可以用它加密,也可以用它解密。
常用的对称加密算法有: DES / 3DES / AES /Blowfish 等。
-
非对称加密:产生一对密钥,一个用于加密,一个用于解密(公钥 ←→ 私钥)
CA (Certificate Authority) - 证书颁发机构
流程:
- 服务器申请证书(CA 通过私钥加密生成)
- 服务器将证书给予客户端 (客户端能拿到CA机构的公钥,证书签名生成算法等便于进行后面的验证)
- 客户端 CA公钥解密拿到服务器公钥 key1
- 通过公开算法进行证书签名验证
- 中间者就无法篡改服务器给予客户端的公钥 key1,从而也就拿不到客户端生成的通过公钥 key1 加密的对称密钥 key2 了。也就无法参与中间信息的篡改…
- 这样就保证了传输过程的安全性(因为一旦证书签名验证不通过,两边都会知道传输过程中出了问题)
证书签名
:由域名、CA 公钥 KEY 和服务器公钥 key 通过公开的算法得出,用于验证签名是否被篡改。
图示(可能证书生成并不像本人画的那样,但主要想表明 服务器公钥是通过CA私钥加密的,且生成唯一的证书签名
):
https 模块
服务器结构
一般是采用 nginx 搭建的基于 https 的代理服务器去与用户进行数据传输,node 在服务器内部搭建基于 http 的与 nginx 服务器交互的服务器结构。
https使用
借用上一篇的例子更改
const https = require('https'); // https 模块
const URL = require('url');
const path = require("path");
const fs = require('fs');
async function getStat(filename) {
try {
return await fs.promises.stat(filename);
} catch {
return null;
}
}
async function getFileContent(url) {
const urlObj = URL.parse(url);
let filename = path.resolve(__dirname, "public", urlObj.pathname.substr(1)),
stat = await getStat(filename);
if (!stat) {
return null;
} else if (stat.isDirectory()) {
filename = path.resolve(__dirname, "public", urlObj.pathname.substr(1), "index.html");
stat = await getStat(filename);
if (!stat) {
return null;
} else {
return await fs.promises.readFile(filename);
}
} else {
return await fs.promises.readFile(filename);
}
}
async function handler(req, res) {
const info = await getFileContent(req.url);
if (info) {
res.write(info);
} else {
res.statusCode = 404;
res.write("Response is no exists...");
}
res.end();
}
const server = https.createServer({
// 在创建服务器时配置私钥与证书 即可
key: fs.readFileSync(path.resolve(__dirname, "/xxxx.pem")), // 私钥
cert: fs.readFileSync(path.resolve(__dirname, "/xxxx.cert")) // 证书
}, handler);
server.on('listening', () => {
console.log("server listing 9999...");
});
server.listen(9999);
node 生命周期 (或事件循环)
nodejs 事件循环中会有六个队列,一个队列到达后,看其中有没有任务,若有,则拿出来执行到清空队列,再进入下一个队列。
-
timers 队列
: 存放计时器的回调函数(循环查看计时器线程按个检查,再运算计时器事件的时间到了没有) -
poll 队列
: 轮询队列(除了 timers、check),绝大部分回调都会放入此队列,比如文件的读取,监听用户请求等运作方式:
- 如果poll中有回调,依次执行回调,直到清空队列
- 如果poll中没有回调
则等待其他队列中出现回调,结束该阶段,进入下一阶段;
如果其他队列也没有回调,则持续等待,直到出现回调为止。
-
check 队列
: setImmediate() 进入的队列(类似于单纯地 push 进数组再依次执行)事件循环中,每打算执行一个回调之前,必须要先清空
nextTick 和 Promise 队列
node事件循环面试题
// 面试题 1 setImmediate(() => { console.log(1); }); process.nextTick(() => { console.log(2); process.nextTick(() => { console.log(6); }); }); console.log(3); Promise.resolve().then(() => { console.log(4); process.nextTick(() => { console.log(5); }); }); // 结果: 3 2 6 4 5 1 // 面试题 2 async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(() => { console.log("setTimeout0"); }, 0); setTimeout(() => { console.log("setTimeout3"); }, 3); setImmediate(() => console.log("setImmediate")); process.nextTick(() => console.log("nextTick")); async1(); new Promise(resolve => { console.log("promise1"); resolve(); console.log("promise2"); }).then(() => console.log("promise3")); console.log("script end"); // 这里在我做的时候没有注意的点是: // 1. async1(); => 是同步代码执行 // 2. new Promise 函数里 resolve() 之后的打印代码也是同步代码 // 结果(前9位可确定,后三位不确定): // script start, async1 start, async2, promise1, promise2, // script end, nextTick, async1 end, promise3, // 最后三个仅 setTimeout0 在 setTimeout3 之前可以确定。 // setImmediate 所在位置不能确定,所以有三种答案,如下: // (1) setTimeout0, setTimeout3, setImmediate // (2) setTimeout0, setImmediate, setTimeout3 // (3) setImmediate, setTimeout0, setTimeout3
EventEmitter
事件处理
const { EventEmitter } = require('events'); // events 模块的事件
// 创建事件处理对象
// 可以注册事件 / 触发事件
const emitter = new EventEmitter();
emitter.on("test", () => { // 注册 test 事件
console.log("test event");
});
emitter.emit("test"); // 手动触发
// 传递数据
emitter.on("transfer", (data1, data2) => {
console.log("传递的数据: ", data1, data2); // 打印: 传递的数据: 123 456
});
emitter.emit("transfer", 123, 456);
应用:
// request.js
const http = require("http");
const { EventEmitter } = require('events');
module.exports = class extends EventEmitter {
constructor(url, options){
super();
this.url = url;
this.options = options;
}
send(body = ""){
const request = http.request(this.url, this.options, res => {
const headers = res.headers;
let result = "";
res.on('data', chunk => {
result += chunk.toString('utf-8');
});
res.on('end', () => {
this.emit('res', {
headers,
result
});
})
});
request.write(body);
request.end();
}
}
// index.js
const ownRequest = require('./request.js');
const url = "http://www.baidu.com";
const request = new ownRequest(url);
request.send();
request.on("res", res => {
console.log(res);
});
重温 MySQL
曾经在学校也学习和使用过 MySQL, SQL Server, SQLite 等关系型数据库。
作为 关系型数据库
,特点就是以表和表的关联构成数据结构,可以表达复杂的数据关系,SQL 语句作为强大的结构化查询语句,能通过条件设置精确地查找想要查询的数据。
但同时,它的结构也就比较死板,读写的性能就差了一些。所以,它一般用作存储结构复杂的数据。它的代表有:(甲骨文的) Oracle 和 MySQL 以及 (微软的) SQL Server等。
与之相对的 非关系型数据库
,本人也曾使用过 MongoDB 。特点是可以以极其简单的结构存储数据,比如文档型、键值对型。格式就比较灵活,针对海量数据的读写效率很高。但它对复杂的数据结构就有些吃力了,难以表示复杂的数据结构,而且对于复杂的数据查询也比较差。所以一般用来存储结构简单的数据。它的代表有: MongoDB, Redis, Membase 等。
相关术语:
DB
: database 数据库DBA
: database administrator 数据库管理员DBS
: database system 数据库系统 (DBS 包含 DB, DBA, DBMS)DBMS
: database management system 数据库管理系统
关于忘记 MySQL root 密码之重新安装 MySQL
18年课设用过 MySQL,后来就没怎么用过,然后 root 密码就忘了。后来重装过系统(对啊,我重装系统的时候C盘全清了啊…),再下就要密码,没搞定也没急着用(就搁置了)。
解决办法:
-
控制面板卸载之前装过的 MySQL(与之相关的全卸掉)
-
正如网上一些博客说的,打开注册表编辑器:
win + R
输入regedit
删除注册表信息:
HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/MySQL
HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/Eventlog/MySQL
好像是,之前我是删了的 ( 没做下面第三步依旧需要验证密码 )
-
关键是这一步: 打开
C:\ProgramData
文件夹, 删掉 MySQL 文件夹, 这里面可能就是记录存储了 root 的密码(重装系统我全格式化了啊???! 疑问)。 -
之后再去官网或者腾讯给予的 MySQL msi 镜像文件下载,安装就可以
重新设置 root 密码
了…(这次得记住了,免得又忘了…)
MySQL 安装好之后使用
- 环境变量 path 里加上 MySQL 安装目录的 bin 目录路径;
- 在 cmd 窗口输入
mysql -uroot -p
进入 mysql 命令交互 (password 就是你安装时设置的 root 密码); show variables like 'character\_set\_%';
命令查看数据库字符编码;- 修改
C:\ProgramData\MySQL\MySQL Server 8.0
(我这里装的是 MySQL 8.0.19版所以可能文件夹名字不一样)目录下的my.ini
配置文件:default-character-set=utf8mb4
character-set-server=utf8mb4
并将修改后的此文件复制到 MySQL 的安装目录下(与bin目录同级即可); - 重启 MySQL 服务,运行
show variables like 'character\_set\_%';
命令即可看到修改后的字符编码。 - 然后下载
Navicat数据库管理工具
(至于怎么永久使用,此处不予说明哈), 用它可进行交互式数据库操作 (命令行顺手的就不用下),可能图形化操作会更顺手,也更简单,但是会让人变"傻"
。
数据库设计
SQL (Structured Query Language 结构化查询语言),可以访问和处理数据库。
这就干脆在用 Navicat 工具时,看看 SQL 预览
,重新熟悉熟悉SQL语句。
DDL
(数据定义语言) 操作数据库、表、视图跟存储视图
DML
(数据操控语言) 操作数据库中的记录
DCL
(数据控制语句) 操作用户权限
数据库表中字段一些类型:
bit
----- (1位,0或1,false或true)int
----- (32位,整数类型)decimal(M, N)
----- (能精确计算的实数,M是总的数字位数,N为小数的位数,超出部分会被截去)char(n)
----- (固定长度位 n 的字符)varchar(n)
----- (长度不固定,最大长度位 n 的字符)text
----- (大量的字符)date
----- 仅日期datetime
----- 日期和时间time
----- 仅时间
主键
: 根据关系型数据库设计规则,每张表都要有 主键
( 单一主键或组合主键 ),唯一且不可更改,而且不能有业务含义
外键
: 用于关联其他表的键
表关系:
一对一
: 一个 A 对应一个 B,一个 B 对应一个 A一对多
: 一个 A 对应多个 B,一个 B 对应一个 A,A 和 B 是一对多,B 和 A 是多对一 (如一个人和他写的博客)多对多
: 一个 A 对应多个 B,一个 B 对应多个 A
三大设计范式:
每一列都是不可分割的原子数据项
非主键列必须依赖于主键列
非主键列必须直接依赖于主键列
数据的基本操作方式: 增删改查
-
增:
INSERT INTO <表名> [(字段1, 字段2, ...)] VALUES ('value1', 'value2', ...)
有默认值 — 对应位置 value 置为default
,或者不设置此字段; [] 中可不写(按默认列一次插值) -
改:
UPDATE <表名> SET 字段名=value WHERE 条件
-
删:
DELETE FROM <表名> WHERE 条件
-
查:
-- 基本结构 select ... from ... where ... order by ... -- 排序 asc (默认升序), - desc 降序 limit m, n -- 跳过 m 条数据,取 n 条数据 -- case用法 对male列进行单独处理 --- 表中存储 0 / 1,希望拿到 男 / 女 -- as 可省略 select id, `name`, case male when 1 then '男' else '女' end as sex, birthDay from students; select DISTINCT `location` FROM student; -- DISTINCT 被查询项去除重复 (注意查询多列也得是相应对应重复项) -- 联表查询 employee 表的外键 departId 关联 department 表的主键 id select * from department as d left join employee as e on d.id = e.departId -- 左连接如果在另一张表找不到相应的对应数据,则左表占一行,连接数据全为 Null -- 右连接(right join)以右表为基准 -- 内连 select * from department as d inner join employee as e on d.id = e.departId -- sql语句运行顺序 ----------------------------------------------------- from → ... join...on... → where → group by → select → having → order by → limit
视图 属于 DDL
CREATE VIEW databaseName <表名> AS SELECT ...
数据驱动和 ORM
mysql 驱动程序
-
驱动程序: 连接内存和其他存储介质的桥梁
mysql 驱动程序是连接内存和 mysql 数据的桥梁
-
-
基本使用
// 回调模式 const mysql = require('mysql2'); // 创建数据库连接 const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: '******', // 你的数据库 root 密码 database: 'localdb' }); // simple query connection.query( 'SELECT * FROM `users` WHERE `name` LIKE "%s%" AND `age` > 22', (err, results) => { console.log(results); // 得到查询数据 } ); connection.end(); // 断开连接 // ----------------- 利用 Promise 创建异步连接 --------------- const mysql = require('mysql2/promise'); async function test(){ // 创建数据库连接 const connection = await mysql.createConnection({ host: 'localhost', user: 'root', password: '******', database: 'localdb' }); const [result] = await connection.query('SELECT * FROM `users` WHERE `name` LIKE "%s%" AND `age` > 22;'); // 解构语法只拿 result console.log(result); connection.end(); } test();
-
上面的 query API 及 sql 语句字符串拼接写法容易造成 SQL 注入攻击
故而提供 预编译 方式处理 sql 语句const mysql = require('mysql2/promise'); async function test(id, name){ // 创建数据库连接 const connection = await mysql.createConnection({ host: 'localhost', user: 'root', password: '******', database: 'localdb' }); const sql = 'SELECT * FROM `users` WHERE id=? or name=?;'; // 固定 sql 语句格式,不可再通过 sql 注入攻击 const [result] = await connection.excute(sql, [id, name]); // 参数作为数组每一项对应 ? 位置依次传入 console.log(result); connection.end(); } test('', `''; delete from ...`);
-
connection pools (连接池)
const mysql = require('mysql2/promise'); async function test(id, name){ // 创建一个连接池 const pool = mysql.createPool({ host: 'localhost', user: 'root', password: '********', database: 'localdb', waitForConnections: true, // 等待创建新连接 connectionLimit: 10, // 连接池最大连接数量 queueLimit: 0 // 排队 0 - 不限制长度 }); const sql = 'SELECT * FROM `users` WHERE id=? or `name`=?;'; // 固定 sql 语句格式,不可再通过 sql 注入攻击 // const sql = `SELECT * FROM \`users\` WHERE \`name\` LIKE concat(%, ?, %);`; // 模糊查询 (两层模板字符串 所以用到转义) const [result] = await pool.excute(sql, [id, name]); // 参数作为数组每一项对应 ? 位置依次传入 console.log(result); connection.end(); } test('', `''; delete from ...`);
-
ORM 框架
ORM (Object Relational Mapping 对象关系映射)
通过 ORM 框架,可以自动地把程序中的对象和数据库中的表进行关联
ORM 框架会隐藏具体的数据库底层细节,让开发者使用统一的数据操作接口,完成对不同数据库的操作。
如上图所示,让我们不用关心数据库具体是哪个,只需关心对象的操作等,也可以轻易地完成数据库的移植,无须拼接复杂的 sql 语句即可完成精确查询功能
Node 中的 ORM: Sequelize
和 TypeORM
(本文选用 sequelize)
-
const mysql = require('mysql2/promise'); const { Sequelize } = require('sequelize'); // 创建数据库连接 const sequelize = new Sequelize('localdb', 'root', 'your root password', { host: 'localhost', dialect: 'mysql' // 指定数据库类型 }); // 测试连接是否成功 --- 若一段时间内没有操作,它会自动关闭连接 sequelize .authenticate() .then(() => { console.log('Connection has been established successfully.'); }) .catch(err => { console.error('Unable to connect to the database:', err); }); // sequelize.close(); // 手动关闭连接(一般不会去手动关闭)
-
模型定义 – 模型同步 - 创建表
const { Sequelize, DataTypes } = require('sequelize'); const sequelize = new Sequelize('database', 'user', 'password', { host: 'localhost', dialect: 'mysql', logging: null // 日志记录禁用(或置为false),默认是控制台输出(console.log) }); const Admin = sequelize.define('Admin', { // 定义模型属性 -- 字段定义 loginId: { type: DataTypes.STRING, allowNull: false }, loginPwd: { type: DataTypes.STRING, allowNull: false }, name: { type: DataTypes.STRING, allowNull: false } }, { paranoid: true, // 使用此配置,该表中的数据不会真的删除,而是给予一个 deleteAt 标记 createAt: false, // 取消创建 创建时间字段(默认为 true) updateAt: false, // 取消创建 更新时间字段(默认为 true) }); const Student = sequelize.define('Student', { name: { type: DataTypes.STRING, allowNull: false }, birthDay: { type: DataTypes.DATE, allowNull: false, get() { // 访问器 -- this 指向模型实例 // 若使用 this.birthDay 将会无限循环调用,所以提供了 getDataValue('birthDay') 方法 // 在查询数据时,就需要拿到 birthDay ,否则虚拟属性 age 无法计算,age 取它的值时会是 undefined 而报错 return this.getDataValue('birthDay').getTime(); // 得到时间戳 }, set(val){ // 设置值 如此处使用 moment 模块设置 utc 时间 this.setDataValue('birthDay', moment.utc()); } }, // 虚拟属性 不会存储到数据库 (如同 vue 的 computed 属性 -- 计算得来) age: { type: DataTypes.VIRTUAL, // 虚拟属性类型 get() { const now = moment.utc(), birth = moment.utc(this.birthDay); return now.diff(birth, 'y'); // 得到两个日期的年份差异(舍位存整) } }, sex: { type: DataTypes.BOOLEAN, allowNull: false }, mobile: { type: DataTypes.STRING(11), allowNull: false }, location: { type: DataTypes.STRING, allowNull: false } }, { paranoid: true, createdAt: false, updatedAt: false }); // 创建模块关联 - 在进行关联表查询时,每次都需要运行关联语句 // 否则就会报错 --- 提示两张表未进行关联而无法查询 const Student = require('./Student'); const Class = require('./Class'); // 相当于创建外键 - 此处是通过在 students 表中生成 ClassId 字段关联 classes 表的 id // Class.hasMany(Student); // Student.belongsTo(Class); // 通过本人测试得知: 上面两句设置模型关联语句每次在关联查询时都需要使用 // 但是会运行一次就创建一个外键关联(待我发现时,查看外键已经有150+了) // 可以改用下面这句,单独放到设置关联的js文件 setRelation.js 中(这里直接 require 导入就可以了) Student.belongsTo(Class, { foreignKey: 'ClassId', // targetKey: 'id' // 这条配置好像可写可不写(没写我也正常建了外键) }); await sequelize.sync({ alter: true }); // 一次同步所有模型 // 在下面插入模拟数据时,通过同步模型的回调函数中运行插入模拟数据的代码即可 // 也可少运行一次设置关联的代码,减少一次新增外键
模拟表格数据 - Mock.js
// mockClass.js -> 模拟 classes 表的数据
const Mock = require('mockjs');
const result = Mock.mock({
"datas|16": [{
"id|+1": 1,
"name": " @id 班",
openDate: "@date"
}]
}).datas;
const Class = require("../models/Class");
Class.bulkCreate(result);
// mockStudent.js -> 模拟 students 表的数据
const Mock = require('mockjs');
const result = Mock.mock({
"datas|500-700": [{ // 500-700 条记录
"id|+1": 1, // id 自增
"name": "@cname", // 随机中文名
birthDay: "@date", // 随机日期
"sex|1-2": true, // 性别: true - false 随机
mobile: /1(3|5|7|8|9)\d{9}/, // 手机号 或者用它 → /^[1]([3-9])\d{9}$/
location: "@county(true)", // 随机 省 市 区 地址
"ClassId|1-16": 1
}]
}).datas;
const Student = require("../models/Student");
Student.bulkCreate(result);
ClassId
的数据插入,都需要两个模块建立关联,否则将无法插入数据
数据查询
-
单条查询
findOne()
:// const Admin = sequelize.define(...); async function getInfo(loginId, loginPwd) { const res = await Admin.findOne({ where: { loginId, loginPwd } }); if (res && res.loginId === loginId && res.loginPwd === loginPwd) { return res.toJSON(); } return null; } // sql 语句中本不区分大小写,因而查询返回结果进行比较判定再返回 getInfo('username', 'password').then(res => console.log(res));
-
按主键查找单条数据
findByPK()
: 传入主键即可(创建表的时候主键默认是自增 id 值) -
查询多个数据
findAll()
, 返回一个数组
:// const Student = sequelize.define(...); async function getStudents() { const res = await Student.findAll(); return JSON.stringify(res); } getStudents().then(res => console.log(res)); // 分页查询 --- 使用 limit 和 offset 参数 ------------------- async function getStudents(page = 1, limit = 10) { const res = await Student.findAll({ offset: (page - 1)*limit limit: +limit }); const total = await Student.count(); // 查询数量 const datas = JSON.parse(JSON.stringify(res)); return { total, datas } } getStudents().then(res => console.log(res));
- 提供
findAndCountAll()
方法 – 结合 findAll 和 count 的便捷方法
const { Op } = require('sequelize'); async function getStudents(page = 1, limit = 10, name = '') { const res = await Student.findAndCountAll({ where: { [Op.like]: `%${name}%` // 模糊查询 }, offset: (page - 1)*limit limit: +limit }); return { total: res.count datas: JSON.parse(JSON.stringify(res.rows)) } } getStudents().then(res => console.log(res)); // 限制查询内容(不想全部查询出来) -- 设置 attributes 属性 findAndCountAll({ attributes: ['id', 'sex', 'name'], // 只查'id', 'sex', 'name' });
- 提供
-
包含关系
include()
- 联表查询:const Class = require('./Class'); const Student = require('./Student'); // require('../models/relation.js'); /** ------------------------------------------------------------------------- * 每次使用关联查询时,必须运行下面进行外键关联的语句 * 否则将无法认为这两张表进行了关联操作,从而导致查询失败 * Class.hasMany(Student); * Student.belongsTo(Class); * 或者更改为一句 ==================== : * Student.belongsTo(Class, { * foreignKey: "ClassId" * }); * 但同时,每次一运行到这两句设置关联的语句时(如关联查询时),都会新增外键;我在测试查询接口时, * 就已经加了 150 多个外键了。(这个问题我目前还没有找到良好的解决办法) * 但后面这句设置关联貌似比前面两句要好,新增外键数量不会每次查询都加一个外键(我不确定是否正确,但测试过几次是这样) * ---------------------------------------------------------------------------- */ async function getInfo(page = 1, limit = 10){ const res = await Student.findAndCountAll({ offset: (page - 1) * limit, limit: +limit, include: Class }); }
数据抓取
-
axios 库
和cheerio 库
配合使用, 抓取数据存储于数据库 books 表中cheerio 库
是 jQuery 的核心库,但它不涉及 DOM 操作以抓取 豆瓣 书籍数据为例:
const axios = require('axios').default; const cheerio = require('cheerio'); const Books = require("../modules/Book"); async function getBooksHTML(url) { const res = await axios.get(url); return res.data; } // 拿到所有详情页链接 async function getBookLnks() { const html = await getBooksHTML("https://book.douban.com/latest"); const $ = cheerio.load(html); const aEle = $("#content .grid-12-12 li a.cover"); const links = aEle.map((i, ele) => { const href = ele.attribs["href"]; return href; }).get(); return links; } /** * 根据详情页地址,得到书籍详情页的书籍详细信息 * @param {*} detailUrl 详情页地址 */ async function getBookDetail(detailUrl) { const res = await axios.get(detailUrl); const $ = cheerio.load(res.data); const name = $('h1').text().trim(); // 书籍名 const imgUrl = $("#mainpic .nbg img").attr("src"); // 图片地址 const spans = $('#info span.pl'); const authorSpan = spans.filter((i, ele) => { return $(ele).text().includes("作者"); }); const author = authorSpan.next("a").text(); // 作者 const publishSpan = spans.filter((i, ele) => { return $(ele).text().includes("出版年"); }); const publishDate = publishSpan[0].nextSibling.nodeValue.trim(); // 出版时间 return { name, imgUrl, author, publishDate } } async function fetchAll() { const links = await getBookLnks(); const pro = links.map(link => { return getBookDetail(link); }); return Promise.all(pro); } async function savaToDB() { const books = await fetchAll(); await Books.bulkCreate(books); console.log("抓取数据并成功保存到数据库..."); } savaToDB();
MD5 加密
hash 算法之一 — 单向加密规则(只可加密,不可解密)
将任何一个字符串加密成任何一个 固定长度
的字符串
同样的原字符串加密结果是固定的
md5 库
--> 提供函数 md5()
let str = md5('jhagfg23jhhih.awfh'); // => edc1cgdcd21ce225d5fb42bfxc74h0dc (这是乱改的 >=.=< )
moment库
进行时间转换以适应不同需求
-
UTC: 世界协调时(英国格林尼治天文台旧址本初子午线时间) 它与东八区相差八个小时
-
时间戳: 一个 UTC 时间到 UTC 1970-1-1 凌晨 0:00 的时间差(默认毫秒)
-
服务器可能会部署到世界的任何位置,所以服务器内部英国统一使用 UTC 时间或时间戳,数据库也一样
-
对于客户端,就需要根据 UTC 时间或时间戳转换为当地时间
const moment = require('moment'); console.log(moment().toString()); // 无参 -> 当前时间(本地) console.log(moment.utc().toString()); // 一样的结果 // 得到当前时间戳 console.log(moment().valueOf()); // 毫秒数 console.log(moment.utc().valueOf()); // 根据指定的日期格式得到时间 console.log(moment.utc("1970-01-01 00:00:00").toString());
-
令牌: 格式化字符串 如:
"YYYY-MM-DD HH:mm:ss"
const moment = require('moment'); // moment.locale("zh-cn"); // 设置中文 const token = ['YYYY-MM-DD HH:mm:ss', 'YY-M-D H:m:s', 'YYYY/MM/DD HH:mm:ss']; // 日期格式 /** 参数 * 1. 日期 * 2. 令牌 - 日期格式数组 * 3. 严格检查(true) */ const m = moment.utc("1970-01-01 00:00:00", token, true); // 调用 moment 对象的 isValid() 方法可以判断是否是合乎规则的日期格式 const flag = m.isValid(); // false // 显示 // 拿到 moment 对象 m.format("YYYY年MM月DD日 HH时mm分ss秒"); // 1970年01月01日 00时00分00秒 -- 显示的是 utc 时间 // 转换成本地时间 调用 local() m.local().format("YYYY年MM月DD日 HH时mm分ss秒"); // 转换成utc时间 调用 utc() const n = moment("2020-05-23 22:43:37"); const toServerTime = n.utc().format('YYYY-MM-DD HH:mm:ss'); // 相较于当前时间的时间差 const last = moment.utc("2020-05-23 22:23:37"); console.log(last.local().fromNow()); // 20 minutes ago
数据验证
- 前端 – 为了用户体验
- 路由层 – 验证格式是否正常
- 业务逻辑层 – 保证业务完整性(含数据)
- 数据库验证 – 保证数据完整性
validator
用于验证某个 字符串
是否满足某个规则
validate.js
用于验证单个数据或某个对象的属性是否满足某些规则
const validate = require('validate.js');
const classServ = require('./classService');
/** 例如:
* stuObj = {
* name: 'xxx',
* sex: 'x',
* mobile: 'xxxxxxxxxxxx'
* ...
* }
*/
async function addStudent(stuObj){
// 定义验证规则
const rule = {
name: {
// 验证 name 属性为 null, undefined 都不通过, 但"" 能通过
presence: true,
// presence: {
// // 验证 name 属性,除上面之外,为 "", " "也不通过
// allowEmpty: false
// },
type: "string", // 验证类型
length: { // 验证 name 长度为 1-10 个字符
minmum: 1,
maximum: 10
}
}
}
const reslut = validate.validate(stuObj, rule); // 同步验证
// 通过验证返回 undefined
// 不通过,默认返回 {name: ["Name can't be blank"]}
}
async function addStudent(stuObj){
// 扩展验证规则
validate.validators.classExists = async value => {
const res = await classServ.getClassById(value);
if(res){
return; // 验证通过
}
return "is not exist"; // 前面会自动拼接 value 值
}
const rule = {
classId: {
presence: true,
classExists: true // 置为 true 会去运行上面定义的函数,并将 classId 的值做为参数传递
}
}
await validate.async(stuObj, rule); // 异步验证 => 通过 - 无返回值; 不通过 - 报错
// 通过就执行下面语句进行添加学生操作;不通过就报错阻塞
const ins = await Student.create(stuObj);
return ins.toJSON();
}
日志记录
-
日志:调试日志、信息日志、错误日志等
-
等级:
all ---------- 低
trance
debug
info
warn
error
fatal
mark
off ---------- 高 -
日志分类:
categroy
如 sql 日志、请求日志等 -
日志出口:
appender
,写到哪儿,写的格式
log4js库
// start -------
const log4js = require('log4js');
const logger = log4js.getLogger(); // 默认类别 - default
logger.level = 'all'; // 设置等级 -- 默认 off 级别
logger.info('abc');
// ----------- 配置
const log4js = require('log4js');
const path = require('path');
log4js.configure({
// 出口
appenders: {
sqlOut: {
type: 'file', // 备份文件会直接以 .1 .2 .3 的格式接在备份文件名之后 -- 改用 "dateFile" 即可以日期格式记录
filename: path.resolve(__dirname, 'logs', 'sql', 'logging.log'),
maxLogSize: 1024, // 配置文件的最大字节数(超过此值即分割备份一个文件) -- 1024 -> 1kb
keepFileExt: true, // 保持备份文件后缀名不变
daysToKeep: 0,
layout: { // 输出配置
type: "pattern",
pattern: "%c [%d{yyyy-MM-dd hh:mm:ss}] [&p] : %m" // 自定义日志记录格式
}
},
// 自己去配置时,必须 配置 default 项,否则会报错
default: {
type: 'file',
filename: path.resolve(__dirname, 'logs', 'default', 'logging.log'),
}
},
// 分类
categories: {
sql: {
appenders: ['sqlOut'], // 该分类使用出口 sqlOut 的配置写入日志
level: 'all'
},
default: {
appenders: ['default'],
level: 'all'
}
}
});
process.on('exit', () => {
log4js.shutdown(); // 程序退出时,让日志记录完成记录工作
});
const logger = log4js.getlogger('sql');
logger.info('info to exit');
针对上述配置,可以专门新建一个 log.js
文件用于管理日志记录处理,导出需要的 logger 对象。在需要日志记录的地方,直接导入使用即可。
总结
在本文中,总结了一下 https 协议安全的原理, 重点了解 node 的事件循环机制,重温了一下 MySQL 及 sequelize ORM 框架的涉水,另外涉及了几个库的使用…就这么多吧,记录一下自己的学习进度