Hello, world!

这里是 @高厉害 的 node.js 笔记

学习中!...

一、node + Gulp 基础


    /// <reference path="../../typings/index.d.ts"/>

    // node.js 的全局对象是 global
    // 一、node 模块化开发
    // (一)、模块导出
    
    // 1.exprots module.exports 为相同引用
    // 改变引用后,以 module.exprots 为准
    // 其可以指向任意数据
    // 注意,同一进程下,不同文件 require 的同一模块为该模块的引用。
    
    // (二)、系统模块 node 提供的 api
    
    // 1.文件读写
    const fs = require('fs');
    // fs.readFile('file_name'[.'enecode'], callback(err, doc))
    // fs.writeFile('file_name'[.'enecode'], callback(err))
    // err 不为 null 说明出错
    
    fs.readFile('./exc.xlsx', (err, data) => {
        if (err !== null) {
            console.log(err);
            return;
        }
        fs.writeFile('./new.xlsx', data, (err) => {
            if (err !== null) {
                console.log(err);
                return;
            }
            console.log('success!');
        })
    })
    
    
    
    // 2.路径拼接
    const path = require('path');
    
    //  path.join(str, str, str...)
    // 大部分情况下使用绝对路径,因为相对路径以当前工作目录为准,而工作目录可变
    // __dirname 可以获得当前脚本所在绝对路径,末尾不带 \
    // 所以一般都这样写 : path.join(__dirname,str...)
    
    
    
    // (三)、第三方模块
    
    // npmjs.com
    // npm (node package manager)
    // npm install [package name] [option]
    // option -g 代表 global,全局安装
    
    // nrm 切换下载地址工具 
    // nodemon 脚本保存即使执行
    
    
    // (四)、Gulp 模块,前端构建工具
    
    // api:
    // 1. gulp.src()
    // 2. gulp.dest()
    //  通过命令行工具执行 gulp task
    // 3. gulp.task('task_name', callback(done_callback)
    // 4. gulp.watch()
    // 另
    // gulp.series() 按照顺序执行
    // gulp.paralle() 并行
    
    // gulp 功能需要通过插件实现
    // gulp-htmlmin
    // file-include
    
    // gulp-csso
    // gulp-less
    
    // gulp-uglify
    // gulp-babel
    
    // 执行构建任务时,如果不带参数,gulp 会自动执行 default 任务
    
    
    
    // (五)、package.json(项目依赖信息) package-lock.json(锁定包版本)
    /// npm init -y 生成项目依赖描述文件
    //  npm i(install) 下载依赖
    //  npm install --production 下载项目依赖
    
    // 项目依赖 和 开发依赖
    // npm install [package] --save-dev 将包信息添加在 devDependencies(开发依赖) 项中
    // 当 npm install --production 时,仅下载项目依赖
    
    
    // (六)、模块加载机制
    // 给出路径
    // 1. 根据模块绝对路径查找模块
    // 2. 省略模块后缀,则寻找同名 js 文件
    // 3. 寻找同名文件夹,然后执行其中的 index.js
    // 4. 寻找 package.json ,执行其中记录的入口文件
    // 无路径无后缀
    // 1. 在系统模块中寻找
    // 2. 在 node_modules 文件夹中寻找 js 文件 (上一级目录也会找)
    // 3. 在 node_modules 文件夹中寻找文件夹,然后执行其中的 index.js
    // 4. 寻找 package.json ,执行其中记录的入口文件


    // (七)、补充
    // 实现深拷贝
    let tmpPackage = JSON.parse(JSON.stringify(API_ROUTES.trademark));
    let cloned = Object.assign({}, source);
    let cloned = { ... source };   // 仅限ES6
        

Gulp 构建任务


    // 示例代码:

    const gulp = require('gulp'),
    htmlmin = require('gulp-htmlmin'),
    fileinclude = require('gulp-file-include'),
    less = require('gulp-less'),
    csso = require('gulp-csso'),
    babel = require('gulp-babel'),
    uglify = require('gulp-uglify');

    // example
    gulp.task('task1', done => {
        gulp.src('./src/css/style.css').pipe(gulp.dest('./dist/task1/css/index.css'));
        console.log('Hollo world!');
        done();

    })


    // htmlmin
    // file-include
    gulp.task('htmlmin', (done) => {
        gulp.src('./src/*.html')
            .pipe(fileinclude()) // 公共代码块 include
            .pipe(htmlmin({ collapseWhitespace: true })) // html 压缩
            .pipe(gulp.dest('./dist/task'));
        done();
    })

    // csso
    // less
    gulp.task('cssmin', (done) => {
        gulp.src('./src/css/*.css')
            .pipe(less()) // less 语法转换
            .pipe(csso()) // css 压缩
            .pipe(gulp.dest('./dist/task/css'))
        done();
    })

    // babel
    // uglify
    gulp.task('jsmin', (done) => {
        gulp.src('./src/js/*.js')
            .pipe(babel({
                presets: ['@babel/env']
            }))
            .pipe(uglify())
            .pipe(gulp.dest('./dist/task/js'));
        done();
    })

    // 复制 文件夹
    gulp.task('copy', (done) => {
        gulp.src('./src/img/*')
            .pipe(gulp.dest('./dist/task/img'));
        gulp.src('./src/media/*')
            .pipe(gulp.dest('./dist/task/media'));
        done();
    })

    // 构建任务

    gulp.task('default', gulp.series('htmlmin', 'cssmin', 'jsmin', 'copy', (done) => {
        console.log('default success!');

        done()
    }))
        

二、服务端编程基础


    // 一、系统模块 http

    // const app = http.createServer() 
    //创建服务器,返回一个服务器对象
    
    // app.listen(port); 
    // 监听端口
    
    // app.on(event, callback(requests, response)) 
    // 注册回调事件 ,http请求':request'
    // response.end(data) 用以结束响应
    
    
    
    
    
    // 二、HTTP 协议(超文本传输协议)

    // 客户端和服务端进行请求和响应的标准
    
    // 1. request.herders
    // 请求报文
    
    // request.url
    // 请求 url
    
    // request.method
    // GET 请求数据   POST 发送数据
    
    
    
    // 2. response._header
    // 响应报文
    
    // state code (http 状态吗)
    // 200 请求成功
    // 404 请求的资源没有找到
    // 500 服务器端错误
    // 400 客户端请求有语法错误
    
    
    // content-type (内容类型)
    // 内容类型
    // text/html
    // text/css
    // application/javascript
    // image/jpeg
    // application/json
    
    // 编码类型
    // charset=tuf-8
    // 例如: 'content-type':'text/html;charset=utf-8;'
    
    // response.writeHead(StatusCode, herder) 
    // 置相应报文
    
    // 重定向
    res.writeHead(301, {
        Location: '/list'
    });
    res.end();
    // --
    res.writeHead(200, {
        'content-type': 'text/html;charset=utf-8;'
    });
    res.end();
    
    
    
    // 3. HTTP 请求与响应处理
    
    // GET
    // 查询参数在 url 中
    // 系统模块 url,用于处理 url
    // url.parse(String_url, bool_parseQueryString) 解析 url
    // 返回对象的属性:
    // query ,查询参数对象 (若第二个参数传 true)
    // pathname ,请求地址
    
    // POST
    // post 参数通过 data 和 end 事件接受
    // data 参数开始传递时回调
    // end 参数传递完成后回调
    // request.on('data', params => {
    //     postData += params;
    // })
    // request.on('end', params => {
    //     console.log(querystring.parse(postData));
    // })
    
    // 系统模块 querystring,处理 post 请求信息
    
    
    // 路由
    // 客户端请求地址与服务端程序代码的对应关系
    
    
    // 静态资源访问
        

静态资源访问


    // 示例代码

    const http = require('http'),
        url = require('url'),
        fs = require('fs'),
        path = require('path'),
        mime = require('mime');
    
    // 设置主页及路由
    const index = 'index.html',
        route = path.join(__dirname, 'web');
    
    app = http.createServer();
    
    app.on('request', (request, response) => {
        let pathname = url.parse(request.url).pathname,
            method = request.method;
        pathname = decodeURI(pathname);
    
        if (method == 'GET') {
            let realPath = path.join(route, pathname, (pathname == '/' ? index : ''));
            let type = mime.getType(realPath);
    
            console.log('method: ', method);
            console.log('realPath: ', realPath);
            console.log('type: ', mime.getType(realPath));
            console.log('*********');
    
            fs.readFile(realPath, (err, data) => {
                if (err != null) {
                    console.log(err);
    
                    response.writeHead(404, {
                        'content-type': 'text/html;charset=utf-8;',
    
                    });
                    response.end('<h1 style="text-align:center;">404 Not Found</h1>');
                    return;
                }
    
                response.writeHead(200, {
                    'content-type': type
                });
                response.end(data)
            });
        } else if (method == 'POST') {}
    });
    
    app.listen(80);
        

三、异步编程


    // 1. 异步 api 执行顺序
    // 异步代码将会被暂时挂起,直到完成所有同步任务
    
    // 例1、
    // 如下方代码,当循环结束后才开始调用定时器,运行观察 callback: 字样输出
    function asyncFun(callback) {
        setTimeout(() => {
            callback('data');
        }, 500)
    }
    let time = +new Date;
    asyncFun((data) => {
        console.log('callback:' + (+new Date - time));
    })
    for (let i = 0; i < 1000000000; i++);
    console.log('sync:' +  (+new Date - time));
    
    // 例2、观察最先输出的内容
    let time = +new Date;
    setTimeout(() => {
        console.log('async' + 1);
    }, 2)
    setTimeout(() => {
        console.log('async' + 2);
    }, 1)
    setTimeout(() => {
        console.log('async' + 3);
    }, 0)
    for (let i = 0; i < 10000000; i++);
    console.log('sync' +  (+new Date - time));
    
    
    // 过度调用异步 api 会陷入 "回调地狱"
    // 例3、回调地狱
    
    // 依次读取 1、2、3 三个文件 
    fs.readFile('./1.txt', (err, data) => {
        console.log('a:', data);
    
        fs.readFile('./2.txt', (err, data) => {
            console.log('b:', data);
    
            fs.readFile('./3.txt', (err, data) => {
                console.log('c:', data);
            })
        })
    })
    
    
    
    // 2. Promise 将异步执行和结果处理进行分离
    // Promise 本质上是一个构造函数,其提供了两个回调函数供我们处理异步函数的结果
    let pro = new Promise((resolve, reject) => {
        resolve();
        // 对应 then
    
        reject();
        // 对应 catch
    })
    pro.then(() => {
            // resolve
        })
        .catch(() => {
            // reject
        });
    
    
    // 例1、读取文件
    const fs = require('fs');
    
    let time = +new Date;
    let pro = new Promise((resolve, reject) => {
        fs.readFile('./form.html', 'utf-8', (err, data) => {
            if (err == null) {
                resolve(data);
            } else {
                reject(err);
            }
        });
    });
    
    pro.then((data) => {
            // console.log(data);
            console.log('callback:' + (+new Date - time));
            // ...
        })
        .catch((err) => {
            // console.log(err);
            console.log('callback:' + (+new Date - time));
            // ...
        });
    for (let i = 0; i < 1000000000; i++);
    console.log('sync:' +  (+new Date - time));
    
    
    // 例2、依次读取三个文件
    const fs = require('fs');
    
    function p1() {
        return new Promise((resolve, reject) => {
            fs.readFile('./1.txt', 'utf8', (err, data) => {
                if (err == null) {
                    resolve(data);
                } else {
                    reject(err);
                }
            });
        });
    }
    
    function p2() {
        return new Promise((resolve, reject) => {
            fs.readFile('./2.txt', 'utf8', (err, data) => {
                if (err == null) {
                    resolve(data);
                } else {
                    reject(err);
                }
            });
        });
    }
    
    function p3() {
        return new Promise((resolve, reject) => {
            fs.readFile('./3.txt', 'utf8', (err, data) => {
                if (err == null) {
                    resolve(data);
                } else {
                    reject(err);
                }
            });
        });
    }
    
    p1().then((data) => {
            console.log(data);
            return p2();
        })
        .then((data) => {
            console.log(data);
            return p3();
        })
        .then((data) => {
            console.log(data);
        })
        .catch((err) => {
            console.log(err);
    
        });
    
    
    // 20,04,09 回来补充, “只能对末尾进行异常处理” 说法是错误的
    // promise 对象允许这样使用
    promise.then((data) => {
        data;
    }, (err) => {
        // 错误处理
    })
    // 即 
    promise.catch(err => { })
    // 等价于
    promise.then(null, err => { })

    
    // Promise 允许我们使用链式编程的方法,也为此提供了方便,但这只能对末尾进行异常处理
    // 所以可以这样写
    
    const fs = require('fs');
    
    function p1() {
        return new Promise((resolve, reject) => {
            fs.readFile('./1.txt', 'utf8', (err, data) => {
                resolve(data, err);
            });
        });
    }
    
    function p2() {
        return new Promise((resolve, reject) => {
            fs.readFile('./2.txt', 'utf8', (err, data) => {
                resolve(data, err);
            });
        });
    }
    
    function p3() {
        return new Promise((resolve, reject) => {
            fs.readFile('./3.txt', 'utf8', (err, data) => {
                resolve(data, err);
            });
        });
    }
    
    p1().then((data, err) => {
            console.log(data);
            return p2();
        })
        .then((data, err) => {
            console.log(data);
            return p3();
        })
        .then((data, err) => {
            console.log(data);
        });
    
    
    
    
    // 4. 异步编程回调地狱的最佳解决方案
    // ES7 新增语法, async、await 关键字
    // async 函数中: return 相当于调用 resolve ,throw 相当于调用 reject
    // await 后跟一个 promise 对象,它可以暂停 async 函数的执行,等待 promise 对象返回结果后再继续执行
    // await 必须在 async 函数中出现
    // 例1、
    const fs = require('fs'),
        promisify = require('util').promisify;
    const readFile = promisify(fs.readFile);
    
    async function fun() {
        console.log(await readFile('./backEnd/1.txt', 'utf8'));
        console.log(await readFile('./backEnd/2.txt', 'utf8'));
        console.log(await readFile('./backEnd/3.txt', 'utf8'));
    }
    fun();
    
    // 理解 Promise 的机制:
    // Promise 的 status 有三个值,对应三个状态,peending、resolve、reject
    // Promise 通过我们在异步任务执行结束后调用回调函数来改变状态
    // 即三种状态分别对应"实例化后" "异步任务调用 resolve 后"  和 "异步任务调用 reject 后" 
    
    // Promise 实例化后,异步代码段将会在合适的时间执行,当 Promise 实例返回时,异步代码段并未执行,处于 peending 状态
    
    // 而 async 函数,我们可以把它理解为一个装有一系列异步函数的集合,而这些异步函数我们都需要同步执行
    // 调试例2,观察其调用顺序
    // 例2、
    
    async function fun() {
        var local_time = +new Date();
        console.log('async function called');
    
        let a = await new Promise(resolve => setTimeout(() => {
            console.log('A');
            resolve('a')
        }, 2000));
    
        console.log('local_clickA:' + (+new Date - local_time));
    
        let b = await new Promise(resolve => setTimeout(() => {
            console.log('B');
            resolve('b')
        }, 1000));
    
        console.log('local_clickB:' + (+new Date - local_time));
    
        return { a, b };
    }
    var time = +new Date();
    fun().then((result) => {
        console.log(result);
    })
    for (let index = 0; index < 100000000; index++) {}
    console.log('click:' + (+new Date - time));
    
    // for 循环在我的机器上耗时约 1000ms
    // 输出为:
    
    // async function called  // 此时 local_time 开始计时
    // click:1172
    // A
    // local_clickA:2014
    // B
    // local_clickB:3016
    // Object {a: "a", b: "b"}
    
    // 从这里能看出来什么?
    // async 函数在运行同步代码时与其他函数并无不同
    // 而一旦在 async 内部碰到 await 关键字,当前函数会被挂起,解释器转而运行外部的下一条代码
    // 直到所有同步任务完成前,该函数不会被继续调用
    
    // 例2 使用 setTimeout 来举例子
    // 这是为了使结果更明显,但理解起来可能比较奇怪,比如,我为什么要 new Promise
    
    // 下面我来揭示一些最核心的东西
    // 观察下面这些代码,它们是将 例2 的 setTimeout 换成 readFile 的结果,它们的结构是等价的
    
    // 例3、
    const fs = require('fs'),
        promisify = require('util').promisify;
    const readFile = promisify(fs.readFile);
    
    async function fun() {
        var local_time = +new Date();
        console.log('async function called');
    
        let a = await readFile('./backEnd/a.txt', 'utf8');
    
        console.log('local_clickA:' + (+new Date - local_time));
    
        let b = await readFile('./backEnd/b.txt', 'utf8');
    
        console.log('local_clickB:' + (+new Date - local_time));
    
        return { a, b };
    }
    var time = +new Date();
    fun().then((result) => {
        console.log(result);
    })
    for (let index = 0; index < 100000000; index++) {}
    console.log('click:' + (+new Date - time));
    
    // 注意到我将
    let a = await new Promise(resolve => setTimeout(() => {
        console.log('A');
        resolve('a')
    }, 2000));
    // 替换为
    const readFile = promisify(fs.readFile);
    let a = await readFile('./backEnd/a.txt', 'utf8');
    // 也可以写成
    let a = await promisify(fs.readFile)('./backEnd/a.txt', 'utf8');
    
    // await 在等待什么?其实与我们之前仅使用 Promise 并无不同,它在等待 resolve 被回调
    // promisify 将 fs.readFile 用 Promise 包装起来,然后为我们做了一些工作
    // 也就是说,它为我们调用了 resolve
    // 这也是我在 setTimeout 中手动调动 resolve 的原因
    
    // 现在一切都很清晰了,我们之前仅使用 Promise 进行文件读写时对 fs.readFile 做的包装工作,实际上与 promisify 是一样的
    // 我们回顾一下
    function readFile(fileName, decode) {
        return new Promise((resolve, reject) => {
            fs.readFile(fileName, decode, (err, data) => {
                if (err == null) {
                    resolve(data);
                } else {
                    reject(err);
                }
            });
        });
    }
    
    // 如果懒得 try,可以这样写
    function readFile(fileName, decode) {
        return new Promise((resolve, reject) => {
            fs.readFile(fileName, decode, (err, data) => {
                resolve({ err, data });
            });
        });
    }
    // await 是 ES7 给出的语法糖,他只是在等待和接收 resolve 而已
    // 总结一下,await 用于接收[Promise 实例的异步任务]回调 resolve 的实参
    
    // 所以我们可以自己包装 Promise,自己调用
    
    // 例4、
    const fs = require('fs'),
        promisify = require('util').promisify;
    // const readFile = promisify(fs.readFile);
    function readFile(fileName, decode) {
        return new Promise((resolve, reject) => {
            fs.readFile(fileName, decode, (err, data) => {
                resolve({ data, err });
            });
        });
    }
    
    async function fun() {
        var local_time = +new Date();
        console.log('async function called');
    
        let a = await readFile('./backEnd/a.txt', 'utf8');
    
        console.log('local_clickA:' + (+new Date - local_time));
    
        let b = await readFile('./backEnd/b.txt', 'utf8');
    
        console.log('local_clickB:' + (+new Date - local_time));
    
        return { a, b };
    }
    var time = +new Date();
    fun().then((result) => {
        console.log(result);
    })
    for (let index = 0; index < 100000000; index++) {}
    console.log('click:' + (+new Date - time));


    // 我们还可以并发执行,语法大概是这样
    // 例5、
    async function fun() {
        let a = await Promise.all([await readFile('./backEnd/a.txt', 'utf8'), readFile('./backEnd/b.txt', 'utf8')])
        return a;
    }

    fun().then((data) => {
        data.forEach((i) => {
            console.log(i);

        })
    });
    // 任务结束后,会将结果打包成一个数组,传入resolve。
        

四、MongoDB 数据库


    // 一、概念
    // 层次关系:
    // database => collection => document => fieeld
    // 数据库(应用)  集合(类别)     文档(data)   字段(prop)

    // 启动 MongoDB
    // 命令行输入 net start / stop mongodb



    // 二、MongoDB api
    Doc -> https://mongoosejs.com/docs/
    // 如果数据库不存在,会自动建立
    // 注意,mongodb 的默认端口为 27017,可以省略
    const mongoose = require('mongoose');
    mongoose.connect('mongodb://localhost/firstDB', { useNewUrlParser: true, useUnifiedTopology: true })
        .then(() => {
            console.log('\033[42;30mDone\033[0m DB connected successfully!');
        })
        .catch((err) => {
            console.log(err);
        });



    // 增删改查

    // 1. 创建集合并保存一个文档

    // 设定集合规则
    const courseSchema = new mongoose.Schema({
        name: String,
        author: String,
        isPublished: Boolean
    });

    // 创建文档
    // 参数 集合名称 集合规则
    const Course = mongoose.model('Course', courseSchema);
    // 返回一个当前集合的构造函数
    // 数据库中集合名字为 'courses'
    // 注意, 对于一个集合的名字 Name, 'name''names''Name''Names' 指的是同一个集合

    // 实例化文档
    const course = new Course({
        name: 'Web',
        author: 'Gaolihai',
        isPublished: true
    });

    // 保存数据
    course.save();



    // 2. 创建并保存文档
    Course.create({
        name: 'java',
        author: 'jiazhouyuan',
        isPublished: false
    }, (err, doc) => {
        console.log(err);
        console.log(doc);
    });
    // 注意, 当传入的字段不存在时,不会插入不存在的字段
    //
    // 3. mongodb 的异步操作
    // mongodb 提供的 api 均为异步 api
    // 故上方的 create 等一些 api 可以使用 Promise 异步获得返回
    // 以 create 方法为例
    Course.create({
            name: 'java',
            author: 'jiazhouyuan',
            isPublished: false
        })
        .then((doc) => {
            console.log(doc);
        })
        .catch((err) => {
            console.log(err);
        });



    // 4. 删除
    // 删除 api
    // 找到一条数据并删除, 若想要操作成功, 一定要用 then 接收结果, 返回删除的数据对象
    User.findOneAndDelete({ name: '王五' }).then((re) => {
        console.log(re);
    })

    // 删除多条数据 返回一个对象 { n: 5, ok: 1, deletedCount: 5 },ok字段 为 1 说明成功。
    User.deleteMany({}).then((re) => {
        console.log(re);
    })



    // 5. 修改 
    // 修改 api
    // 修改单个数据: 返回一个对象 { n: 1, nModified: 1, ok: 1 } n 查询到的个数 nModified 修改的个数 ok 结果状态
    User.updateOne({ age: { $gt: 40 } }, { name: '老年狗' }).then((re) => {
        console.log(re);
    })

    // 修改多个数据: 返回值同上
    User.updateMany({ age: { $lt: 40 } }, { name: '年轻狗' }).then((re) => {
        console.log(re);
    })



    // 6. 查询
    // 命令行导入 json 数据
    // mongoimport -d firstDB -c users --file .\user.json
    // 数据库名称 集合名称 欲导入数据的文件名称

    // 查询 api

    // 返回一个数组,包含查询到的所有文档
    User.find({
        name: '王五'
    }).then((result) => {
        console.log(result);
    })

    // 返回一个文档对象,注意空对象为 null
    User.findOne({
        name: '王五'
    }).then((result) => {
        console.log(result);
    })

    // 更多查询条件
    // 大于小于: age 字段传入一个对象, $gt 字段代表大于 $lt 代表小于
    User.find({
        age: { $gt: 20, $lt: 40 }
    }).then((result => {
        console.log(result);
    }));

    // 字符串包含: $in 字段
    User.find({
        hobbies: { $in: '吃饭' }
    }).then((result => {
        console.log(result);
    }));

    // 仅返回需要的字段: select 方法 - 后跟字段代表不想返回的字段
    User.find({}).select('name email -_id').then((result => {
        console.log(result);
    }));

    // 查询结果排序: sort 方法 加 - 代表降序排列
    User.find().sort('age').then((result => {
        console.log(result);
    }));
    // 或 1 代表升序  -1 代表降序
    User.find().sort({ _id: -1 }).then((result => {
        console.log(result);
    }));

    // 跳过几条数据: skip 方法
    User.find().skip(1).then((result => {
        console.log(result);
    }));

    // 限制返回数据条数
    User.find().limit(1).then((result => {
        console.log(result);
    }));



    // 7. 验证
    // 即设置插入规则
    // 在置定文档规则时, 在某字段填写对象, 设置验证规则
    const articleSchema = new mongoose.Schema({
        title: {
            type: String,
            require: true
        }
    });

    // 自定义错误信息
    const articleSchema = new mongoose.Schema({
        title: {
            type: String,
            require: [true, '\033[41;37m 请传入文章标题 \033[0m']
        }
    });

    // 字符串的一些规则
    const articleSchema = new mongoose.Schema({
        title: {
            type: String,
            require: [true, '\033[41;37m 请传入文章标题 \033[0m'],

            minlength: 5,
            maxlength: 20,
            // 设置字段 size minlength maxlength

            trim: true,
            // 去除字符串两边空格 trim

            enum: [
                    ['limit value'], 'error string'
                ]
                // 指定字段值的限定词, 此外的值会被拒绝从而插入失败
        }
    });

    // 数字的一些规则
    const articleSchema = new mongoose.Schema({
        author_count: {
            type: Number,
            max: 20,
            min: 0,
            // 针对数字的大小
        },
        publishDate: {
            type: Date,
            default: Date.now
                // 指定默认值, 不提供该字段时,即便 required 字段为 true, 也能插入成功
        }

    });

    // 自定义规则
    const articleSchema = new mongoose.Schema({
        title: {
            type: String,

            validate: {
                validator: (value) => {
                    // 验证代码
                    return true / false;
                }
            },

            message: 'error string'
        }
    });
        

五、模板引擎 atr-template


    // 安装
    npm i art-template
    
    // 引入
    const template = require('art-template');
    const html = template('./path', data);
    // path 文件名后缀为 .art
    // data 为一对象,描述模板文件的对应数据

    // 标准语法
    {{字段}}

    // 原始语法
    <%= 字段 %>
    // 两种语法可以混用


    // 1. 替换方式

    // 模板引擎默认对尖括号等字符进行替换
    // 想要按照原文替换,即令浏览器解析代码,需要这样写

    {{@字段}}
    <%- 字段 %>


    // 2. 条件
    {{if condition1}} 
        {{content1}} 
    {{else if condition2}} 
        {{content2}} 
    {{else}} 
    {{/if}}

    <% if (condition1)  { %> 
        content1 
    <% } else if (condition2){ %> 
        content2 
    <% } %>


    // 3. 循环
    {{each target}} 
        {{$index}} {{$value}}
        content
    {{/each}}

    <% for(i in target) { %>
        <%= i %>  <%= target[i] %>
        content
    <% } %>


    // 4. 子模板
    {{include './path'}}
    <%include('./path')%>


    // 5. 模板继承
    // 模板文件 
    // {{block 'name'}}  {{/block}}
    
    // 继承 
    // {{extend './path'}}
    // {{block 'name'}}  content  {{/block}}
    
    
    // 6. 模板配置
    // 向模板中导入模块
    // template.defaults.imports.var = var
    
    // 设置模板根目录
    // template.defaults.root = './path'
    
    // 设置模板后缀
    // template.defaults.extname = '.ext'
        

六、Express 框架开发


    // 安装
    // npm i express
    
    // 引入
    const express = require('express');
    
    const app = express();
    app.get('/', (req, res) => {
        res.send('ok');
    })
    app.listen(prot)
    
    // 1. 基本语法
    const express = require('express');
    
    const app = express();
    app.get('/', (req, res) => {
        res.send('ok');
        // 自动检测响应内容类型
        // 自动设置 http 状态吗
        // 自动设置响应内容的类型和编码
        // send 后相当于已经调用了 end
        // send 里可以填 JSON 对象
    })
    app.listen(prot);
    
    
    
    // 2. 中间件
    // 用来处理 HTTP 请求
    // 例如:
    app.get('/route', (req, res, next) => {
        // do sth.
        next();
        // 交由下一个中间件继续处理
    });
    
    
    // app.use 中间件方法, 接收任何类型请求
    // 第一个参数可以省略, 代表接收所有请求路径的请求
    // 注意, 中间件是有顺序的
    app.use('./path', (req, res, next) => {
        // do sth.
        next();
    });
    
    
    // 中间件应用
    // 路由保护, 判断用户状态, 拦截请求. 
    app.use((req, res, next) => {
        if (loginState) {
            next();
        } else {
            res.send('未登陆');
        }
    });
    
    
    // 通过第一个中间件发布网站维护公告
    app.use((req, res) => {
        res.send('网站正在维护...');
    });
    
    
    // 通过末尾中间件, 定义 404 页面
    app.use((req, res) => {
        res.status(404).send('404 Not Found');
    });
    
    
    // 错误处理中间件
    // 对同步任务
    app.use((req, res) => {
        throw new Error('404 ');
    });
    // 当传入 use 回调函数有四个形参时, 该中间件会被认为是一个错误处理中间件
    app.use((err, req, res, next) => {
        res.status(500).send(err.message);
    })
    
    // 对回调的异步任务 
    // 当 next 传入参数时, 会触发错误处理
    app.use((req, res) => {
        fs.readFile('./123', (err, data) => {
            if (err != null) {
                next(err);
            } else {
                res.send(data);
            }
        })
    });
    // 当传入 use 回调函数有四个形参时, 该中间件会被认为是一个错误处理中间件
    app.use((err, req, res, next) => {
        res.status(500).send(err.message);
    })
    
    // 对异步函数
    app.get('/index', async(req, res, next) => {
        try {
            res.send(await promisify(fs.readFile)('./path'));
        } catch (err) {
            next(err);
        }
    });
    // 当传入 use 回调函数有四个形参时, 该中间件会被认为是一个错误处理中间件
    app.use((err, req, res, next) => {
        res.status(500).send(err.message);
    })
    
    
    
    // 3. 模块化路由
    
    // 多级路由
    const home = express.Router();
    // 返回一个路由对象
    
    app.use('/home', home);
    // home 为一级路由
    
    home.get('/index', (req, res) => {
        // 二级路由
        // 此处路由为 /home/index
        // do sth.
    });
    
    
    // 模块化路由
    // home.js
    const express = require('express');
    const home = express.Router();
    home.get('/index', (req, res) => {
        res.send('/home 页面');
    });
    module.exports = home;
    
    // admin.js
    const express = require('express');
    const admin = express.Router();
    admin.get('/index', (req, res) => {
        res.send('/admin 页面');
    });
    module.exports = admin;
    
    // app.js
    const express = require('express');
    const home = require('./route/home');
    const admin = require('./route/admin');
    
    const app = express();
    
    app.use('/home', home);
    app.use('/admin', admin);
    
    app.listen(80);
    console.log(' \033[42;1m Done \033[0m 服务器启动成功!')
    
    
    
    // 4. 参数获取
    // GET
    req.query
    
    // POST
    // 安装
    // npm i body - parser
    
    // 引入
    const bodyParser = require('body-parser');
    app.use(bodyParser.urlencoded({ extended: false }));
    // 当 extended 为 false 时, 会使用系统模块 queryString 进行处理, 反之则使用第三方模块 qs
    app.post('/path', (req, res) => {
        req.body;
    });
    
    
    // 5. 路由参数, 获取 GET 请求的一种方式
    app.get('/route/:param1/:param2', (req, res) => {
        req.params;
    });
    
    
    // 6. 静态资源访问
    app.use(express.static('./path', { index: 'default.html' }));
    
    
    // 7.express-art-template
    // npm i art-template express-art-template
    
    // 使用
    app.engine('art', require('express-art-template'));
    // 为引入的模板引擎设置一标识 art, 此标识也是默认后缀
    app.set('views', './path');
    // 模板位置
    app.set('view engine', 'art');
    // 通过标识选择模板引擎
    app.get('/route', (req, res) => {
        res.render('fileName', {});
        // 调用 render 的同时响应了客户端
    })
    
    // app.locals 对象
    // 当 app.locals 对象下的属性存在时
    // 模板会直接额获取到这些属性并渲染, 不需要向 render 内传参
        

七、部署 https 服务


    const express = require('express');
    const fs = require('fs');
    const http = require('http');
    const https = require('https');
    
    // options
    const options = {
        key: fs.readFileSync('./xxx.key'),
        cert: fs.readFileSync('./xxx.crt')
    };
    const app = express();
    // server
    const httpServer = http.createServer(app);
    const httpsServer = https.createServer(options, app);
    
    // https redirect
    app.use((req, res, next) => {
        if (process.env.NODE_ENV == 'development') {
        } else {
            if (!req.secure) {
                return res.redirect('https://' + req.get('host') + req.url);
            }
        }
        next();
    });
    
    // router
    app.use((req, res, next) => {
        res.send('Hello world!')
    })
    
    // listen
    httpServer.listen(80, () => {
        console.log('Server starts listening on port 80');
    });
    httpsServer.listen(443, () => {
        console.log('Server starts listening on port 443');
    });
        

八、blog 项目笔记

项目演示地址 → http://gaolihai.top:81/blog/home/index


    // 1. 模板文件外链文件的路径
    // 外链文件的相对路径, 请求地址为相对于当前路由的路径
    // 如 
    // 当前路由为 localhost/blog,外链相对路径为 admin/style.css
    // 此时请求 url 为 localhost/blog/admin/style.css
    
    // 外链文件的绝对路径,请求地址为相对于一级路由的路径
    // 如
    // 当前路由为 localhost/blog,外链相对路径为 admin/style.css
    // 此时请求 url 为 localhost/admin/style.css
    
    // 2. 数据库集合规则 unique:true 意为字段数据必须唯一
    
    // 3. 在 ES6 中, 若对象的某成员键与值相同, 则写一个即可
    // 如: {User:User} <==> {User}
    
    // 4. JQ 获取表单用户输入的内容 serializeArray() 返回一数组, 数组中包含对象
    // 键值对以该形式给出: [{name:'username', value:'gaolihai'}, {name:'password', value:'123456'}]
    // 封装优化
    function serializeToJson(form) {
        var re = {};
        var data = form.serializeArray();
        for (item of data) {
            re[item.name] = item.value;
        };
        return re;
    }
    // 5. str.trim() 返回去除两端空格的 str
    
    // 6. 密码加密
    // 安装
    // npm i bcrypt
    // 依赖
    // python
    // npm i -g node-gyp
    // npm i --global --production windows-build-tools
    // 引入和使用
    const bcrypt = require('bcrypt');
    let salt = await bcrypt.genSalt(10);
    let pass = await bcrypt.hash('password', salt);
    // 密码比对
    let isEqual = await bcrypt.compare('password', 'cryptPass');
    //
    // 7. http 协议的无状态性, 当服务端与客户端进行一次交互后即断开
    
    // 8. cookie 与 session
    // cookie
    // cookie 为在客户端本地储存的数据, 可以储存多条数据
    // cookie 会随 http 请求发送到客户端
    // cookie 可由服务端或前端 webapi 存储
    // cookie 存在过期时间, 过期后会被删除
    // 
    // session
    // session 是一个对象, 在服务端内存中储存
    // session 对象中可以存储多条数据, 每条数据均对应一个唯一的 sessionid
    //
    // 客户端登录 → 服务端生成 sessionid 写入 cookie
    // 客户端访问 → 将 cookie 与 sessionid 比对
    // 插件 express-session
    // 引入及使用
    const session = require('express-session');
    const session = require('express-session');
    router.use(session({
        secret: 'secret key',
        resave: false,
        // 每次强制重新保存(即使未修改)
        saveUninitialized: false,
        // 保存未初始化的 cookie
        cookie: { maxAge: 1000 * 60 * 60 * 24 }
        // 过期时间
    }));
    router.get('/route', (req, res) => {
        // 客户端的 cookie 位于随请求发送的 session 对象中
        req.session;
        // 获取session
        req.session.destroy();
        // 删除 session
    });
    //
    // 9. 在多级路由中,下层路由可由 req.app 获取当前 application
    //
    // 10. 为 req 添加一个 fullUrl 属性的中间件
    module.exports = (req, res, next) => {
        req.fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
        next();
    };
    //
    // 11. 第三方模块 Joi js 对象的规则描述语言和验证器
    // 安装
    // npm i Joi
    // 引入及使用
    const joi = require('joi');
    const schema = {
        username: joi.string().alphanum().min(2).max(20).required().error(new Error('username 格式出错')),
        password: joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
        access_token: [joi.string(), joi.number()],
        email: joi.string().email()
    };
    async function run() {
        try {
            let re = await joi.validate(obj, schema)
            console.log(re);
            // 返回验证对象
        } catch (error) {
            console.log(error.message);
            return;
        }
        console.log('ok');

    }
    run();

    // string() 字符串   alphanum() 只能是字母或数字字符串   min() / max() 最大最小长度
    // required() 必须的   regex(/ /) 正则验证   number() 数值类型   integer() 整数   valid('',''...) 枚举
    // [joi.string(), joi.number()] 用数组指定不同的规则   error() 错误信息
    // 注意, 必须先指定数据类型
    //
    // 11. 登录等页面提示信息
    // 第一种方法: 做重定向, url 带参, 目标路由把 req.query 传进 render 里
    // 第二种方法: 在当前路由直接 render
    // 第一种方法比较方便不用, 而且更加模块化, 不耦合 
    //
    // 12. 数据库查询文档数目
    Doc.countDocuments({});
    //
    // 13. res.send 不可仅发送一个 number
    //
    // 14. 涉及到文件上传的表单,需要在 <form> 中添加属性 enctype(编码类型)
    // 默认类型为 application/x-www-form-urlencoded
    // 设置为 multipart/form-data
    //
    // 15. 第三方面模块 formidable 解析二进制请求体数据
    // 引入及使用
    const formidable = require('formidable');
    // 创建解析对象
    const form = new formidable.IncomingForm();
    form.uploadDir = '/my/dir';
    form.keepExtensions = true;
    // 解析
    form.parse(req, (err, fields, files) => {
        // fields 请求参数
        // files 文件信息
    });

    // 16. 前端 js 读取文件
    var reader = new FileReader();
    reader.readAsDataURL(fileObj);
    reader.onload = function() {
        reader.result;
    };
    //
    // 17. express-art-template 全局配置
    // 导入 art-template 然后直接正常配置
    const dateFormat = require('dateformat');
    const template = require('art-template');
    template.defaults.imports.dateFormat = dateFormat;
    //
    // 18. 第三方模块 mongoose-sex-page 实现数据分页
    const pagination = require('mongoose-sex-page');
    pagination(User)
        .page() // 当前页
        .size() // 每页数据条数
        .display() // 总条数
        .find() // 条件,可缺省
        .exec(); // 查询
    //
    //
    // 19. mongoDB 数据库添加账号
    // 1. 以管理员方式运行 powershell
    // 2. 连接数据库 mongo
    // 3. show dbs, 切换到 admin 数据库 use admin 
    // 4. 创建管理员账户 db.createUser()   db.createUser({user:'gaolihai',pwd:'745663',roles:['root']})
    // 5. 切换到其他数据库 use dbName
    // 6. 创建普通账号db.createUser()   db.createUser({user:'gaolihai',pwd:'745663',roles:['readWrite']})
    // 7. 卸载服务 1. net stop mongodb 停止服务  2. mongod --remove 卸载服务
    // 8. 创建服务 1.  mongod --logpath="A:\Others\MongoDB\log\mongod.log" --dbpath="A:\Others\MongoDB\data" --install -auth 创建服务 \
    // 参数说明: 1. 日志目录  2. mongo data目录  3. 安装服务  4. 设置当前数据库必须进行登录验证才能操作
    // 2. net start mongodb 开启服务
    
    // 命令行 操作过程
    // PS C:\windows\system32> mongo
    // MongoDB shell version v4.2.3
    // connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
    // Implicit session: session { "id" : UUID("55a613b5-4e5b-4bdd-9674-da0b9a2676b9") }
    // MongoDB server version: 4.2.3
    // Welcome to the MongoDB shell.
    // For interactive help, type "help".
    // For more comprehensive documentation, see
    //         http://docs.mongodb.org/
    // Questions? Try the support group
    //         http://groups.google.com/group/mongodb-user
    // Server has startup warnings:
    // 2020-04-02T10:00:27.919+0800 I  CONTROL  [initandlisten]
    // 2020-04-02T10:00:27.919+0800 I  CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
    // 2020-04-02T10:00:27.919+0800 I  CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
    // 2020-04-02T10:00:27.919+0800 I  CONTROL  [initandlisten]
    // ---
    // Enable MongoDB's free cloud-based monitoring service, which will then receive and display
    // metrics about your deployment (disk utilization, CPU, operation statistics, etc).
    
    // The monitoring data will be available on a MongoDB website with a unique URL accessible to you
    // and anyone you share the URL with. MongoDB may use this information to make product
    // improvements and to suggest MongoDB products and deployment options to you.
    
    // To enable free monitoring, run the following command: db.enableFreeMonitoring()
    // To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
    // ---
    
    // > show dbs
    // admin    0.000GB
    // blog     0.000GB
    // config   0.000GB
    // firstDB  0.000GB
    // local    0.000GB
    // > use admin
    // switched to db admin
    // > db.createUser({user:'gaolihai',pwd:'745663',roles:['root']})
    // Successfully added user: { "user" : "gaolihai", "roles" : [ "root" ] }
    // > use blog
    // switched to db blog
    // > db.createUser({user:'gaolihai',pwd:'745663',roles:['readWrite']})
    // Successfully added user: { "user" : "gaolihai", "roles" : [ "readWrite" ] }
    // > exit
    // bye
    // PS C:\windows\system32> net stop mongodb
    // MongoDB Server 服务正在停止.
    // MongoDB Server 服务已成功停止。
    
    // PS C:\windows\system32> mongod --remove
    // 2020-04-02T10:38:19.819+0800 I  CONTROL  [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
    // 2020-04-02T10:38:20.290+0800 I  CONTROL  [main] Trying to remove Windows service 'MongoDB'
    // 2020-04-02T10:38:20.300+0800 I  CONTROL  [main] Service 'MongoDB' removed
    // PS C:\windows\system32> mongod --logpath="A:\Others\MongoDB\log\mongod.log" --dbpath="A:\Others\MongoDB\data" --install -auth
    // 2020-04-02T10:42:31.305+0800 I  CONTROL  [main] log file "A:\Others\MongoDB\log\mongod.log" exists; moved to "A:\Others\MongoDB\log\mongod.log.2020-04-02T02-42-31".
    // PS C:\windows\system32> net start mongodb
    // MongoDB 服务正在启动 ..
    // MongoDB 服务已经启动成功。
    
    // PS C:\windows\system32>
    
    // 通过账号密码连接数据库
    // mongoose.connect('mongodb://username:pwd@localhost:port/databaseName')
    
    // 总结:
    // 1、进入mongodb的shell :
    // mongo
    // 2、切换数据库
    // use admin
    // 3、创建admin超级管理员用户
    // 指定用户的角色和数据库:
    // (注意此时添加的用户都只用于admin数据库,而非你存储业务数据的数据库)
    // (在cmd中敲多行代码时,直接敲回车换行,最后以分号首尾)
    // db.createUser(  
    //   { user: "admin",  
    //     customData:{description:"superuser"},
    //     pwd: "admin",  
    //     roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]  
    //   }  
    // )  
    // user字段,为新用户的名字;
    // pwd字段,用户的密码;
    // cusomData字段,为任意内容,例如可以为用户全名介绍;
    // roles字段,指定用户的角色,可以用一个空数组给新用户设定空角色。在roles字段,可以指定内置角色和用户定义的角色。
    // 超级用户的role有两种,userAdmin或者userAdminAnyDatabase(比前一种多加了对所有数据库的访问,仅仅是访问而已)。
    // db是指定数据库的名字,admin是管理数据库。
    // 不能用admin数据库中的用户登录其他数据库。注:只能查看当前数据库中的用户,哪怕当前数据库admin数据库,也只能查看admin数据库中创建的用户。
    // 4、创建一个不受访问限制的超级用户
    // (跳出三界之外,不在五行之中)
    // db.createUser(
    //     {
    //         user:"root",
    //         pwd:"pwd",
    //         roles:["root"]
    //     }
    // )
    // 5、创建一个业务数据库管理员用户
    // (只负责某一个或几个数据库的増查改删)
    // > db.createUser({
    //     user:"user001",
    //     pwd:"123456",
    //     customData:{
    //         name:'jim',
    //         email:'jim@qq.com',
    //         age:18,
    //     },
    //     roles:[
    //         {role:"readWrite",db:"db001"},
    //         {role:"readWrite",db:"db002"},
    //         'read'// 对其他数据库有只读权限,对db001、db002是读写权限
    //     ]
    // })
    // 数据库用户角色:read、readWrite;
    // 数据库管理角色:dbAdmin、dbOwner、userAdmin;
    // 集群管理角色:clusterAdmin、clusterManager、4. clusterMonitor、hostManage;
    // 备份恢复角色:backup、restore;
    // 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
    // 超级用户角色:root
    // 内部角色:__system
    // Read:允许用户读取指定数据库
    // readWrite:允许用户读写指定数据库
    // dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
    // userAdmin:允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户
    // clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
    // readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
    // readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
    // userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
    // dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
    // root:只在admin数据库中可用。超级账号,超级权限
    // 6、查看创建的用户
    // show users 或 db.system.users.find() 或 db.runCommand({usersInfo:"userName"})
    // 7、修改密码
    // use admin
    // db.changeUserPassword("username", "xxx")
    // 8、修改密码和用户信息
    // db.runCommand(
    //     {
    //         updateUser:"username",
    //         pwd:"xxx",
    //         customData:{title:"xxx"}
    //     }
    // )
    // 9、删除数据库用户
    // use admin
    // db.dropUser('user001')
    // 10、创建其他数据管理员
    // // 登录管理员用户
    // use admin
    // db.auth('admin','admin')
    // // 切换至db001数据库
    // use db001
    // // ... 増查改删该数据库专有用户
    // 重要的一步
    // 启用权限验证(别TM的武装了大半天,大门还一直开着,还抱怨我方防御塔怎么一直被摧毁)
    // mongo --auth
    // 或者修改mongo.conf,最后一行添加
    
    // #启用权限访问
    // auth=true
    // 11、重新启动mongodb
    // net stop mongodb;
    // net start mongodb;
    // 和用户管理相关的操作基本都要在admin数据库下运行,要先use admin;
    // 如果在某个单一的数据库下,那只能对当前数据库的权限进行操作;
    // db.addUser是老版本的操作,现在版本也还能继续使用,创建出来的user是带有root role的超级管理员。
    
    
    // 20. 生产环节与开发环境
    // 通过设置电脑和服务器的环境变量 NODE_ENV : development / production
    if (process.env.NODE_ENV == 'development') {
    
    } else {
    
    }
    
    // 21. 第三方模块 morgan 中间件, 输出请求信息
    const morgan = require('morgan');
    app.use(morgan('dev'));
    
    
    // 22. 第三方模块 config 方便切换配置信息
    // 1. 在项目根目录建立 config 文件夹
    // 2. config 文件夹下三个文件 default.json development.json production.json
    const config = require('config');
    
    // 将敏感配置信息存储在环境变量中
    // 在 config 文件夹中建立 custom-environment-variables.json 
    // 在值中填写环境变量的名称, config 优先从用户变量读取
    
    // 23. mongodb 连接时的query string 
    // authSource=admin 依赖 admin 验证登录    
        

模块等其他补充

Mark 一下,以后用到再看 → https://www.cnblogs.com/fandx/p/12130367.html

    // 一、模块
    // dateformat 日期时间格式化
    // nrm 切换下载源
    // mongoose mongodb数据库
    // mime 使用 getType 方法获取文件类型
    // art-template 模板 (express-art-template)
    // nodemon 运行工具
    // gulp 构建工具
    
    
    // router 路由
    const router = require('router')();
    router().get('./path', (req, res) => {});
    router().post('./path', (req, res) => {});
    server.on('request', (req, res) => router(req, res, callback));
    
    // serve-static 静态资源访问
    const serve = require('serve-static')('./path');
    server.on('request', (req, res) => serve(req, res, callback));
    
    
    // body-parser
    // 引入
    const bodyParser = require('body-parser');
    app.use(bodyParser.urlencoded({ extended: false }));
    // 当 extended 为 false 时, 会使用系统模块 queryString 进行处理, 反之则使用第三方模块 qs
    app.post('/path', (req, res) => {
        req.body;
    });
    
    
    // bcrypt
    // 依赖
    // python
    // npm i -g node-gyp
    // npm i --global --production windows-build-tools
    // 引入和使用
    const bcrypt = require('bcrypt');
    let salt = await bcrypt.genSalt(10);
    let pass = await bcrypt.hash('password', salt);

    
    // express-session
    // 引入及使用
    const session = require('express-session');
    router.use(session({
        secret: 'secret key',
        resave: false,
        // 每次强制重新保存(即使未修改)
        saveUninitialized: false,
        // 保存未初始化的 cookie
        cookie: { maxAge: 1000 * 60 * 60 * 24 }
        // 过期时间
    }));
    router.get('/route', (req, res) => {
        // 客户端的 cookie 位于随请求发送的 session 对象中
        req.session;
        // 获取session
        req.session.destroy();
        // 删除 session
    });
    
    
    // formidable 解析二进制请求体数据
    // 引入及使用
    const formidable = require('formidable');
    // 创建解析对象
    const form = new formidable.IncomingForm();
    form.uploadDir = '/my/dir';
    form.keepExtensions = true;
    // 解析
    form.parse(req, (err, fields, files) => {
        // fields 请求参数
        // files 文件信息
    });


    // 二、控制台输出彩色文字
    console.log("\033[30m 黑色字 \033[0m", '黑色')
    console.log("\033[31m 红色字 \033[0m")
    console.log("\033[32m 绿色字 \033[0m")
    console.log("\033[33m 黄色字 \033[0m")
    console.log("\033[34m 蓝色字 \033[0m")
    console.log("\033[35m 紫色字 \033[0m")
    console.log("\033[36m 天蓝字 \033[0m")
    console.log("\033[37m 白色字 \033[0m")
    
    console.log("\033[40;37m 黑底白字 \033[0m")
    console.log("\033[41;37m 红底白字 \033[0m")
    console.log("\033[42;37m 绿底白字 \033[0m")
    console.log("\033[43;37m 黄底白字 \033[0m")
    console.log("\033[44;37m 蓝底白字 \033[0m")
    console.log("\033[45;37m 紫底白字 \033[0m")
    console.log("\033[46;37m 天蓝底白字 \033[0m")
    console.log("\033[47;30m 白底黑字 \033[0m")
    
    // \33[0m 关闭所有属性
    // \33[1m 设置高亮度
    // \33[4m 下划线
    // \33[5m 闪烁
    // \33[7m 反显
    // \33[8m 消隐
    // \33[30m — \33[37m 设置前景色
    // \33[40m — \33[47m 设置背景色
    // \33[nA 光标上移n行
    // \33[nB 光标下移n行
    // \33[nC 光标右移n行
    // \33[nD 光标左移n行
    // \33[y;xH设置光标位置
    // \33[2J 清屏
    // \33[K 清除从光标到行尾的内容
    // \33[s 保存光标位置
    // \33[u 恢复光标位置
    // \33[?25l 隐藏光标
    // \33[?25h 显示光标
    
    // 封装
    let customizeFn = {
            error(str) {
                console.log("\033[31m " + str + " \033[0m")
            },
            info(str) {
                console.log("\033[36m " + str + " \033[0m")
            },
            warn(str) {
                console.log("\033[43;37m " + str + " \033[0m")
            },
            success(str) {
                console.log("\033[32m " + str + " \033[0m")
            }
        }
    console = Object.assign(console, customizeFn)