EggJS-数据库

Mysql

一、安装配置

    $ npm i --save egg-mysql

    // config/plugin.js  开启插件
    exports.mysql = {
            enable: true,
            package: 'egg-mysql',
    };

egg-monogoose

一、安装配置

    $ npm i egg-mongoose --save

    // {app_root}/config/plugin.js
    exports.mongoose = {
        enable: true,
        package: 'egg-mongoose',
    };

    // {app_root}/config/config.default.js
    module.exports = {
        mongoose: {
            url: 'mongodb://127.0.0.1:27017/ibg_node_core',
            options: {
                db: { native_parser: true },
                server: { poolSize: 5 },
                user: 'core',
                pass: 'we123',
            },
        },
    }

    // 多个mongos配置
    // {app_root}/config/config.default.js
    exports.mongoose = {
        url: 'mongodb://mongosA:27501,mongosB:27501',
        options: {}
    };

    // log
    module.exports = {
        logger: {
            dir: '/home/admin/logs/demoapp',        // logger文件的目录
            level: 'DEBUG',                         // 开deubg模式
        },
    };


二、定义Schema

    // app/modle/reportWarningConfig.js

    module.exports = app => {
        const mongoose = app.mongoose;
        const ReportWarningConfigSchema = new mongoose.Schema({
                actionCode: { type: String, unique: true, trim: true }, // 动作编码
                interval: { type: Number, default: 1800 },  // 扫描间隔时间,单位:秒 默认 30分钟
                emailConfig: { // 发送邮件条件
                    appKey: { type: String, default: '', trim: true },
                    appSecret: { type: String, default: '', trim: true },
                    recipient: { type: [String] },  // 收件人
                },
                lastExecutedTime: { type: Date, default: Date.now },   // 最近执行时间
            }, 
            {
                timestamps: true,               // 设置timestamps,多加两个字段createdTime、updatedTime,来记录插入时间和更新时间
                collection: 'report_warning_config',
            }
        );

        return mongoose.model('ReportWarningConfig', ReportWarningConfigSchema);
    };


三、常用语句

    1、await app.model.login.find(条件): 返回的是一组数据, []

    2、await app.model.login.findOne(条件): 只返回一条数据, 如果没有匹配的返回null,

    3、await app.model.login.update(条件, 要更新的文档, 回调方法): 更新数据

    4、let loginTask = new app.model.login({ username: 'siguang', password: 'ssssss'});     
            let doc = await loginTask.save();        // 插入数据 

    5、exec(callback): 在查询、更新后会执行一个回调函数来查看是否成功执行

    6、save(function(err, kitten){  }): 存储


四、Example

    1、查询

        async queryMonitorTask(){
            let monitorTaskList = await app.model.ReportWarningConfig.find();          // app.model.ReportWarningConfig 来指定用哪个文档
            return app.renderBody({
                statusType: app.statusType.success,
                data: {
                    list: monitorTaskList
                }
            });
        }


    2、更新

        更新需要先将文档查回来后,在修改某一个字段,如果一直更新会覆盖

        async updateMonitorTask(params){
            let response;
            let condition = {_id: params.id};
            let doc = await app.model.ReportWarningConfig.findOne(condition);       // 通过id来取到当前数据

            if(doc){
                doc.interval = params.interval;
                doc.emailConfig.recipient = params.emailRecipient;
                doc.smsConfig.recipient = params.smsRecipient;

                await doc.save();
                response = app.renderBody({
                    statusType: error,
                    error: error,
                });
            }
            else{
                response = app.renderBody({
                    statusType: app.statusType.error,
                    error: doc,
                });
            }

            return response;
        }


    3、插入数据

        插入数据将使用save()

        async insertMonitorTask(){
        const {ctx, app, service} = this;
        const {actionCode, interval, emailRecipient, smsRecipient} = ctx.request.body;

        // 将要插入的数据加到model中
        let monitorTask = new app.model.ReportWarningConfig({   // 注意这里使用new 
            actionCode,
            interval,
            emailConfig: {
                recipient: emailRecipient
            },
            smsConfig: {
                recipient: smsRecipient
            }
        })

        // 使用save()来插入
        let doc = await monitorTask.save();

        let response = app.renderBody({
            statusType: app.statusType.success,
            data: doc._id
        });
        ctx.body = response;
    }

mongoose 积累

一、查询出来的数据不能修改

async getNewsContent(params){
    let doc = await app.model.News.findOne({_id: params.id});

    // 不能直接修改doc的对象,需要通过toObject()方法来转成对象的形势
    let newDoc = doc.toObject();
    newDoc.createdAt = app.dateFormat(new Date(newDoc.createdAt).getTime(), 'yyyy-MM-dd hh:mm:ss')
    newDoc.updatedAt = app.dateFormat(new Date(newDoc.updatedAt).getTime(), 'yyyy-MM-dd hh:mm:ss')
    console.log(`ResponseAAA ----- ${JSON.stringify(newDoc)}`)

    return {
        data: newDoc,
        message: '获取成功',
        status: 0
    }
}

| https://github.com/eggjs/egg-mongoose

MySQL

数据类型: 字符型、整型、浮点型、日期时间型
操作符: 算术运算符、比较运算符、逻辑运算符、位运算符
属性: 自动编号
约束: 主键、外键、表关连、索引
库操作: 创建库、修改库、删除库
表操作: 创建表、删除表、查看表结构、查看库中所有表
列操作: 添加列、删除列、修改列
记录操作: 插入记录、更新记录(单表、多表更新)、删除记录、查询记录(多列查询、分组、指定查询范围、使用别名)
子查询语句、例约束
运算符与函数: 字符函数、数值运算符、比较运算符、日期时间函数、信息函数、聚合函数、加密函数、自定义函数
存储过程: 创建(参数:IN、OUT、INOUT)、调用
其它概念: 存储引擎、并发控制(锁、锁颗粒)、事务

MySQL基础

一、MySQL介绍

    1、MySQL是关系型数据库

    2、安装后的有4个默认库 nformation_schem、performance_schema、test、mysql     

    3、Mysql 端口号 3306

    4、mac下mySQL的配置文件 /usr/local/mysql/my.cnf


二、MySQL 目录结构 

    mysql安装后的路径 /usr/local/mysql

    1、bin: 存储可执行文件

    2、data: 存储数据文件

    3、docs: 文档

    4、include: 存储包含头文件

    5、lib: 存储库文件

    6、share: 错误消息和字符集文件


三、停止和启动mysql服务

    关闭mysql服务    查看进程:ps -ef|grep mysql         杀进程和子进程:sudo kill -9 239 746

    启动mysql服务    sudo /usr/local/mysql/bin/mysqld_safe

    系统偏好设置 -> mysql   启动


四、登录、退出mysql

    1、登录

        mysql -uroot -pssssss -h127.0.0.1          

        -u 用户名    -p 密码       -P 端口号       -h 数据库地址 

        隐藏密码输入 mysql -uroot -h127.0.0.1 -p    回车后在输入密码

    2、退出: exit;


五、语法规范

    1、关键字和函数名全部大写

    2、数据库名称、表名称、字段字都小写

    3、SQL语句必须以";"分号结束

        修改结束符  DELIMITER  //        以 // 为结束


六、数据库概念

    1、库: 一些关联表的集合

    2、表: 行和列组成, 行一条数据

    3、主键: 可以唯一标识不会有重复, 可以通过主键来查询数据

    4、外键: 外键可以引用主键数据,用于关联


七、安装和卸载mysql

    1、homebrew安装mysql

        http://brew.sh/

        $ brew install mysql        // 安装mysql

        $ mysql.server start           // 启动

        $ mysql  -V            // 查看版本


    2、卸载mysql

        sudo rm /usr/local/mysql 

        sudo rm -rf /usr/local/var/mysql 

        sudo rm -rf /usr/local/mysql* 

        sudo rm -rf /Library/StartupItems/MySQLCOM 

        sudo rm -rf /Library/PreferencePanes/My* 

        vim /etc/hostconfig and removed the line MYSQLCOM=-YES- 

        rm -rf ~/Library/PreferencePanes/My* 

        sudo rm -rf /Library/Receipts/mysql* 

        sudo rm -rf /Library/Receipts/MySQL* 

        sudo rm -rf /var/db/receipts/com.mysql.*

库服务命令

1、启动和停止MySQL服务

    service stop mysql

    service start mysql

2、登录: mysql -uroot -p123456 -h127.0.0.1

3、退出: \q 、 exit、 quit

库操作语句

1、创建数据库       CREATE DATABASE 库名;

        创建一个指定字符集的数据库         CREATE DATABASE 库名 CHARACTER SET utf8;

2、修改数据库       ALTER  DATABASE 库名 CHARACTER SET = uft-8;   // 将库编码改成utf8

3、删除数据库     DROP database 库名;

4、查看所有数据库     SHOW databases;

5、打开数据库        USE 库名;

6、查看当前所在的数据库   SELECT database();

7、查看库的编码: SHOW CREATE DATABASE 库名;

操作表语句

1、创建表    CREATE TABLE  表名(字段名 数据类型, 字段名 数据类型);

        CREATE TABLE  表名(字段 数据类型  primary key,  username VARCHAR(20));        // 加主键 primary key 

2、查看库中所有表  SHOW TABLES [db_name] [where expr];

3、查看表结构    SHOW COLUMNS FROM 表名;         // 与 desc 表名;   相同

4、删除表        DROP TABLE 表名;

5、ALTER 列操作: 

    1)添加列: ALTER TABLE 表名 ADD 列名 varchar(20);

    2)删除列: ALTER TABLE 表名 DROP 列名;

    3)修改列的类型信息

        ALTER TABLE 【表名字】 CHANGE 【列名称】【新列名称(这里可以用和原来列同名即可)】 BIGINT NOT NULL  COMMENT '注释说明'

    4)重命名列

        ALTER TABLE 【表名】 CHANGE 【列名称】【新列名称】 BIGINT NOT NULL  COMMENT '注释说明'

    5)重命名表

        ALTER TABLE 【表名字】 RENAME 【表新名字】

    6)删除表中主键

        Alter TABLE 【表名字】 drop primary key

    7)添加主键

        ALTER TABLE sj_resource_charges ADD CONSTRAINT PK_SJ_RESOURCE_CHARGES PRIMARY KEY (resid,resfromid)

    8)添加索引

        ALTER TABLE sj_resource_charges add index INDEX_NAME (name);

    9)添加唯一限制条件索引

        ALTER TABLE sj_resource_charges add unique emp_name2(cardnumber);

    10)删除索引

        ALTER TABLE [表名] index emp_name;

记录操作语句

1、插入记录     

    1) INFO 插入指定字段值:     INSERT INTO 表名(name, age) VALUES (name值, age值);

    2) SET  插入指定字段值:     INSERT 表名 SET name="sssss", age="lll";

    3)插入所有字段值:        INSERT 表名 VALUES (NULL, name值, age值);        // 1、没有INFO  2、如果id为主键、自增涨,需要给一个默认传入NULL 或 DEFAULT

    4)INSERT ... SELECT 将查询结果写入数据表

        INSERT [INTO] 表名 FROM 列名   SELECT 

        // 将查询出restaurant表中的type进行分组内容,插入到rest_type表中的type字段中

        INSERT rest_type(type) SELECT type FROM restaurant GROUP BY type;    


2、更新数据     

    单表更新:  UPDATE 表名 SET 要更新字段名=新值, 要更新字段名=新值  WHERE  条件        // 如果省略where条件,将修改表中的所有记录

    多表更新:  

        UPDATE 表1  [JOIN | CROSS JOIN | INNER JOIN] 表2

        表连接类型,JOIN、CROSS JOIN、INNER JOIN

                   LEFT [OUTER] JOIN 左外连接

                   RIGHT [OUTER] JOIN 右外连接


3、删除记录     

    单表删除:  DELETE FROM  表名 WHERE id=2

    多表删除:  



4、查询记录    

    查询所有列: SELECT * FROM 表名;        // 查询所有列

    查询多列:   SELECT username, age, sex FROM 表名;

    条件查询:   SELECT * FROM 表名  WHERE  条件表达式(id<100);        // 查询表中的id小于100的所有记录

    查询结果分组 GROUP BY: 返回一个列中所有不相同的类别

        SELECT * FROM 表名  GROUP BY  列名 [ASC | DESC];             // 分组以哪列进行返回 ASC | DESC 正、倒序

                例: SELECT * FROM users GROUP BY sex;            # 返回sex列的分组内容,结果为两类: '男' 和 '女'


    HAVING 分组条件: SELECT * FROM 表名  GROUP BY  列名  HAVING  version < 100;   // 分组后进行过滤

    LIMIT 指定查询范围: SELECT * FROM 表名 LIMIT  5, 10;        // 从第5条数据开始,查找出10条数据,也就是查询出第5-15条数据

    AS 给予别名: 当名子太长可以定义一个名,输出的时候就是定义的名字

            SELECT person AS ps, username AS un FORM users;


    --------------- select 其它  ---------------

    SELECT COUNT(*) FROM 表名 WHERE (条件)         // 返回记录的总数,如果不加条件返回整个表的总记录

    SELECT AVG(price) FROM 表名;                    // AVG()来求一个字段的平均值

    SELECT ROUND(AVG(price), 2) FROM 表名;        // ROUND() 对数值进行四舍五入


小技巧:

    1、SELECT * FROM users\G;        // 加\G 以网格形势展示查询,格式化了数据方便来看


设置空值

    例 UPDATE tag SET label='折' WHERE label=NULL;        // 将label列中所有空值更新成'折',失效不会被

    MySQL: null值需要通过几种方式

    1、is null、is not null: UPDATE tag SET label='折' WHERE label is null;

    2、ISNULL(): 与上面一样 UPDATE tag SET label='折' WHERE ISNULL(label);

子查询语句

子查询指嵌套在查询内部,且必须始终出现在圆括号内

子查询包含关键字或条件: DISTINCT、GROUP BY、ORDER BY、LIMIT、函数等

子查询外层查询可以是: SELECT、INSERT、UPDATE、SET、DO

1、按返回结果集分类

    http://www.cnblogs.com/herry52/p/5643986.html

    分为4种:表子查询,行子查询,列子查询和标量子查询

    1) 表子查询:返回的结果集是一个行的集合,N行N列(N>=1)。表子查询经常用于父查询的FROM子句中。

        示例: 获取编号小于10的男性球员的号码

        SELECT playerno FROM (SELECT playerno, sex FROM players WHERE playerno < 10) AS players10 WHERE sex='M';

        分析: 

        // 查询players中playerno小于10的两个字段的记录,并起别名为player10
        SELECT playerno, sex FROM players WHERE playerno < 10 AS players10;    

        // 显示playerno字段内容,条件是子表中的sex='M'
        SELECT playerno FROM 查询出的内容 WHERE sex='M'


    2) 行子查询:返回的结果集是一个列的集合,一行N列(N>=1)。行子查询可以用于福查询的FROM子句和WHERE子句中。

        示例: players表中获取与100号球员性别相同并且居住在同一城市的球员号码。

        SELECT playerno, sex, town FROM players WHERE (sex, town) = (select sex, town FROM players where playerno = 100);


    3) 列子查询:返回的结果集是一个行的集合,N行一列(N>=1)

        列子查询可以使用 IN、ANY、SOME、ALL

        1、修饰的比较运算符: ANY、SOME、ALL

            如果子查询返回:Subquery returns more than 1 row   多于一行记录,使用上面运算符

            ANY和SOME是等价的意思只符合一个就可以

            ALL 需要符合全部


        2、IN、NOT IN 子查询



    4) 标量子查询:可以指定一个标量表达式的任何地方,几乎都可以使用一个标量子查询

        示例: 获取和27号球员出生在同一年的球员的号码

        SELECT playerno, name, birth_date FROM players WHERE YEAR(birth_date) = (SELECT YEAR(birth_date) FROM players WHERE playerno=100) AND playerno != 100;

—————————– 运算符与函数 —————————–

字符函数

1、CONCAT(): 字符连接    SELECT CONCAT('user', '-', 'name');

    // 将tag表中value和label字段的值连接,并通过AS来返回一个别列名为tagName
    SELECT CONCAT(value, '_', label) AS tagName FROM tag;


2、CONCAT_WS(): 使用指定的分隔符进行字符连接

    SELECT CONCAT_WS('|', name, user, id);        以"|"进行分隔


3、FORMAT(): 数据格式化

    SELECT FORMAT(132.34234, 2);        // 132.34


4、LOWER(): 将字符转成小写

5、UPPER(): 将字符转成大写

6、LEFT()、RIGHT(): 获取从左、右侧字符的几位

    SELECT LEFT('what are you doing.', 7);         // what ar

7、LENGTH(): 获取字符串长度

8、LTRIM()、RTRIM()、TRIM(): 删除前空格、删除后空格、删除前后空格

9、SUBSTRING(): 字符串的截取 substring();

        SELECT SUBSTRING('what are you doing.', 3, 7);        // at are

10、RESPLACE(): 字符替换 

        SELECT REPLACE('what are you doing.', ' ', '');      // whatareyoudoing.

11、LIKE(): 模式匹配

        %: 为任意字符            _: 任意一个字符

        ESCAPE: 使用哪个字符后的百分号

        SELECT * FROM sys_user WHERE username LIKE '%@renrendai.com%'\G;    // sys_user表中username字段匹配出 带有@renrendai.com的字符

        SELECT * FROM sys_user WHERE username LIKE '%renren%%' ESCAPE 'renren'\G;    // 匹配出 renren%

数值运算符

1、CEIL(): 向上取整    SELECT CEIL(4.9);        // 5     SELECT CEIL(log) FROM office;

2、FLOOR(): 向下取整    SELECT FLOOR(4.2);        // 4

3、DIV(): 整数除法     SELECT 20 DIV 3;        // 6

4、MOD(): 取模

5、POWER(): 幂运算

6、ROUND(): 四舍五入        SELECT ROUND(2.3464, 2);    // 2.35

7、TEUNCATE(): 数字截取

比较运算符

1、[NOT] BETWEEN ... AND ... :  在不在一个范围内

        // 查找出fruit表中fruit_price字段(价格)10元至30无之间的数据
        SELECT fruit_id, fruit_desc, fruit_price FROM  fruit WHERE fruit_price BETWEEN 10 AND 30;


2、[NOT] IN(): 在不在几个值的内

        SELECT 10 IN (5, 20, 40);        // 返回0

        SELECT 10 IN (2, 10, 30, 49);    // 返回1


3、IS [NOT] NULL: 返回为空, IS NOT NULL 返回不为空

    // 返回 fruit_dim_three 内容为空的值
    SELECT fruit_product_name, fruit_price,fruit_dim_three FROM fruit WHERE fruit_dim_three IS NULL;  

函数

一、日期时间函数

    1、NOW(): 返回一个当前日期和时间

    2、CURDATE(): 当前日期

    3、CURTIME(): 当前时间

    4、DATE_ADD(): 在一个时间内添加多少时间

            SELECT DATE_ADD('2016-12-12', INTERVAL 365 DAY);        // 返回 2017-12-12 , -365就是减365天

    5、DATEDIFF(date1, date2): 两个日期的差值

            SELECT DATEDIFF('2018-12-12', NOW());        // 2018-12-12与今天相差多少天

    6、DATE_FORMAT(): 日期格式化

            SELECT DATE_FORMAT('2016-12-10', '%Y/%m/%d');        // 转换成 2016/12/10 

            SELECT DATE_FORMAT(NOW(), '%Y年-%m月-%d日 %H时:%i分:%s秒');


二、信息函数

    1、CONNECTION_ID(): 连接的ID(线程的ID)

    2、DATABASE(): 查看当前数据库     SELECT DATABASE();

    3、LAST_INSERT_ID(): 最后插入记录

    4、USER(): 当前用户

    5、VERSION(): 版本信息


三、聚合函数

    1、AVG(): 平均值的计算

            SELECT AVG(fruit_price) FROM fruit;        // 将水果的价格字段平均值返回

    2、COUNT(): 返回条件的行数

            SELECT COUNT(列名) FROM 表名;        // 返回数目(NULL 不计)

            SELECT COUNT(*) FROM 表名;        // 返回所有的记录数

    3、MAX(): 最大值

    4、MIN(): 最小值

    5、SUM(): 返回和


四、加密函数

    1、MD5(): 信息摘要算法

            MD5('adf234sdf2=-a1%af123#$');        // 16f7709af2898dd3c9f116d56ea93c82

    2、PASSWORD(): 密码算法

            PASSWORD('adf234sdf2=-a1%af123#$');    // *6D3B0C079BFF7DAD315D9AF131F0868939A74A8D

自定义函数

一、创建自定函数:

    CREATE FUNCTION 函数名 RETURNS {STRING | INTERGER | REAL | DECIMAL} routine_body

    创建     函数      名    返回                    类型                     函数体          


    函数体: 由合法的SQL语句构成,可以是简单的SELECT 或 INSERT语句

    示例1 无参数:

        // 创建一个返回当前格式化的日期
        CREATE FUNCTION newDate() RETURNS VARCHAR(30) 

            RETURN  DATE_FORMAT(NOW(), '%Y年-%m月-%d日 %H时:%i分:%s秒');

        // 调用
        SELECT newDate();        // 2016年-12月-12日 11时:31分:55秒


    示例2 有参数:

        CREATE FUNCTION fun2(num1 SMALLINT UNSIGNED, num2 SMALLINT UNSIGNED) 

            RETURNS FLOAT(10,2) UNSIGNED  

            RETURN (num1+num2)/2;


        SELECT fun2(20, 30);        // 25.00


    示例3 返回语句

        # 需要修改结束符 “;”

        // 定义一个向users表中的username字段插入值

        DELIMITER //;

        CREATE FUNCTION addUser(username VARCHAR(20))

            RETURNS INT UNSIGNED

            BEGIN

            INSERT users(username) VALUES(username);

            RETURN LAST_INSERT_ID();

            END//


二、删除自定义函数: DROP FUNCTION 函数名;

ROW_COUNT(): 用于返回被 update, insert, delete 实际修改的行数

FOUND_ROWS(): 返回上一条执行语句,影响的行数

数据类型

一、int: 整型

二、浮点

    1、float: 单精度

    2、double: 双精度浮点

三、日期

    1、datetime: 日期

    2、timestamp: 日期

    3、year、month、date

四、字符型

    1、char: 定长字符类型

    2、varchar: 不定长字符

    3、tinytext:

    4、text: 

    5、medinmtext:

    6、longtext:

    7、enum: 

    8、set: 

操作符

一、算术运算符

    +     加   SET var1=2+2;       4
    -     减   SET var2=3-2;       1
    *     乘   SET var3=3*2;       6
    /     除   SET var4=10/3;      3.3333
    DIV   整除 SET var5=10 DIV 3;  3
    %     取模 SET var6=10%3 ;     1


二、比较运算符

    >            大于 1>2 False
    <            小于 2<1 False
    <=           小于等于 2<=2 True
    >=           大于等于 3>=2 True
    BETWEEN      在两值之间 5 BETWEEN 1 AND 10 True
    NOT BETWEEN  不在两值之间 5 NOT BETWEEN 1 AND 10 False
    IN           在集合中 5 IN (1,2,3,4) False
    NOT IN       不在集合中 5 NOT IN (1,2,3,4) True
    =            等于 2=3 False
    <>, !=       不等于 2<>3 False
    <=>          严格比较两个NULL值是否相等 NULL<=>NULL True
    LIKE         简单模式匹配 "Guy Harrison" LIKE "Guy%" True
    REGEXP       正则式匹配 "Guy Harrison" REGEXP "[Gg]reg" False
    IS NULL      为空 0 IS NULL False
    IS NOT NULL  不为空 0 IS NOT NULL True


三、逻辑运算符

    与(AND) &&       或(OR)||        非(NOT)!


四、位运算符

    |   位或
    &   位与
    <<  左移位
    >>  右移位
    ~   位非(单目运算,按位取反)

属性

1、AUTO_INCREMENT: 自动编号,必须与主键组合使用,起始值为1,每次增加1

2、primary key         主键约束 每张表只能存在一个主键,主键保证记录的唯一性, 主键自动为 NOT NULL

3、foreign            外键约束 保持数据的一致性, 实现一对一或一对多的关系约束

约束

2、PRIMARY KEY(主键约束): 每张表只能存在一个主键,保证记录的唯一性,主键自动为NOT NULL

3、UNIQUE KEY(唯一约束): 可以保证记录的唯一性,字段可以为空值(NULL),每张数据表可以存在多个唯一约束

4、DEFAULT(默认约束): 默认值,插入记录时没有赋值,自动赋予默认值

    创建表时的性别字段,有三个值1,2,3 => 男,女,不男不女  如果insert插入未写入值,默认加入3
    sex ENUM('1', '2', '3') DEFAULT '3'        

5、FOREIGN KEY(外键约束): 保证数据一致性和完整性,实现一对一 或 一对多关系

5、NOT NULL(非空约束) 不能为空,必须写值要不报错

6、NULL 可以为空

# 约束分为表级约束 和 列级约束

    约束只针对一个字段来约束,叫列级约束

    约束针对两个或以上字段来约束,叫表级约束


# 外键约束的要求

    1、父表和子表必须使用相同的存储引擎,禁止使用临时表

    2、数据表存储只能为InnoDB

    3、外键列和参照必须有相似的数据类型,

CURRENT_TIMESTAMP

—————————– 存储过程 —————————–

MySQL命令执行过程

SQL命令 -> MySQL引擎 -> 语法正确 -> 可识别命令 -> 执行结果 -> 返回客户端

需要这一套流程才能跑完,如果使用存储过程会节省中间的一些步骤,可以节省性能.

什么是存储过程

存储过程: 是SQL语句和控制语句的预编译集合,以一个名称存储并作为一个单元处理

存储过程存储在数据库内,可以通过应用程序调用和输出

存储过程的优点:

    1、增强SQL语句的功能和灵活性

    2、实现较快的执行速度

        存储过程是不需要语法解析等操作,预编译,所以速度上要快

    3、减少网络流量

        后端应用程序与数据库通信,需要传SQL语句,如果有存储过程就直接告诉执行哪个存储过程就可以了

存储过程的使用

一、创建存储过程

    CREATE 

    [DEFINER = {user | CURRENT_USER}]         // 如果省略就是默认走的当前登录客户端的用户

    PROCEDURE 存储过程名(参数)


    参数:

        1) IN: 表示该参数值必须在调用存储过程时指定,这个值不能返回只能进不能出

        2)OUT: 表示该参数值可以审美观点存储过程改变,并且可以返回,

        3)INOUT: 表示该参数调用时指定,并且可以被改变和返回


    过程体

        1)由合法的SQL语句构成

        2)可以是做任意的SQL语句

        3)复合结构则使用BEGIN...END语句,与函数相同,如果执行两个或两个以上语句需要使用BEGIN...END

        4)复合结构可以包含声明、循环、控制结构



    示例1: 无参数

        // 定义一个返回客户端信息的存储过程
        CREATE PROCEDURE sp1() SELEECT VERSION();

        // 调用存储过程
        CALL sp1();                // 如果没有参数可以去掉小括号  CALL sp1;


    示例2: 带参数 IN

        DELIMITER //

        CREATE PROCEDURE removeUserId(IN uid INT UNSIGNED)

        BEGIN

        DELETE FROM users WHERE id=uid;                    // 通过传入的id来删除users表中的一条记录

        END //

        // 调用存储过程
        CALL removeUserId(5);    


    示例3: OUT

        DELIMITER ;;

        CREATE PROCEDURE removeIdAndReturnUserCount(IN pid INT UNSIGNED, OUT userCount INT UNSIGNED)         // OUT 定义输出的变量

        BEGIN 

        DELETE FROM users WHERE id = pid;                      // 删除指定的记录

        SELECT count(id) FROM users INTO userCount;         // 查询id字段所有的记录数,并返回userCount变量中,注意结果返回使用INTO

        END;;


        // 调用
        CALL removeIdAndReturnUserCount(3, @count);        // 执行删除并返回记录数,存储在count变量中

        SELECT @count;                // 显示变量的值



二、删除存储过程: DROP PROCEDURE 存储过程名;

常用概念

一、什么是存储引擎

    MySQL可以将数据以不同的技术存储在文件(内存)中,这种技术就称为存储引擎。

    每种存储引擎使用不同的存储机制、索引技术、锁定水平,最终提供广泛且不同的功能。


二、并发控制

    当多个连接对记录进行修改时保证数据的一致性和完整性

        例: 一个用户删除id=22的操作,另一个用户要读取id=22的操作,这样读取id=22就会报错

            解决这个问题就是使用并发,当迸发读或写时系统会使用锁系统,来控制读写


    1、锁

        共享锁(读锁): 在同一时间段内,多个用户读取同一个资源,读取过程中数据不会发生任何变化.

        排他锁(写锁): 在任何时候只能有一个用户写入资源,当进行写锁时会阻塞其它的读锁或写锁操作.


    2、锁颗粒

        表锁: 开锁最小的锁策略

        行锁: 开锁最大的锁策略

三、事务

    事务的主要作用就是保证数据的完整性

四、外键

    保证数据一致性的策略

五、索引

    是对数据表中一列或多列进行排序的一种结构
一、导入、导出表数据

    1、导出,选择要导出的表右键 Dump SQL File 

    2、导入,选中Table右键 import Wizard...

| 使用工具
| Navicat

Egg.js

| egg-validate POST参数规则校验 https://github.com/eggjs/egg-validate
| egg-mongoose Mongoose https://github.com/eggjs/egg-mongoose

egg-validate

validate只用于post请求的参数校验,get请求取出的都是字符串

$ npm i egg-validate --save

一、在config/plugin.js配置中添加

    exports.validate = {
        enable: true,
        package: 'egg-validate',
    };

二、Exmaple

    async refundApply() {
        const { ctx, app, service } = this;
        const paramRule = {
            partnerUserId: { type: 'string' },
            applyUserName: { type: 'string' },
            amount: { type: 'string' }, 
            sourceAccount: { type: 'string' }, 
        };

        const paramErrors = app.validator.validate(paramRule, ctx.request.body);
        if (paramErrors) {
            ctx.body = app.renderBody({
                statusType: app.statusType.paramsError,
                error: paramErrors,
            });
            return;
        }

        const response = await service.payTool.billquery.refundApply(ctx.request.body);
        ctx.body = response;
    }


三、验证规则  

    1、required - 是否当前字段必须有,required: false 可以为空

    2、allowEmpty - 允许为空

    2、int - 只能为整数

            max - 最大值
            min - 最小值

    3、number - 可以是整数和浮点数

    4、date - 日期 'YYYY-MM-DD'     birthoday: 'date'

    5、dateTime - 日期 YYYY-MM-DD HH:mm:ss

    6、boolean - 是否是布尔值        working: 'boolean'

    7、string - 是否是字符串, 字符串的四个规则:            

        allowEmpty - 允许为空字符串

        format - 使用正则来验证字符串的格式

        max - 字符串最大长度

        min - 字符串最小长度

        const rule = {
            username: { allowEmpty: true, min: 10, max: 100, format: /^\d+$/ }
        }

    8、email - 是否是email格式

    9、password - 密码验证规则 max最大、min最小、compare比较  pass: {type: password, max: 32, min: 6}

    10、url - 是否是url

    11、enum - 如果是枚举需要加一个规则

        // operateType的值必须是values数组中的一项,values必须为数组
        const paramRule = {
            operateType: { type: 'enum', values: ['REPAYMENT', 'CHARGE'] },        
        }
        const paramErrors = app.validator.validate(paramRule, ctx.request.body);

    12、object - 如果是对象,需要加一个规则 

    13、array - 如果是数组,需要加一个规则

        itemType 数组中每一个元素的规则 
        rule - An object that validate the items of the array. Only work with itemType.
        max - 数组最大长度
        min - 数组最小长度

        childrens: {
            type: 'array',
            itemType: 'object',
            required: false,
            rule: {
                name: 'string',
                age: 'int',
                gender: ['male', 'female'],
                birthday: {type: 'date', required: false}
            }
        }


https://github.com/node-modules/parameter
https://github.com/node-modules/parameter/blob/master/benchmark.js
https://github.com/node-modules/parameter/blob/master/example.js

egg-mongoose

$ npm i egg-mongoose --save

$ plugin.js

    exports.mongoose = {
        enable: true,
        package: 'egg-mongoose',
    };

$ config.default.js

    mongoose: {
  url: 'mongodb://127.0.0.1:27017/ibg_node_core',
  options: {},
}

$ 可以多数据库配置

egg-mysql

$ npm i egg-mysql --save

$ plugin.js

    exports.mysql = {
        enable: true,
        package: 'egg-mysql',
    };

$ config.default.js

    mysql: {
        // database configuration
        client: {
            // host
            host: 'mysql.com',
            // port
            port: '3306',
            // username
            user: 'test_user',
            // password
            password: 'test_password',
            // database
            database: 'test',    
        },
        // load into app, default is open
        app: true,
        // load into agent, default is close
        agent: false,
    }

$ app.mysql.query(sql, values)              获取sql

$ 可以多数据库配置

ejs 后端模板

$ npm install ejs

https://segmentfault.com/a/1190000004286562
http://www.360doc.com/content/16/0115/10/597197_528136785.shtml
https://github.com/tj/ejs

渲染模板nunjucks 后端模板

$ npm i egg-view-nunjucks --save

一、tpl文件要放到view目录里

二、配置
// config/plugin.js
exports.nunjucks = {
    enable: true,
    package: 'egg-view-nunjucks'
};

// config/config.default.js
exports.keys = <此处改为你自己的 Cookie 安全字符串>;

// 添加 view 配置
exports.view = {
    defaultViewEngine: 'nunjucks',
    mapping: {
        '.tpl': 'nunjucks',
    },
};

三、Controller
module.exports = app => {
    class NewsController extends app.Controller {
        async newsList(){
            const { ctx } = this;
            const dataList = {
                list: [
                    { id: 1, title: 'this is news 1', url: '/news/1' },
                    { id: 2, title: 'this is news 2', url: '/news/2' }
                ]
            }
            await ctx.render('news/newsList.tpl', dataList);                // 注意这里dataList必须是一个对象
        }
    }

    return NewsController;
}

四、newsList.tpl

<html>
    <head>
        <title>Hacker News</title>
    </head>
    <body>
        <ul class="news-view view">
            {% for item in list %}
					
  • {{ item.title }}
  • {% endfor %} </ul> </body> </html> http://mozilla.github.io/nunjucks/cn/templating.html https://github.com/eggjs/examples/tree/master/hackernews/app/view/news

    其它收集插件

    1、ms返回时间 - https://github.com/zeit/ms       // ms用来返回一个时间  ms('2h') 返回 7200000  
    
    2、MD5加密 - https://github.com/jkiss/crypto-js      npm i crypto-js --save  引用 const MD5 = require('crypto-js/md5');
    
    3、文件上传 - https://github.com/node-modules/formstream
    
    4、JSON串和对象互转 - https://github.com/ljharb/qs  // 可以根据key来进行排序 
    
    5、验证码 - https://github.com/lemonce/svg-captcha/blob/master/README_CN.md
    

    | 参考资料
    | https://github.com/eggjs eggjs官方插件
    |

    EggJS框架-问题积累

    数据库需要指定表

    module.exports = app => {
        const mongoose = app.mongoose;
        const UserSchema = new mongoose.Schema({
            username: { type: String },
            passworrd: { type: String }
        }, {collection: 'user'});
    
        return mongoose.model('User', UserSchema);
    }
    

    不支持ES6的import

    module.exports = app => {}      // egg使用ES5的导出所以不支持 import
    

    1、this.get(‘user-agent’) controller中通过this.get()获取请求头的信息

    bodyParser 中间件,egg内置,用来处里post

    加入全局方法:
    module.exports = {
    generateUuid() {
    const uuid = uuidV1();
    return uuid.replace(/-/g, ‘’).toUpperCase();
    },
    };

    参考资料
    http://koa.bootcss.com/

    EggJS基础

    Egg安装

    Egg.js属于MVC类型的NodeJS框架
    
    $ mkdir egg-example && cd egg-example
    
    $ npm init egg --type=simple
    
    $ npm i
    
    $ npm run dev
    
    $ open localhost:7001
    

    配置文件

    1、config
            |- config.default.js        // 默认配置文件, 所有环境都会加载这个配置文件, 一般用于开发环境的默认配置文件
            |- config.test.js           // 开发环境配置
            |- config.prod.js           // 生产环境配置
            |- config.unittest.js
            |- plugin.js                // 控制插件
            `- config.local.js
    
        module.exports = {
            mysql: {
                enable: true,           // 开启或关闭插件
                package: 'egg-mysql'    // package为一个npm模块,package.json中的dependencies中,框架会在node_modules目录中找到这个插件入口
            },
        };
    
        也可以指定path 来替代 package
        const path = require('path');
        module.exports = {
            mysql: {
                enable: true,
                path: path.join(__dirname, '../app/plugin/egg-mysql'),
            },
        };
    
    2、app.js文件
    
        app.js是入口文件,需要手动创建在根目录下,包括了生命周期函数
        module.exports = app => {
            console.log('start')
        }
    

    Router 路由

    用来接收请求URL,在交给controller处理
    
    app.get(路由名,路由URL路由,middleware(可以配置多个),controller)
    
    一、controller的两个写法
    
        # router
        module.exports = app => {
            // 1、controller目录的home.js进行处理
            app.get('/', 'home');             
    
            // 2、payTool目录、billquery.js文件、getLoanUser方法
            app.post('/getLoanUser', 'payTool.billquery.getLoanUser');
        };
    
        # controller/home.js
        module.exports = function* () {
            this.body = 'Hello World';  // 响应给客户端
        };
    
        # controller/payTool/billquery.js
        module.exports = app => {
            class BillqueryController extends app.Controller {
                async getLoanUser(){
                    const { ctx, app, service } = this;
                    const response = await service.payTool.billquery.getRepayPlan(ctx.request.body);
                    ctx.body = response;
                }
            }
        }
    
    
    二、路由中间件 - 在访问路由之前通过中间件来对路由进行一些处理
    
        app.get('router-name', 'middleware1', 'middleware2', 'controlll');
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
        2、路由中调用
    
            注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
            // 引用中间件
            const checkAuth = app.middleware.checkAuth();
    
            // 不需要验证
            app.get('/', 'home.index');
            app.get('/query', 'query.query.queryPage');
    
            // 需要验证是否登录的
            app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    
    
    三、参数获取
    
        1、获取url参数 ctx.query
    
            http://127.0.0.1:7001/search?name=egg  取出name的值
    
            // app/router.js
            module.exports = app => {
                app.get('/search', app.controller.search);
            };
    
            // app/controller/search.js
            module.exports = function* (ctx) {
                tx.body = `search: ${this.query.name}`;
            };
    
    
        2、路由参数 ctx.params
    
            http://127.0.0.1:7001/user/123/xiaoming   取出 123、xiaoming
    
            // app/router.js
            module.exports = app => {
                app.get('/user/:id/:name', app.controller.user.info);
            };
    
            // app/controller/user.js
            exports.info = function* (ctx) {
                ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
            };
    
    
    四、重定向 - 当路由不存在时会走一个默认的路由
    
        1、内部重定向
    
            // app/router.js
            module.exports = app => {
                app.get('index', '/home/index', 'home.index');
                app.redirect('/', '/home/index', 302);
            };
    
        2、外部重定义 controller中来执行路由的跳转 redirect
    
            // app/router.js
            module.exports = app => {
                app.get('/search', 'search');
            };
    
            // app/controller/search.js
            module.exports = function* () {
                const type = this.query.type;
                const q = this.query.q || 'nodejs';
    
                // 这里跳转
                ctx.redirect('/admin/home');
            };
    
    五、多路由映射 - 将路由分类并用一个路由主文件加载其它的路由文件
    
        // router.js 通过一个主文件将两个路由加载进来
        module.exports = app => {
            require('./router/news')(app);
            require('./router/admin')(app);
        };
    
        // router/news.js
        module.exports = app => {
            app.get('/news/list', app.controller.news.list);
            app.get('/news/detail', app.controller.news.detail);
        };
    
        // router/admin.js
        module.exports = app => {
            app.get('/admin/user', app.controller.admin.user);
            app.get('/admin/log', app.controller.admin.log);
        };
    
    六、路由规则
    
        // app/router.js
        module.exports = app => {
            app.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
        };
    

    Controller

    controller继承于app.controller, 负责解析用户输入,处理后返回相应的结果, 框架推荐Controller层主要对用户请求参数进行处理(校验、转换),然后用service处理业务结果并返回
    
    用户HTTP请求(路由处理) <=> 校验组装(controller处理)<=> 业务处理(service)
    
    一、Controller的两种写法
    
        1、函数写法
    
            // app/controller/posts.js
            exports.index = function* () {};
            exports.new = function* () {};
            exports.create = function* () {};
    
            // router 调用
            app.get('/index', 'posts/index');
            app.get('/new', 'posts/new');
            app.get('/create', 'posts/create');
    
    
        2、类写法
    
            // app/controller/post.js
            module.exports = app => {
                class PostController extends app.Controller {       // 定义一个PostController的类,继承了app.Controller
                    * create() {
                        const { ctx, service } = this;
                        const createRule = {
                            title: { type: 'string' },
                            content: { type: 'string' },
                        };
                        // 校验参数
                        ctx.validate(createRule);
                        // 组装参数
                        const author = ctx.session.userId;
                        const req = Object.assign(ctx.request.body, { author });
                        // 调用 Service 进行业务处理
                        const res = yield service.post.create(req);
                        // 设置响应内容和响应状态码
                        ctx.body = { id: res.id };
                        ctx.status = 201;
                    }
                }
                return PostController;
            }
    
            // router 调用   app/router.js
            module.exports = app => {
                app.post('createPost', '/api/posts', 'post.create');
            }
    
    
    二、app.Controller下this上挂载的几个属性
    
        1、this.ctx: 当前请求的上下文Context对象的实例
    
        2、this.app: Application对象的实例,可以取到框架的全局对象和方法
    
        3、this.service: 取到service下的方法
    
        4、this.config: 获取或添加修改config的配置
    
        5、this.logger: 日志打印
    
    
    三、获取请求
    
        ctx.method: 请求方法
    
        ctx.path: 请求路径
    
        ctx.host: 请求IP
    
        1、query 获取GET请求中传递参数
    
            url: /posts?category=egg&language=node  通过context.query拿到url的参数  
    
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
                    const queryName = ctx.query;    // {category: 'egg', language: 'node'}
                    ctx.body = queryName;
                }
            }
    
        2、queries 能取到重复的参数key
    
            // posts?category=egg&id=1&id=2&id=3 参数解析成对象,id有多个
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
                    const queryName = ctx.queries;    // {category: 'egg', id: [1,2,3]}
                    ctx.body = queryName;
                }
            }
    
        3、params 参数   
    
            路由: app.get('/projects/:projectId/app/:appId', 'app.listApp');
    
            // 请求: GET /projects/1/app/2
            class QueryController extends app.Controller{
                async getParams(app){
                    const { ctx, app, service } = this;
                    const params = {
                        id: ctx.params.projectId,
                        appid: ctx.params.appId
                    };
                    ctx.body = queryName;
                }
            }
    
        4、body 获取post传的数据
    
            框架内置bodyParser中间件,挂载到 context.request.body上,来取post的内容
    
            ctx.request.body  来获取post传过来的参数 {"username":"","password":"1111"}
    
            exports.listPosts = function* (ctx) {
                assert.equal(ctx.request.body.title, 'controller');
                assert.equal(ctx.request.body.content, 'what is controller');
            };
    
            <input type="text" name="title" value="" />
            <input type="text" name="content" value="" />
    
            config.default.js设置, 默认body最大长度为100kb
            bodyParser: {
                jsonLimit: '1mb',
                formLimit: '1mb',
            }
    
        5、获取上传文件
    
            浏览器上都是通过 Multipart/form-data 格式发送文件的,框架通过内置 Multipart 插件来支持获取用户上传的文件
    
            <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
                title: <input name="title" />
                file: <input name="file" type="file" />
                <button type="submit">上传</button>
            </form>
    
            const path = require('path');
            const sendToWormhole = require('stream-wormhole');
    
            module.exports = function* (ctx) {
                const stream = yield ctx.getFileStream();
                const name = 'egg-multipart-test/' + path.basename(stream.filename);
                // 文件处理,上传到云存储等等
                let result;
                try {
                    result = yield ctx.oss.put(name, stream);
                } catch (err) {
                    // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
                    yield sendToWormhole(stream);
                    throw err;
                }
                ctx.body = {
                    url: result.url,
                    // 所有表单字段都能通过 `stream.fields` 获取到
                    fields: stream.fields,
                };
            };
    
    
            在config/config.default.js 中配置来新增支持的文件扩展名,或者重写整个白名单
    
            新增支持的文件扩展名
            module.exports = {
                multipart: {
                    fileExtensions: [ '.apk' ], // 增加对 .apk 扩展名的支持
                },
            };
    
            覆盖整个白名单
            module.exports = {
                multipart: {
                    whitelist: [ '.png' ], // 覆盖整个白名单,只允许上传 '.png' 格式
                },
            };
    
        6、发送HTTP响应
    
            1)设置status
    
                exports.create = function* (ctx) {
                    // 设置状态码为 201
                    ctx.status = 201;
                };
    
            2)设置body 响应给请求方
    
                exports.show = function* (ctx) {
                    ctx.body = {
                        name: 'egg',
                        category: 'framework',
                        language: 'Node.js',
                    };
                };
                exports.page = function* (ctx) {
                    ctx.body = '<html><h1>Hello</h1></html>';
                };
    

    ctx.header 头

    获取整个 header 对象的方法: context.headers、context.header、context.request.headers、context.request.header
    
    get()方法获取请求 header 中的一个字段的值、字段不存在返回null:  context.get(name)、context.request.get(name)
    
    一、获取header
    
        1、ctx.header 取出头信息
    
                async from(ctx){
                    console.log(ctx.header);
                }
                返回结果
                { 
                    host: '127.0.0.1:7001',
                    connection: 'keep-alive',
                    'content-length': '21',
                    'postman-token': 'a7e094b5-21ce-eb94-e403-3d517c832f30',
                    'cache-control': 'no-cache',
                    origin: 'chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo',
                    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
                    'content-type': 'application/json',
                    accept: '*/*',
                    'accept-encoding': 'gzip, deflate, br',
                    'accept-language': 'zh-CN,zh;q=0.8,en;q=0.6',
                    cookie: 'csrfToken=RNHUjeOf0Zsl9jNl2f5BhreT' 
                }
    
        2、ctx.protocol 协议
    
        3、ctx.ips 返回ip 一个数组
    
        4、ctx.ip 返回请求方的ip
    
        5)、ctx.host
    
    
    二、设置header
    
        context.set(key, value) 方法可以设置一个响应头,context.set(headers) 设置多个 Header。
        exports.show = function* (ctx) {
            const start = Date.now();
            ctx.body = yield ctx.service.post.get();
            const used = Date.now() - start;
            // 设置一个响应头
            ctx.set('show-response-time', userd.toString());
        };
    
    可以通过context.cookies来获取和设置cookie值
    
    一、cookie操作
    
        获取 ctx.cookies.get('count');
    
        设置 ctx.cookies.set('count', ++count);
    
        删除 ctx.cookies.set('count', null);
    
    
    二、设置options
    
        context.cookies.set(key, value, options)
    
        1、maxAge: nunber - 在浏览器保存最长时间
    
        2、expires: date - 设置这个键值的失效时间
    
        3、path: string - 设置生效的URL路径
    
        4、domain: string - 设置生效的域名
    
        5、httpOnly: boolean - 设置是否可以被js访问
    
        6、secure: boolean - 设置键值只在HTTPS连接上传输
    
        7、overwrite: boolean - 设置key相当键值如何处理,true为后设置的覆盖前面设置的,false发送两个
    
        8、sign: boolean - 对cookie是否进行签名,true防止前端对这个值进行篡改
    
        9、encrypt: boolean - 对cookie进行加密
    
        ctx.cookies.set(key, value, {
            httpOnly: false,
            sign: false,
        });
    
    
    三、Cookie 密钥
    
        config/config.default.js
    
        module.exports = {
            keys: 'key1,key2',
        };
    

    Session

    context.session 来访问或者修改当前用户 Session 
    
    1、读取和设置 Session
    
        exports.fetchPosts = function* (ctx) {
            // 获取 Session 上的内容
            const user = ctx.session.user;
            const posts = yield ctx.service.post.fetch(userId);
    
            // 修改 Session 的值
            ctx.session.user = ctx.session.user ? ctx.session.user++ : 1;
    
            ctx.body = {
                success: true,
                posts,
            };
        };
    
    
    2、删除session
    
        exports.deleteSession = function* (ctx) {
            ctx.session = null;
        };
    
    
    3、设置 session配置
    
        // config.default.js
    
        exports.session = {
            key: 'EGG_SESS',
            maxAge: 24 * 3600 * 1000, // 1 天
            httpOnly: true,
            encrypt: true,
        };
    

    service 处理业务逻辑

    service 需要继承于 app.Service,service在复杂业务场景下做业务逻辑封装的抽象层
    
    一、service的ctx
    
        1、this.ctx.curl - 发起网络调用
    
        2、this.ctx.service.otherService - 调用service
    
        3、this.ctx.db - 发起数据库调用
    
    
    二、Example
    
        // 调用服务 app/controller/user.js
        module.exports = app => {
            class User extends app.controller {
                async addUser(){
                    let userData = {
                        name: 'siguang',
                        password: 'xxxxxx'
                    }
                    let isAddUser = yield ctx.service.user.createUser(userData)
                    if(setUser){
                        ctx.body = '添加成功'
                    }
                    ctx.body = isAddUser ? '添加成功' : '添加失败';
                }
            }
        }
    
        // 定义一个Service app/service/user.js
        module.exports = app => {                   // 中间件支持两个参数 options中间件配置,app当前应用Application的实例
            class User extends app.Service {
                async createUser(params){
                    const url = '/account/blance/createUser';
                    let result = await this.ctx.curl(url, {
                        method: 'POST',
                        dataType: 'json',   // 自动解析 JSON response
                        data: params,
                        timeout: 15000,     // 15 秒超时
                    }
                    this.ctx.logger.info('响应结果:', result.data);
                    return result.data;
                }
            }
            return User;
        };
    

    Middleware 中间件

    一、config.default.js 配置全局
    
        module.exports = {
            // 配置需要的中间件,数组顺序即为中间件的加载顺序
            middleware: [ 'gzip' ],
            // 配置 gzip 中间件的配置
            gzip: {
                threshold: 1024, // 小于 1k 的响应体不压缩
            },
        };
    
        // 框架中默认的中间件
        module.exports = {
            bodyParser: {
                jsonLimit: '10mb',
            },
        };
    
    
    二、通用配置
    
        enable:控制中间件是否开启。
    
        match:设置只有符合某些规则的请求才会经过这个中间件。
    
        ignore:设置符合某些规则的请求不经过这个中间件。
    
        bodyParser: {
            enable: false,
            jsonLimit: '10mb',
        },
    
    
    三、Example: 
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
    2、路由中调用
    
        注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
        // 引用中间件
        const checkAuth = app.middleware.checkAuth();
    
        // 不需要验证
        app.get('/', 'home.index');
        app.get('/query', 'query.query.queryPage');
    
        // 需要验证是否登录的
        app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    

    JSONP

    jsonp只能get请求
    
    // config/config.default.js  jsonp的配置
    exports.jsonp = {
        callback: 'callback', // 识别 query 中的 `callback` 参数
        limit: 100, // 函数名最长为 100 个字符
    };
    
    
    // app/router.js  路由加jsonp中间件
    module.exports = app => {
        const jsonp = app.jsonp();
        app.get('/api/posts/:id', jsonp, 'posts.show');         // 通过jsonp中间件来让路由支付一个中间件
        app.get('/api/posts', jsonp, 'posts.list');
    };
    
    // app/controller/posts.js
    exports.show = function* (ctx) {
        ctx.body = {
            name: 'egg',
            category: 'framework',
            language: 'Node.js',
        };
    };
    
    用户请求 /api/posts/1?callback=fn,响应为 JSONP 格式,如果用户请求 /api/posts/1
    

    HttpClient

    Node模拟客户端请求,curl请求默认 content-type: application/x-www-form-urlencoded,
    
    app.curl(url, options) 和 app.httpclient.request(url, options) 两种方法相同
    
    一、Example:
    
        module.exports = app => {
            class HttpClientController extends app.Controller{
                async getHttpClient(){
                    const {ctx, app, service} = this;
                    let result = await this.ctx.curl('http://goucai.diyicai.com/lottery/getissue.action?lotteryId=001&issueLen=100&d=1502966960306', {
                        methods: 'get'
                    });
    
                    let phones = [];
                    let resultObj = JSON.parse(result.data.toString());
                    resultObj.forEach((data, idx, arr)=>{
                        console.log('data------', data);
                        phones.push(data.endTime);
                    })
                    ctx.body = phones.join(',')
                }
            }
            return HttpClientController;
        }
    
    二、config配置 // config/config.default.js
    
        exports.httpclient = {
            // 默认开启 http/https KeepAlive 功能
            keepAlive: true,
    
            // 空闲的 KeepAlive socket 最长可以存活 4 秒
            freeSocketKeepAliveTimeout: 4000,
    
            // 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
            timeout: 30000,
    
            // 允许创建的最大 socket 数
            maxSockets: Infinity,
    
            // 最大空闲 socket 数
            maxFreeSockets: 256,
    
            // 是否开启本地 DNS 缓存,默认关闭
            // 一旦设置开启,则每个域名的 DNS 查询结果将在进程内缓存 10 秒
            enableDNSCache: false,
        };
    
    三、options对象
    
        let options = {};
        const result = yield ctx.curl('https://httpbin.org/get?foo=bar', options);
    
        1、mothod: 请求方法
    
        2、data: 需要发送的数据 { foo: 'bar' }
    
        3、dataAsQueryString: Boolean 如果为 true 即使在post情况下,也会强制将options.data以 querystringstringify处理后拼接到url的query参数上
    
        4、content(String|Buffer): 发送请求的正文,如果设置了此参数会忽略data参数
    
                ctx.curl(url, {
                    method: 'POST',
                    // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
                    content: '<xml><hello>world</hello></xml>',
                    headers: {
                        'content-type': 'text/html',
                    },
                });
    
        5、stream(ReadStream): 发送请求正文的可读数据流
    
                ctx.curl(url, {
                    method: 'POST',
                    stream: fs.createReadStream('/path/to/read'),
                });
    
        6、writeStream: 接受响应数据的可写数据流
    
                ctx.curl(url, {
                    writeStream: fs.createWriteStream('/path/to/store'),
                });
    
        7、consumeWriteStream: Boolean 是否等待 writeStream 完全写完才算响应全部接收完毕,默认是 true
    
        8、contentType: 请求数据的格式,默认undefined
    
        9、dataType: 响应数据格式
    
        10、headers: 自定义请求头
    
        11、timeout: 请求超时时间 默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。
    
        12、agent: 
    
        13、httpsAgent
    
        14、auth、digestAuth
    
        15、followRedirect:Boolean 是否自动跟进3xx的跳转响应
    
        16、maxRedirects: number 最大自动跳转次数
    
        17、formatRedirectUrl: 自定义实现302、301
    
        18、beforeRequest: 请求发送前会调用beforeRequest钩子
    
            ctx.curl(url, {
                beforeRequest: options => {
                    // 例如我们可以设置全局请求 id,方便日志跟踪
                    options.headers['x-request-id'] = uuid.v1();
                }
            });
    
        19、streaming:是否直接返回响应流
    
        20、gzip: Boolean是否开始gzip
    
        21、timing: Boolean 是否开启请求各阶段的时间没是
    
    
    四、Get请求
    
        1、options.method = 'post'  设置请求参数,默认是get请求可以不用加
    
            const result = yield ctx.curl('https://httpbin.org/get?foo=bar', {method: post});
    
        2、status: 响应状态码
    
        3、headers: 响应头信息 有{'conent-type': 'text/html'}
    
        4、data: 响应body, 返回的是Buffer类型, 如果设置了options.dataType会根据参数来处理
    
    
    五、Post请求
    
        module.exports = function* post(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method
                method: 'POST',
    
                // 通过 contentType 告诉 HttpClient 以 JSON 格式发送
                contentType: 'json',
    
                // 要传的数据 
                data: {
                    hello: 'world',
                    now: Date.now(),
                },
    
                // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
                dataType: 'json',
            });
            ctx.body = result.data;
        };
    
    
    六、From表单提交
    
        以 ontent-type: application/x-www-form-urlencoded 的格式提交请求数据
    
        // app/controller/form.js
        module.exports = function* form(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT 和 DELETE
                method: 'POST',
                // 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
                data: {
                        now: Date.now(),
                        foo: 'bar',
                },
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.form;
            // 响应最终会是类似以下的结果:
            // {
            //   "foo": "bar",
            //   "now": "1483864184348"
            // }
        };
    
    
    七、Multipart 方式上传
    
        From 表单提交时包含文件的时候,需要用到 multipart/form-data 进行提交了
    
        引用fromstream 第三方模块
    
        // app/controller/multipart.js
        const FormStream = require('formstream');
        module.exports = function* multipart(ctx) {
            const form = new FormStream();
    
            // 设置普通的 key value
            form.field('foo', 'bar');
    
            // 上传当前文件本身用于测试
            form.file('file', __filename);
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 生成符合 multipart/form-data 要求的请求 headers
                headers: form.headers(),
                // 以 stream 模式提交
                stream: form,
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.files;
            // 响应最终会是类似以下的结果:
            // {
            //   "file": "'use strict';\n\nconst For...."
            // }
        };
    
        // 添加更多文件
        form.file('file1', file1);
        form.file('file2', file2);
    
    
    八、以Stream 方式上传文件
    
        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送
    
        // app/controller/stream.js
        const fs = require('fs');
        module.exports = function* stream(ctx) {
            // 上传当前文件本身用于测试
            const fileStream = fs.createReadStream(__filename);
    
            // httpbin.org 不支持 stream 模式,使用本地 stream 接口代替
            const url = `${ctx.protocol}://${ctx.host}/stream`;
            const result = yield ctx.curl(url, {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 以 stream 模式提交
                stream: fileStream,
            });
    
            ctx.status = result.status;
            ctx.set(result.headers);
            ctx.body = result.data;
            // 响应最终会是类似以下的结果:
            // {"streamSize":574}
        };
    

    服务端模板渲染

    读取数据渲染模板,呈现给用户
    
    $ npm i egg-view-ejs --save         // 安装
    
    $ 配置
    
        // config/plugin.js
        exports.nunjucks = {
            enable: true,
            package: 'egg-view-nunjucks'        // 开启插件使用numjucks模板
        };
    
        // config/config.default.js
        exports.view = {
            defaultViewEngine: 'nunjucks',
            mapping: {
                '.tpl': 'nunjucks',
            },
        };
    
    render、renderString两个方法
    
        render(fileName, locals, viewOptions): filenName: 文件路径、locals: 渲染的数据、viewOptions: 用户传入的配置
    
        render(name, locals) 渲染模板文件, 并赋值给 ctx.body
    
        renderView(name, locals) 渲染模板文件, 仅返回不赋值
    
        renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
    
    - numjucks模板语法: http://mozilla.github.io/nunjucks/cn/templating.html  见模板md -
    

    静态资源

    Egg内置static插件,static默认映射到 public目录
    
    app/public
    ├── css
    │   └── news.css
    └── js
            ├── lib.js
            └── news.js
    

    框架扩展

    一、application 全局应用对象扩展
    
        访问 ctx.app,  Controller,Middleware,Helper,Service 中都可以通过 this.app 访问到 Application 对象
    
        // app.js
        module.exports = app => { 
            app.config
        };
    
        // 扩展 app.foo() 方法 app/extend/application.js
        module.exports = {
            foo(param) {
                // this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
            }
        };
    
    二、context 扩展,可以在ctx中调用扩展方法
    
        extend/content.js
        module.export = {
            async isIOS() {
                const iosReg = /iphone|ipad|ipod/i;
                return iosReg.test(this.get('user-agent'));
            },
        }
        引用 await this.ctx.isIOS();
    
    三、helper 用来编写一些实用函数
    
        // app/extend/helper.js
        const moment = require('moment');
        exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();
    
        // 在controller里使用
        async newsList(){
      const { ctx } = this;
      const time = ctx.helper.relativeTime();
      this.ctx.logger.info('data: %j', time)
    }
    
        // 也可以在模板里面使用:
        <!-- app/views/news/list.tpl -->
        {{ helper.relativeTime(item.time) }}
    
    
    四、request
    
    五、response
    

    Schedule 定时任务

    有些任务需要定时来完成,如定时上报,定时远程更新本地缓存,定时任务都统一存放在 app/schedule 目录下
    
    创建一个定时任务 app/schedule/update_cache.js
    
    module.exports = {
        // 通过 schedule 属性来设置定时任务的执行间隔等配置
        schedule: {
            interval: '1m',     // 1 分钟间隔
            type: 'all',        // 指定所有的 worker 都需要执行
            immediate: false,
      disable: !app.config.enableWarningSchedule,
        },
    
        // task 是真正定时任务执行时被运行的函数,第一个参数是一个匿名的 Context 实例
        * task(ctx) {
            const res = yield ctx.curl('http://www.api.com/cache', {
                dataType: 'json',
            });
            ctx.app.cache = res.data;
        }
    };
    
    1、type: 两种类型 worker 或 all,worker是每台机器只有一个worker执行定时任务, all是每台机器上的每个worker都会执行这个定时任务
    
    2、interval:定时执行的时间
    
    3、cron:指定一个执行的时间 ss mm hh dd mm dd
    
        // 每三小时准点执行一次
    cron: '0 0 */3 * * *',
    
    4、immediate:如果为true,定时任务会在启动并ready后立即执行一次任务
    
    5、disable:为true,这个定时任务不会被执行
    

    启动自定义

    用来进行应用启动时进行初始化工作
    
    // 通过入口文件app.js
    module.exports = app => {
        app.beforeStart(function* () {
            // 应用会等待这个函数执行完成才启动
            app.cities = yield app.curl('http://example.com/city.json', {
                method: 'GET',
                dataType: 'json',
            });
        });
    };
    

    logger 日志

    this.ctx.logger.info('xxxxxx');
    
    logger.debug()、logger.info()、logger.warn()、logger.error()
    
    config.deubgger 文件配置
    
    logger: {
        level: 'DEBUG',
        dir: '../app/logger',
    }
    

    本地开发

    egg-bin模块(用于本地开发和单元测试)
    
    package.json:
    
    {
        "scripts": {
            "dev": "egg-bin dev --port 7001"
        }
    }
    

    插件的开发

    自定义插件存储目录:  lib/plugin/...
    
    egg-ua插件
    
    1、创建package.json
    
        {
            "eggPlugin": {
                "name": "ua"
            }
        }
    
    2、egg-ua/us.js插件文件
    
        module.exports = {
            get isIOS() {
                const iosReg = /iphone|ipad|ipod/i;
                return iosReg.test(this.get('user-agent'));
            },
        };
    
    3、config/plugin.js   通过path来挂载插件
    
        const path = require('path');
        exports.ua = {
            enable: true,
            path: path.join(__dirname, '../lib/plugin/egg-ua'),
        };
    

    项目目录

    ├── package.json
    ├── app.js (可选)            // 启动初始化
    ├── agent.js (可选)
    ├── app
    |   ├── router.js           // 路由规则
    │   ├── controller          // 处理用户的输入,返回相应的结果
    │   |   └── home.js
    │   ├── service (可选)       // 服务,编写业务逻辑
    │   |   └── user.js
    │   ├── middleware (可选)    // 中间件
    │   |   └── response_time.js
    │   ├── schedule (可选)      // 定时任务
    │   |   └── my_task.js
    │   ├── public (可选)        // 静态目录
    │   |   └── reset.css
    │   ├── view (可选)          // 模板
    │   |   └── home.tpl
    │   └── extend (可选)        // 框架扩展
    │       ├── helper.js (可选)
    │       ├── request.js (可选)
    │       ├── response.js (可选)
    │       ├── context.js (可选)     // ctx扩展
    │       ├── application.js (可选)     // 全局方法
    │       └── agent.js (可选)
    ├── config                  // 配置
    |   ├── plugin.js
    |   ├── config.default.js
    │   ├── config.prod.js
    |   ├── config.test.js (可选)
    |   ├── config.local.js (可选)
    |   └── config.unittest.js (可选)
    └── test                    // 测试
            ├── middleware
            |   └── response_time.test.js
            └── controller
                    └── home.test.js
    

    积累

    一、egg启动时指定端口
    
        在package.json中
        script: {
            "dev-fundmgmt": "PLATFORM=fundmgmt egg-bin dev --port 20101",            // --port 20101
        }
    
    二、vue-cli中webpack是否使用热更新
    
        script: {
            "dev-fundmgmt": "HOT_ENV=true node build/dev-server.js FUNDMGMT",
        }
        HOT_ENV=true   为false
    
    
    二、vue中取启动里的变量
    
        script: {
            "dev-fundmgmt": "HOT_ENV=true node build/dev-server.js FUNDMGMT",
        }
    
        vue-cli中的dev.env.js
    
        var PLAT = process.argv;            // 取出命令中返回数组 ['HOT_ENV=true', 'node', 'build/dev-server.js', 'FUNDMGMT'];
        if (PLAT.toUpperCase().trim() === 'FUND') {
            PLAT = 'fund'
        } 
        else if(PLAT.toUpperCase().trim() === 'FUNDMGMT'){
            PLAT = 'fundmgmt';
        }
        else {
            PLAT = 'portal'
        }
    

    | http://koa.bootcss.com/

    Gulp

    gulp

    https://github.com/gulpjs
    
    可以自动化执行任务的工具,主要用于生产环境和开发环境的文件处理
    
    Gulp是一个新的基于流的管道式构建系统,需要很少的配置并且更快
    
    主要作用:
    
        1、多个js或css文件合并、压缩
    
        2、Sass或Less的编译
    
        3、图像的压缩
    
        3、实时刷新页面的本地服务器
    
    gulp对按需加载不好做到,如A和B页面都依赖base.js,但又各依赖a.js和b.js,这种情况gulp打包是将三个都包括了,所以css和html部分gulp来处理,js的webpack处理
    

    安装、使用

    $ npm install -g gulp     // 全局安装Gulp
    
    $ npm init    // 创建package.json 项目描述文件
    
    $ npm install --save-dev gulp    // 作为项目的开发依赖(只在开发时用,不会发布到线上)
    
    $ touch gulpfile.js
    
        var gulp = require('gulp');
        gulp.task('default', function(){
    
        })
    
    $ gulp     // 运行gulp
    

    gulp 方法

    1、gulp.src(): 获取到想要处理的文件流,可以是一个字符串或者数组,返回一个stream
    
        1)、*.{png,jpg,gif,ico}      // 匹配文件
    
        2)、**/*.html        // 目录用**、文件名用*
    
        gulp.task('count', function(){
            gulp.src(['aa.html', 'bb.html'])    // 或 gulp.src('src/*.html');
                .pipe(gulp.uglify())
        })
    
    2、gulp.pipe(): 将获取到的文件流通过pipe()导入到gulp的插件中进行处理
    
    3、gulp.dest(): 用来写文件的,插件处理完,通过dest()处理完成后把流中的内容写到指定的文件中
    
        var gulp = require('gulp');
        gulp.src('js/main.js')                 // 获取文件的流的api
            .pipe(gulp.uglify())            // 文件进行压缩
            .pipe(gulp.dest('dist/[name].min.js'));     // 写文件的api
    
    
    4、gulp.task('任务名' [依赖任务], callback): 定义任务,依赖任务会在当前任务执行前完成
    
        gulp.task('test', ['styles', 'task'], function() {
            // 将你的默认的任务代码放在这
        });
    
        # gulp test     // 执行任务
    
    
    5、gulp.watch('要监视文件的匹配模式', [要执行的task定义的任务], callback): 监视文件的变化,变化后来执行定义的任务
    

    gulp 执行命令

    -v 或 --version 会显示全局和项目本地所安装的 gulp 版本号
    
    --gulpfile 手动指定一个 gulpfile 的路径,这在你有很多个 gulpfile 的时候很有用。这也会将 CWD 设置到该 gulpfile 所在目录
    
    --cwd dirpath 手动指定 CWD。定义 gulpfile 查找的位置,此外,所有的相应的依赖(require)会从这里开始计算相对路径
    
    -T 或 --tasks 会显示所指定 gulpfile 的 task 依赖树
    
    --tasks-simple 会以纯文本的方式显示所载入的 gulpfile 中的 task 列表
    
    --color 强制 gulp 和 gulp 插件显示颜色,即便没有颜色支持
    
    --no-color 强制不显示颜色,即便检测到有颜色支持
    
    --silent 禁止所有的 gulp 日志
    

    Gulp 扩散操作

    1、复制单个文件task
    
        gulp.task('copy-html',function(){
            return gulp.src('app/index.html').pipe(gulp.dest('dist'));
        });
    
    
    2、复制多个文件
    
        gulp.task('copy-images',function(){
            return gulp.src('app/imgs/**/*.{jpg,png}').pipe(gulp.dest('dist'));
        });
    
        gulp.task('copy-other',function(){
            return gulp.src(['app/css/*.css','app/js/*.js'],{base:'app'}).pipe(gulp.dest('dist'));
        });
    

    gulp 插件

    https://gulpjs.com/plugins/
    
    $ gulp
    $ gulp-babel babel-core babel-preset-env        // ES6转ES5
    $ babel-plugin-transform-runtime        // ES6转ES5运行时转换
    $ gulp-uglify            // 压缩js
    $ gulp-concat            // 合并js
    $ gulp-jshint            // js语法检测
    $ gulp-less              // less编译
    $ gulp-ruby-sass        // sass编译
    $ gulp-minify-css        // css压缩
    $ gulp-css-base64        // 生成64位图标
    $ gulp-htmlmin           // html压缩
    $ gulp-file-include        // includer嵌入到另一个html文件
    $ gulp.spritesmith       // 雪碧图
    $ gulp-imagemin          // 压缩图片
    $ imagemin-pngquant      // 图片的深度压缩
    $ gulp-cache             // 只压缩修改的图片
    $ gulp-rev-append        // 加版本号,给页面文件引用的js和css资源
    $ gulp-autoprefixer     // 自动补齐前缀,根据设置浏览器版本自动处理浏览器前缀 CSS3的前缀
    $ gulp-react             // react区分jsx文件进行转换
    $ gulp-webpack           // webpack
    $ gulp-amd-optimizer     // requirejs 压缩
    $ vinyl-buffer           // 流缓存
    $ merge-stream           // 合并流
    $ gulp-watch             // 侦听变化自动执行task
    $ gulp-livereload        // 当监听文件发生变化时,浏览器自动刷新页面
    $ gulp-header            // 注释自定义插件
    $ gulp-rimraf            // 清除文件
    $ gulp-clean             // 删除文件或目录
    $ gulp-plumber           // 错误提示
    $ gulp-notify            // 消息提示
    $ gulp-rename            // 重命
    $ gulp-connect           // gulp的服务器
    $ gulp-browserify          // 
    $ gulp-open                // 打开浏览器
    $ gulp-md5-plus            // 生成md5码
    
    一、gulpfile.js中引用插件   
    
        var uglify = require('gulp-uglify');
    
    
    二、gulp-load-plugins 模块化管理插件
    
        1、一般情况下,gulpfile.js中的模块需要一个个加载。
    
            var gulp = require('gulp')
                   jshint = require('gulp-jshint'),
                uglify = require('gulp-uglify'),
                concat = require('gulp-concat');
    
            gulp.task('js', function () {
                   return gulp.src('js/*.js')
                      .pipe(uglify())
                      .pipe(concat('app.js'))
                      .pipe(gulp.dest('build'));
            });
    
    
        2、gulp-load-plugins 会加载 package.json中存在下面的插件依赖:
    
            "devDependencies": {
                "gulp": "^3.9.0",
                "gulp-uglify": "^2.6.0",
                "gulp-concat": "^2.2.0"
            }
    
            var gulp = require('gulp');
            var $ = require('gulp-load-plugins')();            // 加载gulp-load-plugins插件,并立刻运行
    
            使用:$.uglify  可以省去单个引入
            gulp.task('add', function(){
                gulp.src(['js/main.js'])
                    .pipe($.concat)
                    .pipe($.uglify)            // $.uglify就可以调用了
                    .pipe(gulp.dest('dist/[name].min.js'));    // 如果不改变文件名就直接写成 dist/js/
            })
    

    gulp + webpack构建多页面

    ├── .DS_Store
    ├── src                // 生产目录
    ├── dist            // 发布目录
    ├── gulpfile.js
    ├── node_modules
    └── package.json
    

    | 参考资料
    | http://www.gulpjs.com.cn/ gulp中文网
    | http://www.ydcss.com/archives/category/%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7
    | http://www.gulpjs.com.cn/docs/recipes/ gulp 技巧集
    | http://www.gulpjs.com.cn/docs/api/ gulp api
    | http://www.techug.com/gulp gulp使用指南
    | http://www.w3ctech.com/topic/134 gulp开发教程
    | http://www.qianduancun.com/nodejs/33.html gulp-load-plugins[模块化管理插件]
    | http://segmentfault.com/a/1190000003098076#articleHeader0 利用 gulp 处理前端工作流程
    | http://www.cnblogs.com/2050/p/4198792.html 前端构建工具gulpjs的使用介绍及技巧
    | https://www.cnblogs.com/weixing/p/5474324.html
    |
    | 多页面
    | https://www.cnblogs.com/maskmtj/archive/2016/07/21/5597307.html
    | https://github.com/fwon/gulp-webpack-demo
    |
    | 文件流
    | http://segmentfault.com/a/1190000000519006 nodejs中流(stream)的理解
    | http://www.it165.net/pro/html/201406/15924.html Node.js Stream(流)的学习笔记
    | http://segmentfault.com/a/1190000000357044 Node 中的流(Stream)
    |
    | Promise
    | http://segmentfault.com/a/1190000002591145 深入理解Promise实现细节
    | http://www.tuicool.com/articles/fe6Jbyz Promise实现原理
    | http://segmentfault.com/a/1190000003028634 异步编程 promise模式 的简单实现
    | http://sentsin.com/web/861.html

    webpack插件类

    插件类详解

    • webpack-dev-server - webpack自带服务

      webpack自带的插件

      • CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin - 提取公用部分

      外部加载插件

      • HotModuleReplacementPlugin() - 热替换
      • html-webpack-plugin - 解析html模板
      • UglifyjsWebpackPlugin - js压缩
      • web-webpack-plugin - 与html-webpack-plugin类型国内人写的https://github.com/gwuhaolin/web-webpack-plugin/blob/master/readme_zh.md
      • extract-text-webpack-plugin - 单独打包css文件
      • open-browser-webpack-plugin - 资源构建成功后自动打开浏览器
      • clean-webpack-plugin - 清除文件夹,添加hash后,会导致改变文件内容重新打包时文件名不同越来越多,使用clean-webpack-plugin

      • babel-plugin-react-transform react-transform-hmr - react的热更新, 需要在配置bable.rc

      • ExtractTextPlugin - 将js中引用的css文件抽取出一个单独的css文件
      • webpack-merge - 合并数组、函数
      • webpack-dev-middleware - devServer就是基于webpack-dev-middleware和ExpressJS来实现的
      • imagemin-webpacl-plugin - 压缩图片
      • webpack-spritesmith - 制作雪碧图
      • HotModuleReplacementPlugin - devServer热更新

    HotModuleReplacementPlugin() 热替换

    webpac-dev-server支持模块热替换,在前端代码变动的时候无需整个刷新页面,只把变化的部分替换掉。使用HMR功能也有两种方式: 命令行方式和Node.js API。

    1、script: {
    “start”: “webpack –config webpack.config.js”,
    “dev”: “webpack-dev-server –inline –hot”
    }

    2、webpack.config

    entry: [
      'webpack/hot/dev-server',
      path.resolve(__dirname, 'src/index.js')
    ],
    devServer: {
      hot: true       // 这里需要配置与HotModuleReplacementPlugin配合使用
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
    ]
    

    注意,devServer开启不会自动编译,解决就在启动一个执行webpack的侦听

    html-webpack-plugin

    创建HTML文件,或将以有的HTML文件移动目录

    $ npm i –save-dev html-webpack-plugin // 安装

    const HtmlWebpackPlugin = require(‘html-webpack-plugin’); // 引用插件

    option 选项

    plugins: [
    new HtmlWebpackPlugin({

      // 改变页面的<title>标签的内容 
      title: '首页',                   // 页面调用<%= htmlWebpackPlugin.options.title %|
    
      // 模版地址
      template: path.resolve(basePath, 'main.html'),
    
      // 构建后的文件名和目录
      filename: path.resolve(buildPath, 'main.html'),
    
      // chunks这个参数告诉插件要引用entry里面的哪几个入口
      chunks:['main'],
    
      // 不引用的模块
      excludeChunks: [],
    
      // 要把script插入标签里 head or body
      inject:'body',
    
      // 设置meta标签
      meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'},
    
      // 基础路径
      base: 'https://example.com/path',
    
      // js或css文件是否加时间缀
      hash: true,
    
      // 缓存
      cache: true,
    
      // xhtml
      xhtml: true, 
    
      minify: {
        collapseWhitespace: true,           // 去掉空格
        removeComments: true,               // 移除注释
        removeRedundantAttributes: true,    
        removeScriptTypeAttributes: true,
        removeStyleLinkTypeAttributes: true,
        useShortDoctype: true
      }
    }),
    

    ],

    open-browser-webpack-plugin

    devServer下资源构建成功后自动打开浏览器 — 已可以在命令中配置

    $ npm install open-browser-webpack-plugin –save-dev

    var OpenBrowserPlugin = require(‘open-browser-webpack-plugin’);

    plugin: [
    new OpenBrowserPlugin({ url: ‘http://localhost:3000' })
    ]

    也可以使用在package.json中加 –open

    "scripts": {
      "start": "webpack-dev-server --hot --inline --open",
    }
    

    https://github.com/baldore/open-browser-webpack-plugin

    CleanWebpackPlugin

    用于删除目录、文件

    $ npm install –save-dev clean-webpack-plugi

    const CleanWebpackPlugin = require(‘clean-webpack-plugin’);

    plugins: [
    new CleanWebpackPlugin(),
    ],

    https://github.com/johnagan/clean-webpack-plugin

    UglifyjsWebpackPlugin

    压缩文件

    $ npm i -D uglifyjs-webpack-plugin

    const UglifyJsPlugin = require(‘uglifyjs-webpack-plugin’)
    module.exports = {
    plugins: [
    new UglifyJsPlugin()
    ]
    }

    extract-text-webpack-plugin

    单独打包css文件

    $ npm i -D extract-text-webpack-plugin

    clean-webpack-plugin 清除文件夹

    $ npm install clean-webpack-plugin –save-dev

    const CleanWebpackPlugin = require(‘clean-webpack-plugin’);
    plugins: [
    new CleanWebpackPlugin([‘dist’]),
    new HtmlWebpackPlugin({
    title: ‘Output Management’
    })
    ]

    react的热替换

    $ npm install –save-dev babel-plugin-react-transform react-transform-hmr

    .babelrc配置
    {
    “presets”: [“react”, “env”],
    “env”: {
    “development”: {
    “plugins”: [[“react-transform”, {
    “transforms”: [{
    “transform”: “react-transform-hmr”,
    “imports”: [“react”],
    “locals”: [“module”]
    }]
    }]]
    }
    }
    }

    | https://zhuanlan.zhihu.com/p/43778591
    | https://www.webpackjs.com/concepts/ 中文文档

    webpack

    模块化

    模块化分为AMD、CMD、CommonJS、ES6四类
    
    一、CommonJS 
    
        Node、webpack使用的CommonJS规范, require加载是同步的
    
        // 导入
        const moduleA = require('./moduleA');
        moduleA.someFun(1,2);            // 需要等moduleA加载后在执行,这里是同步
    
        // 导出
        module.exports = moduleA.someFunc
    
    
    二、AMD 异步加载模块
    
        可异步加载依赖模块,可并行加载多个依赖,可以运行浏览器和node环境下,缺点是需要加载AMD的库才能使用,常用的require.js
    
        // 定义模块
        define('moduleA', [依赖文件], function(jquery){
                var someFun = (x, y) => { return x + y}
                return {
                    someFun,
                }
        })
    
        // 导入和使用
        require(['moduleA'], function(moduleA){
            module.someFun(1,2);
        })
    
    
    三、ES6
    
        JS的标准化模块加载,会逐渐的取代commonJS和AMD
    
        // 导入
        import React from 'react'
        import ReactDOM, { render } from 'react-dom';
    
        // 导出
        export function hello(){ ... }
        export default {
            ...
        }
        export { fn1, fn2 }
    
    
    四、CMD 同步加载模块
    
        代表seajs
    
        // 定义模块
        define(function(require,exports,module){
            var $ = require('jquery');        // 用来加载模块
    
            exports.sayHello = function(){    // 导出的接口
                ...
            }
        });
    

    构建工具的什么用

    1、代码转换 - 将ES6转ES5, 将SCSS转成css
    
    2、文件优化 - 压缩JS、CSS、HTML代码,压缩合并图片
    
    3、代码分割 - 提取多个页面的公共代码,提取首屏不需要执行代码,其它的异步加载
    
    4、模块合并 - 将多个模块合并成一个文件
    
    5、自动刷新 - 监听本地代码变化,并自动构建和刷新浏览器
    
    6、代码校验 - 校验代码是否符合规范
    
    7、自动发布 - 自动构建出线上发布代码并传输给发布系统
    

    webpack安装和命令

    $ npm i -D webpack webpack-cli         // 安装稳定版本,4版本以上需要装webpack-cli, -D是--save-dev的缩写
    
        npm i -D webpack@3.10.0                  // 安装指定版本
    

    $ touch webpack.config.js // 创建配置文件

    一、配置npm运行命令
    
        "scripts": {
            "start": "webpack --config webpack.config.js"
        }
        $ npm start         // 执行
    
    
    二、webpack命令
    
        1、webpack            // 最基本的启动webpack命令
    
        2、webpack --config XXX.js   // 如果默认的配置文件不叫webpack.config.js,就需要在这指定配置文件的名称
    
            script: {
                "build": "webpack --config production.config.js",
                "dev": "webpack --config development.config.js"
            }
    
        3、webpack -w         // watch方法,时实预览进行打包 相当于 --watch 
    
        4、webpack -p         // 对打包后的文件进行压缩
    
        5、webpack -d         // 提供SourceMaps,方便调试
    
        6、webpack --colors     // 输出结果带彩色,比如: 会用红色显示耗时较长的步骤
    
        7、webpack --profile // 输出性能数据,可以看到每一步的耗时
    
        8、webpack --display-modules     // 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
    
        9、webpack --progress --colors   // 展示一些进度条,同时增加颜色
    
        10、webpack --display-error-details    // 打印出错在哪个文件和行
    

    Entry

    entry是配置模块的入口,执行构建开始时递归解析所有的入口依赖,配置值: string | object | array
    
    // 单入口
    module.exports = {
        entry: './src/main.js'
    }
    
    // 多入口
    module.exports = {
        entry: {
            main: ["./main.js", "./list.js"]
        }
    }
    
    // 多入口,app(应用主入口),vendors(公共库)入口
    module.exports = {
        entry: {
            main: './src/main.js',
            list: './src/list.js',
            vendors: './src/jquery.js'                // 加载第三方模块
        }
    }
    

    Output

    配置 output 选项可以控制 webpack 如何向硬盘写入编译文件
    
    output的配置属性:
    
    1、path: 输出的目录,绝对路径
    
    2、filename: 用于输出文件的文件名
    
        变量: 1) id chunk 的唯一标识从0开始      2)name chunk 的名称        3)hash 输出hash值        4)chunkhash 内容的hash值
    
        filename: 'bundle.js'
    
        filename: '[name].js'            // 用于多入口
    
        filename: '[chunkhash].js'    // 一个随机数,入口为entry chunk
    
        filename: '[name].js[hash]'    // 带hash值 [hash:8] 代表8位的hash,默认20位
    
    3、publicPath: 复杂项目里会有一些构建出的资源需要异步加载,加载这些异步资源需要对应的URL
    
        output: {
            filename: '[name]_[chunkhash:8].js',
            publicPath: 'https://cdn.example.com/assets/'            // 发布到线上的HTML <script src="https://cdn.example.com/assets/main.12345678.js"</script>
        }
    

    Loader

    它的基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换, 预处理 CoffeeScript、    TypeScript、ESNext (Babel)、Sass、Less、Stylus
    
    1、rules: 配置模块的读取和解析,将所有引用资源(.css、.html、.scss、.jpg)作为模块处理
    
    2、条件匹配: 通过test、include、exclude三个配置项来选中loader规则文件
    
        test: 匹配哪些文件
    
        include: 指定一个目录下的所有文件,可以加快webpack的搜索速度
    
        exclude: 排除某个目录,不在这个目录搜索文件
    
    3、应用规则: 对选中的文件通过usr配置来应用loader
    
    4、重置顺序: 默认从右向左执行, 通过enforce选项将其中一个loader的顺序放到最前或最后
    
    module: {
    rules: [
            {
        test: /(\.jsx|\.js)$/,
        use: {
          loader: "babel-loader",
        },
                include: [path.resolve(__dirname, 'src)],        // 只在src目录下搜索js文件
      }
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader?minimize", "sass-loader"],        // minimize 压缩css
                exclude: [/node_modules/]                // 除了node_modules目录不搜索
      }
    ]
    

    }

    插件: npm install --save-dev css-loader
    

    resolve

    webpack启动后会从入口找出所有的依赖模块,resolve配置如何寻找模块对应的文件
    
    1、alias 让模块引入变得简单
    
        import $ from '/js/lib/jquery.min.js';      开发中需要引入jquery就需要写这类路径
    
        通过alias来解决
        resolve: {
            alias: {
                jquery: path.resolve(__dirname, "/js/lib/jquery.min.js"),
            },
            extensions: ['', '.js', '.vue', '.scss', '.css']             //设置require或import的时候可以不需要带后缀
        }
    
        引用
        import $ from 'jquery';        // jquery就会被替换成依赖的路径
    

    plugins

    plugins扩展webpack功能
    
    一、webpack自带插件,可以直接使用
    
        pulgins: [
            new webpack.optimize.UglifyJsPlugin(),
            new webpack.optimize.CommonsChunkPlugin({    // webpack自带插件在webpack.optimize内
                name: 'vendor',                                // 上面入口定义的节点组
                filename: 'jquery.vendor.js?[hash]'     // 最后生成的文件名
            })
        ]
    
    二、非自身插件,需要先调用插件
    
        例: main.js
    
            require('./main.css')
            const show = require('./show.js)
            show(1,2)
    
        webpack.config.js
        // 将引用的css文件提取出来 
        const ExtractTextPlugin = require('extract-text-webpack-plugin');        
    
        pulgins: [
            new ExtractTextPlugin({
                filename: '[name].[contenthash:8].css',            // main.11ad8d2c.css
                allChunks: true,
            })
        ]
    
    webpack插件列表: https://www.webpackjs.com/plugins/
    

    devServer

    webpack-dev-server - 创建一个本地服务,并能设置代理服务,并且能够实时重新加载
    

    一、安装webpack-dev-server

    $ npm i --save-dev webpack-dev-server
    

    二、创建文件

    // 创建项目目录
    $ mkdir webpack_base_demo && cd webpack_base_demo      
    
    // 创建配置文件
    $ touch README.md  .gitignore  .babelrc  webpack.config.js
    
    // 创建package.json
    $ npm init 
    
    // 配置.babelrc文件
    {
        "presets": ["react", "env"],
        "env": {
            "development": {
            <!-- "plugins": [["react-transform", {
                "transforms": [{
                    "transform": "react-transform-hmr",
                    "imports": ["react"],
                    "locals": ["module"]
                }]
            }]] -->
            }
        }
    }
    

    三、安装需要的loader插件

    // 如果使用react框架来做项目,先下载包
        $ npm i --save react react-dom
    
    // babel的插件,在react热更新
        $ npm i --save babel-plugin-react-transform react-transform-hmr   
    
    // 使用Babel-loader来解析es6和react
        $ npm i --save babel-core babel-loader babel-preset-env babel-preset-react
    
        // vue-load - 解析和转换.vue文件,vue-template-compiler - 将vue-load提取出的HTML模板编译成可执行的JS代码,vue项目可直接安装vue-cli
        $ npm vue-load vue-template-compiler
    
        // jsx转换
        $ npm i --save jsx-loader
    
        // css模块化,可以在组件中引用指定模块的css文件, import './main.css'
        $ npm i --save style-loader css-loader 
    
        // 可以将js和css中的导入的图片替换成正确的地址,同时将文件输出到对应的位置
        $ npm i --save file-loader
    
        // 将文件的图片经过base64编码后注入js或css中
        $ npm i --save url-loader    
    
        // 加载SVG
        $ npm i --save svg-loader
    
        // CSS预处理器  autoprefixer 自动加载前缀
        $ npm i --save less-loader sass-loader
    

    五、package.json配置

    "scripts": {
        "dev": "webpack-dev-server --hot --inline --progress",
        "start": "webpack --progress --profile --colors --config webpack.config.js",
    },
    $ npm run start        // 启动开发环境
    

    六、创建生成和开发配置文件

    webpack.config.js 开发环境所用配置文件
    webpack.pub.config.js  生产环境所用配置文件
    

    七、注意

    1、启动两个侦听一个是webpack,一个是devServer,单独启devServer热更新不执行编译,暂没找到解决方法
    
      $ npm run build
    
      $ npm run dev
    

    devServer的执行命令

    webpack-dev-server - 在 localhost:8080 建立一个 Web 服务器

    webpack-dev-server –devtool eval - 为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号

    webpack-dev-server –progress - 显示合并代码进度

    webpack-dev-server –colors - 命令行中显示颜色

    webpack-dev-server –content-base build // webpack-dev-server服务会默认以当前目录伺服文件,如果设置了content-base的话,服务的根路径则为build目录

    webpack-dev-server –inline 可以自动加上dev-server的管理代码,实现热更新

    webpack-dev-server –hot 开启代码热替换,可以加上HotModuleReplacementPlugin

    webpack-dev-server –port 3000 设置服务端口

    webpack与gulp

    gulp 是 task runner,Webpack 是 module bundler
    
    webpack对多入口支持的不太好
    
    gulp没有模块化的支持,需要与webpack结合
    

    Source Map

    启动时需要加 --devtool source-map 参数重启DevServer后刷新页面,在chrome开发者工具就可以调试源码
    

    常用的处理g

    一、css处理
    
        基本问题包括:
    
            预编译语言转换
            样式文件挂载方式选择
            代码优化(合并及压缩)
            去除或保留指定格式的注释
            资源定位路径的转换
            响应式布局单位转换【可选】
            模块化【可选】
            处理浏览器兼容【可选】
    
        1、style-loader - 将处理结束的CSS代码存储在js中,运行时嵌入&lt;style&gt;后挂载至html页面上
    
        2、css-loader - 加载器,使webpack可以识别css模块
    
        3、postcss-loader - 加载器,下一篇将详细描述
    
        4、sass-loader - 加载器,使webpack可以识别scss/sass文件,默认使用node-sass进行编译
    
        5、mini-css-extract-plugin - 提取单独的css文件插件,4.0版本启用的插件,替代原extract-text-webpack-plugin插件,将处理后的CSS代码提取为独立的CSS文件
    
        6、optimize-css-assets-webpack-plugin - 插件,实现CSS代码压缩
    
        7、autoprefixer - 自动化添加跨浏览器兼容前缀
    
    
    二、Assets 资源(图片、json、xml)
    
        * 体积压缩
        * 雪碧图合并及引用修正
        * 资源的引用路径自动替换
    
        1、file-loader - 处理资源文件打包
    
        2、url-loader - 优化项目中对于资源的引用路径,并设定大小限制
    
        3、html-loader - html中的静态资源替换
    
        module: {
            rules: [
                {    
                    test:/\.(jpg|png|svg|gif)/,    
                    use:[{      
                        loader:'file-loader',      
                        options:{        
                            outputPath:'imgs/'
                        }
                    }]
                },
                {    
                    test:/\.(jpg|png|svg|gif)/,    
                    use:[{      
                        loader:'url-loader',      
                        options:{        
                            limit: 8129,            // 限制图片转base64引用大小
                            fallback: 'file-loader',        // 大小limit的交给file-loader处理
                            ouputpath: 'imgs/'        // 指定输出路径
                        }
                    }]
                }
            ]
        }
    
    
    三、处理JS和splitChunk
    
        Js文件打包需求
    
        * 代码编译(TS或ES6代码的编译)
        * 脚本合并
        * 公共模块识别
        * 代码分割
        * 代码压缩混淆
    
        1、Babel转换ES6+
    
            webpack.config.js: 
                module: {    
                    rules: [
                        {        
                            test: /\.js$/,        
                            exclude: /node_modules/,        
                            use: [
                                {             
                                    loader: 'babel-loader'
                                }
                            ]
                        }
                    ]
                },
    
            .bablerc: 
                {    
                    "presets":[
                        [
                            "env",
                            {            
                                "targets":{                
                                    "browsers":"last 2 versions"
                                }
                            }
                        ]
                    ],    
                    "plugins": [         
                        "babel-plugin-transform-runtime" 
                    ]
                }
    
        2、脚本合并
    
    
        3、脚本分割
    
            为什么要分割,如果遇到比如使用Echarts第三方库或公用文件,这个时候我们不希望把它与业务一起合并到一个文件中,会使这个文件变得非常打,不利于加载
    
    
        4、代码混淆压缩
    
            UglifyJs
    
    
        5、splitChunks
    
            用于提取公用文件或第三方组件,4.0废弃了CommonsChunkPlugin插件,使用optimization.splitChunks和optimization.runtimeChunk来代替
    
            vendor 加载公用文件或第三方组件
    
            entry: {
                index: path.resolve(basePath, 'js/index.js'),
                list: path.resolve(basePath, 'js/list.js'),
                vendors:  path.resolve(basePath, 'js/common.js'),
            },
    
            // 使用optimization,与entry同级,不需要放到plugins内
            optimization: {
                splitChunks: {
                    cacheGroups: {
                        commons: {
                            name: "vendors",
                            chunks: "initial",
                            minChunks: 2
                        }
                    }
                }
            },
    
            plugins: [
                new HtmlWebpackPlugin({
                    title: '首页', 
                    template: path.resolve(basePath, 'index.html'),
                    filename: path.resolve(buildPath, 'index.html'),
                    chunks:['index', 'vendors'],            // 这里加vendors
                    inject:'body',
                })
            ]
    
            <!-- module.exports = { 
                optimization: {    
                    splitChunks: {      
                        chunks: 'async',                        // 默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效
                        minSize: 30000,                            // 合并前模块文件的体积
                        minChunks: 1,                                // 最少被引用次数
                        maxAsyncRequests: 5,      
                        maxInitialRequests: 3,      
                        automaticNameDelimiter: '~',        // 自动命名连接符
                        cacheGroups: {        
                            vendors: {          
                                test: /[\\/]node_modules[\\/]/,          
                                minChunks:1,                        // 敲黑板
                                priority: -10                        // 优先级更高
                            },        
                            default: {          
                                test: /[\\/]src[\\/]js[\\/]/
                                minChunks: 2,                        // 一般为非第三方公共模块
                                priority: -20,          
                                reuseExistingChunk: true
                            }
                        },      
                        runtimeChunk:{         
                             name:'manifest'
                        }
                    }
                } -->
    

    解决问题

    1、devServer启动时不编译,分别侦听
    
    2、js文件引用动态生成到html文档内
    
    3、第三方插件或公用组件splitChunk
    

    | https://doc.webpack-china.org/concepts/ webpack中文网 v3.10.0
    | https://www.jianshu.com/p/42e11515c10f
    | http://webpack.wuhaolin.cn/ // book
    | https://webpack.js.org/plugins/ // 自带插件
    | https://zhuanlan.zhihu.com/p/32148338
    | https://blog.csdn.net/keliyxyz/article/details/51571386
    | http://react-china.org/t/webpack-output-filename-output-chunkfilename/2256/2 // output.filename 和output.chunkFilename
    | http://www.alloyteam.com/2016/02/code-split-by-routes/ 按需加载
    |
    | 插件
    | http://www.cnblogs.com/haogj/p/5160821.html // html-webpack-plugin html多页面构建
    |
    | 热更新
    | https://github.com/gaearon/react-hot-loader // react
    | https://github.com/vuejs/vue-loader // vue

    VueJS之-“Vuex篇“(四)

    Vuex

    Vuex借鉴了Flux和Redux设计思想.
    
    优点:
    
        Vue组件父子之间通过$on、$emit自定义事件来进行通信
    
        Vuex可以解决同级组件之间无法传递消息
    

    执行步骤

    Store: 数据仓库,包含state对象,组件通过getter从store读取数据,通过Getter
    
    核心模块:
    
        1、state: 定义存储状态
    
        2、getter: 对数据进行过滤,获取state数据,在计算属性中获取state的值
    
        3、mutation: 同步操作,更改 Vuex 的 store 中的state值的唯一方法是提交 mutation
    
        4、action: 异步操作,Action通过commit()方法来调用mutation中的方法在改变来改变state的值,而不是直接改变state中的值
    
                分发Action: store.dispatch()方法触发
    
        5、modules: 如果应用过大,便可以使用 modules 来分割管理,不至于 store 变得非常臃肿
    
    
        调用同步的mutation  this.$store.commit('mutationName')
    
        调用异步的Action    this.$store.dispatch('mutationName')
    
            export default {
                insertMessage({commit}, insertData){
                    fetch('/login',{
                        username,
                        password
                    })
                    .then((res)=>{
                        return res.json();
                    })
                    .then((res) => {
                        // 调用mutation
                        commit('insertMessage', res.data);
                    })
                }
            }
    

    State

    用来存储数据,如果在组件中获取state中的数据,可以通过两种方法来获取: 计算属性computed 和 Getters
    

    getter

    在组件中用来获取Store中state,通过getter获取,组件中在通过computed来调用getter
    
    getters.js
    export const sideData = (state) => {
        return state.sideData;
    } 
    
    <template>
        <div class="box">
            {{ getSideData }}
        </div>
    </template>
    
    // 方法1: computed
    computed: {
        getSideData () {
            return this.$store.state.sideData;            // 当getSideData的值改变就会显示出来
        }
    }
    
    
    // 方法2: mapState函数获取
    import { mapState } from 'vuex';        // 引用mapState()
    export default {
        name: 'side',
        computed: {
            ...mapState({    // 对象展开运算符
                getSideData: (state) => {
                    return state.sideData
                }
            })
        }
    }
    

    Mutations

    用来更改Store中state的数据,它是同步的
    
    1、commit()触发Action, 之后Action触发mutation来改变state的值
    
        // 定义mutations 
        export default {
            changeSideData(state, sideJson){
                state.sideData.push(sideJson)
            }
        }
    
        // 触发
        methods:{
            addSideData(){
                let side = {
                    id: 1,
                    name: this.name,
                    introduce: this.introduce
                }
                this.$store.commit('changeSideData', side);
            }
        }
    
    2、使用mapMutations() 来定义改变
    

    Actions

    actions提交到 Mutations中,action而不能直接改变state的值
    
    actions可以包含异步操作,mutations是同步的
    
    // 定义action
    export const holderSilde = ({commit}, {name, introduce}) => {    
        // {name, introduce} 这里注意一定对象传过来,这里为解析赋值写法,函数不接收第三个参数
        var side = {
            id: 1,
            name: name,
            introduce: introduce
        }
        commit('changeSideData', side)
    }
    
    
    // 调用 分发dispatch
    methods:{
        addSideData(){
            // 第二个为传过去的参数
            this.$store.dispatch('holderSilde', {name: this.name, introduce: this.introduce});
        }
    }
    

    Modules

    Vuex可以将Store分割到各模块,每个模块都有自己的store、mutations、actions、getters
    
    const moduleA = {
        state: { ... },
        mutations: { ... },
        actions: { ... },
        getters: { ... }
    }
    
    const moduleB = {
        state: { ... },
        mutations: { ... },
        actions: { ... }
    }
    
    const store = new Vuex.Store({
        modules: {
            a: moduleA,
            b: moduleB
        }
    })
    
    store.state.a // -> moduleA 的状态
    store.state.b // -> moduleB 的状态
    

    示例

    // store.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    import mutations from './mutations.js';
    import actions from './actions.js';
    import getters from './getters.js';
    Vue.use(Vuex);
    
    // 初始化数据
    const state = {
        currentId: 0,
        currentUsername: '',
        sideData: [
            {
                id: '1',
                username: 'siguang',
                introduce: '用户描述'
            },
            {
                id: '2',
                username: 'lulu',
                introduce: '用户描述'
            }
        ]
    }
    
    export default new Vuex.Store({
        state,
        getters,
        actions,
        mutations,
    })
    
    
    // main.js
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import store from './store.js'
    Vue.config.productionTip = false;
    
    let vm = new Vue({
        el: '#app',
        router,
        store,
        components: { App },
        template: '<App/>'
    })
    
    window.vm = vm;
    

    | https://vuex.vuejs.org/zh

    VueJS 之 “插件篇“(三)

    | vue-router 路由插件
    | vue-validator 表单校验插件
    | vue-resource ajax插件

    vue-cli3 脚手架

    一、安装
    
        $ npm install -g @vue/cli            // 最新版本3.0
    
        $ vue create hello-world            // 创建项目 vue create --help
    
            注意 如果安装sass选择手动
    
            1、> default (babel, eslint)
             > Manually select features
    
            2、选择预解释器
            >    (*) CSS Pre-processors                // 空格选择
    
            // 如果安装2.0的模板
            $ vue init webpack my-project        // vue init webpack-simple#1.0 mynewproject
    
        $ cd hello-world
    
        $ yarn serve     // 启动服务
    
        $ yarn build  // 打包
    
        // 安装插件
        $ vue add router
        $ vue add vuex
    
    
    二、package.json
    
        script: {
            "serve": "vue-cli-service serve --mode test231",
        }
    
        --mode        指定环境模式 (默认值:production)
        --dest        指定输出目录 (默认值:dist)
        --modern      面向现代浏览器带自动回退地构建应用
        --target      app | lib | wc | wc-async (默认值:app)
        --name        库或 Web Components 模式下的名字 (默认值:package.json 中的 "name" 字段或入口文件名)
        --no-clean    在构建项目之前不清除目标目录
        --report      生成 report.html 以帮助分析包内容
        --report-json 生成 report.json 以帮助分析包内容
        --watch       监听文件变化
    
        环境变量设置
        script: {
            "serve": "FOO=bar vue-cli-service serve --mode test231",
        }
    
        vue.config.js中获取
        console.log(process.env.FOO);        // bar
    
    
    三、vue.config.js 配置
    
        'use strict'
        module.exports = {
    
            // 修改webpack配置的方式, 该对象会对webpack-merge合并
            configureWebpack: {
              optimization: {
                splitChunks: false
              }
            },
            // 修改 Loader选项
            chainWebpack: config => {
                config
                    .plugin('html')
                    .tap(args => {
                        // 防止 Cyclic dependency 错误
                        args[0].chunksSortMode = 'none'
                        return args
                    })
            },
            // 配置代理
            devServer: {
                // mock
                // 在这里定义mock需要重启server,改为在 utils/mock 中定义
                // https://webpack.js.org/configuration/dev-server/
                before: function (app) {
                    app.get('/api/version', function (req, res) {
                        res.json({
                            data: 'V1.0.0',
                            message: '',
                            status: 0,
                        });
                    });
                },
    
                // 反向代理配置
                // https://github.com/chimurai/http-proxy-middleware#proxycontext-config
                proxy: {
                    '/api': {
                        target: 'http://172.13.3.232:12080',
                        // ws: true,
                        changeOrigin: true,
                        autoRewrite: true,
                        pathRewrite: {
                            '^/api/': '/'
                        }
                    },    
                },
                disableHostCheck: true
            }
        }
    
    
    四、public目录、css样式
    
        public目录存放静态资源
    
        cli支持Sass、Less、Stylus预处理器
    
    
    二、main.js 入口文件
    
        import Vue from 'vue'
        import chat from './components/chat.vue';
    
        new Vue({
            el: '#app',
            render: h => h(chat)         // 调用其它.vue文件必须这么写
        })
    
        render: h => h(chat) 相当于
    
        render: (function (h) {  
              return h(App);
        });  
    
    
    三、不需要代码校验
    
        在webpack.base.conf.js里面删掉下面:
    
        preLoaders: [
            {
                test: /\.vue$/,
                loader: 'eslint',
                include: projectRoot,
                exclude: [/node_modules/, /ignore_lib/]
            },
            {
                test: /\.js$/,
                loader: 'eslint',
                include: projectRoot,
                exclude: [/node_modules/, /ignore_lib/]
            }
        ]
    
    
    https://cli.vuejs.org/zh/
    

    vue-router 路由

    一、安装
    
        $ npm install vue-router --save-dev
    
    
    二、引用路由:
    
        import Vue from 'vue'
        import VueRouter from 'vue-router'
        Vue.use(VueRouter)
    
        const routes = [
            { path: '/foo', component: Foo },        // 一个路由对应一个组件
            { path: '/bar', component: Bar }
        ]
        const route = new Router(    
            mode: 'history',
            routes,
        })
    
        let vm = new Vue({
            el: '#app',
            route,
            components: { App },
            template: '<App/>'
        })
    
    
    三、使用路由
    
        html:
    
            <div id="app">
                <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
                <router-link to="/foo">Go to Foo</router-link>
    
                <!-- 加载路由视图 -->
                <router-view></router-view>
            </div>
    
         router.js
    
            import Vue from 'vue'
            import VueRouter from 'vue-router'
            import login from './components/login'
    
            // 将路由插件加载到Vue中
            Vue.use(VueRouter);
    
            // 路由映射配置
            const routes = [
                { 
                    path: '/login', 
                    component: login
                },
                {
                    path: '/*',
                    redirect: '/404'        // 重定向
                }
            ]
    
            // 创建路由实例
            const router = new VueRouter({
                routes        // 将定义的配置当参数注入路由
            })
    
            // 创建和挂载根实例
            const app = new Vue({
                router
            }).$mount('#app')
    
    
    四、动态路由参数
    
        const router = new VueRouter({
            routes: [
                // 动态路径参数 以冒号开头
                { path: '/user/:id', component: User }
            ]
        })
    
        // 路由参数  /user/:username     匹配/user/haha    $route.params 来取参数  { username: 'haha' }
        mounted(){
            let usernmae = this.$route.params.username;
        }
    
        1、router-link 参数
    
            <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
    
        2、this.$router.push中参数
    
            router.push({ name: 'user', params: { userId: 123 }})
    
    
    五、路由跳转
    
        1、$router.push(location): js内加路由跳转
    
            // 字符串
            router.push('home')
    
            // 对象
            router.push({ path: 'home' })
    
            // 命名的路由
            router.push({ name: 'user', params: { userId: 123 }})
    
            // 带查询参数,变成 /register?plan=private
            router.push({ path: 'register', query: { plan: 'private' }})
    
        2、router.replace(): 与push很像,不同的是replace不会向history添加到新记录
    
        3、router.go(n): 指定向后跳几步,与window.history.go(n)相同,history历史记录跳转
    
        4、<router-link to="home">home</router-link>            // 编辑出来是<a>标签
    
    
    五、<router-view> 渲染路由视图组件
    
        <router-view></router-view>
    
        支持多个渲染:
        <router-view></router-view>
        <router-view name="a"></router-view>
        <router-view name="b"></router-view>
    
        const router = new VueRouter({
            routes: [
                {
                    path: '/',
                    components: {
                        default: Foo,
                        a: Bar,
                        b: Baz
                    }
                }
            ]
        })
    
    
    六、捕获所有路由或 404 Not found路由
    
        {
            path: '*',
            redirect: function () {
                    return '/admin';
            }
        }
    
    
    七、History模式
    
        1、hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
    
        2、history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式. (url不会带hash “#“)
    
        3、abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式
    
        export default new VueRouter({
            mode: 'history',
            routes: [...]
        })            
    
    
    八、路由钩子
    
        1、beforeEach、afterEach 路由跳转前后的钩子
    
            var auth = {
                loggedIn: function () {
                    return localStorage.getItem(KEY_OF_LOGGEDIN) === 'true'; 
                }
            }
    
            router.beforeEach((to, from, next) => {
                if (to.matched.some(record => record.meta.requiresAuth)) {
                    if (!auth.loggedIn()) {
                        next({
                            path: '/login',
                            query: {redirect: to.fullPath},
                        });
                    } else if (auth.requiresAlterPassword()){
                        next({
                            path: '/firstLogin'
                        });
                    } else {
                        next();
                    }
                } else {
                    next();
                }
            });
    
        2、to: 要进入目录的路由对象
    
        3、from: 要离开的路由对象
    
        4、next: function, 用该方法来resolve这个钩子
    
            next({
                path: '/login',        // 跳转的
                query: {redirect: to.fullPath},                // to.fullPath是当前的路径
            });
    
    
    九、meta 定义路由的时候可以配置meta字段
    
        1、设置meta属性,可以通过它来设置不需要判断登录的路由,在beforeEach的时候进行处理
    
            {
                path: '/firstLogin',
                name: 'firstLogin',
                meta: {
                    requiresAuth: false
                },
                component: (resolve) => require(['../views/firstLogin.vue'], resolve),
            }
    
        2、matched来访问meta属性
    
    
    十、路由对象
    
        1、$route.path: 当前路由的路径,总是解析为绝对路径,如 "/foo/bar"
    
        2、$route.params: 获取动态路由的参数
    
        3、$route.query:  URL 查询参数, 例如,对于路径 /foo?user=1,则有 $route.query.user == 1
    
        4、$route.hash: 路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
    
        5、$route.matched: 
    
        6、$route.name: 路由的名称
    

    vue-resource ajax插件

    一、它提供了两种方式来处理HTTP请求: 
    
        使用Vue.http或this.$http
    
        使用Vue.resource或this.$resource
    
    
    二、特点: 支持拦截器
    
        拦截器是全局的,拦截器可以在请求发送前和发送请求后做一些处理。
    
        拦截器在一些场景下会非常有用,比如请求发送前在headers中设置access_token,或者在请求失败时,提供共通的处理方式。
    
    
    三、使用方式: 
    
        1、拦截器  使用inteceptor
    
        Vue.http.interceptors.push((request, next) => {
            // 请求发送前的处理逻辑
            next((response) => {
                // 请求发送后的处理逻辑
                // 根据请求的状态,response参数会返回给successCallback或errorCallback
                return response
            })
        })
    
    
    四、请求类型
    
        get(url, [options])
        head(url, [options])
        delete(url, [options])
        jsonp(url, [options])
        post(url, [body], [options])
        put(url, [body], [options])
        patch(url, [body], [options])
    
        <script>
            var demo = new Vue({
                el: '#app',
                data: {
                    gridColumns: ['customerId', 'companyName', 'contactName', 'phone'],
                    gridData: [],
                    apiUrl: 'http://211.149.193.19:8080/api/customers'
                },
                ready: function() {
                    this.getCustomers();
                },
                methods: {
                    getCustomers: function() {
                        // get请求
                        // then方法只提供了successCallback,而省略了errorCallback。
                        // catch方法用于捕捉程序的异常,catch方法和errorCallback是不同的,errorCallback只在响应失败时调用,而catch则是在整个请求到响应过程中,只要程序出错了就会被调用。
    
                        this.$http.get(this.apiUrl)
                            .then((response) => {
                                // Vue实例方法,设置gridData属性赋值,并触发视图更新
                                this.$set('gridData', response.data)
                            })
                            .catch(function(response) {
                                console.log(response)
                            })
                    }
                }
            })
        </script>
    
    5、options对象
    
        发送请求时的options选项对象包含以下属性: 
    
        参数             类型                                    描述
        url             string                      请求的URL
    
        method        string                请求的HTTP方法,例如: 'GET', 'POST'或其他HTTP方法
        body            Object                 FormData string    request body
        params        Object                请求的URL参数对象
        headers        Object                request header
        timeout        number                单位为毫秒的请求超时时间 (0 表示无超时时间)
        before        function(request)    请求发送前的处理函数,类似于jQuery的beforeSend函数
        progress     function(event)      ProgressEvent回调处理函数
        credientials    boolean      表示跨域请求时是否需要使用凭证
        emulateHTTP        boolean      发送PUT, PATCH, DELETE请求时以HTTP POST的方式发送,并设置请求头的X-HTTP-Method-Override
        emulateJSON        boolean      将request body以application/x-www-form-urlencoded content type发送
    

    | https://router.vuejs.org/zh-cn/
    | https://vuex.vuejs.org/zh-cn/
    | https://github.com/dai-siki/vue-image-crop-upload 头像上传组件
    | http://www.cnblogs.com/pandabunny/p/5417938.html // vue引用jquery
    | http://router.vuejs.org/zh-cn/installation.html // 路由插件
    | http://yuche.github.io/vue-strap/ // vueStrap
    | http://bootstrap-table.wenzhixin.net.cn/zh-cn/ // bootstrap Table
    | https://github.com/PanJiaChen/vue-element-admin
    | https://github.com/opendigg/awesome-github-vue?f=tt&hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io // vue项目汇总