-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 328 KB
/
content.json
1
{"meta":{"title":"SWM博客","subtitle":"常写常做!","description":"工作点点","author":"SWM Lee","url":"http://swmlee.gitee.io","root":"/"},"pages":[{"title":"关于","date":"2019-12-12T02:15:52.000Z","updated":"2020-01-09T13:43:47.475Z","comments":true,"path":"about/index.html","permalink":"http://swmlee.gitee.io/about/index.html","excerpt":"","text":"whatever is worth doing at all, is worth doing well."},{"title":"分类","date":"2019-07-08T16:00:00.000Z","updated":"2019-12-18T14:08:07.063Z","comments":true,"path":"categories/index.html","permalink":"http://swmlee.gitee.io/categories/index.html","excerpt":"","text":""},{"title":"标签","date":"2019-07-08T16:00:00.000Z","updated":"2019-12-18T14:08:21.553Z","comments":true,"path":"tags/index.html","permalink":"http://swmlee.gitee.io/tags/index.html","excerpt":"","text":""},{"title":"友情链接","date":"2020-01-09T13:46:05.000Z","updated":"2020-01-09T13:46:58.776Z","comments":true,"path":"link/index.html","permalink":"http://swmlee.gitee.io/link/index.html","excerpt":"","text":""}],"posts":[{"title":"(十八)MariaDB简单的复制(replication)使用示例","slug":"TechnicalEssays/MariaDBSeries/18mariadb-replication-example","date":"2020-06-18T13:07:38.000Z","updated":"2023-05-16T13:54:36.565Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/18mariadb-replication-example/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/18mariadb-replication-example/","excerpt":"","text":"简单的做一个标准模式复写的示例。 为了方便建立多个 MariaDB 的实例,使用 MariaDB docker 镜像做 replication。 会创建 3 个安装 MariaDB 的 docker 容器,分别命名为 mariadb1(设为 Master)、mariadb2 和 mariadb3(Slave1 和 Slave2)。端口映射依次 3307、3308、3309。 简单的 replication 前置准备1、MariaDB docker 准备与配置快速安装 docker: sudo sh -c "$(curl -fsSL https://get.docker.com)" sudo usermod -aG docker $USER 第一行用 docker 官方提供的 script 快速安装。第二行将现有的使用者加入 docker 群组,否则会没有权限操作 docker 命令。记得注销账号重登,以获取 docker 操作权限。 拉取 MariaDB 镜像: docker pull mariadb/server:10.4创建 3 个用于安装 mariadb 的容器,命名为 mariadb1、mariadb2、mariadb3: sudo docker run -it -p 3307:3306 --name mariadb1 -e MYSQL_ROOT_PASSWORD=root -d mariadb/server:10.4 2、基准文件的准备拷贝一份同样的数据库结构文件到 3 个容器中,并导入各自数据库 准备一份 sql script。 我这份示例是一个名为 test_replication 的数据库,以下有一个名为 call_record 的表,结构如下: CREATE TABLE `call_record` ( `start_time` char(19) DEFAULT NULL, `dial_out_number` varchar(12) DEFAULT NULL, `dial_out_name` varchar(20) DEFAULT NULL, `dial_out_department` varchar(20) DEFAULT NULL, `call_duration` char(19) DEFAULT NULL, `uuid` varchar(36) NOT NULL, `create_time` char(19) NOT NULL, `create_user` varchar(20) NOT NULL, `update_time` char(19) DEFAULT NULL, `update_user` varchar(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 同时插入了 10w 条测试数据。 将示例 sql 脚本,复制到 3 个容器中 sudo docker cp /home/sanotsu/Dump20200303.sql mariadb1:/home/Dump20200303.sql 为了方面修改配置文件,给 3 个容器中安装 vim。 sudo docker exec -it mariadb1 bash apt update apt install vim 导入数据库 就在刚刚的容器的交互界面,继续执行: mysql -uroot -p < /home/Dump20200303.sql mysql_upgrade -uroot -proot 账号的密码就是之前创建容器时指定的 MYSQL_ROOT_PASSWORD 参数的值。mysql_upgrade 用于更新表格到最新的版本。 3、Master-Slaves 设置(主从服务器设定)配置 3 个容器中的 MariaDB server-id 分别为 1、2、3,log-basename 分别为 master01、slave01、slave02,并建立 relication 用账号。 修改配置文件: 使用 vim 打开容器中 mariadb 的配置文件,在容器的终端运行 vim /etc/mysql/my.cnf修改或添加以下参数: server-id = 1 log_bin = /var/log/mysql/mariadb-bin log_basename = master01 binlog_format=mixed示例的设定是,mariadb1(master)容器如下: mariadb2(slave1)容器如下: mariadb3(slave2)容器如下: 建立 replication 用账号: 在容器中,进入到 MariaDB 的命令窗口,创建一个用于备份的账号和赋予复制权限,记得要刷新权限。 CREATE USER 'reptest'@'%' IDENTIFIED BY '123456'; GRANT REPLICATION SLAVE ON *.* TO 'reptest'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; 设定完权限后,重启 docker 容器来重启 MariaDB 。 重启之后,查看下 master01 binarylog 状态,后续将 master01 作为 MASTER 主机,slave01 和 slave02 作为 SLAVE 主机。 mariadb1 会作为 master,查看状态如下 mariadb2 和 mariadb3 要作为 slave,需要在 mariadb 命令窗口 执行以下配置: CHANGE MASTER TO MASTER_HOST='192.168.28.93', MASTER_USER='reptest', MASTER_PASSWORD='123456', MASTER_PORT=3307, MASTER_LOG_FILE='master01-bin.000001', MASTER_LOG_POS= 331, MASTER_CONNECT_RETRY=6; 注意配置的属性都要与 master 的状态一致。 配置完之后在启动 slave,然后查看下 slave 状态: START SLAVE; SHOW SLAVE STATUS\\G;两台 slave 实例应该会现实如下 到这里,简单的 master-slave 的设定就完成了,可以测试一下。 简单的 replication 测试示例1、master 的修改是否会同步到 slave 去先查看目前 3 个 MariaDB 的测试数据,可见 master 和两个 slave 在 test_replication.call_record 测试数据都是 10w 条。 对 MASTER 进行删除操作: delete from test_replication.call_record limit <X>; 注意:如果想马上看到级联更新的效果,建议对一条数据进行修改删除插入的操作。如果如示例一样进行了 5w 条的删除,可能需要等待一段时间才能完成同步到 Slaves。 一般情况下,同步都是实时的,如果上述的操作是删除 1 条数据或者修改 1 条数据可以更加清晰地看到。(没有截图,有实际删除 1 条看效果,目前 3 个表中数据还有 49999 条) 但是也有可能需求,master 更新之后,要延迟一点时间,再同步到 slave。 只需简单设定如下: 在需要被延迟更新的 SLAVE 中,设定延迟时间: STOP SLAVE; CHANGE MASTER TO master_delay= 60; START SLAVE;再次测试结果(3 个表分表有 49999 条数据)选取 SLAVE02,设定 60s 延迟更新: 查看两个 slave 的状态可见 SQL_Delay 的数值变化: show slave status\\G; 在 master 表删除 1 条数据,从下图可以看出,slave01 是很快就同步了,二 slave02 是延迟时间到达之前,是没有更新的。 2、SLAVE 取代 MASTER在 master 主机因为一些情况不能继续使用的时候,可以快速地升级某一台 SLAVE 机器替换 master,避免相关业务中断。 基本步骤如下: 1> 停止 master 写入 设置 mater 读锁定 FLUSH TABLES WITH READ LOCK ;检查 master、slave 数据是否同步完成 master: SHOW MASTER STATUS;slave: SHOW SLAVE STATUS\\G;(注意红框中参数) 2> 设置 slave02 主机为 MASTER STOP ALL SLAVES; RESET SLAVE ALL; SHOW MASTER STATUS; SELECT @@global.gtid_binlog_pos; SET @@global.read_only=0; 3 更改 slave01 主机连接配置 之前 slave01 连接的主服务器是 master01,现在要为 slave02。在原 salve01 中执行: STOP SLAVE; RESET SLAVE; CHANGE MASTER TO MASTER_HOST='192.168.28.93', MASTER_USER='reptest', MASTER_PASSWORD='123456', MASTER_PORT=3309, MASTER_LOG_FILE='slave02-bin.000002', MASTER_LOG_POS= 331, MASTER_CONNECT_RETRY=6; START SLAVE; SHOW SLAVE STATUS\\G; 4 更改原 master 主机配置,修改为 slave 主机 原来的主服务器,现在是从服务器了,修改主服务器连接为原 slave02。 UNLOCK TABLES; SET @@global.read_only=1; STOP ALL SLAVES; RESET MASTER; RESET SLAVE ALL; CHANGE MASTER TO MASTER_HOST='192.168.28.93', MASTER_USER='reptest', MASTER_PASSWORD='123456', MASTER_PORT=3309, MASTER_LOG_FILE='slave02-bin.000002', MASTER_LOG_POS= 330, MASTER_CONNECT_RETRY=6; START SLAVE; SHOW SLAVE STATUS\\G; 完成之后,可以测试一下,在新 master 删除两条数据,然后可以看到新的两个 salve 中数据少了两条: 当然,除了 delete 其它语句可以自行测试。 3、设定并行复制 Parallel Replication可以在从属服务器上并行(同时)执行从主服务器复制的某些写入操作。 将 slave01 设置并行复制: STOP SLAVE SQL_THREAD; SET GLOBAL slave_parallel_threads = 4; SET GLOBAL slave_parallel_max_queued=67108864; START SLAVE SQL_THREAD; SELECT @@slave_parallel_threads; show processlist;有看到进程列表中有 4 个 slave_worker STOP SLAVE; Change master to master_use_gtid =slave_pos; START SLAVE;使用 gitd 位置: 查看 slave 状态: master 主机查看: select @@gloabl.gtid_binlog_pos;slave server 查看 slave 状态: SHOW SLAVE STATUS\\G; 设定 salve01 可并行复制完成。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十七)MariaDB的复制(replication)简介","slug":"TechnicalEssays/MariaDBSeries/17mariadb-replication","date":"2020-06-18T13:07:16.000Z","updated":"2023-05-16T13:54:36.566Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/17mariadb-replication/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/17mariadb-replication/","excerpt":"","text":"注意区分:在一开始的时候,有讲到备份和还原,有详细提到两个命令 mysqldump 和 mariabackup,用于备份(Backing Up)。 repelication 基础简介replication 概述复制(Replication)是一个允许一个或多个主(master)服务器的内容镜像复制到一个或多个从(slave)服务器上的特性。 可以控制要复制的数据:所有数据库、一个或多个数据库或数据库中的表。 复制中使用的主要机制是二进制日志(binary log)。如果启用了二进制日志记录,则数据库的所有更新(数据操作和数据定义)都将作为 binlog 事件写入二进制日志。从服务器(Slaves)从每个主机(each master)读取二进制日志,以便访问要复制的数据。中继日志(relay log)是在从属服务器上创建的,使用与二进制日志相同的格式,用于执行复制。旧的中继日志文件将在不再需要时被删除。 从服务器保持轨迹(track)在主服务器的 binlog 中记录的最后一次应用到从服务器的事件的位置。这就可以允许从服务器重新连接,并从临时停止后中断的位置恢复复制操作。它还允许从服务器断开连接、克隆,然后从同一主服务器恢复新的从服务器复制。 主从服务器之间不需要持续通讯(constant communication)。服务器脱机或断开与网络的连接情况很常见,但当它们重连时,复制将在中断的地方继续执行。 replication 用途 可扩展性(Scalability)。通过拥有一个或多个从属服务器,可以将读取分散到多个服务器上,从而减少主服务器上的负载。对于高读、低写环境,最常见的情况是有一个主节点,所有的写操作都在其中进行,复制到多个从节点,这些从节点处理大部分的读操作。 数据分析(Data analysis)。分析数据可能会对主服务器产生太大的影响,这同样可以在从属服务器上处理,而主服务器继续不受额外负载的影响。 备援(Backup assistance)。如果服务器不主动更改数据,备份(Backups)可以更轻松地运行。一个常见的场景是将数据复制到从属服务器,然后在数据处于稳定状态时从主服务器断开连接。然后从该服务器执行备份。 数据分发(Distribution of data)。与其连接到远程主机,还可以在本地复制数据,并从该数据开始工作。 通用的复制设定 Common Replication Setups标准复制 Standard Replication特点: 提供无限的读取横向扩展。 通过将从机(Slave)升级为主机(Master)来提供高可用性。 环状复制 Ring Replication特点: 提供读写缩放(scaling)。 不处理冲突。 如果一个主服务器发生故障,复制将停止。 星状复制 Star Replication特点: 提供读写缩放。 不处理冲突。 必须使用复制过滤器以避免重复数据。 多源复制 Multi-Source Replication特点: 允许合并来自不同来源的数据。 在所有的从服务器上,不同域独立并行执行。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十六)MariaDB部分内置JSON函数简介","slug":"TechnicalEssays/MariaDBSeries/16mariadb-json-function","date":"2020-06-18T13:07:02.000Z","updated":"2023-05-16T13:54:36.566Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/16mariadb-json-function/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/16mariadb-json-function/","excerpt":"","text":"如今,越来越多的 IOT 设备被推广使用,收集到的数据,习惯性的,都会是 nosql 类型的,例如 JSON。 MariaDB 10.2.7 加入了 JSON 的数据类型,用于处理该格式的数据,但在 MariaDB 10.2.3 已加入多个 JSON 用途 functions,支持所有文字型别字段 ( char, varchar, text …)。 此处简单介绍下常用的 MariaDB 内建的 JSON 相关的函数。 示例直接复制 MariaDB 命令窗口测试执行语句及结果。 JSON_VALID语法: JSON_VALID(value)说明: 显示给定值是否为有效的 JSON 文档(JSON document)。如果有效,则返回 1;如果无效,则返回 0;如果参数为空,则返回 NULL。 在 MariaDB 10.4.3 中,JSON_VALID 函数自动用作 JSON 数据类型别名的检查约束,以确保插入有效的 JSON 文档。即字段是 JSON 类型,则会自动检查值是否为 JSON 格式。 示例: MariaDB [(none)]> set @json1='{"id": 1, "name": "David"}'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_VALID(@json1); +--------------------+ | JSON_VALID(@json1) | +--------------------+ | 1 | +--------------------+ 1 row in set (0.000 sec) 或简单写成一句: MariaDB [(none)]> SELECT JSON_VALID('{"id": 1, "name": " David "}'); +--------------------------------------------+ | JSON_VALID('{"id": 1, "name": " David "}') | +--------------------------------------------+ | 1 | +--------------------------------------------+ 1 row in set (0.000 sec) json 数据类型自动检查: -- 建表 CREATE TABLE test200222.jsont ( uid INTEGER UNSIGNED auto_increment NOT NULL, uattr json NULL, PRIMARY KEY (uid) ) -- 插入测试 INSERT INTO jsont VALUES(1,NULL); -- Query OK, 1 row affected (0.01 sec) INSERT INTO jsont VALUES(2,'{"size": 42, "colour": "white"}'); -- Query OK, 1 row affected (0.01 sec) INSERT INTO jsont VALUES(3,'{"colour": "white}'); -- SQL 错误 [4025] [23000]: (conn=82) CONSTRAINT `jsont.uattr` failed for `test200222`.`jsont` JSON_CONTAINS_PATH语法: JSON_CONTAINS_PATH(json_doc, return_arg, path[, path] ...)说明: 显示给定的 JSON 文档是否包含指定路径处的数据。如果是,则返回 1;如果不是,则返回 0;如果任何参数为空,则返回 NULL。 返回参数可以是一个或全部: one-如果 JSON 文档中至少存在一个路径,则返回 1。 all-仅当 JSON 文档中存在所有路径时返回 1。 Path 路径表示 $. -> 起始 $.Key -> $.id —> { “id”: 5 } $.key.subkey -> $.data.subject -> { “data”: { “subject”:”h”} } 示例: MariaDB [(none)]> SET @json = '{"A": 1, "B": [2], "C": [3, 4]}'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D'); +------------------------------------------------+ | JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D') | +------------------------------------------------+ | 1 | +------------------------------------------------+ 1 row in set (0.001 sec) MariaDB [(none)]> SELECT JSON_CONTAINS_PATH(@json, 'all', '$.A', '$.D'); +------------------------------------------------+ | JSON_CONTAINS_PATH(@json, 'all', '$.A', '$.D') | +------------------------------------------------+ | 0 | +------------------------------------------------+ 1 row in set (0.000 sec) JSON_EXISTS语法: JSON_EXISTS(<given data>,<json value>)说明 确定给定数据中是否存在指定的 JSON 值。如果找到,则返回 1;如果没有,则返回 0;如果任何输入为空,则返回 NULL。 示例: MariaDB [(none)]> SELECT JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2"); +------------------------------------------------------------+ | JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2") | +------------------------------------------------------------+ | 1 | +------------------------------------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]"); +---------------------------------------------------------------+ | JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2[1]") | +---------------------------------------------------------------+ | 1 | +---------------------------------------------------------------+ 1 row in set (0.000 sec) JSON_CONTAINS语法: JSON_CONTAINS(json_doc, val[, path])说明: 返回指定的值是否在给定的 JSON 文档中找到,或者是否在文档中的指定路径(可选)处找到。 如果是,则返回 1;如果不是,则返回 0;如果任何参数为空,则返回 NULL。 如果文档或路径无效,或包含*或**通配符,则会发生错误。 示例: MariaDB [(none)]> SET @json = '{"A": 0, "B": {"C": 1}, "D": 2}'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_CONTAINS(@json, '2', '$.A'); +----------------------------------+ | JSON_CONTAINS(@json, '2', '$.A') | +----------------------------------+ | 0 | +----------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_CONTAINS(@json, '2', '$.D'); +----------------------------------+ | JSON_CONTAINS(@json, '2', '$.D') | +----------------------------------+ | 1 | +----------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_CONTAINS(@json, '{"C": 1}', '$.A'); +-----------------------------------------+ | JSON_CONTAINS(@json, '{"C": 1}', '$.A') | +-----------------------------------------+ | 0 | +-----------------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_CONTAINS(@json, '{"C": 1}', '$.B'); +-----------------------------------------+ | JSON_CONTAINS(@json, '{"C": 1}', '$.B') | +-----------------------------------------+ | 1 | +-----------------------------------------+ 1 row in set (0.000 sec) JSON_DEPTH语法: JSON_DEPTH(json_doc)说明: 返回给定 JSON 文档的最大深度,如果参数为 NULL,则返回 NULL。如果参数是无效的 JSON 文档,则会发生错误。 标量值(scalar values)或空数组或对象的深度为 1。 非空但仅包含深度为 1 的元素或成员值的数组或对象的深度为 2。 在其它情况下,深度将大于 2。 示例: MariaDB [(none)]> SELECT JSON_DEPTH('[]'), JSON_DEPTH('true'), JSON_DEPTH('{}'); +------------------+--------------------+------------------+ | JSON_DEPTH('[]') | JSON_DEPTH('true') | JSON_DEPTH('{}') | +------------------+--------------------+------------------+ | 1 | 1 | 1 | +------------------+--------------------+------------------+ 1 row in set (0.043 sec) MariaDB [(none)]> SELECT JSON_DEPTH('[1, 2, 3]'), JSON_DEPTH('[[], {}, []]'); +-------------------------+----------------------------+ | JSON_DEPTH('[1, 2, 3]') | JSON_DEPTH('[[], {}, []]') | +-------------------------+----------------------------+ | 2 | 2 | +-------------------------+----------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_DEPTH('[1, 2, [3, 4, 5, 6], 7]'); +---------------------------------------+ | JSON_DEPTH('[1, 2, [3, 4, 5, 6], 7]') | +---------------------------------------+ | 3 | +---------------------------------------+ 1 row in set (0.000 sec) JSON_OBJECT语法: JSON_OBJECT([key, value[, key, value] ...])说明: 返回包含给定键/值对的 JSON 对象。键/值列表可以为空。 如果参数的数目为奇数,或者任何键名为空,则将发生错误。 示例: MariaDB [(none)]> SELECT JSON_OBJECT("id", 1, "name", "David"); +---------------------------------------+ | JSON_OBJECT("id", 1, "name", "David") | +---------------------------------------+ | {"id": 1, "name": "David"} | +---------------------------------------+ 1 row in set (0.000 sec) JSON_KEYS语法: JSON_KEYS(json_doc[, path])说明: 从 JSON 对象的顶层值(top-level value)以 JSON 数组的形式返回键,如果提供了可选的 path 参数,则从 path 返回顶层键(top-level keys)。 从顶层值中的嵌套子对象中排除关键帧。如果所选对象为空,则生成的数组将为空。 如果任何参数为空、给定路径未找到对象或 json_doc 参数不是对象,则返回 NULL。 如果 JSON 文档无效、路径无效或路径包含*或**通配符,则会发生错误。 示例: MariaDB [(none)]> SELECT JSON_KEYS('{"A": 1, "B": {"C": 2}}'); +--------------------------------------+ | JSON_KEYS('{"A": 1, "B": {"C": 2}}') | +--------------------------------------+ | ["A", "B"] | +--------------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_KEYS('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C'); +-----------------------------------------------------+ | JSON_KEYS('{"A": 1, "B": 2, "C": {"D": 3}}', '$.C') | +-----------------------------------------------------+ | ["D"] | +-----------------------------------------------------+ 1 row in set (0.000 sec) JSON_VALUE语法: JSON_VALUE(json_doc, path)说明: 给定一个 JSON 文档,返回路径指定的标量(scalar)。如果没有给出有效的 JSON 文档,或者没有匹配项,则返回 NULL。 示例: MariaDB [(none)]> select json_value('{"key1":123}', '$.key1'); +--------------------------------------+ | json_value('{"key1":123}', '$.key1') | +--------------------------------------+ | 123 | +--------------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> select json_value('{"key1": [1,2,3], "key1":123}', '$.key1'); +-------------------------------------------------------+ | json_value('{"key1": [1,2,3], "key1":123}', '$.key1') | +-------------------------------------------------------+ | 123 | +-------------------------------------------------------+ 1 row in set (0.000 sec) JSON_INSERT语法: JSON_INSERT(json_doc, path, val[, path, val] ...)说明: 将数据插入到 JSON 文档中,如果任何参数为 NULL,则返回结果文档或 NULL。 如果 JSON 文档不是无效的,或者如果任何路径无效或包含*或**通配符,则会发生错误。 JSON_INSERT 只能插入数据,而 JSON_REPLACE 只能更新。JSON_SET 可以更新或插入数据。 示例: MariaDB [(none)]> SET @json = '{ "A": 0, "B": [1, 2]}'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_INSERT(@json, '$.C', '[3, 4]'); +--------------------------------------+ | JSON_INSERT(@json, '$.C', '[3, 4]') | +--------------------------------------+ | {"A": 0, "B": [1, 2], "C": "[3, 4]"} | +--------------------------------------+ 1 row in set (0.000 sec) JSON_REPLACE语法: JSON_REPLACE(json_doc, path, val[, path, val] ...)说明: 替换 JSON 文档中的现有值,返回结果;如果任何参数为空,则为空。 如果 JSON 文档无效、路径无效或路径包含*或**通配符,则会发生错误。 路径和值是从左到右计算的,前面的计算结果将用作下一个的值 示例: MariaDB [(none)]> SELECT JSON_REPLACE('{ "A": 1, "B": [2, 3]}', '$.B[1]', 4); +-----------------------------------------------------+ | JSON_REPLACE('{ "A": 1, "B": [2, 3]}', '$.B[1]', 4) | +-----------------------------------------------------+ | {"A": 1, "B": [2, 4]} | +-----------------------------------------------------+ 1 row in set (0.000 sec) JSON_SET语法: JSON_SET(json_doc, path, val[, path, val] ...)说明: 在 JSON 文档中更新或插入数据,返回结果;如果任何参数为 NULL 或可选路径找不到对象,则返回 NULL。 如果 JSON 文档无效、路径无效或路径包含*或通配符,则会发生错误。 示例: MariaDB [(none)]> SET @json = '{"A": 0, "B": "hello", "C": {"msg": "check"} }'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_VALID(@json); +-------------------+ | JSON_VALID(@json) | +-------------------+ | 1 | +-------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_SET(@json , '$.B' , '"WORLD"'); +---------------------------------------------------+ | JSON_SET(@json , '$.B' , '"WORLD"') | +---------------------------------------------------+ | {"A": 0, "B": "\\"WORLD\\"", "C": {"msg": "check"}} | +---------------------------------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_SET(@json , '$.D' , '"INSERT"'); +------------------------------------------------------------------+ | JSON_SET(@json , '$.D' , '"INSERT"') | +------------------------------------------------------------------+ | {"A": 0, "B": "hello", "C": {"msg": "check"}, "D": "\\"INSERT\\""} | +------------------------------------------------------------------+ 1 row in set (0.000 sec) JSON_EXTRACT语法: JSON_EXTRACT(json_doc, path[, path] ...)说明: 从 JSON 文档中提取数据。从与路径参数匹配的部分中选择提取的数据。返回所有匹配的值。要么作为单个匹配的值,要么在参数可以返回多个值的情况下,则结果将自动包装为匹配顺序的数组。 如果没有匹配的路径或任何参数为空,则返回 NULL。 如果任何 path 参数不是有效的 path,或者 json_doc 参数不是有效的 json 文档,则会发生错误。 示例: MariaDB [(none)]> SET @json = '[1, 2, [3, 4]]'; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> SELECT JSON_EXTRACT(@json, '$[1]'); +-----------------------------+ | JSON_EXTRACT(@json, '$[1]') | +-----------------------------+ | 2 | +-----------------------------+ 1 row in set (0.000 sec) MariaDB [(none)]> SELECT JSON_EXTRACT(@json, '$[2]'); +-----------------------------+ | JSON_EXTRACT(@json, '$[2]') | +-----------------------------+ | [3, 4] | +-----------------------------+ 1 row in set (0.001 sec) MariaDB [(none)]> SELECT JSON_EXTRACT(@json, '$[2][1]'); +--------------------------------+ | JSON_EXTRACT(@json, '$[2][1]') | +--------------------------------+ | 4 | +--------------------------------+ 1 row in set (0.000 sec) 更多 MariaDB 内建 JSON 函数,可参看官网:https://mariadb.com/kb/en/json-functions/","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十五)Spider功能函数说明","slug":"TechnicalEssays/MariaDBSeries/15mariadb-spider-function","date":"2020-06-18T13:06:37.000Z","updated":"2023-05-16T13:54:36.567Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/15mariadb-spider-function/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/15mariadb-spider-function/","excerpt":"","text":"Spider FunctionsSpider 存储引擎提供了以下 4 个 UDF 函数,是与 Spider 存储引擎一起安装的 UDF: SPIDER_DIRECT_SQL:在远程服务器中执行 sql(Execute SQL on the remote server)SPIDER_BG_DIRECT_SQL:后台 SQL 执行(Background SQL execution)SPIDER_COPY_TABLES:(复制表数据)Copy table dataSPIDER_FLUSH_TABLE_MON_CACHE:(刷新 Spider 监视服务器信息)Refreshing Spider monitoring server information (UDF:User-Defined Functions,用户定义函数,随着 Spider 存储引擎的安装一并安装,是一种用新函数扩展 MariaDB 的方式,该新函数的工作方式类似于本机(内置)MariaDB 函数) 本节中,没有特殊说明,一般都是在 spider server 中执行的语句 SPIDER_DIRECT_SQL语法: SPIDER_DIRECT_SQL('sql', 'tmp_table_list', 'parameters')说明: 一个与 Spider Storage Engine 一起安装的 UDF,此功能用于在远程服务器上执行 SQL 字符串 sql,如参数中所定义。 如果返回任何结果集,它们将存储在 tmp_table_list 中。 如果 SQL 成功执行,则该函数返回 1;如果失败,则返回 0。 示例: -- 创建一个临时表 CREATE TEMPORARY TABLE test.res ( id int(10) unsigned NOT NULL, accountName varchar(20) NOT NULL DEFAULT '', name varchar(128) NOT NULL DEFAULT '' ) ENGINE=MEMORY; -- 执行SPIDER_DIRECT_SQL语句 SELECT SPIDER_DIRECT_SQL('SELECT * FROM test.opportunities', 'res', 'srv "backend1", port "3306"'); -- 查询执行结果 SELECT * FROM test.res; SPIDER_BG_DIRECT_SQL语法: SPIDER_BG_DIRECT_SQL('sql', 'tmp_table_list', 'parameters')描述: 在远程服务器上后台执行给定的, 如参数列表中所定义的 SQL 语句; 如果查询返回结果集,则将结果存储在给定的临时表中。 当给定的 SQL 语句执行成功时,此函数返回被调用的 UDF 的数目。当给定的 SQL 语句失败时,它返回 0。 示例: 准备操作,在 backend2 的远程主机 MariaDB 上,给 test.opportunities 表添加一条数据: INSERT INTO test.opportunities (id, accountName, name, owner, amount, closeDate, stageName) VALUES(1, 'ubut18test', 'spider', 'spider', 30, '2020-02-28', 'halo'); 在 spider server 本机的 MariaDB 的 test 数据库新建一个临时表 res2: -- 创建一个临时表 CREATE TEMPORARY TABLE if not exists test.res2 ( id int(10) unsigned NOT NULL, accountName varchar(20) NOT NULL DEFAULT '', name varchar(128) NOT NULL DEFAULT '' ) ENGINE=MEMORY; 执行 SPIDER_BG_DIRECT_SQL 示例语句: SELECT SPIDER_BG_DIRECT_SQL('SELECT * FROM test.opportunities', 'test.res2', 'srv "backend2", port "3307"') AS "Direct Query"; 查看结果,就是刚刚 backend2 新加的那条数据: SELECT * FROM test.res2; SPIDER_COPY_TABLES(无果)语法: SPIDER_COPY_TABLES(spider_table_name, source_link_id, destination_link_id_list [,parameters])说明: 此函数可以在不停止 MariaDB service 的情况下,把表数据从source_link_id复制到destination_link_id_list。 如果 spider 表已被分区,那么表名就需要是table_name#P#partition_name格式。 (to be honest,这两个 id 还没有研究透彻,测试示例会报错,可以协助指出以下示例无法执行的原因,谢谢。) 示例: 例如有执行之前的按 List 分区操作,那么执行SELECT table_name FROM mysql.spider_tables;语句,可以看到类似的数据: 而此时该表的数据应该是: 为了方便查看分区的效果,按照 bylist 建立分区的规则,向 opportunitiesByList 表插入一些数据: INSERT INTO test.opportunitiesByList (id, accountName, name, owner, amount, closeDate, stageName) VALUES (4, 'spiderserver', 'spiderserver', 'Bill', 30, '2020-02-28', 'hall'), (5, 'spiderserver', 'spiderserver', 'Bob', 30, '2020-02-28', 'hall'), (6, 'spiderserver', 'spiderserver', 'Chris', 30, '2020-02-28', 'hall'), (7, 'spiderserver', 'spiderserver', 'Maria', 30, '2020-02-28', 'hall'), (8,'spiderserver', 'spiderserver', 'Olivier', 30, '2020-02-28', 'hall'); SELECT * FROM test.opportunitiesByList; 结果如下: 而 pt1 分区的值: 所以,如果要复制 opportunitiesByList 表数据,就例如:opportunitiesByList#P#pt1 的数据复制到 opportunitiesByList#P#px 中去: select spider_copy_tables('test.opportunities','1','2'); select spider_copy_tables('test.opportunitiesByList#P#pt1','1','0');############# 这个实际的用法,还是没有搞清楚,这两个< source_link_id > 到底是什么意思,执行上述语句,报错 SQL 错误 [12704][hy000]: (conn=74) Source table is not found,暂时没有找到相关资料。 ############# SPIDER_FLUSH_TABLE_MON_CACHE语法: SPIDER_FLUSH_TABLE_MON_CACHE()描述: 此函数用于刷新监控服务器(monitoring server)的信息。返回 1。 示例: SELECT SPIDER_FLUSH_TABLE_MON_CACHE(); 到这里就简单说完 spider 存储引擎的使用了,主要特点就是处理不同的 MariaDB 实例就像在处理同一个实例一样。 更多的内容,可以去官网学习了解:https://mariadb.com/kb/en/spider/ 补充:查看 MariaDB 使用硬盘空间情况安装 DISKS 插件: INSTALL SONAME 'disks'使用: SELECT * FROM Information_Schema.DISKS 限制: MariaDB 10.1.32 加入; 从 MariaDB 10.4.7,MariaDB 10.3.17,MariaDB 10.2.26 和 MariaDB 10.1.41 开始,它需要 FILE 权限; 该插件仅适用于 Linux。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十四)Spider使用示例","slug":"TechnicalEssays/MariaDBSeries/14mariadb-spider-example","date":"2020-06-18T13:06:21.000Z","updated":"2023-05-16T13:54:36.568Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/14mariadb-spider-example/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/14mariadb-spider-example/","excerpt":"","text":"Spider 使用簡單示例:準備工作有三個設備安裝 MariaDB 簡單的架構一個 spider server,兩個后端 server:backend1 和 backend2; 在我的例子里,對應的主機名和 IP 分別是:spider server(ubt18) :主機名:sanotsu,ip:192.168.28.93;backend1(ubt18):主機名:david,,ip:192.168.28.72;backend2(win7):主機名:davidsu,,ip:192.168.28.80。 1、spider server 安裝 SpiderMariaDB package 並無相關套件,需要終端安裝 sudo apt install mariadb-plugin-spider確認是否安裝成功 使用任一指令:show plugins;,show engines;或show tables from mysql like '%spider%';. 有看到 spider 相關值或變量就說明成功。 2、backend MariaDB 建立 spider 使用的賬戶分別在兩個 backend 創建 spider server 可訪問的賬戶: grant all on test.* to spider@'192.168.28.93' identified by 'spider';創建完之後,在 spider server 測試能否連接到兩個 backend: 只用終端輸入指令:mysql -uspider -p -h 192.168.28.72,或者直接用工具 dbeaver 通過 spider 帳號連接到 backend。 3、在 backend 創建示例表在 backend1 和 backend2 設備的 MariaDB 創建 test 數據庫(如果沒有的話),再創建一個示例表,如下: create table opportunities ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11), primary key (id), key (accountName) ) engine=InnoDB; 4、在 spider server 上創建服務器條目(server entries)虽然连接信息也可以在注释中内联指定,但是定义一个代表每个远程后端服务器连接的服务器对象更简洁。語句如下: create server backend1 foreign data wrapper mysql options (host '192.168.28.72', database 'test', user 'spider', password 'spider', port 3306); create server backend2 foreign data wrapper mysql options (host '192.168.28.80', database 'test', user 'spider', password 'spider', port 3307); flush tables; 端口不同是因為 win7 主機上有安裝 mysql 和 MariaDB,區分了端口,注意遠端 MariaDB server 主機的 IP 地址正確。 注意: 请记住,如果出于任何原因需要删除、重新创建或以其他方式修改服务器定义,则还需要执行FLUSH TABLES语句。 否则,Spider 会继续使用旧的服务器定义,这可能导致查询引发错误:Error 1429: Unable to connect to foreign data source 5、spider 用例:5.1 處理遠端表在这种情况下,将创建一个 spider 表,以允许远程访问 backend1 上托管的机会表。 然后,这将允许从 spider server 向 backend1 服务器執行查询和远程 DML 操作. 在 spider server 創建一個 spider table,連接到遠端主機 backend1: create table test.opportunities ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11), primary key (id), key (accountName) ) engine=spider comment='wrapper "mysql", srv "backend1" , table "opportunities"'; 此時,在 spider server 的 test 數據庫中,建立了一個直接關聯到了 backend 主機上的 test.opportunities 表。 在 spider server 對該表做 DML 都會對 backend1 中關聯表生效,執行查詢也是對該表數據的查詢。 演示:在 backend1 中 test.opportunities 插入一條數據 INSERT INTO test.opportunities (id, accountName, name, owner, amount, closeDate, stageName) VALUES(1, 'backend1', 'backend1', 'back1', 30, '2020-02-28', 'halo'); 再在 spider server 中查詢該表,可以得到該數據: SELECT id, accountName, name, owner, amount, closeDate, stageName FROM test.opportunities; 因為有設定 id 為主鍵,所以在 spider server 對該表新加一條已存在值的 id,會報錯: INSERT INTO test.opportunities (id, accountName, name, owner, amount, closeDate, stageName) VALUES(1, 'spiderserver', 'spiderserver', 'test', 30, '2020-02-28', 'hall'); 不過正確插入值之後,執行成功,然后可以查詢到新增的值 再回到 backend1,查看該 test.opportunities 表,雖然未在 backend1 中新增,但已有新增的值: 5.2 數據分片(sharding)按 hash 分区在本例中,通过对 id 进行散列(hashing)处理,创建了一个 spider 表,以便在 backend1 和 backend2 之间分布(distribute )数据。 如果 id 是一个自增的值,散列處理將可以确保值在 2 个节点之间均匀分布。 create table test.opportunitiesByHash ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11), primary key (id), key (accountName) ) engine=spider COMMENT='wrapper "mysql", table "opportunities"' PARTITION BY HASH (id) ( PARTITION pt1 COMMENT = 'srv "backend1"', PARTITION pt2 COMMENT = 'srv "backend2"' ) ; 按 range 分区示例使用 accountName 來進行 range 分區,那么依照 MariaDB 的規范,需要將 accountName 欄位加入到主鍵中去。具體分區條件見示例: create table test.opportunitiesByRange ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11), primary key (id, accountName), key(accountName) ) engine=spider COMMENT='wrapper "mysql", table "opportunities"' PARTITION BY range columns (accountName) ( PARTITION pt1 values less than ('M') COMMENT = 'srv "backend1"', PARTITION pt2 values less than (maxvalue) COMMENT = 'srv "backend2"' ) ; 按 list 分区示例使用 owner 來進行 list 分區,那么依照 MariaDB 的規范,需要將 owner 欄位加入到主鍵中去。具體分區條件見示例: create table test.opportunitiesByList ( id int, accountName varchar(20), name varchar(128), owner varchar(7), amount decimal(10,2), closeDate date, stageName varchar(11), primary key (id, owner), key(accountName) ) engine=spider COMMENT='wrapper "mysql", table "opportunities"' PARTITION BY list columns (owner) ( PARTITION pt1 values in ('Bill', 'Bob', 'Chris') COMMENT = 'srv "backend1"', PARTITION pt2 values in ('Maria', 'Olivier') COMMENT = 'srv "backend2"' ) ; 根據之前的說明,list 分區還可以加DEFAULT收納所有不滿足的值。 ref:(沒用到,后續可以用來補充)https://mariadb.com/resources/blog/uses-for-mariadb-and-the-spider-storage-engine/","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十三)MariaDB存储引擎Spider简介","slug":"TechnicalEssays/MariaDBSeries/13mariadb-spider-storage-engine","date":"2020-06-18T13:05:54.000Z","updated":"2023-05-16T13:54:36.569Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/13mariadb-spider-storage-engine/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/13mariadb-spider-storage-engine/","excerpt":"","text":"新的问题: 若数据量即使使用 Partition,都不是单一台主机有办法处理;存放要分散,但存取要集中。 这个时候,就可以考虑 Spider 存储引擎。 事实上,对于数据驱动的功能,都可能面临类似的问题,数据库日益膨大,单表、单数据库、单机器已经不能存储和处理数据了。 所以会有上述说明的分片设计。 针对不同程度的数据,会选择到不同的具体分片实现,这也是上述说明的各种单表的分区作业。 不过基于有些引擎的限制或者功能不够强大,可能在数据库分片上无法实现跨设备的作业。 但是 Spider 引擎可能提供了一个比较好的解决方案。 Spider 是什么?Spider 是 MariaDB 内置的一个可插拔用于 MariaDB/MySQL 数据库分片的存储引擎,充当应用服务器和远程后端 DB 之间的代理(中间件),它可以轻松实现 MariaDB/MySQL 的横向和纵向扩展,突破单台 MariaDB/MySQL 的限制,支持范围分区、列表分区、哈希分区,支持 XA 分布式事务,支持跨库 join。完成数据库跨越多组实例(instances)。 使用 Spider 存储引擎创建表后,该表将链接到远程服务器上的表。远程表可以是任何存储引擎。通过建立从本地 MariaDB 服务器到远程 MariaDB 服务器的连接,可以具体实现表链接。该链接对于属于同一事务的所有表共享。 简单说起来: Spider 首先提供的是从另一个 MariaDB 服务器访问一个 MariaDB 服务器(也可以是其它类型数据库服务器)上的表的方法。 保存实际表数据的 MariaDB 服务器上根本没有任何特定的 Spider 代码,它是一个普通的 MariaDB 服务器。MariaDB 服务器被配置为访问该数据,然后使用 Spider 存储引擎使用通常的 MariaDB 协议访问另一台服务器上的数据。 图源 mariadb 博客(https://mariadb.com/resources/blog/uses-for-mariadb-and-the-spider-storage-engine/) 可以看到,Spider 只在引用节点上处于活动状态,目标节点不需要安装 Spider。创建“spider 表”意味着我们定义一个表,该表包含目标表中相同列或列的子集,并引用目标服务器。 还要注意,“spider 节点”上没有这些表的数据,也没有重复的数据,所有数据都驻留在目标节点上。 Spider 存储引起核心概念这是几乎每一篇介绍 Spider 的文章都会提到的东西。 典型的 Spider 部署有一个无共享的集群架构(shared-nothing clustered architecture)。这个系统可以使用任何满足对硬件或软件有最低的特定要求的硬件。它由一组运行有一个或多个称为节点(node)的 MariaDB 进程的主机(computers)组成。 存储数据的节点将被设计为后端节点(Backend Nodes),可以是使用后端中可用的任何存储引擎的任何 MariaDB、MySQL、Oracle 服务器实例。 Spider 代理节点(Spider Proxy Nodes)是至少运行 MariaDB 10 版本上,用于向后端节点声明每个表的附件(attachment)。此外,还可以设置 Spider 代理节点,以便将表拆分并镜像到多个后端节点。 Spider 常见用例: 图源官网(https://mariadb.com/kb/en/spider-storage-engine-core-concepts/) 此外 Spider 引擎的底层架构和优化设计还是比较复杂的,有兴趣可以查看官网了解,或者从下图中窥见一二: 图源官网(https://mariadb.com/kb/en/spider-storage-engine-core-concepts/)","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十二)MariaDB中的分区操作","slug":"TechnicalEssays/MariaDBSeries/12mariadb-partitioning","date":"2020-06-18T13:05:22.000Z","updated":"2023-05-16T13:54:36.570Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/12mariadb-partitioning/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/12mariadb-partitioning/","excerpt":"","text":"数据量不大的中小型规模 size 的 Table 原则是还是采用 Table+Index 设计为最佳化的思考重点。 在大数据考虑数据分片的时候,有两点也是重点: 一是空间,数据存放的存储空间是否足够,易于扩展。二是时间,对于数据存取是否有限制,能否优化。 此外就是: 基本的数据与索引区隔无法满足 Big Data 所需的数据分片 高达数百 TB 的数据表毫无疑问将导致 DML 语法执行缺乏效率 海量型数据建议采用 分区(Partition) 机制分离储存数据 数据分区存放可以有效提升数据查询与异动操作 对于 MyISAM 引擎,可以设计分离数据文件和索引文件来加快数据存取。例如: CREATE TABLE X ( … ) ENGINE=MyISAM, DATA DIRECTORY = '/var/p1', INDEX DIRECTORY = '/var/p2'; 当然,InnoDB 采用 System TableSpace 集中存放,无法支持此种方式,需要通过设定去转换成一个 table 一个 file。 MariaDB 表分区(Table Partition)MariaDB 10 提供 Table 分区储存功能,大量数据切割成不同储存区域(Partition),Partiton 底层的档案可再切成多档方式储存(Sub-Partition)。 使用 Plugin 方式扩充 由 Storage Engine 自行实作,MariaDB 已支持的包括 InnoDB, TokuDB , Memory, Aria, Spider、MyISAM, Archive, BLACKHOLE MySQL 仅支援 InnoDB MariaDB 透过内建的 Partiton Storage Engine 提供此项分区服务。 查看是否有安装此引擎: SELECT * FROM information_schema.PLUGINS WHERE PLUGIN_NAME like "%part%"; 系统管理方式与一般 tables 相同,System Partition 提供元数据(Metadata): Informaton_Schema.PARTITIONS。 分区作业时,有些东西还需要特别考虑,例如分区类型、分区计算、目标表的形态、分区前后的应用等等。 MariaDB 的分区类型使用示例MariaDB 的分区类型主要有: RANGE ( 单一字段) LIST ( 单一字段) RANGE COLUMNS and LIST COLUMNS, HASH COLUMNS(多栏) HASH ( 单一字段) KEY ( 单一字段 ) LINEAR HASH, LINEAR KEY SYSTEM_TIME RANGE 分区类型 RANGE 分区类型用于为每个分区分配由分区表达式生成的值的范围。范围必须是有序的,连续的且不重叠的。最小值始终包含在第一个范围内。最高值可以包含在最后一个范围内,也可以不包含在最后一个范围内。 这种分区方法的一种变体 RANGE COLUMNS 允许我们使用多列和更多数据类型。 语法: CREATE TABLE 语句的最后一部分可以是新表分区的定义。对于 RANGE 分区 PARTITION BY RANGE (partitioning_expression) ( PARTITION partition_name VALUES LESS THAN (value), [ PARTITION partition_name VALUES LESS THAN (value), ... ] ) 说明: partitioning_expression 是一个 SQL 表达式,从每行返回一个值,最简单的就是一个字段名(column name),用于确定哪个分区需要包含一行数据( which partition should contain a row)。 partition_name 是分区的名称。 value 指示该分区的上限,表达式必须回传 deterministic / nonconstant 值(Integer 或 NULL) 不支持 stored functions 以及 user-defined functions 不支持 / 除法运算子( DIV , MOD 可 ) ( / 回传的是 Float 10/3 = 3.3 ) 表达式不可消耗过多资源/时间 使用针对 Partition 最佳化过的分区函数(partitioning function),例如 YEAR() , TO_DAYS(), TO_SECONDS() 。 如果存在问题,可以将 MAXVALUE 指定为最后一个分区的值。但是请注意,不能拆分现有 RANGE 分区表的分区。可以附加新的分区,但是如果最后一个分区的上限为 MAXVALUE,则将无法添加新分区。 示例: 通过年份对日志表进行分区 CREATE TABLE test200221.log ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, timestamp DATETIME NOT NULL, user INT UNSIGNED, ip BINARY(16), action VARCHAR(20), PRIMARY KEY (id,timestamp) ) ENGINE = InnoDB PARTITION BY RANGE (YEAR(timestamp)) ( PARTITION p0 VALUES LESS THAN (2013), PARTITION p1 VALUES LESS THAN (2014), PARTITION p2 VALUES LESS THAN (2015), PARTITION p3 VALUES LESS THAN (2016) ); 注意: 1、partitioning_expression 表达式中所引用的字段必须是 Primary key 中的成员 2、Primary key 包含了表达式中所有字段的组合,限缩 Unique keys 的使用 如果 partitioning_expression 表达式引用的字段不是,就会出现以下错误: SQL 错误 [1503] [HY000]: (conn=51) A PRIMARY KEY must include all columns in the table's partitioning function 3、一般情况下,不支持的分区字段类型有 TEXT,LongText,BLOB,CLOB …(前面有提到,返回值需要是 Integer 或 NULL) 4、此外,在使用分区函数(partitioning function)时,要注意函数和字段的正确性,否则可能会出现类似的问题: SQL 错误 [1486] [HY000]: (conn=51) Constant, random or timezone-dependent expressions in (sub)partitioning function are not allowed 5、注意插入的数据是否有对应的分区。例如示例中分区有 4 个,对应 2013~2016 年的日志,如果此时插入的有是 2020 年的日志,没有该分区,可能出现类似的错误: 但可以添加 IGNORE 关键词去去除不符合的值。上述就会插入 2015 年的那条: 6、部分 Partition 语法 需要对 Partition 进行命名,MariaDB 可自行命名( pN , N(0~N ) ),人工编码最长长度 61 字符,推荐有意义的命名。 LIST 分区类型 LIST 分区在概念上类似于 RANGE 分区。在这两种情况下,都需要确定一个分区表达式(一个字段(column),或者稍微复杂一些的计算),然后使用它来确定哪些分区将包含每一行。但是,对于 RANGE 类型,分区是通过为每个分区分配一个值范围来完成的。对于 LIST 类型,将会为每个分区分配一组值。如果分区表达式可以返回一组有限的值(a limited set of values),则通常是首选方法。 这种分区方法的一种变体 LIST COLUMNS 允许使用多列和更多数据类型。 语法: PARTITION BY LIST (partitioning_expression) ( PARTITION partition_name VALUES IN (value_list), [ PARTITION partition_name VALUES IN (value_list), ... ] [ PARTITION partition_name DEFAULT ] ) 说明: 依据数据类型,符合指定项目则存入指定 Partition partitioning_expression 是一个 SQL 表达式,从每行返回一个值,最简单的就是一个字段名(column name),用于确定哪个分区需要包含一行数据( which partition should contain a row)。 partition_name 是分区的名称。 value 是一系列值(a list of values),表达式返回这些值,就能存入该分区 EXPRESSION 表达式 必须是 Integer 回传值 搭配 DEFAULT 收纳所有不符合其它分区条件的数据( 10.2 之后 加入),但也只能有一个 DEFUILT 分区。可设定超出范围的处理: NULL 示例: 依据文章语言分类,所有可能的筛选值必须判断,Language 事实上是 Foreign key 对应到 Language Table(所以用 id 表示),Partition table 不支持 Fkey,Language 必须是 正整数或 null。 DROP table if exists test200221.article; CREATE TABLE test200221.article( id integer unsigned not null auto_increment, date date not null, author varchar(100), language tinyint unsigned, text text, primary key(id,language) )ENGINE=InnoDB partition by LIST (language)( PARTITION p0 VALUES IN (1), PARTITION p1 VALUES IN (2,3), PARTITION p2 VALUES IN (4,5,6,7,8,9), PARTITION px VALUES IN (NULL ) -- 或 PARTITION px DEFAULT ) RANGE COLUMNS 和 LIST COLUMNS 分区类型 RANGE COLUMNS 和 LIST COLUMNS 分别是 RANGE 和 LIST 的变体。对于这些分区类型,不是一个分区表达式(partitioning _expression)。而是接受一个或多个字段(columns)。适用以下规则: 该列表可以包含一个或多个字段(columns)。 字段(columns)可以是任何 integer,string,DATE 和 DATETIME 类型。 仅允许使用纯字段(bare columns),不能加表达式。将所有指定的列与指定的值进行比较,以确定哪个分区应包含特定的行。 字段型别:Integer ( 不可产生负数 )、Date, DateTime、CHAR, VARCHAR, BINARY , VARBINARY、不可使用任何 functions, 运算子符号 (只能单纯使用字段…) 语法: RANGE COLUMNS 分区类型: PARTITION BY RANGE COLUMNS (col1, col2, ...) ( PARTITION partition_name VALUES LESS THAN (value1, value2, ...), [ PARTITION partition_name VALUES LESS THAN (value1, value2, ...), ... ] ) LIST COLUMNS 分区类型: PARTITION BY LIST COLUMNS (partitioning_expression) ( PARTITION partition_name VALUES IN (value1, value2, ...), [ PARTITION partition_name VALUES IN (value1, value2, ...), ... ] [ PARTITION partititon_name DEFAULT ] ) 两者的区别: RANGE COLUMNS 是返回的值小于指定的值,第一个匹配条件的分区将包含该值;LIST COLUMNS 返回的值包含在给定的值里面,同样允许且仅运行一个 DEFAULT 分区。 示例,修改上述 article 表,新加 year 字段: DROP table if exists test200221.article2; CREATE TABLE test200221.article2 ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, date DATE NOT NULL, year CHAR(4) NOT NULL, author VARCHAR(100), language TINYINT UNSIGNED, text TEXT, PRIMARY KEY (id, language, year) ) ENGINE = InnoDB PARTITION BY RANGE COLUMNS (language, year) ( PARTITION p0 VALUES LESS THAN (1, '2010'), PARTITION p1 VALUES LESS THAN (1, '2020'), PARTITION p2 VALUES LESS THAN (100, '2010'), PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE) ); MariaDB 的分区的限制(Partitioning Limitations) 每个表最多可包含 8192 个分区(自 MariaDB 10.0.4)。在 MariaDB 5.5 和 10.0.3 中,限制是 1024。 目前,查询永远不会并行化,即使它们涉及多个分区。 只有当存储引擎支持分区时,才能对表进行分区。 所有分区必须使用相同的存储引擎。 分区表不能包含外键,也不能被外键引用。 查询缓存(query cache)不知道分区和分区修剪(partitioning and partition pruning)。修改分区将使与整个表相关的条目失效。 当 binlog_format=ROW 且分区表被更新(update)时,更新的速度可能比等效的非分区表更慢。 分区表的分区表达式中使用的所有字段(column)必须是必须是 unique 结果( PRIMARY, UNIQUE_KEY 可支持)。 分区文件分割后的 Table 将产生多个个别档案 文件名编码: table_name#P#partition_name.ext 在 InnoDB 下,会有以下 3 类: table_name.frm table_name.par table_name#P#partition_name.ibd 分区修剪(Partition Pruning)和分区选择(partition selection) 当 WHERE 子句与分区表达式有关联时,优化器知道哪些分区与查询相关。其它分区将不会被读取。这种优化称为分区修剪。 可以使用 EXPLAIN 分区来了解将为给定的查询读取哪些分区。名为 partitions 的列将包含以逗号分隔的被访问分区列表。 以之前的 article 表为例,回顾一下创建语言: CREATE TABLE test200221.article( id integer unsigned not null auto_increment, publish_date date not null, author varchar(100), language tinyint unsigned, text text, primary key(id,language) )ENGINE=InnoDB partition by LIST (language)( PARTITION p0 VALUES IN (1), PARTITION p1 VALUES IN (2,3), PARTITION p2 VALUES IN (4,5,6,7,8,9), PARTITION px VALUES IN (NULL ) -- PARTITION px DEFAULT ) 使用EXPLAIN PARTITIONS来查看哪些分区会被使用到: EXPLAIN PARTITIONS SELECT * FROM article WHERE language < 4; 从结果来看,的确在WHERE language < 4;的条件下,只有 p0 和 p1 分区会有访问到。 如果优化器不知道或无法推断出哪些分区会被使用到,可以通过 PARTITION 子句强制 MariaDB 仅访问给定分区(MariaDB 10.0 开始),这也被称为分区选择。 例如: SELECT * FROM article PARTITION (p2) WHERE language = 5; 所有 DML 语句均支持 PARTITION 子句:SELECT、INSERT、UPDATE、DELETE、REPLACE、LOAD DATA 等。 通常情况下,分区修剪会用在触发器(triggers)语句中。 但是如果在表上定义了 BEFORE INSERT 或者 BEFORE UPDATE 的触发器,则 MariaDB 不会预先知道分区表达式中使用的字段(column)是否会更改。因此,被迫锁定所有分区。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十一)数据分片(Sharding)和数据分区(PARTITIONing)简述","slug":"TechnicalEssays/MariaDBSeries/11data-sharing-and-partitioning","date":"2020-06-18T13:04:39.000Z","updated":"2023-05-16T13:54:36.572Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/11data-sharing-and-partitioning/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/11data-sharing-and-partitioning/","excerpt":"","text":"即便是 MariaDB,也有一个想要处理大数据的心。虽然可能跟其它的例如 HBase、Hive 之类的比有些差异和不足,但并不影响壮志。 简单列举两个要处理大量数据的例子: 1、IoT Sensor Networks 存取特性: 很少大量写入,但多大量读取 事务需求: 少 资料量: 累积数量庞大 2、AI Machine Learning 领域 搜集大量数据进行分析 使用 MariaDB 处理大量数据,先来了解一下这两点。 DATA Sharding (数据分片)和 Data Partition(数据分区)数据分片简述 单一数据库系统已无法处理 Big Data 服务需求 大部分数据库瓶颈皆处于 I/O 效能问题 提供数据分片技术将数据分散储存于多个数据库实例中 提供水平式,垂直档案分布于不同的 I/O 系统,加速数据存取 可横跨多种不同功能数据库系统 相关技术或引擎:FEDERATEDX , CONNECT , SPIDER , MAXSCALE 关于数据分片,这里有一篇 2019 年 2 月发布的文章,到今天(2020/06/18)一年多,有 169.9k 的浏览量,讲解说明还不错,推荐阅读: Understanding Database Sharding 个人理解来说: 分片(Sharding) 是一种与水平切分(horizontal partitioning)相关的数据库架构模式,用于在特定的 SQL 操作中减少数据读写的总量以缩减响应时间。——例如将一个表里面的行(row),分成多个不同的表的。 分区(PARTITIONing) 是分片的具体做法实现,例如水平分区、垂直分区。 分片(Sharding)将一个数据分成两个或多个较小的块,称为逻辑分片(logical shards)。然后,逻辑分片(logical shards)分布在单独的数据库节点上,称为物理分片(physical shards)。物理分片(physical shards)可以容纳多个逻辑分片(logical shards)。尽管如此,所有分片中保存的数据,共同代表整个逻辑数据集。 数据库分片(Database shards)是无共享架构( shared-nothing architecture)的一个例子。这意味着分片是自治的:分片间不共享任何相同的数据或服务器资源。但是在某些情况下,将某些表复制到每个分片中作为参考表是有意义的。例如,假设某个应用程序的数据库依赖于重量测量的固定转换率。通过将包含必要转换率数据的表复制到每个分片中,有助于确保查询所需的所有数据都保存在每个分片中。 通常,分片(Sharding)在应用程序级别进行实现。这意味着应用程序包含“要向哪个分片发送读和写”的代码。但是,某些数据库管理系统内置了分片功能,允许您直接在数据库级别实现分片。 分片的优点: 高可用性(High Availability):对于分片数据库,如果一个数据库分片出现故障,则仅会使部分用户无法使用应用程序或网站的一部分,而其它分片可以继续运行而不会出现任何问题。如果数据库未分片,则中断可能会导致整个应用程序不可用。 更快的查询响应(Faster queries response):对尚未分片的数据库提交查询时,它可能必须搜索查询表中的每一行,才能找到您要查找的结果集。对于具有大型整体数据库的应用程序,查询会变得异常缓慢。但是,通过将一个表拆分为多个表,查询必须遍历更少的行,并且其结果集可以更快地返回。 更多的写带宽(More write bandwidth):无需主数据库序列化写操作,就可以并行写操作,从而提高了写吞吐量。写作是许多网站的主要瓶颈。 向外扩展(Scaling out):对数据库进行分片可以帮助促进水平扩展(称为向外扩展)。 分片的缺点: 增加系统的复杂性(Adds complexity in the system):恰当地实现分片数据库体系结构是一项复杂的任务。如果处理不正确,则存在很大的风险,即分片过程可能导致数据丢失或表损坏。分片对团队的工作流程也有重大影响。用户必须从多个入口位置管理数据,而不是从单个入口点管理和访问数据,这可能对某些团队具有潜在地破坏性。 重新平衡数据(Rebalancing data):在分片数据库体系结构中,有时分片会超出其它分片而变得不平衡,这也称为数据库热点(database hotspot)。在这种情况下,分片数据库的任何好处都会被抵消。数据库可能需要重新分片以允许更均匀的数据分发。必须从一开始就建立重新平衡,否则在重新分片时,将数据从一个分片移动到另一个分片需要大量的停机时间。 从多个分片连接数据(Joining data from multiple shards):要实现一些复杂的功能,我们可能需要从分布在多个分片中的不同来源提取大量数据。我们无法发出查询并从多个分片获取数据。我们需要对不同的分片发出多个查询,获取所有响应并将其合并。 没有原生支持(No Native Support):并非每个数据库引擎都原生支持分片。因此,分片通常需要自己实现。这意味着通常很难找到有关分片的文档或解决问题的技巧。 ref: https://medium.com/system-design-blog/database-sharding-69f3f4bd96db","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十)MariaDB存储引擎CONNECT使用介绍","slug":"TechnicalEssays/MariaDBSeries/10mariadb-connect-storage-engine","date":"2020-06-18T13:04:01.000Z","updated":"2023-05-16T13:54:36.577Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/10mariadb-connect-storage-engine/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/10mariadb-connect-storage-engine/","excerpt":"","text":"事实上,采用 MariaDB 作为数据库,一般是两种情况:1 是觉得 MySQL 被 Oracle 收购之后,继续使用 MySQL 心里不踏实(license 和发展前景等)。2 是想另外找一个生命力比较旺盛、小区比较丰富的关系型数据库。 所以一般默认的 InnoDB 和 Aria 就足够满足大部分的需求了。 不过连接处理到其它数据库,或者加载到 MariaDB 中的需求还是比较多的。例如将一份 json 文件 存在大量需要加载到 MariaDB 数据库中的数据时,这个时候就比较需要使用 CONNECT 引擎了。 注意:CONNECT 是连接到远程数据,并没有转存到 MariaDB 中。所以远程数据源的异动,MariaDB 处理查德德询得到的也是异动后的数据。 在官网(https://mariadb.com/kb/en/introduction-to-the-connect-engine/)可以看到 CONNECT 更多特性。 CONNECT 的安装与卸载Connect Storage Engine 并未封装于 MariaDB Package 内,需要透过 Repository 安装: sudo apt install mariadb-plugin-connect会同时安装依赖库 libodbc1 然后再安装插件(我测试时已有默认安装上并启用,如果没有默热安装启用,执行此句) install soname 'ha_connect'此次,查看引擎可以看到新装的 CONNECT: 卸载,执行UNINSTALL SONAME 'ha_connect';即可。 使用 CONNECT 连接处理 json 文件有两点要做:1 是指定表的类型,2 是指定要连接的文件(远程表)的类型和文件名。 前者是创建表时要指定engine=CONNECT,后者是要针对需要访问的不同类型的文件,指定 table_type 参数为指定类型。 目前,table_type 的类型有很多种,除了常见的 json、csv、xml,还有例如BIN, DBF, DIR, DOS, FIX, ZIP, JDBC, ODBC, MONGO, MYSQL, WMI, MAC 等。 以 json 为例 1、准备一份JsonDemo.json文件如下(官网示例)[ { "ISBN": "9782212090819", "LANG": "fr", "SUBJECT": "applications", "AUTHOR": [ { "FIRSTNAME": "Jean-Christophe", "LASTNAME": "Bernadac" }, { "FIRSTNAME": "François", "LASTNAME": "Knab" } ], "TITLE": "Construire une application XML", "PUBLISHER": { "NAME": "Eyrolles", "PLACE": "Paris" }, "DATEPUB": 1999 }, { "ISBN": "9782840825685", "LANG": "fr", "SUBJECT": "applications", "AUTHOR": [ { "FIRSTNAME": "William J.", "LASTNAME": "Pardi" } ], "TITLE": "XML en Action", "TRANSLATED": { "PREFIX": "adapté de l'anglais par", "TRANSLATOR": { "FIRSTNAME": "James", "LASTNAME": "Guerin" } }, "PUBLISHER": { "NAME": "Microsoft Press", "PLACE": "Paris" }, "DATEPUB": 1999 } ] 2、放到一个 MariaDB 可以访问的路径这一点很重要,否则可能报错: SQL 错误 [1296] [HY000]: (conn=65) Got error 174 'Open(map) error 13 on /<path>/JsonDemo.json' from CONNECT例如本例放到了/tmp 活页夹下 3、MariaDB 命令窗口执行测试语句及说明如下: -- 创建一个数据库 CREATE DATABASE test200222; -- 在数据库中新建一张表,并制定engine为CONNECT, -- table_type为JSON,File_name为json文件存放位置 DROP table if exists test200222.jsample; create table test200222.jsample ( ISBN char(15), LANG char(2), SUBJECT char(32), AUTHOR char(128), TITLE char(32), TRANSLATED char(80), PUBLISHER char(20), DATEPUB int(4) ) engine=CONNECT table_type=JSON File_name='/tmp/JsonDemo.json'; -- 条件查询,查看是否有数据 select isbn, author, title, publisher from test200222.jsample; 查询结果应当如下 问题说明:从查询的结果可以看出,在 JSON 中,isbn 为 9782212*的书的作者,是两个人,读入的存储结果只有一个人。这是因为,默认情况下,遇到数组时,它只会读取数组的第一个值。事实上,json 文件大部分情况下,都不可能只有一层,值是数据应该常见。 因此 CONNECT 启用一个特殊的字段field_format选项 Jpath,用来描述如何显示和处理数组。 新建 jsample2 表,指定 field_format 字段: DROP table if exists test200222.jsample2; create table test200222.jsample2 ( ISBN char(15), Language char(2) field_format='LANG', Subject char(32) field_format='SUBJECT', Author char(128) field_format='AUTHOR.[" and "]', Title char(32) field_format='TITLE', Translation char(32) field_format='TRANSLATOR.PREFIX', Translator char(80) field_format='TRANSLATOR', Publisher char(20) field_format='PUBLISHER.NAME', Location char(16) field_format='PUBLISHER.PLACE', Year int(4) field_format='DATEPUB' ) engine=CONNECT table_type=JSON File_name='/tmp/JsonDemo.json'; 注意,在 Connect 1.5,json 对象取值用的是”:”,Connect 1.6 使用”.”。 同样查询一次: select isbn, author, title, publisher from jsample2; 当然,除了把数组的值合并到一起,还可以根据数组的值将数据拆成 2 条。 新建 jsample3 表如下: DROP table if exists test200222.jsample3; create table test200222.jsample3 ( ISBN char(15), Title char(32) field_format='TITLE', AuthorFN char(128) field_format='AUTHOR.[*].FIRSTNAME', AuthorLN char(128) field_format='AUTHOR.[*].LASTNAME', Year int(4) field_format='DATEPUB' ) engine=CONNECT table_type=JSON File_name='/tmp/JsonDemo.json'; 注意,在 Connect 1.5,1.6,json 对象拓展符用的是”X”,Connect 1.06.006:使用”*“。 查看结果: SELECT * FROM test200222.jsample3; 从上可以简单窥见,’:’已被’.’取代,’[*]’已用来表示扩展,而’[X]’表示乘法。 更多 Jpath 数组规范可见下表:Jpath 规范 更多 CONNECT 对 json 文件的处理,可参看官网:https://mariadb.com/kb/en/connect-json-table-type/ 此外,除了对 JSON 文件,还有对其它常见文件的处理例如 csv、xml,都可以到官网https://mariadb.com/kb/en/connect-table-types/查看对应的table_type了解实践。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(九)MariaDB存储引擎简介","slug":"TechnicalEssays/MariaDBSeries/09mariadb-storage-engine","date":"2020-06-18T13:03:25.000Z","updated":"2023-05-16T13:54:36.578Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/09mariadb-storage-engine/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/09mariadb-storage-engine/","excerpt":"","text":"存储引擎简述 简单说来,存储引擎是数据库管理系统用来从数据库创建、读取、更新数据的软件模块。 负责提供数据实体储存的算法 提供数据文件与索引档案的管理 MariaDB 采用 Plugin 方式动态加载/卸载 引擎模块 可透过外部安装的方式添加新的 Storage Engine 查询指令: 查看所有的已启用的存储引擎:show engines; 查询预设引擎:select @@global.storage_engine; 简单介绍几个 MariaDB 的存储引擎1、InnoDB/XtraDB XtraDB 属于 InnoDB 分支( Percona 负责维护),针对“效能与监控”进行强化,兼容 InnoDB 引擎。MariaDB 10.1 采用 (MariaDB 10.1),但在 MariaDB 10.2 回归 MySQL InnoDB 。 支持 Trasaction/Savepoints 以及 XA Transaction。 现代 IoT/BigData: 大量数据与快速写入上出现瓶颈。 2、MyISAM MySQL/MariaDB 最早的预设引擎 轻量化设计不支持交易(Trasaction)处理 适合 read-heavy workload 无事务无日志,因此档案容易因其它因素而损毁 过渡时期的 Big Data 处理方式 3、Aria 原名 Maria,MariaDB 5.1 导入 MariaDB 10.4 后 System Tables 全面改用 Aria Crash Safe ,采用 log 进行 数据还原(data recovery) 采用 page 提供更快速 不易产生 Fragment 的储存算法 建议改用 Aria 取代 MyISAM 4、TokuDB 由 Tokutek 负责开发,MariaDB 5.5 纳入此引擎模块 支持数据压缩(data compression) 支持大型数据处理,速度快于 InnoDB 适合高效能与写密集型(write-intensive) 需求的应用环境 5、MyRocks Facebook 所发展的数据储存技术 MyRocks 是将 RocksDB 数据库添加到 MariaDB 的存储引擎。RocksDB 是一个 LSM 数据库,具有很大的压缩率,已针对闪存进行了优化 提供高效能的压缩与 I/O 效能 降低数据空间需求 6、Connect MariaDB 10.0 导入,透过 Connect Plugin 让 MariaDB 连接不同的数据来源, 提供外部数据(MED: Management External Data)给 MariaDB Client 标准规范: SQL/MED 提供多种类型的数据连接服务 定义 Wrapper Table 提供 Client 存取 按用途选择存储引擎MariaDB 有几十种存储引擎,但并不一定都是最佳。官网有简单针对各种用于,建议使用不同的引擎。大概如下: 一般用途: 在 MariaDB 10.1 之前,XtraDB 是大多数情况下的最佳选择。它是 InnoDB 增强性能的分支,并且是 MariaDB 10.1 之前的默认引擎。 InnoDB 是一个很好的常规事务存储引擎。它是 MariaDB 10.2(以及 MySQL)的默认存储引擎。对于早期版本,XtraDB 是 InnoDB 的性能增强分支,通常是首选。 Aria 是 MariaDB 基于 MyISAM 上的更加现代改进,占用空间小,并且让系统之间相互复制很简单。 MyISAM 占用空间小,也可轻松在系统之间进行复制。MyISAM 是 MySQL 最古老的存储引擎。但是除了解决遗留问题用途,通常没有其它理由使用它。Aria 是 MariaDB 的更现代改进。 缩放,分区(Scaling, Partitioning):如果想要拆分数据库并加载在几个服务器上,或者优化缩放,建议使用 Galera(一个同步多主集群)。 TokuDB 是一个事务性存储引擎,它针对不适合内存的工作负载进行了优化,并提供了良好的压缩比。 Spider 使用分区(partitioning)通过多个服务器提供数据分片(data sharding)。 ColumnStore 采用大规模并行分布式数据体系结构,专为大数据扩展而设计,可处理 PB 级别的数据。 MERGE 存储引擎是一个相同 MyISAM 表的集合,所有表具有相同的列和索引信息。 压缩/归档(Compression / Archive) MyRocks 相比与 InnoDB,可以实现更大的压缩,更小的写入放大率(write amplification),从而可以更好地承受闪存存储并提高整体吞吐量。 TokuDB 是一个事务性存储引擎,它针对不适合内存的工作负载进行了优化,并提供了良好的压缩比。 Archive 存储引擎,勿庸置疑,最适合用于归档。 连接到其它数据源如果要使用的数据没有存放到 MariaDB 数据库,但可以通过以下的数据引擎去连接访问。 CONNECT 允许访问不同类型的文本文件和远程资源,就像它们是常规的 MariaDB 表一样。 CSV 存储引擎可以读取并附加到以 CSV(逗号分隔值)格式存储的文件。然而,自从 MariaDB 10.0 以来,CONNECT 是一个更好的选择,并且能够更灵活地读写这样的文件。 FederatedX 使用 libmysql 与远程 RDBMS 数据源沟通。目前,由于 FederatedX 只使用 libmysql,它只能与另一个 MySQL RDBMS 通信。 CassandraSE 是一个允许访问旧版本的 Apache Cassandra NoSQL DBMS 的存储引擎。不过它是相对实验性的,并且不再被积极开发。 搜索优化 SphinxSE 用作在远程 Sphinx 数据库服务器上运行语句的代理(主要用于高级全文搜索)。 Mroonga 使用列存储提供快速的 CJK 就绪全文搜索。 缓存,只读 MEMORY 不会在磁盘上写数据(崩溃时所有行都会丢失),并且最适合用于其它表中数据的只读缓存或临时工作区。借助默认的 XtraDB 和其它具有良好缓存的存储引擎,与过去相比,对该引擎的需求减少了。 其它专用引擎 S3 存储引擎是一个只读存储引擎,它将数据存储在 amazons3 中。 Sequence 允许使用给定的起始值、结束值和增量创建数字(正整数)的升序或降序序列,并在需要时自动创建虚拟的临时表。 BLACKHOLE 存储引擎接受数据,但不存储数据,并始终返回空结果。这在复制环境中非常有用,例如,如果您希望在从机上运行复杂的筛选规则,而不会在主机上产生任何开销。 OQGRAPH 允许处理层次结构(树结构)和复杂图(在多个方向上有多个连接的节点)。 总结:关于 MariaDB 存储引擎的一般性常规选择(先不考虑拓展和集群),其实大体看来只有以下几个 一般使用:InnoDB 快速存取,不使用事务:Aria 高压缩和吞吐,需要降低数据空间占比:MyRocks 或 TokuDB 归档专用:Archive 连接到其它文本或远程数据源:CONNECT 更多 MariaDB 的存储引擎详细,可参看官网https://mariadb.com/kb/en/storage-engines/","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(八)MariaDB的备份还原(mysqldump及mariabackup)","slug":"TechnicalEssays/MariaDBSeries/08mariadb-dump-backup","date":"2020-06-18T13:02:52.000Z","updated":"2023-05-16T13:54:36.578Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/08mariadb-dump-backup/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/08mariadb-dump-backup/","excerpt":"","text":"备份简介备份一般分为逻辑备份 Logical Backup ( Hot Backup )和物理备份 Physical Backup ( Cold Backup )。 逻辑备份 由恢复数据所需的 SQL 语句组成,例如 CREATE DATABASE,CREATE TABLE 和 INSERT。 逻辑备份的特点: 无需停机作业 还原弹性较佳 硬件无关,可任意还原到指定的数据库 档案较大, 备份与还原时间较长 无法备份 log 与配置文件 无事务的表(Non-transaction table) 必须被锁定 可能影响联机操作处理效能 物理备份 是通过复制单个数据文件或目录来执行的。 物理备份的特点: 需停机作业( MariaDB 10 可执行 Hot Physical Backup ) 目录/文件(Directories/Files) 操作 执行速度较快 档案 size 较小 可备份日志和配置文件 因此,逻辑备份和物理备份主要区别如下: 逻辑备份更加灵活,因为可以在其它不同的硬件配置、MariaDB 版本甚至其它 DBMS 上恢复数据,而物理备份不能在明显不同的硬件、不同的 DBMS 或可能甚至不同的 MariaDB 版本上导入。 逻辑备份可以在数据库和表级别执行,而物理数据库是目录和文件级别。在 MyISAM 和 InnoDB 存储引擎中,每个表都有一组等效的文件。(在 MariaDB 5.5 之前的版本中,默认情况下,多个 InnoDB 表存储在同一文件中,在这种情况下,无法按表进行备份。) 逻辑备份的大小 大于 等效物理备份的大小。 与等效的物理备份相比,逻辑备份花费更多的时间进行备份和还原。 日志文件和配置文件不是逻辑备份的一部分 备份工具:mysqldump 和 Mariabackup mysqldumpmysqldump 简述 mysqldump 执行逻辑备份。这是执行备份和还原的最灵活的方法,并且是当数据量较小时的理想选择。 对于大型数据集,备份文件可能很大,并且恢复时间很长。 mysqldump 将数据转储为 SQL 格式(它也可以转储为其它格式,例如 CSV 或 XML),然后可以轻松地将其导入另一个数据库。假设转储中没有版本或特定于 DBMS 的语句,则可以将数据导入到其它版本的 MariaDB,MySQL 甚至是其它 DBMS 中。 mysqldump 将触发器(triggers)与表一起转储,因为它们是表定义的一部分。但是,存储过程,视图和事件(stored procedures, views, and events)不是,并且需要额外的参数来显式地重新创建(例如–routines 和–events)。 但是,过程和函数也是系统表的一部分。 使用语法备份语法 mysqldump db_name > backup-file.sql具体例如: shell> mysqldump [options] db_name [tbl_name ...] > backup-file.sql # 指定数据库的某些表 shell> mysqldump [options] --databases db_name ... > backup-file.sql # 指定某几个数据库 shell> mysqldump [options] --all-databases > backup-file.sql # 备份所有数据库 还原语法 mysql db_name < backup-file.sqlmysqldump 使用实例(实际 demo 使用说明,后续一些指令说明可能会接续使用此演示。若不感兴趣可略过) 在 MariaDB 的命令窗口执行以下语句(创建示例表及其数据): CREATE DATABASE testbak; CREATE TABLE testbak.tablebak ( `create_time` char(19) NOT NULL, `create_user` varchar(20) NOT NULL, `update_time` char(19) DEFAULT NULL, `update_user` varchar(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; INSERT INTO testbak.tablebak (`create_time`,`create_user`,`update_time`,`update_user`) VALUES ('2020/02/21 15:41:20','david','2020/02/21 15:43:20','david'), ('2020/02/21 15:41:20','david','2020/02/21 15:43:20','david'), ('2020/02/21 15:41:20','david','2020/02/21 15:43:20','david'), ('2020/02/21 15:41:20','david','2020/02/21 15:43:20','david'), ('2020/02/21 15:41:20','david','2020/02/21 15:43:20','david'); CREATE DATABASE testbres; 作用时创建一个名为 testbak 的数据库,里面一个名为 tablebak 的表,添加了 5 条数据。并创建了一个空的数据库 testres,用于测试还原。 完成之后在终端中,使用 mysqldump 备份该数据库。 mysqldump -u test -p testbak > backup-testbak.sql -- >后是备份路径和文件名,此处就在用户名根目录下。 还原的话需要指定还原的数据库名称。否则会报错类似ERROR 1049 (42000): Unknown database 'testres' 还原语句: 在终端中执行 mysql -u test -p testres< backup-testbak.sql 更多 mysqldump 的使用,可访问官网https://mariadb.com/kb/en/mysqldump/了解。 MariabackupMariabackup 简介:Mariabackup 是 MariaDB 提供的开源工具,用于执行 InnoDB,Aria 和 MyISAM 表的物理在线备份。对于 InnoDB,可以进行“热在线”备份。 当然,mariabackup 同样适用于 MySQL。 支持的功能 MariaDB 一开始使用 Percona 的 XtraBackup (因为采用 Xtra Storage)衍生自/并取代 Percona XtraBackup 内建于 MariaDB 10.1.23 版之后 使用静态数据加密备份/还原表。 使用 InnoDB 页面压缩备份/恢复表。 支持 Galera Cluster(MariaDB Galera Cluster 是仅在 Linux 上运行的同步多主群集(synchronous multi-master cluster)。) Microsoft Windows 支持。 从 MariaDB 10.2.16 和 MariaDB 10.3.8 开始,使用 MyRocks 存储引擎备份/还原表。 使用前先安装 ubuntu 下,终端执行: sudo apt-get install mariadb-backup其它系统可去官网https://mariadb.com/kb/en/mariabackup-overview/#installing-mariabackup 查看对应下载方式。 语法示例: mariabackup <options>备份的选项 mariabackup --backup --target-dir /path/to/backup \\ --user user_name --password user_passwd使用者信息,可以放到配置文件中 新增参数[mariabackup]并添加使用者和密码: [mariabackup] user=<mariabackup> password=<mypassword>mariabackup 的备份完整备份(full backup):示例:备份现有的 MariaDB: mariabackup --backup \\ --target-dir=/home/sanotsu/mariadb/backup/ \\ --user=test --password=P@ssw0rd;有两点值得提醒一下:1、–target-dir 后面跟的是数据库备份的路径,最好是提前创建(如果用 sudo 执行则不需要);2、可能会出现操作系统无法处理文件/活页夹的问题,可尝试加 sudo,或者把活页夹的所有权从组 root 更改为 mariadb。 完整备份出错: 正常执行: 可以比较备份的活页夹和原始数据库的活页夹: 部分备份(partial backup)上述是备份的全部,如果只想备份某个/某些数据库,需要再指定–databases 参数。如果还需要指定备份某个数据库的某张表,就需要再指定–tables 参数。这两个参数都支持正则表达式,可以更方便筛选。 例如:执行 mariabackup --backup \\ --target-dir=/home/sanotsu/mariadb/partialBackup/ \\ --databases='testbak' \\ --tables='tab_*' \\ --user=test --password=P@ssw0rd; 可以查看备份结果: 类似的部分备份的参数还有: databases-exclude:指定不需要备份的数据库;tables-exclude:指定不需要备份的表。 增量备份(incremental backup):增量备份的基础是完整备份,想要进行增量备份,首先需要进行一次完整备份,再执行增量备份时,就会在原本的完整备份的基础上,备份尚未备份的部分,而不是重新再备份所有,即递增而不是覆盖。 语法也简单,在完整备份下再加一个–incremental-basedir 参数指定基于哪一个备份的递增: mariabackup --backup \\ --target-dir=/home/sanotsu/mariadb/incrementalBackup/ \\ --incremental-basedir=/home/sanotsu/mariadb/backup/ \\ --user=test --password=P@ssw0rd;注意 –incremental-basedir 的值,每一个新的增量备份应该以上一个备份的目标路径为基准。 因为在执行这次增量备份之前,并没有新增或删除过其它数据库/表,但是也可以看出他们的区别。 下图是增量备份(左)和完整备份(右)的活页夹,可以从占用 size 大小,和指定数据库的指定表的相关信息看出,的确不是完全覆盖的备份: mariabackup 还原还原实际上会有两步需要执行,一是准备(prepare),二是还原(restore)。 准备作业:准备的作用是检查用于还原的备份的数据文件一致性。如果不一致,InnoDB 会中断避免数据库损坏。 不同的备份准备作业略有些不同 完整备份的准备:语法: mariabackup --prepare \\ --target-dir=/home/sanotsu/mariadb/backup/如果权限不够,可能无法读取文件,解决方法与备份时说明一致,加 sudo 或者把该文件/夹 root 权限赋予 mariadb 部分备份的准备:部分备份的还原前准备除了检查数据文件一致性外,还依赖 InnoDB 的可传输表空间。为了让 MariaDB 导入此类表空间,InnoDB 会寻找带有.cfg 扩展名的文件。为了让 Mariabackup 创建这些文件,还需要–export 在准备步骤中添加选项。 语法: mariabackup --prepare --export \\ --target-dir=/home/sanotsu/mariadb/backup/注意:在 MariaDB 10.2.8 及之前的版本, Mariabackup 不支持 –export 选项。更多区别可查看https://mariadb.com/kb/en/partial-backup-and-restore-with-mariabackup/#preparing-the-backup 增量备份的准备:在 MariaDB 10.2 及其之后,准备作业是这样: 先准备完整备份 mariabackup --prepare \\ --target-dir=/home/sanotsu/mariadb/backup/再准备增量备份: mariabackup --prepare \\ --target-dir=/home/sanotsu/mariadb/backup/ \\ --incremental-dir=/home/sanotsu/mariadb/incrementalBackup/ \\这里的–incremental-dir 是增量备份时–target-dir 的值,–target-dir 是–incremental-basedir 的值。注意多个层级的增量备份还原时的准备,有层级依赖,不要跨级。即先还原 inc1,再还原 inc2、inc3……即依照增量顺序修改–incremental-dir 的值就好。 还原作业:完整备份和增量备份还原就稍微麻烦一点,不过以上完整备份和增量备份的还原方式都通用。需要依次执行以下步骤: 1、停止(stop) MariaDB 服务进程; 2、确保用于还原的存储数据的目录为空。(mariadb 服务器系统参数 datadir) 3、使用--copy-back或--move-back参数,执行还原操作,指令语法如: mariabackup --copy-back \\ --target-dir=/home/sanotsu/mariadb/backup/–copy-back 选项允许您保留原始备份文件。–move-back 选项实际上将备份文件移到 datadir,因此原始备份文件会丢失。 4、调整数据目录的所有者,以匹配 MariaDB 服务器(通常 mysql 是两者)的用户和组。 例如,要将文件的所有权递归更改给 mysql 用户和组,执行 sudo chown -R mysql:mysql /var/lib/mysql/5、启动 MariaDB 服务进程。 示例如下(部分显示删减): sudo service mariadb stop # 关闭mariadb服务进程 sudo rm -rf /var/lib/mysql/* #清空mariadb datadir目录下数据 mariabackup --copy-back --target-dir=/home/sanotsu/mariadb/backup/ #执行还原 sudo chown -R mysql:mysql /var/lib/mysql/ #给还原后的文件/夹附加mysql(mariadb的实际用户名)用户/组权限 sudo service mariadb start #启动mariadb服务进程 如果一切正常则还原成功。 部分备份的还原注意,部分备份的还原,和完整备份的还原过程完全不同(quite different)。 部分备份不是功能齐全的数据目录。InnoDB 系统表空间中的数据字典仍将包含未包含在备份中的数据库和表的条目。 每个单独的 InnoDB 每表文件空间表空间文件都必须手动导入到目标服务器中。用于导入文件的过程将取决于是否涉及分区。 如果单表没有分区 恢复单个非分区表 通过丢弃表的原始表空间,将表.ibd 和.cfg 文件从备份位置复制到表的相关表空间位置,然后告诉服务器导入表空间,可以导入未分区的表。 1、在备份中找到该表的.ibd 和.cfg 文件; 2、目标服务器上,您需要创建表的副本。使用与 CREATE TABLE 在原始服务器上创建表相同的语句。 3、使用ALTER TABLE <table_name> DISCARD TABLESPACE丢弃新表的表空间。 4、将该表的.ibd 和.cfg 文件从备份位置复制需要还原的 mariadb server 的相关路劲。 5、确定文件放到了正确的位置,使用ALTER TABLE <table_name> IMPORT TABLESPACE语句导入新表的表空间。 如果单表有涉及到分区 恢复单个分区和分区表 创建占位符表,丢弃占位符表的原始表空间,将分区的文件.ibd 和.cfg 文件从备份位置复制到占位符表的相关表空间位置,然后告诉服务器导入表空间来导入分区表。此时,服务器可以将占位符表的表空间与分区的表空间交换。 1、将保存的表空间文件从原始服务器复制到目标服务器; 2、将分区表空间导入到目标服务器上。 2.1、首先,如果它尚不存在,那么我们需要在目标服务器上创建一个与原始服务器上的分区表匹配的分区表: 2.2、然后,使用该表作为模型,我们需要使用不使用分区的相同结构创建该表的占位符。这可以通过以下CREATE TABLE <table_name> AS SELECT 语句完成 3、针对每个分区进行以下步骤 3.1、使用 ALTER TABLE <table_name> DISCARD TABLESPACE 丢弃占位符表的表空间; 3.2、将下一个分区的.ibd 和.cfg 文件复制到占位符表目标 MariaDB 服务器上表的相关目录中 3.3、文件位于目标服务器上的正确目录中后,使用 ALTER TABLE <table_name> IMPORT TABLESPACE 来导入新表的表空间。操作成功,则可以在占位符表中查看到包含源服务器上分区中的数据 3.4、通过 ALTER TABLE EXCHANGE PARTITION 语句将分区从占位符转移到目标表,若成功,目标表将包含源表中的第一个分区。 3.5、对要导入的每个分区重复以上 3.X 过程。对于每个分区,我们需要丢弃占位符表的表空间,然后将分区表的表空间导入到占位符表中,然后在占位符表和目标表的分区之间交换表空间。 3.6、所有分区都完成之后,目标表应该包含所有的对应的数据,则可以从数据库中删除占位符表。 单表(部分)备份,使用 mariabackup 的物理备份还原还是比较麻烦的,使用其它方法可能更简单便捷 更多 mariabackup 的部分备份/还原信息,可见官网 更多 mariabackup 的使用可见官网","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(七)MariaDB设定最佳性能优化简述","slug":"TechnicalEssays/MariaDBSeries/07mariadb-performance-optimization","date":"2020-06-18T13:02:16.000Z","updated":"2023-05-16T13:54:36.579Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/07mariadb-performance-optimization/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/07mariadb-performance-optimization/","excerpt":"","text":"在使用 MariaDB 之后,肯定也会希望它能够按照一定设定、规则等进行合理的性能优化,提高效能等。此篇将从常见的一些 mariadb-server 的设定上,简单介绍调整 MariaDB 期望能以最佳性能进行运行。 主要简介以下几种: 1、启用服务器内置特定最佳化参数设定(optimizer_switch) 2、线程池(thread pool) 3、查询缓存(query cache) 4、MyISAM 键缓存(Key Cache) 5、InnoDB 缓冲池(Buffer Pool) 6 优化表 (OPTIMIZE TABLE) 在此之前,需要记住 mariadb server 常用的查看状态的几个命令:show status;、show variables;、show engines;,当然都可以附加like关键词筛选。 1、启用服务器内置特定最佳化参数设定使用 switch 方式开关 MariaDB 特定的最佳化机制,系统变量:@@optimizer_switch。 这一个服务器变量,可以用来启用/禁用特定的优化。 可以使用SELECT @@optimizer_switch;来查看 mariadb-server 默认支持哪一些参数的优化(为了一眼看到全貌,此处就用 MariaDB 的命令窗口看): 可见,很多 server 的配置的优化,都是有开启的。后续在使用时,可以根据使用到的功能,启用这些设定。 修改方式一如既往: SET [GLOBAL|SESSION] optimizer_switch='cmd[,cmd]...';例如set optimizer_switch="engine_condition_pushdown=on"; 当然,重启就失效了,修改配置文件操作,在配置文件[mysqld]下新增: [mysqld] optimizer_switch="engine_condition_pushdown=on"更多最佳化开关的信息,可以查看官网: https://mariadb.com/kb/en/optimizer-switch/ 了解。 2、线程池(thread pool)简介 MariaDB 5.5 引入 传统 MySQL 采用 One Thread Per Client 设计 改用 dynamic/adaptive Pool 方式提供所有 Clients 依据状况自动 grows/shrink Pool size 依据操作系统自动调整最佳设定 降低内存使用量 , 降低 context switch 造成的问题 注意:建立 Threads 需要时间 !!! Windows 版本默认值: ThreadPool (排队等候 Threads 使用权) Linux 版本预设: One thread Per Client (所有 Threads 都会轮流) 使用时机: 适合 Short-queries , CPU-Bound 类型的应用 例如 Web Site 或是 OLTP 类型的应用 IoT 类型应用: 短查询, 短 I/O ,密集 CPU 使用 不适合时机: 高负载/瞬间爆量/不容许延迟 长时间无任务, 瞬间爆量然后又消失一段时间的查询 |— 回收线程, —->新增线程 —-> 回收线程 此类任务,使用 一核心 一线程策略取代 大量 同时发生/长时间执行/长时间占用 的查询应用 使用少量线程负担所有联机 需要 queue—> scheduler 低延迟要求的查询应用 使用配置linux 启用线程池在配置文件的[mariadb]参数下: [mariadb] thread_handling=pool-of-threadswindows 下启用线程池在配置文件的[mariadb]参数下: [mariadb] thread_handling=one-thread-per-connection3、查询缓存(query cache) 查询缓存存储 SELECT 查询的结果,以便将来以后收到相同的查询时,可以快速返回结果。 这在高读、低写环境(例如大多数网站)中非常有用。在多核计算器上具有高吞吐量的环境中,它无法很好地扩展。 可以查看对应的参数设定:show variables like '%query_cache%';和show status like 'Qcache%'; ,并修改为符合自身设备和需求需要的值。 说明:此种类似的系统变量,一般在配置文件的[mysqld]参数下寻找来修改或添加。若不是,查看官网指定参数是否是在其它变量下。 如果某些查询,要求不允许使用查询缓存里面的值,则可以在 select 加入 SQL_NO_CACHE 去说明。同样,指定要从查询缓存中查询,也可以在 select 后指定 SQL_CACHE。 示例: Select SQL_NO_CACHE … from table …或者 Select SQL_CACHE … from table …注意:这些参数的设定还是有一些限制的,例如因为查询缓存大小以 1024 字节做分配,因此应该将 query_cache_size 设置未 1024 的倍数。 例如,设置 query_cache_size 大小为 40000 就会出现警告: 查询结果存储使用的最小块大小是 query_cache_min_res_unit。 更多信息可以访问官网https://mariadb.com/kb/en/query-cache/#limiting-the-size-of-the-query-cache了解。 子查询缓存(SubQuery Cache)MariaDB 独有的快取设计 ,在 MariaDB 5.3.2 预设启用。透过绑定主要查询与子查询结果,避免重复执行子查询。 设定方式: 透过 optimizer_switch 设定 SET optimizer_switch="subquery_cache=on"4、MyISAM 键缓存(Key Cache)在 MariaDB 使用 MyISAM 引擎时,可以设定 key_cache_segments 进行一些优化。 分段密钥缓存(segmented key cache)是常规 MyISAM 密钥缓存(key caches)的结构的集合,称为密钥缓存段(key cache segments)。分段密钥缓存减轻了简单密钥缓存的主要问题之一:密钥缓存锁(mutex)的线程争用。对于常规的键高速缓存,键高速缓存接口功能的每次调用都必须获得此锁。因此,即使线程已获取文件的共享锁并且要从中读取的页面位于键高速缓存缓冲区中,线程也争夺该锁。 使用分段键高速缓存时,仅需要一页的任何键高速缓存接口功能都必须仅为分配该页面的段获取键高速缓存锁。这使线程不必竞争同一密钥缓存锁的机会就更好了。 (更多内容查看官网https://mariadb.com/kb/en/segmented-key-cache/) 在并行(concurrent)不成熟的年代使用 SingleThread Access 特性,造成大量存取产生 Mutex Lock & wait 问题。 将 key cache 切成多段可允许同时多个 MyISAM Threads 同时存取,减少锁和等待。 配置: 设定全局变量 Set Global key_cache_segments=n注意,n 只能时 0~64,大于 64 会被截断成 64 并报警告。 或在配置文件修改,找到[mysqld]参数下添加: [mysqld] key_cache_segments = 645、InnoDB 缓冲池(Buffer Pool) XtraDB / InnoDB 的缓冲池是用于优化 MariaDB 的一个关键组成部分。它存储数据和索引,通常希望它尽可能大,以便将尽可能多的数据和索引保留在内存中,从而减少磁盘 IO 成为主要瓶颈。 一般设定 70% ~ 80% 的主机内存用于存放 XtraDB/InnoDB 的数据与索引,将常用的 data-blocks & index-blocks 暂存在内存中。 缓冲池如何工作 缓冲池尝试将经常使用的块保留在缓冲区中,因此实际上起着两个子列表的作用,一个是最近使用信息的新子列表(New sublist),另一个是旧信息的旧子列表(Old sublist)。默认情况下,列表的 37%保留用于旧子列表。 当访问未出现在列表中的新信息时,它将被放置在旧子列表的顶部,旧子列表中最旧的项目将被删除,其它所有内容都将返回列表中的一个位置。 当访问的信息出现在旧子列表中时,它将被移到新列表的顶部,并且上方的所有内容将移回一个位置。 InnoDB 的 Buffer 管理一般 select 的执行:Select SQL—> InnoDB 引擎–> Cache 寻找 –>无 —>执行查询–>结果–>存入 InnoDB Buffer ( New | Old ? ) –> 存入 old-list buffer 后续的查询: SQL —> InnoDB 引擎 —> cache 中发现(old-list) –> 将该笔快取资料从 old-list 放入 new-list 中的第一顺位 –> 被挤出去的数据 存入 old-list –> Old-list 最后一笔被踢出 buffer (从 cache 中移除) InnoDB 4 个重要的缓冲池服务器系统变量:innodb_buffer_pool_size (innodb 缓存池大小)设定 70~80% Memory Size ( + 10% 的控制暂存区使用 )。过大容易造成 OS Swapping 或是过长的初始化时程。 例如:启动后 8G ram 被吃掉 6.4G 用于快取, 当 server 需要执行其它应用程序时,需要 2G ram , 此时将导致 OS 发生 ram-disk swapping。 在 MariaDB 10.2.2 之后,可以动态调整 InnoDB Buffer size。 调整缓冲池大小的过程由 innodb_buffer_pool_chunk_size 变量的大小确定。 调整大小操作将一直等到所有活动事务和操作完成,并且需要访问缓冲池的新事务和操作必须等到调整大小完成为止(尽管减小大小时,在对页面进行碎片整理和撤回时允许访问)。 如果在缓冲池调整大小开始之后启动嵌套事务,则该事务可能会失败。 新的缓冲池大小必须是 innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances 的倍数。如果尝试设置其它数字,则该值将自动调整为至少为尝试的大小的倍数。请注意,调整 innodb_buffer_pool_chunk_size 设置可能会导致缓冲池大小发生变化。 为了避免性能问题,由 innodb_buffer_pool_size / innodb_buffer_pool_chunk_size 计算的块数不应超过 1000。 innodb_buffer_pool_instances (inondb 缓存池示例数量) innodb_buffer_pool_size 超过 1G 时, 透过设定的 pool instances 将其切割成多个,instances 提供同时 (concurrent) 存取, 增进效能。 MariaDB 10 预设为 8 (请参考 CPU Core 数,勿无谓增加)。 每个 instance 负责管理各自所保存的 buffer 数据。 例如:若 pool size 设定为 4G , instances 设定为 4,则产生四组 1G size 的 instances 保存 buffer 数据。 innodb_old_blocks_pct可以通过更改 innodb_old_blocks_pct 的值来调整为旧子列表保留的默认 37%的值。它可以接受 5%至 95%之间的任何值。 innodb_old_block_time指定之前的块可以从旧子列表被移动到新子列表的延迟。 MariaDB 5.5 时默认值为 0,表示没有延迟,而自 MariaDB 10.0 起已设置为默认值 1000ms。 在更改这两个值(innodb_old_blocks_pct 和 innodb_old_blocks_time)的默认值之前,请确保了解其影响以及系统当前使用缓冲区的方式。它们存在的主要原因是为了减少全表扫描的影响,这种情况通常很少见,但很大,而且以前可以从缓冲区中清除所有内容。在快速连续执行全表扫描的情况下,设置非零延迟可能会有所帮助。 转储和还原缓冲池自 MariaDB 10.0 起,可以转储并还原缓冲池。 服务器启动时,缓冲池为空。在开始访问数据时,缓冲池将慢慢填充。随着将访问更多数据,最常访问的数据将被放入缓冲池,并且旧数据可能会被驱逐。这意味着缓冲池真正有用之前需要一定的时间。该时间段称为预热。 从 MariaDB 10.0 开始,InnoDB 可以在服务器关闭之前转储缓冲池,并在再次启动时将其还原。如果使用此功能,则无需预热。 要分别在关闭时启用缓冲池转储和在启动时启用还原,可以将 innodb_buffer_pool_dump_at_shutdown 和 innodb_buffer_pool_load_at_startup 系统变量设置为 ON。 在服务器运行时,还可以随时转储 InnoDB 缓冲池,并且可以随时恢复上一次缓冲池转储。为此,可以将特殊的 innodb_buffer_pool_dump_now 和 innodb_buffer_pool_load_now 系统变量设置为 ON。选择后,它们的值始终为 OFF。 通过将 innodb_buffer_pool_load_abort 设置为 ON ,可以中止在启动时或在其它任何时间进行的缓冲池还原。 包含缓冲池转储的文件是通过 innodb_buffer_pool_filename 系统变量指定的。 转储和还原缓冲池这一段官网描述得非常清楚明了,我就不再加工说明了。 6 优化表 (OPTIMIZE TABLE)优化表的功能有两个:对表进行碎片化处理(defragment tables),或者更新 InnoDB 全文索引(update the InnoDB fulltext index.) 语法: OPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE tbl_name [, tbl_name] ... [WAIT n | NOWAIT]碎片整理 OPTIMIZE TABLE 适用于 InnoDB(在 MariaDB 10.1.1 之前,仅当设置了 InnoDB_file_per_TABLE server 系统变量)、Aria、MyISAM 和 ARCHIVE 表时,如果删除了表的很大一部分,或者对具有可变长度行(具有 VARCHAR、VARBINARY、BLOB 或 TEXT 列的表)的表进行了许多更改,则应使用 OPTIMIZE TABLE。 已删除的行保留在链接列表中,后续插入操作将重用旧的行位置。 此语句要求表具有选择和插入权限。 默认情况下,OPTIMIZE TABLE 语句将写入二进制日志并进行复制。NO_WRITE_TO_BINLOG 关键字(LOCAL 是别名)将确保语句不会写入二进制日志。 分区表也支持优化表。您可以使用 ALTER TABLE <table_name>优化分区以优化一个或多个分区。 可以使用优化表回收未使用的空间并对数据文件进行碎片整理。对于其它存储引擎,OPTIMIZE TABLE 默认情况下不执行任何操作,并返回以下消息:“表的存储引擎不支持 OPTIMIZE”。但是,如果服务器已使用–skip new 选项启动,那么 OPTIMIZE TABLE 将链接到 ALTER TABLE,并重新创建该表。此操作释放未使用的空间并更新索引统计信息。 自 MariaDB 5.3 以来,Aria 存储引擎支持此语句的进度报告。 如果 MyISAM 表是分段的,则除非在该表上执行优化表语句,否则不会执行并发插入,除非将 concurrent_insert server 系统变量设置为 ALWAYS。 更新 InnoDB 全文索引 当向 InnoDB 全文索引添加或删除行时,不会立即重新组织索引,因为这可能是一个代价高昂的操作。更改统计信息存储在单独的位置。只有在运行优化表语句时,全文索引才会完全重新组织。 默认情况下,优化表将对表进行碎片整理。为了使用它更新全文索引统计信息,innodb_optimize_fulltext_only 系统变量必须设置为 1。这是一个临时设置,应在重新组织全文索引后重置为 0。 由于全文重新组织可能需要很长时间,innodb_ft_num_word_optimize 变量将重新组织限制为多个单词(默认为 2000)。可以运行多个优化语句来完全重新组织索引。 值得一提:碎片化整理和更新全文索引的时机,在大量数据得删除或大量 可变长度行字段异动之后 一个非常好的例子,MariaDB 的数据库文件.ibd 文件,在大量删除之后,体积不会变小,执行OPTIMIZE TABLE <table_name>(MyISAM 优化 table)或ALTER TABLE <table_name> ENGINE='InnoDB'; (‘InnoDB’优化 table)之后,体积就会肉眼可见的变小。 (.ibd 文件默认位置/var/lib/mysql//) 更多性能优化相关知识,可到官网https://mariadb.com/kb/en/optimization-and-tuning/ 学习了解。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(六)实用插件ServerAudit测试使用示例","slug":"TechnicalEssays/MariaDBSeries/06mariadb-server-audit-plugin","date":"2020-06-18T13:01:09.000Z","updated":"2023-05-16T13:54:36.603Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/06mariadb-server-audit-plugin/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/06mariadb-server-audit-plugin/","excerpt":"","text":"server audit 插件测试使用示例简介与安装SERVER_AUDIT 是一个非常不错的插件,在 MariaDB 5.5.34 加入,提供服务器事件(activities) 记录功能。例如联机 client 信息(账号, 主机 ..)、Queries 执行、Tables 信息;或者 Server 变数异动等。是 MariaDB Package 内建的插件。 该 server_audit 插件记录服务器的活动。对于每个客户端会话,它记录谁连接到服务器(即用户名和主机),执行了哪些查询,访问了哪些表以及更改了服务器变量。此信息存储在循环日志文件( rotating log file)中,或者可以发送到本地 syslogd。 因为是内建的,所以安装简单: INSTALL SONAME 'server_audit';安装成功后,可以查看 MariaDB 的全局参数,了解 server_audit 的配置变量信息: show global variables like 'server_audit%'; 几个重要变量的说明:server_audit_events审核日志记录的事件类型。默认为空字符串,表示审计日志记录每一种事件类型。可以设定值,用于记录某些指定的事件,例如连接操作、查询操作、操作涉及到的表格等。 如下: SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';或者配置文件指定[mysqld]添加: [mysqld] server_audit_events=connect,query,tableserver_audit_file_path审核日志文件的名称和路径。默认值为“server_audit.log”,这意味着将在数据库目录中创建此文件。 server_audit_logging启用或禁用日志记录。默认是未启用(OFF)的,要记录,则需启用: SET GLOBAL server_audit_logging=on配置文件修改:找到 my.cnf,在[mysql]参数下配置: [server] server_audit_logging=OFF更多参数含义,访问官网:https://mariadb.com/kb/en/mariadb-audit-plugin-options-and-system-variables/#server_audit_events了解。 测试使用:加入我现在继续修改账号 test2 的密码,执行以下语句: GRANT all ON *.* TO 'test2'@'%' identified by '1234';根据之前的介绍,因为有安装密码校验插件,所以会报错。可以打开审核日志文件 server_audit.log,查看是否有记录这条日志,默认地址在/var/lib/mysql/server_audit.log: 可以看到,在启用 server_audit_logging 之后,就开始记录所有事件信息,修改密码的操作也有记录。 server_audit 在审核分析数据时可能会提供很多的帮助。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(五)非MariaDB内建插件","slug":"TechnicalEssays/MariaDBSeries/05not-mariadb-build-in-plugin","date":"2020-06-18T13:00:17.000Z","updated":"2023-05-16T13:54:36.643Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/05not-mariadb-build-in-plugin/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/05not-mariadb-build-in-plugin/","excerpt":"","text":"非内嵌的插件有些插件,是 MariaDB 内建的,本地安装 MariaDB 就激活了;有的是在服务器的插件,需要 install plugin;还有就是第三方,需要安装到本地,再激活使用。 区别可以简单这样认为: SHOW PLUGINS;看到所有已安装的激活的插件,可见数量等于SELECT * FROM information_schema.PLUGINS;; SHOW PLUGINS SONAME;在 plugin_dir 目录中显示有 关已编译和所有服务器插件的信息,包括尚未安装的插件,可见数量等于SELECT * FROM information_schema.all_plugins。 还有就是第三种,不在 MariaDB 服务器的插件,就是不在information_schema.all_plugins的表中的第三方的插件。可能需要在终端中进行额外安装。 例如 Cracklib Password Check,可以 select 一下: ok,为了测试该插件的使用效果,先把之前安装的 simple_password_check 卸载了: UNINSTALL PLUGIN IF EXISTS simple_password_check;Cracklib Password Check 插件简单说明: 插件 Cracklib Password Check 是 MariaDB 10.1.2 加入的; 需要搭配: crackle 2.9.0 (Debian 8 Jessie / Ubuntu 14.04 Trusty,RedHat Enterprise Linux / CentOS 6 之后,系统默认已有) 非属 MariaDB Package Component, 必须额外安装 终端安装 cracklib-password-check 插件: sudo apt install mariadb-plugin-cracklib-password-check 安装完之后,就可以在 all_plugin 表中看到了,默认安装完成后激活。 这也是一个检查密码强度的插件,用于检查设定的密码强度是否足够。测试也简单,同样新建个用户,赋予简单的密码,是不允许的: SET PASSWORD FOR 'test2'@'%' = PASSWORD('abc'); 修改为账号 test2 为复杂密码即可通过 SET PASSWORD FOR 'test2'@'%' = PASSWORD('P@ssw00d');","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(四)MariaDB密码验证插件使用示例","slug":"TechnicalEssays/MariaDBSeries/04mariadb-simple_password_check-plugin","date":"2020-06-18T12:59:41.000Z","updated":"2023-05-16T13:54:36.662Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/04mariadb-simple_password_check-plugin/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/04mariadb-simple_password_check-plugin/","excerpt":"","text":"插件(PLUGIN)使用示例示例介绍密码验证插件 Simple Password Check Plugin (MariaDB 10.1.2.加入)——(顺带简单介绍一些 DBeaver 的使用) 之前我们在设定 root 密码时,使用的是简单密码“root”,虽然后续连接时,要加 sudo,但是该账号密码依旧可用。 以 mariadb-server 现有的状态,新建一个账号: 再输入账号信息: 然后点击右下角的保存,会有执行语句窗口,语句如下 CREATE USER 'test'@'%'; ALTER USER 'test'@'%' IDENTIFIED BY '123456' ; GRANT Create user ON *.* TO 'test'@'%'; GRANT Event ON *.* TO 'test'@'%'; GRANT File ON *.* TO 'test'@'%'; GRANT Process ON *.* TO 'test'@'%'; GRANT Reload ON *.* TO 'test'@'%'; GRANT Replication client ON *.* TO 'test'@'%'; GRANT Replication slave ON *.* TO 'test'@'%'; GRANT Show databases ON *.* TO 'test'@'%'; GRANT Shutdown ON *.* TO 'test'@'%'; GRANT Super ON *.* TO 'test'@'%'; GRANT Create tablespace ON *.* TO 'test'@'%'; GRANT Usage ON *.* TO 'test'@'%'; FLUSH PRIVILEGES; 再点击执行,可见保存成功。test 账号新建成功。 从代码上,可以看到账号 test 的密码是 123456,但是这个账号的权限是 check all 选择的所有,相当大的权限。所以这个密码不是很安全。 事实上,这种权责很大、很重要的账号,其密码就不应该运行设定得如此简单,但现在的默认情况下,MariaDB 是可以这样设定密码的。 这样,可以使用simple_password_check 插件,避免设定账号的密码时过于简单。它可以检查密码是否至少包含一定数量的特定类型的字符。 首次安装时,密码必须至少为八个字符,并且至少需要一个数字,一个大写字母,一个小写字母以及一个既不是数字也不是字母的字符。 安装: INSTALL SONAME 'simple_password_check';或 INSTALL PLUGIN simple_password_check;注意一个是 library,一个是 name,一个有引号,一个没有。 安装完使用select * from mysql.plugin;查看是否安装成功(不出意外都成功的)。 注意:插件还可以在配置文件中启用。 找到 MariaDB 的配置文件,默认应该是文件/etc/mysql/my.cnf。 打开文件并在末端添加以下红框参数 [mariadb] plugin_load_add = simple_password_check 修改完配置文件,要重启 MariaDB 服务,例如终端执行sudo service mariadb restart。 重启完之后,在 dbeaver 中或 mysql 的命令窗口查看 simple_password 的相关参数,就可以看到以下信息: 注意:在配置文件中添加的插件配置,select * from mysql.plugin;是查询不到的。不用该插件了,从配置文件中删除即可,当然也要重启生效。 (后续插件的安装卸载,还是使用指令,不去修改配置文件。) 从上图参数设定也可看到,simple_password_check 要求密码必须至少为八个字符,并且至少需要一个数字,一个大写字母,一个小写字母以及一个既不是数字也不是字母的字符。 可以测试: 创建账号 test2,密码 123456 GRANT all ON *.* TO 'test2'@'%' identified by '123456; 改成符合要求的密码例如 P@ssw0rd 就可以了。 GRANT all ON *.* TO 'test2'@'%' identified by 'P@ssw0rd'; 当然,这些限制是可以修改的 例如,原本是数字至少 1 位,现在改成 2 位 SET GLOBAL simple_password_check_digits=2 使用show variables like 'simple_password%';查看异动。 之前的密码只有 1 个数字,已经不符合要求了。 同理,其它参数也是可以修改的,指令类似(n 正整数): SET GLOBAL simple_password_check_letters_same_case=n; SET GLOBAL simple_password_check_minimal_length=n; SET GLOBAL simple_password_check_other_characters=n;说明:SET GLOBAL 操作修改变量,重启服务之后可能就会被重置,需要永久生效,还是放到配置文件中好些。后续所有提到的 SET GLOBAL,大部分都会有对应的配置文件修改设定。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(三)MariaDB插件(Plugin)简介","slug":"TechnicalEssays/MariaDBSeries/03mariadb-plugin","date":"2020-06-18T12:58:42.000Z","updated":"2023-05-16T13:54:36.662Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/03mariadb-plugin/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/03mariadb-plugin/","excerpt":"","text":"注:既然已经使用 DBeaver 了,后续许多 MariaDB 的指令,就直接在 dbeaver 的 sql script 里面写(工具栏“SQL 编辑器”->“新建 SQL 编辑器”),如果习惯使用终端窗口,在终端连接 MariaDB 之后,在其 MariaDB 命令窗口输入一样的。 什么是插件(plugin)?MariaDB 的插件是: 是通过某种方式增强 MariaDB-server 组件。这些可以是新存储引擎中的任何东西,用于增强全文本分析的插件,甚至是一些小的增强功能,例如将时间戳记作为整数的插件。 说起来: MySQL/MariaDB 使用 Plugins 方式扩充 Database 核心功能 使用外挂方式可避免软件必须重新编译 动态挂载/ 卸除,提供更弹性的功能扩充。例如 特定用途的 Storage 引擎 安全模块 稽核纪录 插件管理查看插件指令SHOW PLUGINS; -- 查看所有已安装的插件命令 SELECT * FROM information_schema.all_plugins; -- 查看所有的插件,包括未安装的 SELECT * FROM information_schema.PLUGINS; -- 查看插件更多细节信息 SELECT plugin_name, plugin_version, lugin_maturity FROM information_schema.plugins ORDER BY plugin_name; -- 查询某些指定信息 安装插件官网介绍有 3 种方式,使用其一即可。简单归结为 INSTALL SONAME INSTALL PLUGIN mysql_plugin 命令 INSTALL PLUGIN语法: INSTALL PLUGIN [IF NOT EXISTS] <plugin_name> SONAME '<plugin_library>' SONAME: 插件 so 的原始文件名(非必要参数) plugin_name: 定义于插件内的名称 新增后,系统于 mysql.plugin 添加一笔纪录 安装后,查询 Plugin 对应的模块名称,语法show plugins soname < plugin_name>就是 Name 字段了,’就是 library 字段了。 示例,安装一个名为 BLACKHOLE 的插件 执行安装语句: install plugin [IF NOT EXISTS] BLACKHOLE soname 'ha_blackhole.so' 新安装的插件,会显示在mysql.glugin表里面。所以查看此表,可以看到新安装的插件。也可用于查看是否安装成功: 卸载插件同样,卸载组件也类似三种方式,任选其一即可: UNINSTALL SONAME UNINSTALL PLUGIN mysql_plugin 命令 例如,卸载刚刚安装的 BLACKHOLE: UNINSTALL PLUGIN IF EXISTS BLACKHOLE; 再查询select * from mysql.plugin;就没有 BLACKHOLE 了。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(二)MariaDB可视化工具——DBeaver","slug":"TechnicalEssays/MariaDBSeries/02mariadb-gui","date":"2020-06-18T12:57:45.000Z","updated":"2023-05-16T13:54:36.662Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/02mariadb-gui/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/02mariadb-gui/","excerpt":"","text":"这个之前有说过,不过不介意再说一遍,当然,也可跳过。 如果之前使用 mysql,可能会有习惯使用 mysql-workbench。目前 mysql-workbench 依然可以连接 MariaDB,作为其可视化工具。 当然,其它付费的 GUI 例如 Navicat 的产品也挺好用,有足够预算也可试用后确定是否进行购买。 不过正如之前 git/gitlab 系列教程中连接 PostgreSQL 所说,DBeaver-ce 是个不错的免费通用型关系型数据库 GUI。 DBeaver 简介: Java based GUI 数据库管理工具 支持目前主流数据库 ( JDBC-Aware Databases )https://dbeaver.com Enterprise Edititon 可支持更多非 JDBC 数据库( WMI , Redis, Cassandra, MongoDB..) 安装:事实上,官网(https://dbeaver.io/download/)有各个系统的直接的可执行安装包,下载双击即可。 指令安装如下: 1 jdk 依赖 确保有安装 jdk: 如果没有,使用指令进行安装(openjdk 即可): sudo apt install openjdk-8-jre-headless2 加入 gpg 金钥 wget -O - https://dbeaver.io/debs/dbeaver.gpg.key | sudo apt-key add -3 加入 repository echo "deb https://dbeaver.io/debs/dbeaver-ce /" | sudo tee /etc/apt/sources.list.d/dbeaver.list4 更新库文件 sudo apt update 5 安装 dbeaver sudo apt install dbeaver-ce6 查看版本使用apt policy dbeaver-ce或dpkg -l | grep dbeaver什么的都可以 7 卸载 dbeaver 可以使用dpkg -l | grep dbeaver查找确切的软件包名称,使用sudo dpkg -P dbeaver-ce删除它,或sudo dpkg -r dbeaver-ce保留配置文件的卸载。 使用 dbeaver这个就是 dbeaver 的图标: 打开之后,点击工具栏的“数据库”->“新建连接”: 找到自己需要的数据库,例如 MariaDB: 数据连接信息,然后点击“测试连接”,如果连接上,则如下 如果是首次连接,可能会需要联网下载数据库的驱动文件,若有,确认即可。连接成功,即可看到数据库内容: 从个人使用角度来看,还行,很多地方还有 mysql-workbench 的味道。可能一开始用起来觉得不咋样,但习惯就好了。最主要,这一个 GUI 小区版就可以连接很多的数据库(不包含任何 nosql),也算比较良心了。","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(一)MariaDB的安装与卸载","slug":"TechnicalEssays/MariaDBSeries/01install-mariadb","date":"2020-06-18T12:54:59.000Z","updated":"2023-05-16T13:54:36.694Z","comments":true,"path":"2020/06/18/TechnicalEssays/MariaDBSeries/01install-mariadb/","link":"","permalink":"http://swmlee.gitee.io/2020/06/18/TechnicalEssays/MariaDBSeries/01install-mariadb/","excerpt":"","text":"一 安装 MariaDB 10.4本系列文章均在 Ubuntu18.04 下进行测试或示例。 查询已安装的 maria 关键词 终端执行 sudo dpkg -l | grep maria。可用此检查之前是否已有安装过mariadb。 安装 MariaDB注意:可以在https://downloads.mariadb.org/mariadb/repositories/中去设定添加,选择自己的系统及版本、要安装的 MariaDB 版本、符合自己国家区域的仓库,加快下载数据。 五月份的时候mariadb10.5.3 RC也已经发布了,感兴趣可以试下。 Step 1:安装 software-properties-commonsudo apt-get install software-properties-commonStep 2:将 Repository Key 放置系统中如果网页https://downloads.mariadb.org/mariadb/repositories/中获取到的添加 key 指令运行失败,可使用以下替代: sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8Step 3:新增 apt 储存库sudo add-apt-repository "deb [arch=amd64,arm64,ppc64el] http://mariadb.mirror.liquidtelecom.com/repo/10.4/ubuntu $(lsb_release -cs) main"不行再执行下方指令 sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] http://ftp.utexas.edu/mariadb/repo/10.4/ubuntu bionic main'(需注意版号,如:10.4) 添加成功后再更新套件: sudo apt updateStep 4:于系统上安装 MariaDBsudo apt-get install mariadb-server安装完成,检查 MariaDB 服务状态: sudo systemctl status mariadb 如上图,则说明安装成功。 MariaDB 基本配置安装完后,可以先进行重启 sudo service mariadb restart再进行环境基本设定: sudo mysql_secure_installation执行命令之后,终端出现以下问题: 1、Enter current password for root (enter for none):(直接 enter,预设 MariaDB 没有密码)2、Switch to unix_socket authentication Y/n3、Change the root password? Y/n(注意:root 密码最好是复杂密码,否则可能会每次连接 MariaDB 需要加 sudo 的问题,例如我设定的是 root,稍后会提到) 4、Remove anonymous users? Y/n5、Disallow root login remotely? Y/n6、Remove test database and access to it? Y/n7、Reload privilege tables now? Y/n 登入指令 sudo mysql -u root -p 注意:解决 MariaDB 需要使用 sudo 才能连接的问题如果使用 Ubuntu 18.04 版本中安装 MariaDB 后,MariaDB 每次访问需要加 sudo 的才能访问 MariaDB,原因是因为 MariaDB 中 root 用户的密码强度不是强类型。 mysql 亦然。 分析说明:进入 MariaDB,查看用户权限: use mysql; select User,host,plugin from user; 虽然官方不建议使用 mysql_native_password 来作为高强度密码安全机制,但它对密码的设定仍有一些限制。 例如现在出现的使用密码是 root,可以访问 MariaDB,虽然需要 sudo。 想要直接命令访问 MariaDB 不加 sudo,只需把该账户的密码改为强类型的密码,例如包含大小写字母、数字、符号的至少 8 位数。 例如,我将密码修改为“P@ssw0rd”,执行以下指令: SET PASSWORD = PASSWORD('P@ssw0rd'); 可以看到,修改密码之后,可以不加 sudo 直接访问 MariaDB 了。这目前同样可以解决 mysql 无法直接连接的问题。 后续会介绍更多关于 MariaDB 账户强密码限制等其它插件,可期。 如果还只是刚配置 MariaDB,也可以重新运行sudo mysql_secure_installation重新设置。 解决远程不能访问的问题 找到 MariaDB 的配置文件,默认地址/etc/mysql/my.cnf。修改[mysqld]参数下bind-address = 127.0.0.1 => bind-address = 0.0.0.0,再重启 MariaDB service。 ref:https://medium.com/@jscinin/ubuntu-linux-18-04%E5%AE%89%E8%A3%9Dmariadb%E5%8F%8A%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE-%E7%A7%BB%E9%99%A4%E6%8C%87%E4%BB%A4-8d6d2ce0a73a 二、卸载 MariaDB卸载所有 mariadb/mysql 相关的软件 sudo apt-get remove mysql-\\*如果安装的是 mariadb 不是 mysql,也可以,有类似以下提示: ... 注意,选中 'mariadb-client-10.4' 而非 'mysql-client-5.5' 注意,选中 'mariadb-client-10.4' 而非 'mysql-client-5.6' 注意,选中 'mariadb-server-core-10.4' 而非 'mysql-server-core-5.6' 注意,选中 'mariadb-client-core-10.4' 而非 'mysql-client-core-5.5' 注意,选中 'mariadb-client-core-10.4' 而非 'mysql-client-core-5.6' ...再执行 dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P会出现窗口: 这是清理 MariaDB 中的数据库,是彻底地卸载的话,一并移除即可。 点击“是”之后,再执行一次dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P。如果出现以下类似的信息: 则说明卸载成功。 当然,以上指令是卸载 MariaDB 或 mysql 都可。如果明确知道安装的是 MariaDB,直接使用sudo apt-get purge mariadb-*即可。 ref:https://wsonh.com/article/145.html","categories":[{"name":"MariaDB系列","slug":"MariaDB系列","permalink":"http://swmlee.gitee.io/categories/MariaDB系列/"}],"tags":[{"name":"mariadb","slug":"mariadb","permalink":"http://swmlee.gitee.io/tags/mariadb/"},{"name":"数据库","slug":"数据库","permalink":"http://swmlee.gitee.io/tags/数据库/"},{"name":"db","slug":"db","permalink":"http://swmlee.gitee.io/tags/db/"},{"name":"运维","slug":"运维","permalink":"http://swmlee.gitee.io/tags/运维/"}]},{"title":"(十八)Git管理项目开发一般规范和开发使用Git常规作业流程","slug":"TechnicalEssays/AboutGit/18use-git-as-a-team","date":"2019-12-30T09:57:04.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/30/TechnicalEssays/AboutGit/18use-git-as-a-team/","link":"","permalink":"http://swmlee.gitee.io/2019/12/30/TechnicalEssays/AboutGit/18use-git-as-a-team/","excerpt":"","text":"团队开发使用代码版控也是有必要的一个开发者想要对自己的代码进行管控,有很多的云带代码托管平台,喜闻乐见的 github、gitlab、gitee、coding(原)、华为云、阿里云代码托管等等。个人使用,那么了解一点点 git 的知识即可满足需求,且也不需要什么规范,如果管理凭自己喜好即可。 但若是一个团队,需要使用代码托管工具管理自己的代码,那就不一样了。团队协助,最好不能各自按照各自的喜好,统一的作业流程能更加做好合作开发工作,缩短作业流程。 毕竟,使用这样的方式,肯定比用 U 盘把代码拷来拷去,然后复制粘贴好的多。可能需要传统行业的软件开发代码合并方式就是如此,并不需要惊讶。虽然这样做的风险和不足很明显,但是简单,不出错的话成本也低。 本章内容本篇主要讲解,一个团队,想要统一使用 git 搭配 gitlab 做代码管理的一般思路规范,并不一定适用,但是可以做参考,指定符合自己团队需要的规范。 基本内容有: 明确团队组织架构分层; 项目准备工作 根据组织架构分成,做 group/subgroup 准备 项目初始化准备 小组成员开发就绪 后续步骤 Git 补充的一些说明 开发使用 Git 常规操作 gitlab 协作开发模式说明 日常 Git 操作 具体内容参考github 分享 PPT之《17-Git管理项目开发准备和开发使用Git常规作业流程.pdf》。 到这里,这个 git/gitlab 使用系列的内容基本就说完了,不算全面,看起来也不算专业,若是能有所收获固然是极好了。倘若真的看完还是一无所获,那真是对你浪费的宝贵时间表示遗憾和难过了。 “只有用心,才能做出最好的菜。”相关内容网络资源还是很多的,多多学习,总有收获,并不一定需要在这里。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"},{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(十七)Git进阶与测试--使用Git、Git LFS搭配gitlab管控大文件","slug":"TechnicalEssays/AboutGit/17about-git-lfs","date":"2019-12-29T10:12:32.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/29/TechnicalEssays/AboutGit/17about-git-lfs/","link":"","permalink":"http://swmlee.gitee.io/2019/12/29/TechnicalEssays/AboutGit/17about-git-lfs/","excerpt":"","text":"本章主要测试讲解 git lfs 一些基本命令使用; 使用 Git/Git LFS 搭配 gitlab 管控大文件之测试。 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明 1、Git 本身作为代码管控软件,是以代码为主,没有直接管控到比较大的文件。 2、目前比较流行的 git 管理大文件的方式是使用 git-lfs。 3、git-lfs 是 git 的一个插件,官网说明如下: 4、使用比较简单,简单两三步即可完成大文件的提交。 5、gitlab 新版本默认已经支持 lfs,在配置文件 gitlab.rb 中可见: 预期中可能出现的问题 1、因为是管理到大文件,所以在 push 到远程服务器或者从远程服务器 pull 可能会因为网络带宽、文件大小、同时操作人员过多等因素导致操作耗时较长。 2、git 记录每次 add、commit 等操作,又以大文件为主,本地的.git 文件夹可能会极速增大。 3、gitlab 服务器若是还添加定期备份,因为文件管理可能导致 gitlab server 需要较大的硬盘存储数据。 大概流程 1 安装 git lfs 插件 2 准备两份大文件,两份 txt,分别作为文件管理和代码管理。 3 在 gitlab server 新建仓库,作为文件管理远程仓库。 4 使用 git lfs 将文件上传到 gitlab server 远程仓库。 5 克隆远程仓库到本地,查看文件是否完整。 测试步骤下载安装插件在git lfs 官网下载插件并安装。 本地项目准备本地新建文件夹 gitLfsTest,并准备两个较大压缩包文件作文件管理对象。同时新建两份 test3.txt,test4.txt 作为代码管理对象。 如下图: 创建远程仓库在 gitlab server 创建一个仓库,作为远程仓库 初始化文件夹为本地 git 仓库,初始化 git lfs。执行命令: git init git lfs install如下图: 使用 git lfs 追踪大文件使用 git lfs 追踪(track)大文件(*指代所有),执行之后,在文件根目录会出现.gitattributes 文件,内容即为追踪的大文件类型。执行命令: git lfs track "*.zip"如下图 确保.gitattributes 文件会被添加到追踪(tracked)执行命令: git add .gitattributes一般来说如果都是执行的git add .,那就没有必要再这样作 如下图: 到这里,git lfs 追踪管理大文件就基本完成了,后续就像是管理一般代码一样,进行 add,commit,push 等操作。 将 gitLfsTest 专案 push 到远程仓库。当然,要记得先 add,commit。注意:首次 add 时,我使用的是add .,会添加 2 个 zip 文件合计 2.26G 左右,2 个 txt 文件,总共 4 个文件,使用 time 指令记录耗时: time git add .如下图: 再 commit: time git commit -m '初次提交两个大文件'如下图: 后续常规,添加远程仓库地址,推送到远程仓库: git remote add origin http://192.168.28.83/david/gitlfstest.git time git push -u origin master如下图: 远程仓库内容如下: 可见两个 zip 包后面有 LFS 标识。 修改本地的 zip 包,再上传到远程仓库。修改文件前,留意下.git 文件的大小,如下图: 修改 test2.zip 文件大小修改前如下图: 修改后如下图: 同样的,add,commit,push: time git add . time git commit -m '修改test2.zip文件大小' time git push -u origin master如下图: 远程也是一样有新的 commit 信息 如下图: 此时,再关注下.git 文件夹的大小 如下图: 由 2.26G 上升到了 2.99G。所以,看起来随着修改的次数变多,.git 文件会无休止的增大下去。不过 git lfs 有相关指令避免它: git lfs prune它会删除本地旧的 fls 文件。 如下图: 从远程仓库 clone 一份 gitLfsTest 项目,查看文件内容。此时的项目应该是这样的:如下图: 我们从远程克隆一份下来: 查看内容,注意.git 的大小。如下图: 可见项目内容是完整的,test1.zip 和 test2.zip 都完整存在。至此使用 git 搭配插件 git lfs 管理大文件的测试就完成。 如下图: 总结 git 配合 git lfs 和 gitlab server 管控大文件操作还算简单方便,在系统安装完插件之后,在 git 管理的项目内: # 添加对大文件的追踪(.zip后缀) git lfs install git lfs track "*.zip" git add .gitattributes # 常规的添加、提交、推送 git add . git commit -m '{message}' git remote add origin {origin-url} git push -u origin master # 删除本地旧的fls文件 git lfs prune 即便是针对大文件,add,commit 等在本机的 git 指令依旧不算耗时; 在将项目需要放到远程 server 管理时,主要的耗时,来源于 push 和 clone 等跨主机操作,因此可以考虑到主要耗时取决网络。 .git 中的确会保留 fls 文件操作历史,导致 size 变得很大,不过可以使用git lfs prune尽量减少大小。 更多git lfs指令,可參看official man pages.,或者直接終端輸入: git lfs help <command> # 或 git lfs <command> -h","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十六)Git进阶与测试--将单一项目拆成多个小项目并保留各自的历史记录","slug":"TechnicalEssays/AboutGit/16project-split","date":"2019-12-28T11:12:02.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/28/TechnicalEssays/AboutGit/16project-split/","link":"","permalink":"http://swmlee.gitee.io/2019/12/28/TechnicalEssays/AboutGit/16project-split/","excerpt":"","text":"本章主要测试讲解 如何将一个 git 管理的较大的项目按文件夹拆分成几个小项目,且各个小项目只保留自己文件夹中的文件的历史记录。 两种方式实现 使用git filter-branch的实现 使用git subtree的实现(推荐) 本篇内容,不建议跳过,如果有这个需求,还请仔细详细查看,谢谢。 测试过程前置说明与项目合并相反,这是需要把单一项目的.git 的提交记录,抽取出各自子项目想要的部分。不过,相对于合并,拆分要简单些。 就不考虑把一个单独的文件拆成一个项目了。如果实在需要,就把这个单个文件,也放到一个文件夹中,再拆这个文件夹即可。 本地 git 仓库使用的是相对路径,所以直接修改 root folder 不会影响 git 历史记录 项目准备不将上一篇合并好的 timetools 再拆开。直接使用之前从 github 上克隆的 dayjs 项目作业。 在作业前,dayjs 的结构是这样的: 查看一下,此时所有的日志 log 修改的文件名。如下图(这里是可以看到所有文件修改内容和提交数量): dayjs 项目中,dayjs/src/有两个文件夹,locale/和 plugin/,示例假装 locale/需要拆出来,做一个单独的项目。 大概流程 1 使用git filter-branch 重写提交历史记录 清除.git 的 object 文件夹(如果觉得有必要的话) 查看日志信息 2 使用git subtree 进入 dayjs,创建一个临时分支 创建一个新的 git 空项目 将原仓库的临时分支 my-locale 拉到新仓库 使用git filter-branch实现–不推荐,仅作了解,不感兴趣可略过重写提交历史记录$ git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter src/locale -- --all命令说明: git filter-branch通过重写分支来重写 Git 修订历史记录,并在每个修订版上应用自定义过滤器。 –tag-name-filter <command> 该参数控制我们要如何处理旧的 tag,cat 即表示原样输出; –prune-empty 删除空的(对子目录没有影响的)提交; –subdirectory-filter <directory> 指定子目录路径; – –all -- all参数必须跟在 – 后面,表示对所有分支进行操作。如果你只想保存当前分支,也可以不添加此参数。 此操作会花点时间,执行完成,可以看到,原版的 dayjs 下的文件,变成了这样: 清除.git 的 object 文件夹(如果觉得有必要的话)git reset --hard git for-each-ref --format="%(refname)" refs/original/ |xargs -n 1 git update-ref -d git reflog expire --expire=now --all git gc --aggressive --prune=nowgit for-each-ref 根据给定的集合对它们进行排序之后,迭代所有匹配的 ref <pattern>并根据给定的显示它们。 –format=<format> %(fieldname)从所显示的 ref 及其指向的对象插入的字符串。 git-update-ref-安全地更新存储在 ref 中的对象名称 带-d 标志,它在验证命名的<ref>仍然包含<oldvalue>后将其删除 git reflog expire 修剪较旧的引用日志条目。 –expire=<time> 修剪早于指定时间的条目。 –all 处理所有引用的引用日志。 git-gc 清理不必要的文件并优化本地存储库 –aggressive 此选项将导致 git gc 更积极地优化存储库,但花费更多时间。 –prune=<date> 修剪比日期更旧的松散对象(默认为 2 周前) 清除的执行如下图: 查看日志信息其实最重要就只有第一步而已,完成之后,现在可以查看到现在的 dayjs 的日志如下图: 现在只有原来在 dayjs/src/locale 里面的文件被修改的提交记录被保留,其它的都丢掉了。 只要把这个现在的 dayjs 修改成 locale,就完成了大项目拆分成子项目的操作。 使用git subtree实现–推荐做法同样,使用最开始的从 github 中拉下来的 dayjs 做源项目。 进入 dayjs,创建一个临时分支git subtree split -P src/locale -b my-locale命令说明: git subtree 合并子树并将存储库拆分为子树。这个命令不在原本的 git 参考文件上,不过用法比较简单,可以在这里查看的更多用法。 split从<prefix>子树的历史中提取一个新的合成项目历史。新的历史记录仅包括影响<prefix>的提交(包括合并),并且这些提交中的每个现在都在项目的根目录而不是子目录中具有<prefix>的内容。因此,新创建的历史记录适合作为单独的 git 存储库导出。 -P <prefix> = –prefix=<prefix> 在存储库中指定的子树的路径 -b <branch> 创建一个分支 如下图 创建一个新的 git 空项目在与 dayjs 平级的路径下,创建一个文件夹,例如 my-locale(和上一步创建的分支名没任何关系),并进入,然后 git 初始化 cd .. mkdir my-locale cd my-locale git init如下图: 将原仓库的临时分支 my-locale 拉到新仓库git pull ../dayjs my-locale如下图 查看日志,效果一致,如下图: 对于一开始说的,单个文件,想要这样做,似乎不行。如下图。 还是放到文件夹吧。 总结 使用git filter-branch拆分子项目,是会把除了需要被拆分的部分,全部都删除了,包括.git 仓库里的东西。这是在原本的项目上进行作业。 使用git subtree是在原项目中创建了临时分支,再拉取到新的 git 空项目,不会对原项目进行异动,原项目甚至不会知道子项目的存在。 如果提交的次数较多,为了筛选所有的历史记录,都会比较耗时间。但不修改原项目的git subtree,显然更好。 git subtree 创建子项目命令小计 cd <source-project> git subtree split -P <prefix> -b <temp-branch> cd .. mkdir <sub-project> cd <sub-project> git init git pull <source-project path> <temp-branch>","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十五)Git进阶与测试--合并两个项目为一个并保留合并前所有历史记录","slug":"TechnicalEssays/AboutGit/15combine-projects","date":"2019-12-27T11:27:45.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/27/TechnicalEssays/AboutGit/15combine-projects/","link":"","permalink":"http://swmlee.gitee.io/2019/12/27/TechnicalEssays/AboutGit/15combine-projects/","excerpt":"","text":"本章主要测试讲解 如何将两个 git 管理的项目合并成一个项目,并保留住各自项目中所有的历史提交记录。(后续可以自行拓展到三个、四个……的合并) 本篇内容,不建议跳过,如果有这个需求,还请仔细详细查看,谢谢。 测试过程前置说明git 管理的项目合并,还要保留之前的提交历史记录,则表明不能直接异动到原项目.git 文件夹仓库,需要通过其它方式把子项目的.git 文件夹合并到一起,才能保存住所有提交信息。 项目准备 先从 github 克隆了两个项目用于测试,一个是 dayjs,一个是 moment; 计划是把 dayjs 和 moment 合并为一个项目,名为 timetools; 该项目包含两个文件夹,一是 dtool,对应原本的 dayjs; 二是 mtool,对应原本的 moment。 效果,合并前 - dayjs => 独立的 dayjs 项目,有自己的.gitignore 和 .git/ - moment => 独立的 moment 项目,有自己的.gitignore 和 .git/合并后: - timetools/ => 即最开始的 dayjs,整合完后更名 - .gitignore => 合并两个 repos 的忽略文件 - .git/ => 最终仅余一个 repo + dtool/ => 对应 dayjs + mtool/ => 对应 moment克隆的项目,内容如下: 使用git log --oneline | wc -l获取提交数量。 粗略可见,dayjs 的提交为 1046 次,moment 提交为 3724 次。注意分支名,dayjs 为 dev,moment 为 develop。 大概流程 1 进入 dayjs 文件夹,将 moment 作为远程仓库添加到 dayjs 来; 2 合并添加的库 moment 到原本的 dayjs 项目; 3 创建 mtool 文件夹,把 moment 的 develop 分支合并到 mtool 文件夹; 4 完成 moment 转移提交; 5 完成 dayjs 文件的迁移; 6 完成项目合并并查看历史记录。 测试步骤进入 dayjs 文件夹,将 moment 作为远程仓库添加到 dayjs 来使用命令: git remote add -f moment D:/davidsu/Desktop/GitlabTest/full/moment命令说明:git remote add 添加远程仓库路径 -f 的作用是在添加后立刻 fetch。 D:/davidsu/Desktop/GitlabTest/full/为需要被合并 moment 项目的绝对路径 。 如下图: 合并添加的库 moment 到原本的 dayjs 项目使用命令(注意分支名): git merge --strategy ours --no-commit --allow-unrelated-histories moment/develop命令说明:git merge 为合并分支 –strategy ours 解析合并,在合并时,如果遇到冲突,以我的为准。(本例是在 dayjs 中合并 moment,遇到冲突以 dayjs 的为准)。结果就是: moment 的历史记录被合并到 dayjs 的历史记录中。 moment 的文件树被读取并和 dayjs 的文件树比对进行冲突解析。 –no-commit 合并解析完成后中断,停留在最后的提交步骤之前。 只要你还没 commit,那么 merge 的结果就暂时保存在缓存区中,只有完成提交步骤合并才算彻底完成(文件树被正式改变)。 –allow-unrelated-histories 允许合并无关的历史记录。 如果不添加此选项,可能会出现fatal: refusing to merge unrelated histories错误。 如下图: 创建 mtool 文件夹,把 moment 的 develop 分支合并到 mtool 文件夹使用命令: mkdir mtool # 创建文件夹 git read-tree --prefix=mtool/ -u moment/develop 命令说明:git read-tree 给定的树信息读入索引 –prefix 用于指定文件树读取后保存的路径,相对于当前路径并且一定要追加 /。 -u 是说在读取后更新 index,使得 working tree 与 index 保持同步。 这个命令的意义在于,之前的git merge命令可能会在解决冲突的时候,把 moment 的文件树弄得比较混乱,再使用read-tree去修复一下。 如下图(再次提醒,=前后不要空格): 完成 moment 转移提交上一步已经修复了文件数,执行完之后,仍然在 MERGING 状态,需要提交一次修改。 使用命令: git commit --message '完成moment 的迁移,新目录为 mtool'如下图(MERGING 过成已完成): 完成 dayjs 文件的迁移以上,已把 momet 项目迁移到了 dayjs 下的 mtool 文件夹了。现在要迁移 dayjs 文件夹了。 在 dayjs 文件夹下,新建文件夹 dtool mkdir dtool再把所有原本属于 dayjs 的文件移动到新建的 dtool 去。也就是除了 dtool/、mtool/、.git/、.gitignore 之外的所有。 剪切完之后,把这个原本的 dayjs 的文件夹重命名为 timetools。 完成项目合并并查看历史记录到这一步,原本的 dayjs 项目,就变成了 timetools 项目,里面的 dtool 文件夹就是 dayjs 项目。 不过还差一点,之前只是把文件放进去了,还需要合并.gitignore 文件。 在新的 timetools 文件夹下,运行: cat mtool/.gitignore >> .gitignore把原本 moment 项目,现在的 mtool 文件夹下的.gitignore 的内容,合并到现在 timetools 文件夹下的.gitignore 中,完成忽略文件的合并。 如下图: 到这里,合并作业就基本完成了,最后在添加提交一次,做合并项目的记录 git add --all; git commit --message '迁移整合完成!'如下图: 合并前后的文件夹结构如下: 查看合并后的项目历史记录,如下图: 提交数:4772 = 1046 + 3724 + 2。 总结以上,完成了两个项目合并到一个项目的示例接操作演示,后续有更多的项目想要合并,可以类似。 本示例使用步驟 进入 dayjs git remote add -f moment D:/davidsu/Desktop/GitlabTest/full/moment git merge –strategy ours –no-commit –allow-unrelated-histories moment/develop mkdir mtool git read-tree –prefix=mtool/ -u moment/develop git commit –message ‘完成 moment 的迁移,新目录为 mtool’ mkdir dtool (还在 dayjs 下面) 拷贝 dayjs 的原始项目文件(除了 .git/ 和 .gitignore 以外)至 dtool/; 拷贝完之后,可以把原本文件夹名 dayjs 改为 timetools; 把此时 mtool 下的.gitignore 文件内容,整理合并到 timetools 下的.gitignore 文件中去。 合并完之后,再全部添加提交一次,做为整合操作的记录: git add –all; git commit –message ‘迁移整合完成!’ 整理引用原作者步骤:目标:将 frontend 项目与 backend 项目,合并到 project 项目下的 client 文件夹与 server 文件夹。 $ [~] cd frontend $ [frontend] git remote add -f backend /fullpath/to/backend $ [frontend] git merge --strategy ours --no-commit backend/master $ [frontend] mkdir -p server $ [frontend] git read-tree --prefix=server/ -u backend/master $ [frontend] git commit --message '完成 backend 的迁移,新目录为 server' $ [frontend] mkdir -p client # 拷贝 frontend 的原始项目文件(除了 .git/ 和 .gitignore 以外) 至 client/ $ [frontend] cd ..; mv frontend/ project/; cd project $ [project] cat server/.gitignore >> .gitignore # 整理合并后的 .gitignore,修复其中的路径缺失并保存;修复各种项目依赖的缺失,本地测试。 $ [project] git add --all; git commit --message '迁移整合完成!' 参考文件:https://segmentfault.com/a/1190000000678808","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十四)Git进阶与测试--如何把文件真正的从Git里删除","slug":"TechnicalEssays/AboutGit/14delete-file-permanently","date":"2019-12-26T11:12:22.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/26/TechnicalEssays/AboutGit/14delete-file-permanently/","link":"","permalink":"http://swmlee.gitee.io/2019/12/26/TechnicalEssays/AboutGit/14delete-file-permanently/","excerpt":"","text":"本章主要测试讲解 利用git filter-branch命令,彻底删除已提交文件。 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明例如在开发过程中,把私用账号密码文件提交了,或者其它机密文件提交了,那么在 git 中就会保留那份文件,如果 push 到远程,那么其它人也有可能看到这份他们可能不应该看到的文件,所以,如何把这份文件从 git 中移除掉? 大概流程: 1、新建一个项目 test-filter-branch,git 初始化,并放入两个文件,初始化提交 2、模拟误操作添加了不应该提交的 password.txt 文件,并提交 3、误提交 password.txt 之后,模拟又多次提交 4、将每个提交中的 password.txt 移除,并清理垃圾回收中的相关设定 测试步骤新建一个在 test-filter-branch 项目并初次提交 之后,在项目中创建了一个 config,里面有个 password.txt 文件。假设属于私有机密文件,不应该被提交,但是又忘记在创建前加入.gitignore 文件。且已经提交了。 密码提交之后,又进行了很多次提交。 突然想起密码被提交,需要从 commit 中,把文件删除,此时 commit 记录如下 使用以下命名: git filter-branch -f --tree-filter "rm -f config/password.txt" 此时,config/password.txt 文件是不见了 即便如此,还是可以通过 git reset 指令找回来。所以,还有几个跟资源回收有关的事情需要处理一下删除备份点:rm .git/refs/original/refs/heads/master设置 reflog 立即过期:git reflog expire --all --expire=now 此时再查看 git reflog 会发现没有记录,使用 git fsck 指令就可以看到很多 Unreachable 的对象了 不过,查看日志还能看到提交秘密时的 commit id,使用 git reset 能恢复 password.txt 文件吗,试一下。 查看日志(注意,删除后 commit 日志和删除之前的,有涉及到 password.txt 的 commit id 全都变了。) 使用 git reset 之后,可以看到已然没有 config/password.txt 文件了。 注意:删除 commit 中 password.txt 文件,在本地已经完成了,如果在之前已经推送到了远程,则需要使用git push -f覆盖掉。 总结删除文件步骤主要使用命令: git filter-branch -f --tree-filter "rm -f config/password.txt" # 重写分支 rm .git/refs/original/refs/heads/master # 删除备份点 git reflog expire --all --expire=now # 设置reflog立即过期 git push -f # 同步远程 特别说明: 重写的历史将具有所有对象的不同对象名称,并且不会与原始分支会聚。您将无法在原始分支的顶部轻松推送和分发重写的分支。如果您不知道完整的含义,请不要使用此命令,并且无论如何都要避免使用它。 此外 git 官网git-filter-branch WARNING也不建议使用此命令: https://linux.die.net/man/1/git-filter-branch","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十三)Git进阶与测试--多样化处理本地commit记录","slug":"TechnicalEssays/AboutGit/13handle-local-commit-message","date":"2019-12-25T10:11:24.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/25/TechnicalEssays/AboutGit/13handle-local-commit-message/","link":"","permalink":"http://swmlee.gitee.io/2019/12/25/TechnicalEssays/AboutGit/13handle-local-commit-message/","excerpt":"","text":"本章主要测试讲解 利用git rebase命令,多样化处理本地 commit 记录。例如 修改提交信息 删除提交信息 合并提交信息 …… 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明在本地开发时,有提交很多次,但是其中提交信息,可能有些是可以合并到一起,有的可能没必要可以删除,有的需要修改 commit measssge…… 这些都可以使用git rebase命令的相关参数指令实现。 项目准备在本地的 tensorflow 项目中添加几次提交 测试步骤修改非最近一次的提交信息假如需要修改 commit id 为 699b10610 的 commit message。因为这不是最近的一条 commit,所以 git commit --amend无法使用。 需要使用 git rebase 指令相关参数 输入git rebase -i <commit id>,进入 interactive 模式其中为此次想要调整这个节点(commit id)开始至 HEAD 中间的提交纪录。 因为我们要修改 commit id 为 699b10610 的 commit message,所以此处git rebase -i 后面的 commit id,可以是 699b10610 的前一个 4f092caa。 输入指令后,会弹出文件,编辑需要调整的 commit 记录,以及指令说明,如下图 注意:在 Rebase 状态看到的这个纪录是跟我们平常看的纪录是反过来的,越新的 Commit 在越下面。 常用指令说明: pick: 只接使用这个 commit,不做任何调整 reword: 使用这个 commit,只调整 commit message squash: 使用这个 commit 融入前一个 commit 中,合并两个 commit message 来表示(可以修改) fixup: 使用这个 commit 融入前一个 commit 中,使用前一个 commit 的 message 来表示(不可修改) drop: 直接移除这个 commit 所以,我需要修改 commit id 为 699b10610 的 commit message,只需要把这份文件中第一行改为reword + 修改后的message再保存执行即可 修改后的文件 保存并关闭文件之后,会弹出一份新的文件,主要用于确认和修改 commit meassge,如下图 保存并退出后,可以看到git rebase命令执行完成 查看日志,之前 commit id 为 699b10610 的提交,已经被修改为 commit id 为 11d4ec53,内容也为修改后的内容。但是其它内容没有异动。 合并多条 commit 内容同上,使用git rebase -i 指令即可修改 squash: 使用这个 commit 融入前一个 commit 中,合并两个 commit message 来表示(可以修改) fixup: 使用这个 commit 融入前一个 commit 中,使用前一个 commit 的 message 来表示(不可修改) 根据上述指令说明,这可能需要执行多次。如果需要一并修改 commit message,要使用 squash,用第一条代替合并后的 commit message,使用 fixup。示例:合并 commit id aa0312a 和 c78d4fe 和 026345ca,并修改 commit meassge 为:“修改 tensorflow1.txt 内容”使用git rebase -i进入 interactive 模式 修改弹出文件第三句指令为 squash (注意,这个 commit 的 squash,需要从下往上合并,否则可能会报 error: cannot 'squash' without a previous commit错误) 然后会弹出合并 commit message 的确认和修改画面 我修改如下 保存退出后可看到如下信息 此时查看日志,就发现之前的 3 条 commit,变成两条了 所以,再执行一次,即可达到最开始的需求,3 合 1 修改后 修改前 删除指定 commit message这个不再赘述,同样使用git rebase -i <commit id>进入 interactive 模式,把需要删除的信息从 pick 改为 drop,或者直接删除掉,再保存即可。 注意:不想删除的信息,就不要异动;rebase 的 <commit id>要在被删除提交信息之前,不然看不到;如果删除的那次提交会导致冲突,根据解决冲突的效果,影响对应 commit 信息保留结果。 其它其它指令不再一一说明,简单带过。例如 想要调整提交信息顺序,直接把 interactive 模式中看到的信息对应调整即可,诸如此类。 想要在已提交信息中间添加新的 commit 信息, 在新增起点的<commit id>的 interactive,模式改为 edit, 这会中止 rebase,然后就可以使用git commit --amend修改当前信息, 或者实际修改文件/夹,再使用git add和git commit命令去添加新的 commit 信息。 直到添加完成,再使用git rebase --continue即可。 …… 不要一定要注意,慎重,因为有一些提交和后续的提交是相互依存的,删除或者变动之前的可能会导致后续的提交出现异常,导致项目出现问题。 总结使用git rebase -i <commit id>,可以对本地的提交记录做很多变动。在进入 interactive 模式后,对该文件(git-rebase-todo)做相应修改: 想要删除,使用 drop; 想要修改,使用 reword; 想要合并,使用 squash 和 fixup; 想要新增,使用 edit; 想要调整顺序,调整 pick …… 更多内容可以自行尝试。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十二)Git进阶与测试--三种实现undo(还原)操作的命令对比","slug":"TechnicalEssays/AboutGit/12git-three-undo","date":"2019-12-24T11:55:54.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/24/TechnicalEssays/AboutGit/12git-three-undo/","link":"","permalink":"http://swmlee.gitee.io/2019/12/24/TechnicalEssays/AboutGit/12git-three-undo/","excerpt":"","text":"本章主要测试讲解 git reset、git checkout和git revert在撤销修改,内容还原功能中的对比。 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明使用需求大概都是一致,就是在修改了代码之后,想要会到旧版本去。虽然列示的三种操作过程不一样,但都达到了那样的效果。 指令原理说明简单说明区别git revert:只能用于 commit-level,会新建一个 commit,在历史记录中说明还原了什么 git reset:可用于 commit-level 和 file-level,用于撤销未被提交到远程(remote)的改动,即撤销本地(local)的修改。 可以将其 git revert 视为撤销已提交更改的工具,同时 git reset HEAD 用于撤销未提交的更改。 git checkout:可用于 commit-level 和 file-level,是将 HEAD 指针移动到想要回退的版本,不会修改历史记录 示例演示说明项目准备,一个有简单修改的 tensorflow 项目 git checkout <commit id>示例例如,查看 tensorflow 的历史记录,HEAD 目前在 12589 使用git checkout到 47092caa 去。再查看历史记录,HEAD 已经指向了 4f092caa 了。 项目中也把文件还原到了当时提交的情况 git revert <commit id>示例先将分支切回到 dev 使用 git revert 还原到 a122a10dcd还原过程中会弹窗窗口提示输入 commit 内容 revert 完成之后,查看日志可以看到,会看到新增一条 commit 而项目中已经有还原到当时的文件 但是通常情况下,git revert 不会带 commit id,直接使用,用于撤销最近一次提交。 git reset示例注意 git reset 的参数 在实际使用时,如果有确切的撤销需求,例如撤销后的提交内容都应该是不需要的,历史记录也不在保留,index 和工作区也回退到撤销的版本,那么就需要使用–hard 参数。 使用git reset前的日志 使用git reset恢复到 4f092caa 直接使用git log,则看到第一条已经是 4f092caa 那条提交 不过使用git log --all --oneline还是可以看到 但是注意,使用 git log --pretty=oneline是不行的 总结表格列示三种指令的作用范围和效果: git 命令 作用范围 常见用例 git reset commit-level 放弃在私有分支中的提交或丢弃未提交的更改 git reset file-level 取消暂存文件 git checkout commit-level 在分支之间切换或检查旧快照 git checkout file-level 放弃工作目录中的更改 git revert commit-level 撤消在公共分支中提交 git revert file-level (N/A) git 官方文档已有做出意见: git revert is used to record some new commits to reverse the effect of some earlier commits (often only a faulty one). If you want to throw away all uncommitted changes in your working directory, you should see git-reset[1], particularly the –hard option. If you want to extract specific files as they were in another commit, you should see git-checkout[1], specifically the git checkout – syntax. 即: git revert 用于记录一些新的提交,以逆转某些较早提交的效果(通常只有一个错误的提交)。 如果要丢弃工作目录中所有未提交的更改,则应看到 git-reset,尤其是–hard 选项。 如果要提取特定文件,就像在另一个提交中一样,则应该看到 git-checkout ,特别是git checkout <commit>-<filename>语法。 参考内容: https://git-scm.com/docs/git-revert https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E7%BD%AE%E6%8F%AD%E5%AF%86 https://segmentfault.com/a/1190000009126517 https://dev.to/neshaz/when-to-use-git-reset-git-revert--git-checkout-18je https://stackoverflow.com/questions/8358035/whats-the-difference-between-git-revert-checkout-and-reset","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十一)Git进阶与测试--clone远程仓库很慢的处理","slug":"TechnicalEssays/AboutGit/11handle-slow-clone","date":"2019-12-24T09:43:21.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/24/TechnicalEssays/AboutGit/11handle-slow-clone/","link":"","permalink":"http://swmlee.gitee.io/2019/12/24/TechnicalEssays/AboutGit/11handle-slow-clone/","excerpt":"","text":"上一篇有讲到克隆远程仓库,在 clone 时,如果每次都 clone 完整的仓库的话,随着提交的次数变多,在项目变大之后,clone 的速度会非常的慢。 所以克隆时可以设定参数 --depth 1,加快 clone 速度 -- depth代表克隆的深度,--depth 1代表只克隆最新一次提交记录以及这次提交之后的最新内容,不克隆历史提交。 这样所造成的影响就是不能查看历史提交记录,但是克隆速度大大提升。 完整命令: git clone --branch <branch_name> <remote-address> --depth 1查看 commit 总数,可用: git rev-list --all --count 或者 git log --oneline | wc -l后续补充:注意,这里 git rev-list 查看到的提交数量,并不一定和仓库中显示的提交数一样,获取的原理不同。后者是一致的。详细请查看官方文档关于git rev-list的说明。 查看简要显示日志,可用: git log --all --oneline示例,今日(2019/12/25)克隆 github 中 tensorflow 项目,深度只有 1 层,编写本文示例测试耗时大约4 分 15 秒,其它内容如下图: 而直接 clone master 分支的全部,编写本文示例测试耗时大约13 分 30 秒,其它内容如下图: 2019/12/26 补充,如果想看实际耗时,在 git 命令前加 time 关键词。 如果后续想看完整的历史记录,可以将浅层克隆转换为常规克隆。使用: git pull --unshallow 或者 git fetch --unshallow不过,这就是重新抓取了该分支所有的提交,也就不如直接一开始就拉取所有了。 使用示例(同样编写本文示例测试耗时大约13 分 30 秒): 查看当前分支所有提交者及其提交次数,按次数由高到低排序,可用: git log | grep "^Author: " | awk '{print $2}' | sort | uniq -c | sort -k1,1nr","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(十)使用docker安裝gitlab-ce","slug":"TechnicalEssays/AboutGit/10folder-or-file-not-effect-restore","date":"2019-12-23T12:38:48.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/23/TechnicalEssays/AboutGit/10folder-or-file-not-effect-restore/","link":"","permalink":"http://swmlee.gitee.io/2019/12/23/TechnicalEssays/AboutGit/10folder-or-file-not-effect-restore/","excerpt":"","text":"本章主要测试讲解 项目中的文件数量消长(新增或删除文件时) 与 存在的文件内容消长(同一份文件修改多次) ,这两种情境的版本还原有无区别 测试误删除文件,然后还原 本章使用 git reset,更多还原操作可参看下一章 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明这里是测试不同正常修改的操作会不会影响到项目还原,按道理来讲,代码写到一般,文件未保存计算机电源被扒了,这部分如果编辑器没有缓存,应该是不会在 git 的还原操作内了。 项目准备1、远程仓库准备 在 github 上找到了一个 commit 次数较多的项目,放置到测试的远程仓库,本例是 tensorflow 2、本地仓库的准备 clone 一个到本地,作为测试仓库 创建一个 dev 分支,进行测试 将本地 dev 分支推到远程 dev 分支 大概流程 1、使用 git rm 指令删除 tensorflow 子文件夹,commit 一次; 2、然后再新建 tensorflow 文件夹,并加上两个文件,commit 第二次; 3、再使用手动删除 third_party 文件夹,commit 第三次; 4、然后再新建 third_party 文件夹,并放上两个文件,commit 第四次; 5、最后 push 到远程 dev 分支。 6、再删除本地 tensorflow,clone 一份远程 dev 分支的 tensorflow,查看此时的 tensorflow 和 third_party 子文件夹内容 7、还原 clone 下来的项目到删除文件前,比较是否和之前的内容一致。 注意都是在同一分支(示例使用 dev)中进行的 测试步骤1 操作前,项目中的 tensorflow 和 third_party 文件夹信息如下: 2 指令删除,添加,并提交 git rm tensorflow -r -f git add . git commit -m '指令删除tensorflow子文件夹' 3 新建 tensorflow 子文件夹并放两个文件 添加并提交 4 手动删除 third_party 文件夹,并提交 5 新建 third_party 子文件夹并放两个文件 添加并提交 6 推送到远程 dev 分支最好先 pull(因为与 origin 的 dev 比较,文件夹中内容有冲突,pull 会先合并 merge,因此会多一条 commit),再 push 到 origin 7 删除本地项目,再 clone 一份远程的 dev 分支项目 查看内容与推送 push 之前是一致的(一致的) 查看一下近 10 条记录 8 将 clone 下来的项目,还原到 27cfc……提交的状态 查看一下文件内容,完全一致 总结由此看出: 无论是手动删除,使用 git 命令删除,无论子文件夹是否有多个层级,还是删除后有其它 commit 操作,只要找到对应的 commit id,使用 git reset 方法都能还原到该次 commit 的状态,文件保持一致。 文件数量的增减,文件内容的增减,提交后的还原效果一致。 (更多还原方式,参看下一章)","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://swmlee.gitee.io/tags/docker/"},{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(九)Git进阶与测试--merge和rebase分支合并、解决冲突及特征对比","slug":"TechnicalEssays/AboutGit/9git-merge-and-rebase","date":"2019-12-22T12:17:29.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/22/TechnicalEssays/AboutGit/9git-merge-and-rebase/","link":"","permalink":"http://swmlee.gitee.io/2019/12/22/TechnicalEssays/AboutGit/9git-merge-and-rebase/","excerpt":"","text":"本章主要测试讲解 git merge和git rebase指令的用法和进行分支合并,并做简单比较分析。 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程前置说明关于无论是使用 merge 还是 rebase 进行合并,出现冲突的原因都是在不同的分支修改了同一处的代码,合并时版控工具 git 不知道保留哪一份的修改,从而导致冲突,需要合并人员去判断并解决。 使用 merge 和 rebase 合并的做法 大概流程:测试 git merge 和 git rebase 的合并及解决冲突效果: 1 准备测试项目及分支记录 创建 master、dev1、dev2 三个分支 除了 master 创建 dev1 和 dev2 的初始提交外,3 个分支各自有两个不同的提交记录 时间顺序为:master 新建空文件,再初始提交 –>创建 dev2 –> dev2 commit 第一次文件修改 –> dev2 commit 第二次文件修改 –>切回 master 创建 dev1 –> dev1 commit 第一次文件修改 –> dev1 commit 第二次文件修改 –>切回 master –> master commit 第一次文件修改 –> master commit 第二次文件修改 –>进行使用 git merge/git merge 合并测试…… 2 使用 git merge 进行合并 3 使用 git rebase 进行合并 4 对比两者合并的历史记录,分析优缺点和使用场景 测试步骤准备测试项目及分支记录相关命令请看截图 准备测试项目 test-conflict,新建一个 test1.txt 文件(后续新建的文件,最好都改成 UTF-8,windows 下默认是 ANSI,操作可能会产生乱码),内容为空,并 master 分支初始提交。 以 master 分支创建 dev2 分支 dev2 以 master 分支为基准,所以 test1.txt 还是为空的,两次修改 test1.txt 此时 dev2 的历史记录为 切换到 master 分支,并创建分支 dev1 并修改文件 test1.txt,然后分别提交 然后再修改一次,然后第二次提交 此时 dev1 的日志记录如下 此时切回到 master 分支,自行也修改并提交两次 test1.txt 的修改 此时 master 分支的记录为 直至,准备工作完整,我们打包一份,保留 2 个一模一样的项目,一个测试 git merge,一个测试 git rebase 使用 git merge 进行合并测试步骤说明: 切换到 master 分支,先合并 dev1 到 master,再合并 dev2 到 master 因为 dev1、dev2 和 master 原本分支都有修改到 test1.txt 的同一行,所以会出现多次的合并冲突,需要手动解决 合并完成之后,查看 git merge 的 master 的日志 创建时现有 dev2 的提交,但是合并时,先合并 dev1合并 dev1 到 master,出现冲突 手动解决冲突并提交 再合并 dev2 到 master,依然报冲突 手动解决并提交 至此,使用 git merge 已把 dev1 和 dev2 合并到 master 分支,查看 master 分支的日志。 至此使用 git merge 合并和解决冲突测试完成。 使用 git rebase 合并冲突使用之前的备份项目测试 git rebase 步骤说明: 切到 dev1 分支,rebase dev1 到 master,解决合并冲突 切回 master 分支(此时 master 的日志还没变),进行一次快速合并到 dev1(现在就变了) 切到 dev2 分支,rebase dev2 到 master,解决合并冲突 切回 master 分支,进行一次快速合并到 dev2 合并完成之后,查看 git rebase 的 master 的日志 切回 dev1 分支,rebase dev1 到 master,会产生冲突。 修改成以下内容之后,再执行 git add .(没有执行 commit),然后继续进行 rebase 继续进行 rebase,则弹出第二次冲突提示 从文本来看,因为有两行是冲突的,第一次解决冲突是 master 中于 dev1 第一次提交有冲突的内容,现在报的是与 dev1 第二次提交有冲突的内容,同样手动解决,然后继续,可见 rebase 完成。 至此,使用 rebase,已经把 dev1 上的两次提交的修改变基到了 master 分支的提交修改上。 现在切回 master 分支(当然,此时 master 的日志还没变),进行一次快速合并到 dev1(现在就变了) 同样的,再合并 dev2,同样解决两次冲突,第一次冲突 解决如下 第二次冲突 解决如下: 合并完成 同样,现在切回 master 分支,进行一次快速合并到 dev2,查看日志 git merge 和 git rebase 的结果对比使用 git rebase 后,3 个分支的历史记录如下 git merge 历史记录如下 总结git merge 和 git rebase 进行合并的区别: 历史记录不同 git merge 保留了各个分支各自的提交记录,如果有解决冲突,会单独创建一次 commit 记录冲突的解决,历史记录完整。 git rebase 只有一根线的分支记录历史,手动解决的冲突不会创建保留记录,历史记录清晰简单 操作步骤不同 git merge 操作简单, git rebase 操作步骤繁琐。 影响范围不同 git merge 操作未对 dev1 和 dev2 的项目内容(或提交记录)进行异动,不会影响后续人员对此两个分支进行接续作业。 git rebase 操作已经对 dev1 和 dev2 分支进行了异动,如果有后续分支使用了这两个分支,可能会导致提交历史记录的混乱和其它异常情况。 建议:只对尚未推送或分享给别人的本地修改执行变基操作清理历史,不要对已推送至别处的提交执行变基操作。 个人建议对于合并,如果始终不清楚 merge 和 rebase 的区别,推荐使用 merge。merge 的一大优点是简单,不会对其它分支造成影响。唯一可能的不足就是会有比较多(不清晰)的提交记录,这一点可以使用 git rebase -i ,进入 interactive 模式(后续文章有介绍),对历史记录进行修改 新手提醒:如果需要合并的分支还有未提交的修改,是没有办法合并的。 Bonus:修改分支的名字 假如从 master 分支创建了一个 feature-branch 分支,结果写成了 future-brunch 直接使用git branch -m参数修改即可 git branch -m future-brunch feature-branch","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(八)Git进阶与测试--.git文件夹中object数量过大是否影响commit效率?","slug":"TechnicalEssays/AboutGit/8object-effect-commit-performance","date":"2019-12-21T12:51:33.000Z","updated":"2020-01-09T13:41:14.885Z","comments":true,"path":"2019/12/21/TechnicalEssays/AboutGit/8object-effect-commit-performance/","link":"","permalink":"http://swmlee.gitee.io/2019/12/21/TechnicalEssays/AboutGit/8object-effect-commit-performance/","excerpt":"","text":"在此往后的几篇文章,主要是说明一些使用 Git 时比较高级一点的问题或者比较重要的问题。除了一些测试说明、功能介绍、操作引导之外,还可以增长见识和思考方法,可以一看。 有些测试截图是比较旧(也就几个月),但是目前来看,依然是 ok 的。 测试过程内容较多,每个步骤都逐一截图以便真实说明,也有列示用法。若不感兴趣,可直接查看总结部分。 测试过程众所周知,.git 文件夹是 git 管理项目的本地仓库。objects 目录存储所有数据内容,每一次 git commit 都会将信息存到该文件夹。这部分不清楚,可去官网Git 内部原理 - 底层命令和高层命令和Git 内部原理 - Git 对象等地方再巩固。所以,一般情况下来看,git commit 的次数过多,object 文件夹体积就会变大,如果非常巨大了,会不会影响到提交的速度? 前置准备将 github 中的两个项目 tensorflow 和 liunx,clone 一份到本地,并推到自建 gitlab 服务器(内网 1.0Gbps)中,作为测试项目远程仓库。主要说明 Git 客户端的使用,所以本地需要安装 Git 大概流程准备两个比较大的项目,分别测试单次 add 和 commit 的耗时。 测试步骤1 以 tensorflow 项目为例进行 commit 测试准备了一个 git 文件夹有 360M,提交数量超过 5W 的项目 tensorflow 测试 commit 之前,先提交一次已有的文件,以免出现干扰 在 tensorflow 中新建一个文件夹 newfoler,并随意放入几个文件 再测试 add 及 commit 的效果 两个命令耗时较短,并没有出现耗时的情况(第一个是 add,第二个是 commit)。排除切换页面和鼠标移动点击的时间,这两个命令耗时只有 1s 左右。 测试结果看来,一个有着 5W+commit 数量,git 文件夹超过 360M 的项目,在 commit 时并没有发生耗时很长的情况。 其实 git folder 要大于 500M 其实还是非常少的,我在 github 中找了非常久,到目前为止,就只发现 linux 项目超过 500M,提交数量超过 10W。 2 1 以 linux 项目为例进行 commit 测试准备了一个提交数量和 git folder 都很大的项目(github 上的 linux 项目) 测试前,先add .一次,避免对 commit 提交速度进行干扰(实际测试,clone 下来后,第一次git add .是比较耗时间的,好好几分钟)。 在 linux 下新建一个 new_folder 文件夹,并随意放入几个文件,如下 测试结果来看,完成 add 和 commit 的速度还是很快的 两个命令耗时较短,并没有出现耗时的情况(第一个是 add,第二个是 commit)。排除切换页面和鼠标移动点击的时间,这两个命令耗时只有 1s 左右。 再修改一些文件,删除一些文件 add 和 commit 的耗时依然不多 总结 这个 Linux 项目的 git 文件夹和 commit 数量是目前已经发现最大的项目,测试得出 add 和 commit 的时间都是很短,所以应该是不会出现 commit 非常耗时的情况。 所以 commit 的数量太大不会影响 commit 的速度。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"git","slug":"git","permalink":"http://swmlee.gitee.io/tags/git/"}]},{"title":"(七)Git入门及常用命令","slug":"TechnicalEssays/AboutGit/7git-basic","date":"2019-12-21T10:54:51.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/21/TechnicalEssays/AboutGit/7git-basic/","link":"","permalink":"http://swmlee.gitee.io/2019/12/21/TechnicalEssays/AboutGit/7git-basic/","excerpt":"","text":"罗杰•杜德勒编写整理了一个不错的git - 简易指南,可以查看学习。网上也有很多内容,没记住的,要用时搜一下就好了。 另外,我在github有放一个简单的《7-Git入门指南》的 PPT,可以一并查看,主要说明的是: Git 是什么? Git 简明指南补充说明 Git 常用指令(本文正文) 使用 Git 一般开发规范 Git Client GUI 及在 VS code 中使用 Git 此处只是列示一些可能常用的 git 指令: 配置使用 Git 的账号密码: git config --global user.name "Your Name" git config --global user.email [email protected]初始化一个 Git 仓库: git init添加文件到 Git 仓库,分两步:添加到暂存区: git add <file> #注意,可反复多次使用,添加多个档; 提交到仓库 git commit -m <message>。查看工作区的状态: git status。可以查看修改内容: git diff关联一个远程库: git remote add origin git@server-name:path/repo-name.git;关联后,使用命令第一次推送 master 分支的所有内容: git push -u origin master此后,每次本地提交后,推送最新修改; git push origin master要克隆一个仓库,首先必须知道仓库的地址,然后使用 git clone git@server-name:path/repo-name.git。查看分支: git branch创建分支: git branch <name>切换分支: git checkout <name>创建+切换分支: git checkout -b <name>合并某分支到当前分支: git merge <name>删除分支: git branch -d <name>看到分支合并图: git log –graph查看远程库信息: git remote -v;从本地推送分支 git push origin <branch-name>,抓取远程的新提交; git pull在本地创建和远程分支对应的分支,使用 git checkout -b <branch-name> origin/<branch-name> # 本地和远程分支的名称最好一致; 建立本地分支和远程分支的关联 git branch --set-upstream branch-name origin/branch-name;","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://swmlee.gitee.io/tags/docker/"},{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(六)远端访问gitlab默认PostgreSQL数据库","slug":"TechnicalEssays/AboutGit/6remote-access-gitlab-ce-postgresql","date":"2019-12-21T09:58:28.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/21/TechnicalEssays/AboutGit/6remote-access-gitlab-ce-postgresql/","link":"","permalink":"http://swmlee.gitee.io/2019/12/21/TechnicalEssays/AboutGit/6remote-access-gitlab-ce-postgresql/","excerpt":"","text":"gitlab 默认的数据库是 PostgreSQL ,用它官网的话来说就是“The World’s Most Advanced Open Source Relational Database”。 一般情况下,我们没有必要去直接访问它。但是,没必要不代表没需求。gitlab 结构、用户数据、配置信息等。 值得注意的是: 在 GitLab 12.1 中删除了对 MySQL 的支持。建议在 MySQL / MariaDB 上使用 GitLab 的现有用户在升级之前迁移到 PostgreSQL。从 GitLab 10.0 开始,需要 PostgreSQL 9.6 或更高版本,并且不支持较早的版本。我们强烈建议用户使用 PostgreSQL 9.6,因为这是用于开发和测试的 PostgreSQL 版本。 本地访问 PostgreSQLgitlab 默认有可以直接访问内部 postgreSQL 的命令: sudo gitlab-rails dbconsole 或者 sudo gitlab-psql -d gitlabhq_production这样就进入了 postgreSQL 命令窗口,可以输入 sql 语句进行作业。例如,输入\\list查看所有数据库: sanotsu@sanotsu-ubt18:~$ sudo gitlab-rails dbconsole psql (10.9) Type "help" for help. gitlabhq_production=> \\list List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ---------------------+-------------+----------+-------------+-------------+--------------------------------- gitlabhq_production | gitlab | UTF8 | zh_CN.UTF-8 | zh_CN.UTF-8 | postgres | gitlab-psql | UTF8 | zh_CN.UTF-8 | zh_CN.UTF-8 | template0 | gitlab-psql | UTF8 | zh_CN.UTF-8 | zh_CN.UTF-8 | =c/"gitlab-psql" + | | | | | "gitlab-psql"=CTc/"gitlab-psql" template1 | gitlab-psql | UTF8 | zh_CN.UTF-8 | zh_CN.UTF-8 | =c/"gitlab-psql" + | | | | | "gitlab-psql"=CTc/"gitlab-psql" (4 rows) gitlabhq_production=>或者输入select * from namespaces;查看 gitlab 中已经有了哪些用户。输入select * from projects;查看有哪些项目文件等等。 注意,在不能完全把控风险的情况下,最好不要擅自使用 SQL 的 DDL、DML、DCL 语言,避免造成 gitlab 运行意外。 特别注意:这里显示的 Name 有 4 个,除了 gitlabhq_porduction 的 owner 是 gitlab 之外,其它的是 gitlab-psql。所以,在连接到 gitlab 内部的 postgresql 数据库时,指定数据库名称为 gitlabhq_porduction 才有实际意义,才能看到需要的信息。这里的 dbconsole 默认是选择的 gitlabhq_production,但后续外部连接就不一定了。因为我去看过其它几个数据库,啥都没有,我还以为是权限问题,不让我看,搞了半天…… 配置远程访问 PostgreSQL默认情况下,外部是无法访问 Gitlab 内部的 postgreSQL 的。实际上,现在很多的数据库,在初始默认安装时,都不允许外部直接访问的。 了解一下 Gitlab 数据库各个配置文件(不感兴趣可跳到下一节)要验证上述结论,除了亲自在外部试连之外,还可以直接看配置文件。 默认的 gitlab 数据库配置文件在/var/opt/gitlab/gitlab-rails/etc/databse.yml。 打开之后,看到的内容应该如下: # This file is managed by gitlab-ctl. Manual changes will be # erased! To change the contents below, edit /etc/gitlab/gitlab.rb # and run `sudo gitlab-ctl reconfigure`. production: adapter: postgresql encoding: unicode collation: database: gitlabhq_production pool: 1 username: "gitlab" password: host: "/var/opt/gitlab/postgresql" port: 5432 socket: sslmode: sslcompression: 0 sslrootcert: sslca: load_balancing: {"hosts":[]} prepared_statements: false statements_limit: 1000 fdw:可以看到,host 属性的值是本地文件路径,外部自然连不到的。 当然,可以直接查看 postgreSQL 的用户权限配置文件查看,默认路径在/var/opt/gitlab/postgresql/data/pg_hba.conf。 打开默认应该可以看到只有这样一句配置(Local): # TYPE DATABASE USER CIDR-ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer map=gitlab此外,在同路径下的postgresql.conf文件中,也能看到(监控地址为空): # - Connection Settings - listen_addresses = '' # what IP address(es) to listen on; # comma-separated list of addresses; # defaults to 'localhost', '*' = all # (change requires restart) port = 5432 # (change requires restart) max_connections = 200 # (change requires restart)以上内容,也为了更加清楚的认识各个文件的构成和作用。 修改 gitlab 配置文件实现远程访问 PostgreSQL实际上,可以一一修改上述文件去实现远程访问,只不过就是重启 gitlab 之后失效。但是从配置文件修改,更加简单,一劳永逸。 打开/etc/gitlab/gitlab.rb配置文件,找到## Gitlab PostgreSQL区块,在### Advanced settings最末,加上以下内容: postgresql['listen_address'] = '{gitlab主机IP}' postgresql['port'] = 5432 postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/24) postgresql['md5_auth_cidr_addresses'] = %w({gitlab主机IP}/0) postgresql['sql_user'] = "gitlab" postgresql['sql_user_password'] = Digest::MD5.hexdigest "gitlab" << postgresql['sql_user']把{gitlab 主机 IP}替换成你 gitlab 主机的真实 IP 即可。 其实把{gitlab 主机 IP}和 127.0.0.1 换成 0.0.0.0 也行。如果不清楚限制,全部给到最大总能有效。 这几行的配置分别是: 添加 postgresql 的监听地址, 添加 postgresql 的监听端口, 本地访问(127.0.0.1 或者 localhost)postgresql 不用输密码, 需要输入密码的访问地址, 连接到 postgresql 数据库的账号(示例中为 gitlab), 连接到 postgresql 数据库的密码(示例中为 gitlab)。 然后,找到### Gitlab database settings,在最末添加以下内容: gitlab_rails['db_username'] = "gitlab" gitlab_rails['db_password'] = "gitlab" gitlab_rails['db_host'] = "{gitlab主机IP}" gitlab_rails['db_port'] = 5432 gitlab_rails['db_database'] = "gitlabhq_production"依次是:数据库用户名、密码、地址、端口和默认数据库名称。如果不设定最后一行,那么默认连接的数据库就是 postgres。 到这里,配置就修改完了,运行sudo gitlab-ctl reconfigure重新加载配置运行。 注意,重新加载配置运行时,可能会从出现以下错误: There was an error running gitlab-ctl reconfigure: bash[migrate gitlab-rails database] (gitlab::database_migrations line 54) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '1' ---- Begin output of "bash" "/tmp/chef-script20191224-30773-18wzcfl" ---- STDOUT: rake aborted! PG::ConnectionBad: FATAL: no pg_hba.conf entry for host "192.168.XX.XX", user "gitlab", database "gitlabhq_production", SSL on FATAL: no pg_hba.conf entry for host "192.168.XX.XX", user "gitlab", database "gitlabhq_production", SSL off /opt/gitlab/embedded/service/gitlab-rails/lib/tasks/gitlab/db.rake:48:in `block (3 levels) in <top (required)>' /opt/gitlab/embedded/bin/bundle:23:in `load' /opt/gitlab/embedded/bin/bundle:23:in `<main>' Tasks: TOP => gitlab:db:configure (See full trace by running task with --trace) STDERR: ---- End output of "bash" "/tmp/chef-script20191224-30773-18wzcfl" ---- Ran "bash" "/tmp/chef-script20191224-30773-18wzcfl" returned 1那是因为,需要重启 postgresql,重新配置才能生效。所以,先运行sudo gitlab-ctl restart postgresql,再运行sudo gitlab-ctl reconfigure即可。 在旧一点的版本,7.x,8.x,9.x,10.x,11.x 我似乎都没有遇到过。可能是新数据库需求和版本有了变化吧。 其它的配置,按照实际需求添加即可,也可访问官网数据库设置查看更多信息 到此,应该就可以在远程连接192.168.XX.XX(你的 gitlab 主机 IP),通过账号 gitlab、密码 gitlab 连接到 gitlab 内部的 postgresql 数据库了。 关系型数据库图形化工具(GUI)推荐及连接说明之前我使用连接到 postgresql 的图形化工具是 PgAdmin4,连接 mysql 用的是 MySQL Workbench,还有连接 SQL Server 用了 SQL Server Management Studio,连接 mariadb 用了 heidiSQL,还有 SQLite 等,遇到一个就去找一个,很麻烦,其实也没必要。 最近我发现一个还不错的 GUI,ce 版本可以支持连接这绝大部分常用的关系型数据库,叫 DBeaver。nosql 也支持,不过这部分就要收费了。所以我上面才没有列 Redis,MongoDB 什么的。 Windows 下,直接去dbeaver 官网下载一个安装包即可。 linux 下稍微麻烦一点,以 Ubuntu18 为例,安装 DBeaver: 1、因为 DBeaver 是 java base,所以需要安装 java,openjdk 即可 sudo apt-get install openjdk-8-jdk2、 添加 GPG key: wget -O - https://dbeaver.io/debs/dbeaver.gpg.key | sudo apt-key add -3、 添加仓库: echo "deb https://dbeaver.io/debs/dbeaver-ce /" | sudo tee /etc/apt/sources.list.d/dbeaver.list4、 更新,然后安装 sudo apt update sudo apt -y install dbeaver-ce5、 检查 dbeaver 版本,有就安装成功 apt policy dbeaver-ce使用上就是选择连接的数据库类型,输入地址、端口、账号、密码、数据库名称等等,就不赘述了。 工作界面如下: 注意,如果在外部使用 DBeaver 或者其它 GUI 连接到 gitlab 内部的 postgresql 时,没有填写数据库名称为 gitlabhq_production,那默认连接的就是 postgres。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(五)Gitlab用户数据备份与定时备份","slug":"TechnicalEssays/AboutGit/5gitlab-ce-usedata-backup","date":"2019-12-20T12:01:16.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/20/TechnicalEssays/AboutGit/5gitlab-ce-usedata-backup/","link":"","permalink":"http://swmlee.gitee.io/2019/12/20/TechnicalEssays/AboutGit/5gitlab-ce-usedata-backup/","excerpt":"","text":"在安装 gitlab 的时候,有使用命令sudo gitlab-rake gitlab:backup:create备份用户数据。该备份路径是默认配置中的路径,我们可以对其进行修改。 此外,仅仅是备份在 gitlab 的主机中对数据丢失也有风险,例如硬盘坏了。 所以这里我简单列举了 gitlab-ce userdata(用户数据)备份到本机其它位置、备份到远程主机 2 种方式。 更多的,gitlab 还默认支持将用户数据备份到云端,配置中列示有 Amazon S3、Digital Ocean Spaces、 Google Cloud Storage 等。 具体详细的内容,可以查看官网的Backing up and restoring GitLab 常规备份设定——备份到本机其它位置打开/etc/gitlab/gitlab.rb文件,找到Backup Settings区块。可以看到,默认的备份地址配置gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"。 所以,只需要修改这一句,调整路径,例如gitlab_rails['backup_path'] = "/home/{username}/gitlab,后续备份的用户数据,就在/home/{username}/gitlab 下了。{username}为你的主机名。 备份到远程主机备份到远程主机,一开始不是很清楚这个配置设定,看着那段英文字翻译成中文没有看懂,假装配置几次失败后,就放弃了。使用了透过 ssh 使用 scp 指令,编写脚本文件,将 gitlab 主机的用户数据,copy 到其它主机。 以上做法是 ok 的,实际运行这么久也没什么问题。不管,这里我还是说明一下,如何使用 gitlab 的配置完成备份时一并备份到远程主机。当然,前提条件是,在局域网内,可以直接访问指定位置。如果本来就无法访问,那肯定是这么配置都没用的。 仔细查看官方文档的说明,整理出实现步骤: 将远程主机路径,挂载到 gitlab 主机, 将挂载地址的所有权赋予 git 账号, 在 gitlab.rb 中配置备份设定, 重新加载配置文件运行 gitlab。 ubuntu 中挂载远程主机共享文件夹一般情况下,我们在 ubuntu 中,点击‘其它位置’–>’连接到服务器’–>输入服务器地址–>输入授权账号密码网域等,就可以直接访问到对应的位置。此次的挂载效果类似。 我的示例,是将 gitlab 服务器的用户数据,备份到一台 windows 系统的远程主机,所以需要将 windows 指定文件夹,挂在到 gitlab 所在的 ubuntu 系统。 1 gitlab 主机安装 cifs 工具 gitlab 主机是 ubuntu,运行sudo apt-get install cifs-utils即可。 2 新加需要挂载的目标文件夹 我的示例,是想把 windows 系统中的//192.168.XX.XX/share/GitlabBackupDir挂载到 ubuntu 中的/mnt/backups中。所以执行mkdir /mnt/backups去创建对应文件夹,权限不够在前面加sudo。 3 挂载文件夹一般直接使用 mount 指令的话,是临时挂载,计算机重启之后就没有了。现在这个场景,比较适合永久挂在,所以调价配置到文件为佳。在挂载前,仔细阅读一下官网这句话: The directory pointed to by the local_root key must be owned by the git user when mounted (mounting with the uid= of the git user for CIFS and SMB) or the user that you are executing the backup tasks under (for Omnibus packages, this is the git user). 经过我的测试分析,它的意思大概说明,这个挂载的地址,本例中为/mnt/backups,必须是执行挂载动作时的 git 用户,或者执行备份作业是的用户。使用 Omnibus packages 安装的 gitlab,这个执行的用户,就是 git 用户。 什么意思呢,简单理解就是,这个挂载地址文件夹的拥有者,必须是 git 用户。 到这里,我们再来添加挂载配置。 打开/etc/fstab文件,在最后添加以下内容: {被挂载的远程主机源路径} {gitlab主机的目标路径} cifs auto,username={远程主机的用户名},password={远程主机用户名的密码},domain={远程主机的网域},gid={ubuntu下git用户的gid},uid={ubuntu下git用户的uid} 0 0空白留个 tab 键间隔或者空格就好了。各个间隔的参数含义,配置文件有说明,分别是 <file system> <mount point> <type> <options> <dump> <pass> 如果获取 git 用户的 uid 和 gid?在终端中输入id git即可。其它用户就是id {user}。 我的挂载命令就是 //192.168.XX.XX/share/GitlabBackupDir /mnt/backups cifs auto,username=XXX,password=XXX,domain=XXX,gid=998,uid=998 0 0要让挂载立即生效,执行sudo mount -a即可。查看是否挂载成功,执行mount查看,应该可以看到类似如下一句: …… //192.168.XX.XX/share/GitlabBackupDir on /mnt/backups type cifs (rw,relatime,vers=2.1,cache=strict,username=XXX,domain=XXX,uid=998,forceuid,gid=998,forcegid,addr=192.168.XX.XX,file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,rsize=1048576,wsize=1048576,bsize=1048576,echo_interval=60,actimeo=1) ……说明挂载成功。 额外说一句,如果只有临时挂载,重启就没有了,那就不写到配置文件,只需要在终端执行: mount -t cifs {被挂载的远程主机源路径} {gitlab主机的目标路径} -o username="{username}",password="{password}",domain={domain}注意: 如果是 windows10 系统,可能需要在最末加一句 vers=2.0,写在配置文件也是一样要加。 终端中输入 option 要加引号。写到配置时,不要加,否则会报错。 如果没有网域,当然就不用添加这个参数。 到这里挂载文件夹和赋予所有权给 git 用户已完成。 修改 gitlab.rb 对应配置文件打开/etc/gitlab/gitlab.rb文件,找到 # gitlab_rails['backup_upload_connection'] = { # 'provider' => 'AWS', # 'region' => 'eu-west-1', # 'aws_access_key_id' => 'AKIAKIAKI', # 'aws_secret_access_key' => 'secret123' # } # gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' # gitlab_rails['backup_multipart_chunk_size'] = 104857600最好复制一份,修改为: gitlab_rails['backup_upload_connection'] = { 'provider' => 'Local', 'local_root' => '/mnt/backups' } gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups'这个配置完成,那么在执行 geilab 备份时,会在/mnt/backups文件夹下创建gitlab_backups子文件夹,并放入该次备份的用户数据。又因为这个路径,实际是 windows 下//192.168.XX.XX/share/GitlabBackupDir的挂载路径,所以实际上,用户数据的备份文件,就在这里。 以上,就完成了备份到远程主机的操作。配置好之后可能执行sudo gitlab-rake gitlab:backup:create命令测试一下,看是否在上述的路径下备份了用户数据。 当然,备份到 U 盘,外挂硬盘什么的,操作类似,不重复。至于备份到云,我没有这个条件,但是文檔也写得比较清楚,照做即可。 此外,编写脚本,使用其它系统指令也可以实现类似的效果,这个不用 gitlab 进行配置,所以不赘述。 实现定时备份可以将备份操作写到 cron 定时备份任务中去,那么就可以省略手动备份的操作了。 这个比较简单,在终端执行sudo crontab -e,或者 sudo su - crontab -e系统自动备份的话,用户还是用 root 较好选择一个编辑器,在最末,加一句(示例是每天凌晨 2 点进行备份,等级优先) 0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1注意,GitLab 12.1 及之前的版本, 使用 0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1. 保存,重启 cron 服务 sudo service cron restart后续要修改,可以直接修改其文件,位置在/var/spool/cron/crontabs文件夹,如果是 root 账户,这里面就有个 root 文件。如果是其它用户{user},那就是{user}文件。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(四)gitlab-ce设置SMTP","slug":"TechnicalEssays/AboutGit/4gitlab-ce-set-smtp","date":"2019-12-20T09:55:53.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/20/TechnicalEssays/AboutGit/4gitlab-ce-set-smtp/","link":"","permalink":"http://swmlee.gitee.io/2019/12/20/TechnicalEssays/AboutGit/4gitlab-ce-set-smtp/","excerpt":"","text":"Gitlab-ce smtp 设定在安装 Omnibus package 时,有推荐安装 postfix 搭建 mail 服务器。这可能比较麻烦。 此外,使用 gitlab 作为简单版控工具或其它功能,大部分都不需要接收其它用户发送的邮件,而是发出邮件。例如用户注册需要验证用户账户,合并冲突发送给对应使用者提醒有冲突,重大任务分配设置邮件提醒指定开发者……考虑到这些功能 gitlab 主要是作为发件者,配置 SMTP 即可。gitlab 默认支持 SMTP 的配置。 SMTP 是一种提供可靠且有效的电子邮件传输的协议。SMTP 是建立在 FTP 文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。 它与 POP3 和 IMAP 可共同使用。SMTP 是用于发送邮件,而 POP3 和 IMAP 用于接收邮件。 基本步骤如下: 修改 gitlab-ce 默认 SMTP 配置打开 gitlab-ce 的配置文件,默认在/etc/gitlab/gitlab.rb,找到对应配置 smtp 的位置,按照实际配置修改,如下图: (为了方便修改,我把 Email Settings 部分和 GitLab email server settings 内容放到了一起) 对应的主要配置说明: 设定 Email 相关信息(设定档中「Email Settings」区块为设定 Email 相关信息部分。) gitlab_email_enabled:启用 Email 功能。 gitlab_email_from:寄件人信箱。 gitlab_email_display_name:寄件人显示名称(预设为 GitLab 或是 GitLab 账号名称)。 gitlab_email_reply_to:回复信箱(预设为 noreply@ + 外部 URL) 设定 SMTP Server(设定档中「GitLab email server settings」区块为设定 SMTP Server 部分) smtp_enable:启用 SMTP 功能。 smtp_address:SMTP Server。 smtp_port:SMTP Port。 smtp_user_name:SMTP 使用者账号。 smtp_password:SMTP 使用者密码。 smtp_domain:SMTP 网域。 smtp_authentication:SMTP 验证模式。 smtp_enable_starttls_auto:SMTP 开启 TLS 设定。 smtp_tls:使用 TLS 设定。 smtp_openssl_verify_mode:SMTP SSL 验证模式。 更多对应的配置,可参考一下官网的SMTP settings 配置更改完成之后,需要执行sudo gitlab-ctl reconfigure使配置生效。 测试是否成功控制台命令测试终端输入sudo gitlab-rails console,进入 gitlab-rails 工作区. 在命令行输入测试命令,测试命令格式: Notify.test_email({收件者邮箱地址},{邮件主题},{邮件内容}).deliver_now按照实际内容替换{}及其中文字即可。 如果发送成功,终端会显示测试命令发出的邮件信息,或者直接到收件者邮箱从查看。如果失败,可以根据终端中错误提示进行判断。 如下图: 值得注意,虽然我确认我是安装的 gitlab-ce 版本,但是这里显示的是 12.6.0-ee。我猜是一个小的 bug。 设定 gitlab 注册用户需验证邮箱或者直接使用 giltab 相关功能测试 可以测试,注册用户时,需要邮件确认之后,才能登入。gitlab 账号注册时验证邮箱的正确性,在注册时可以减少使用不存在的邮箱进行注册,保证 user 的有效性。 首先开启注册需要邮件验证。 使用 root 管理员账号登入,点击“管理员区域”–>“设定”–>”注册限制”,把Send confirmation email on sign-up打勾,最后保存。 然后新注册一个用户。 注销 root 账号之后,输入注册信息,点击注册按钮,会跳转到发送确认邮件的画面。 如果直接返回到登入页面,使用刚刚注册的账号登入,则会出现以下You have to confirm your email address before continuing字样。 这样,说明 gitlab 的注册验证邮箱的设定生效了。 然后查看刚刚注册账号使用的邮件,如果有收到自己 gitlab 发送的注册确认邮件,那么 gitlab SMTP 设定也是成功了。 当然,后续点击蓝色连接,则会跳转到登入页面,并有显示邮箱已被成功确认的信息,这时再使用刚刚注册账号登入即可成功。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(三)gitlab-web基本功能说明","slug":"TechnicalEssays/AboutGit/3gitlab-web-basic","date":"2019-12-19T12:43:15.000Z","updated":"2020-01-09T13:41:14.886Z","comments":true,"path":"2019/12/19/TechnicalEssays/AboutGit/3gitlab-web-basic/","link":"","permalink":"http://swmlee.gitee.io/2019/12/19/TechnicalEssays/AboutGit/3gitlab-web-basic/","excerpt":"","text":"gitlab 安装成功之后,登录网页,可以看到它提供的很多功能。这个东西要写的话,恐怕还是非常复杂和麻烦。 例如其核心的自动部署和 CI/CD,到现在我也没有在生产环境下使用过。如果把 gitlab-ce 作为一个内部代码版控工具,也就还用不到这样的功能。 所以,这部分,使用者的 gitlab 基本功能使用介绍,我不再赘述,可以查看我之前有简单做的 PPT,放置在github《3-gitlab基本功能测试使用介绍.pdf》。 主要内容有介绍: 项目私有性测试 问题追踪 其它实用功能测试说明 专案 细节 活动 档案库 档案 更动记录 分支 标签 协作者 图表 比较 统计图 议题 里程碑 标签 清单 广告牌 合并请求 Wiki 程序代码片段 因为当时作业环境是繁体,截图依旧保留繁体。 一般使用者和 gitlab 管理员的权限有所区别。主要在于管理员都一个admin area,这个最大最高权限,少数者拥有就好了,更多内容可参看官方文档GitLab Admin Area","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(二)omnibus package安裝gitlab-ce","slug":"TechnicalEssays/AboutGit/2omnibus-gitlab-ce","date":"2019-12-19T11:24:32.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/19/TechnicalEssays/AboutGit/2omnibus-gitlab-ce/","link":"","permalink":"http://swmlee.gitee.io/2019/12/19/TechnicalEssays/AboutGit/2omnibus-gitlab-ce/","excerpt":"","text":"omnibus-package 安装 gitlab-ceubuntu 下安装 gitlab-ce 官方推荐安装,步骤也非常简单. 安装并配置需要的依赖1 先更新 apt-get: sudo apt-get update2 再安装 openssh-server sudo apt-get install -y curl openssh-server ca-certificates注意,在 ubuntu18 安装时 openssh-server 时,可能会出现类似这样的错误: sanotsu@sanotsu-ubt18:~$ sudo apt-get install -y curl openssh-server ca-certificates 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 ca-certificates 已经是最新版 (20180409)。 ca-certificates 已设置为手动安装。 curl 已经是最新版 (7.58.0-2ubuntu3)。 有一些软件包无法被安装。如果您用的是 unstable 发行版,这也许是 因为系统无法达到您要求的状态造成的。该版本中可能会有一些您需要的软件 包尚未被创建或是它们已被从新到(Incoming)目录移出。 下列信息可能会对解决问题有所帮助: 下列软件包有未满足的依赖关系: openssh-server : 依赖: openssh-client (= 1:7.6p1-4) 依赖: openssh-sftp-server 但是它将不会被安装 推荐: ssh-import-id 但是它将不会被安装 E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。 sanotsu@sanotsu-ubt18:~$这是因为安装 openssh-server 依赖 openssh-client。ubuntu 默认有安装 openssh-client,但是版本可能不满足,所以只需再安装一次需要的版本。 例如上面的出错信息,则需要降级,命令安装如下: sudo apt-get install openssh-client=1:7.6p1-4安装成功之后,再安装 openssh-server 即可。 安装邮箱服务器 Postfix(非必要,大可不必)sudo apt-get install -y postfix然后按照提示,输入自己的配置。大概有需要输入的邮箱服务器地址,邮件名等等,还有一些使用 default 就好了。 如果不用 Postfix 来配置邮件收发,例如 gitlab 配置使用 SMTP 来发送邮件(后续文章再说明),这个部分可以跳过。 如果安装之后,想要移除,可使用sudo apt-get remove postfix来卸载。 添加 GitLab 软件包存储库并安装软件包添加 GitLab 软件包存储库命令: curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash安装命令: sudo EXTERNAL_URL="http://192.168.XX.XX" apt-get install gitlab-ce其中 EXTERNAL_URL 为 gitlab-ce 的访问地址,如果不设定,默认就是 http://127.0.0.1。 注意,在之前的版本,设置EXTERNAL_URL="192.168.XX.XX"是可以的,不过我现在使用 12.6.0 这样设置,会报错,错误如下: There was an error running gitlab-ctl reconfigure: GitLab external URL must include a schema and FQDN, e.g. http://gitlab.example.com/下载和安装过程需要一点时间。 安装成功之后,打开之前设置的访问地址,应该会出现修改管理员密码画面,就表示 gitlab 安装成功。 如果安装完成之后的自动运行配置出现错误,例如 url 配置出错等等,解决问题后再手动再运行一次sudo gitlab-ctl reconfigure。 安装完成之后的组件分析: gitlab 的结构分析,可参看官网说明,有助于理解各个组件的作用。 一些常用指令1.查看状态: sudo gitlab-ctl status2.停止/启动/重启 gitlab sudo gitlab-ctl stop sudo gitlab-ctl start sudo gitlab-ctl restart3.重新加载配置 sudo gitlab-ctl reconfigure3 关闭/启用开机自启动(慎重) 这个 Gitlab 默认是开机自启动的。 Ubuntu 下禁止 Gitlab 开机自启动: sudo systemctl disable gitlab-runsvdir.service如果要设置开机自启动,Ubuntu 下启用 Gitlab 开机自启动: sudo systemctl enable gitlab-runsvdir.service自启动也还 ok,如果 disable 掉,开机在使用sudo gitlab-ctl start就启动不了了,因为所有的服务都关了,要启动起来才行。 注意,如果 docker 的 gitlab 和 omnibus-package gitlab 安装在同一台机器,注意只开一个,因为端口什么的是一样的。 如果安装完 omnibus-package gitlab,发现启动不了 docker 的 gitlab,并提示端口 22 已被占用,可能就是 sshd 占用了端口,关闭 ssh 即可。 /etc/init.d/ssh stop当然,开启就是 /etc/init.d/ssh start如果要关闭开机自启动 ssh(沒必要),删除其自动配置 sudo mv /etc/init/ssh.conf /etc/init/ssh.conf/disabled备份和还原用户数据备份对应命令: sudo gitlab-rake gitlab:backup:create默认的备份地址在/var/opt/gitlab/backups中,查看该路径可以看到以下内容,即为备份的 userdata。 格式是:{时间戳10位}_{年_月_日}_{gitlab版本号}_gitlab_backup.tar。 注意,在还原时,版本不一致的备份,是不能还原的,所以需要将 gitlab 的版本保持在一个固定的版本。 还原前提条件: 您已经安装了与创建备份的 GitLab Omnibus 完全相同的版本和类型(CE / EE)。 你 sudo gitlab-ctl reconfigure 至少跑了一次。 GitLab 正在运行。如果没有,请使用它 sudo gitlab-ctl start。 操作步骤: 停止连接到数据库的进程,剩下部分继续 running sudo gitlab-ctl stop unicorn sudo gitlab-ctl stop sidekiq可以在停止之后,查看 gitlab 的状态sudo gitlab-ctl status,确认一下两个服务已经停止。 再在已有的备份中,选择需要恢复的版本 sudo gitlab-rake gitlab:backup:restore BACKUP={备份的文件夹名} 恢复过程中,会有几次确认信息要手动确认,如果是确认要恢复,按照提示点击 yes 就好了。 恢复完之后,要重启 gitlab 服务 sudo gitlab-ctl restart此时再去访问 gitlab 的访问地址,就恢复到重置 root 密码的状态了。 如果是例如公司内部需要一个私有的仓库,最好还是使用 omnibus-package 安装,官方推荐,直接在设备的安装,配置等比较方便。 后续的内容都是以这种方式安装为例子。docker 之类的也可参考,不过是需要透过一层 docker 指令了。 默认配置文件地址/etc/gitlab/gitlab.rb,后续会说明更多的配置,非常重要。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"(一)使用docker安裝gitlab-ce","slug":"TechnicalEssays/AboutGit/1docker-install-gitlab-ce","date":"2019-12-19T10:12:43.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/19/TechnicalEssays/AboutGit/1docker-install-gitlab-ce/","link":"","permalink":"http://swmlee.gitee.io/2019/12/19/TechnicalEssays/AboutGit/1docker-install-gitlab-ce/","excerpt":"","text":"系列文章前言此 git/gitlab 系列文章,预计会分为 3 个部分 gitlab 的安装及常用配置说明 Git 入门和高级功能及常见问题的测试与解决 gitlab/git 推行使用规范示例 当然不会非常全面,着重于代码托管和合作开发部分。部分章节主用 PPT 说明,可通过相关章节内容去 Github 获取。 虽然目前相关内容很多,不过对于此份内容部分,都是亲自测试并在内部进行了推广测试,比较详实。对于团体希望使用 git/gitlab 管理代码和合作开发,多多少少能有些参考作用;对于个人学习使用 git 和 gitlab,也有更多一点的帮助。 一些测试和功能实现部分,未必是最优解,但的确是一个解,可做参考。 此篇后续十七篇文章,一起入门 git/gitlab 的世界。 如果命令中有诸如 <XXX> 或者 {XXX}的指代,记得把符号一起替换成实际的参数。 Git入门及常用命令、Gitlab用户数据备份与定时备份、gitlab-web基本功能说明、omnibus package安裝gitlab-ce 的cover图源网络。 docker 安装 gitlab-ce安装 dockersudo apt install docker.io安装完成之后,直接使用 docker 命令,可能会出现权限不足(permission denied)。 解决方法,将一般用户加入可用: sudo usermod –aG docker <username>设置完之后,一定要注销用户,再登入,才能生效,生效之后。ubuntu18 可能需要重启。 更多 docker 常用指令的简单说明,可参看之前使用 《Ubuntu18.04下docker基本指令和使用docker安装mysql》 安装前的清理因为重头来过,所以,我们先查看下是否有之前安装过的名叫 gitlab 的容器 docker ps 此处若没有,则没有 gitlab 的 docker;如果有,则用命令:docker rm gitlab移除 抓取官方 gitlab image 并使用 docker 运行容器直接终端输入指令 docker pull gitlab/gitlab-ce:latestpull 可能需要一点时间,取决你下载的网速。 下载成功之后,使用docker images命令可以看到下载的 docker 镜像列表。 使用下载好的 images,创建容器,例如: docker run --detach \\ --hostname 192.168.XX.XX \\ --publish 443:443 --publish 80:80 --publish 22:22 \\ --name gitlab \\ --restart always \\ --volume /srv/gitlab-ce/config:/etc/gitlab \\ --volume /srv/gitlab-ce/logs:/var/log/gitlab \\ --volume /srv/gitlab-ce/data:/var/opt/gitlab \\ gitlab/gitlab-ce:latest以上配置了:hostname:gtilab 的访问地址;publish:映像主机端口和 docker 中访问 gitlab 的端口;name:docker 容器名称;restart:是否自动重启;–volume:设定创建存放配置、日志、数据的文件夹. 在设定的位置(–volume),会生成以下几个文件夹,如下图 其作用可参考以下: 本地位置 容器位置 用途 /srv/gitlab-ce/data /var/opt/gitlab For storing application data /srv/gitlab-ce/logs /var/log/gitlab For storing logs /srv/gitlab-ce/config /etc/gitlab For storing the GitLab configuration files 正常的话,在创建完之后,会自动开启,开启成功之后,效果如下图(当看到 STATUS 为 healthy,表明已经正常启动): 登入之前创建 container 时的 hostname,可以查看到,如下图: 首次访问时,需要设定 root 管理员的密码,账号默认为 root,修改密码成功之后,则进入到登入画面,即可输入 root 账号密码,进入查看。 如果需要关闭容器,使用 stop 命令,例如停止/开启已有的 gitlab( 例如 docker name 为 gitlab) docker stop gitlab # 停止 docker start gitlab # 开启 如果发现启动不了 docker 的 gitlab,并提示端口 22 已被占用,可能就是 sshd 占用了端口。 关闭 ssh 即可: /etc/init.d/ssh stop当然,开启就是 /etc/init.d/ssh start如果配置有错,需要修改 gitlab 的配置文件gitlab.rb,可是使用命令 sudo docker exec -it gitlab editor /etc/gitlab/gitlab.rb去打开文件编辑。不过一旦打开了这个文件,就注意给external_url参数,赋予一个有效的值。就是 gitlab 访问地址需要可用。 修改了配置文件,需要重启该容器使其生效: docker restart gitlab备份和还原用户数据手动备份用户数据目前除了设定了一个 root 账号的密码之外,其它什么都没有,我现在备份一次此时的 gitlab user data,使用指令 docker exec -it gitlab gitlab-rake gitlab:backup:create备份成功之后,默认位置在存放 data 的路径下,也就是创建并运行容器时的配置路径:/srv/gitlab-ce/data/backups。 在 root 权限或者当前用户取得该文件夹权限后,可以看到该文件夹内部文件,gitlab-ce 备份的用户数据的格式例如: {时间戳10位}_{年_月_日}_{gitlab版本号}_gitlab_backup.tar 或者可直接使用命令行查看:docker exec -it gitlab ls /var/opt/gitlab/backups/ 还原备份的文件备份之后,后续作业如果出现问题,可以还原到这个原始版本;当然,如果定时备份,则可以随时还原到需要的时间节点版本。 还原命令: docker exec -it gitlab /opt/gitlab/bin/gitlab-rake gitlab:backup:restore BACKUP={时间戳10位}_{年_月_日}_{gitlab版本号}BACKUP=后面输入需要返回的备份文件名称 注意,gitlab 只能还原版本相同的备份文件,版本不同不能还原。 使用 docker 安装就像是装在了沙盒,不想要了可以直接删除,没有什么顾虑。但是操作起来比较麻烦,毕竟中间隔了一层。用于测试等轻量使用较宜。","categories":[{"name":"Git/Gitlba系列","slug":"Git-Gitlba系列","permalink":"http://swmlee.gitee.io/categories/Git-Gitlba系列/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://swmlee.gitee.io/tags/docker/"},{"name":"gitlab","slug":"gitlab","permalink":"http://swmlee.gitee.io/tags/gitlab/"}]},{"title":"Angular i18n使用说明","slug":"TechnicalEssays/angular-i18n-demo","date":"2019-12-18T14:32:47.000Z","updated":"2020-01-09T13:41:14.888Z","comments":true,"path":"2019/12/18/TechnicalEssays/angular-i18n-demo/","link":"","permalink":"http://swmlee.gitee.io/2019/12/18/TechnicalEssays/angular-i18n-demo/","excerpt":"","text":"angular 项目,在实际使用开发时,难免会遇到需要使用多国语言的需求,也就是 angular 的国际化 i18n 的需求。 那么如何做到呢? 本文主要通过对一个常见的注册页面,实现多国语言版本的切换,来说明 angular i18n 的用法。代码已放到github。 创建一个 angular 项目,并编写英文版注册页面1、创建项目创建就是直接终端输入ng new angular-i18n-demo即可。因为只是说明 i18n 的使用,不会过多复杂,不需要其他的模块例如 router、service 等。 目前前端开发,不使用一些样式(说白了就是 UI 组件)就很累,所以创建好 angular 项目之后,添加 angular material,使得注册页面稍微好看一点。在 angular 项目根目录终端输入ng add @angular/material即可。 2、编写页面不需要实现注册功能,构建一个注册页面就好了。因为已经使用了 angular material,就稍微用几个模块。 2.1、在 app.module.ts 引入 angular material 模块在 app.module.ts 的@NgModule 装饰器下的 imports 属性中,添加以下内容: BrowserAnimationsModule, MatToolbarModule, MatCardModule, MatInputModule, MatButtonModule, MatIconModule然后逐一导入,在顶部应该会有: import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatInputModule, MatCardModule, MatToolbarModule, MatButtonModule, MatIconModule } from '@angular/material';2.2 在 app 目录下,创建一个注册的组件如下图: 我这个是直接使用指令ng g component register生成,angular-cli 工具的操作,它会直接把这个组件也添加到模块文件: 2.3 添加代码:修改register.component.html代码如下: <div class='my-div'> <mat-toolbar> <mat-icon>create</mat-icon>&nbsp;&nbsp; Registration </mat-toolbar> <mat-card> <mat-card-content> <form> <div class="word-align">First Name</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="First name" name="fname" required> </mat-form-field> <br /> <div class="word-align">Last Name</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="Last Name" name="lname" required> </mat-form-field> <br /> <div class="word-align">Address</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="Address" name="address" required> </mat-form-field> <br /> <div class="word-align">Email</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="Email" name="email"> </mat-form-field> <br /> <div class="word-align">Password</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="Password" name="password"> </mat-form-field> <br /> <div class="word-align">Confirm Password</div> &nbsp;&nbsp;&nbsp; <mat-form-field> <input matInput placeholder="Confirm Password" name="confirmPassword"> </mat-form-field> <br /> </form> </mat-card-content> <mat-card-actions> <button mat-raised-button (click)="register()" color="primary">REGISTER</button> </mat-card-actions> </mat-card>修改register.component.scss代码如下: // div居中 .my-div { width: 40%; margin-left: 30%; } // 设定form中label等宽 .word-align { display: inline-block; text-align: justify; text-justify: distribute-all-lines; /*ie6-8*/ text-align-last: justify; /* ie9*/ -moz-text-align-last: justify; /*ff*/ -webkit-text-align-last: justify; /*chrome 20+*/ width: 70px; margin-right: 0px; }因为我们不会去实现注册功能,所以在register.component.ts中,只需要添加一个空白注册函数就好了: import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['./register.component.scss'] }) export class RegisterComponent implements OnInit { constructor() { } ngOnInit() { } register() { } // 新建一个空白注册函数 }2.4 将注册页面显示出来现在运行这个 angular 项目,首页看到的还是默认的界面。删除 app.component.html 原本的所有东西,添加一句各个注册组件的 selector: <app-register></app-register>我用ng server -o --port 1234启动,在浏览器中应该看到如下画面: 到这里,常规的注册页面就完成了,是个英文版本,i18n 准备工作就好了,下面开始实现变简体中文。 修改注册页面为简体中文版本在 angular 官方文档中,我们可以看到i18n 模板翻译分为 4 个阶段,主要就是 在组件模板中标记需要翻译的静态文本信息。 创建翻译文件:使用 Angular CLI 的 xi18n 命令,把标记过的文本提取到一个符合行业标准的翻译源文件中。 编辑所生成的翻译文件:把提取出的文本翻译成目标语言。 把翻译完成的文件合并回应用 一步一步来。 1、组件模板中标记需要翻译的静态文本信息常规的标签直接在标签中添加 i18n 关键字即可,如下图: 要翻译的内容的元素标签中其他内容时,可以使用 ng-container 将文本包裹起来,示例如下: 如果是要翻译某个标签的属性值,则需要添加 i18n-<属性名>标签,示例如下: 更多用法参看 angular 官网i18n 模板翻译 2、创建翻译源文件将模板做好标记后,使用指令ng xi18n --output-path <path>,生成翻译源文件,<path>则是文件位置。 此外还有属性: –i18nFormat:明确指定想用的格式(默认 XLIFF 1.2) –out-file: 为提取工具生成的翻译源文件改名(默认为 messages ) –i18n-locale:指定应用的基本地区(对 angular 没有,但可能对其他需求有用) 等等 我的指令是ng xi18n --output-path src/locale --out-file register.zh-Hans.xlf,生成的翻译源文件如下图: 3、翻译文本节点打开生成的翻译源文件,找到文中的标签,可以看到里面有一个标签,这标签里面的文字,就是需要多国语言需要翻译的文字。 现在要做的就是,复制标签这一行,放到原本这一行下面,并把它的标签名名为 target,并把它的内容改为需要翻译的语言文字。 例如,我现在要做的是把英文翻译成简体中文,那么修改示例如下: 注意所有的标签都添加对应的标签。 特别注意: 翻译源文件里面有很多的 id,而且还特别长. 这是因为在“1、组件模板中标记需要翻译的静态文本信息”时,没有给每一个标记 i18n 的地方添加 id,所以工具自动生成了随机 id。 这个 id 最好不要修改,一旦修改可能在文字替换时就对应不上。 如果模板文件有修改,则可能需要重新再生成一份翻译源文件,因为这些随机 id 可能已经变了。 当然,如果不嫌麻烦,可以给每个模板文件有标记 i18n 的地方,都给他添加 id,那么生成的翻译源文件的 id 就不会变。 模板中添加 id 使用示例: <div class="word-align" i18n="@@firstName">First Name</div> &nbsp;&nbsp;&nbsp;(因为工具会生成随机 id,且如果需要翻译的文字太多,逐一设置 id 比较麻烦,所以我没有这样设 id) 4、修改 angular.json 配置文件4.1、添加编译配置打开 angular.json 文件,找到属性 projects–>{angular-i18n-demo(angular 项目名)}–>architect–>build–>configurations;在 production 同级属性,添加对不同语言的编译配置,如下图: 配置内容简单说明: 使用 AOT 方式编译 文件输出路径 i18n 文件位置 i18n 使用的格式 i18n 的位置 如果翻译有缺失报警告 添加基本路径 最后一条需要多说一点,就是翻译后,例如英文版本编译后访问的地址是http://localhost:4200/register,如果要看到中文版,则需要访问http://localhost:4200/register/zh-Hans 4.2、添加测试运行配置同样在 angular.json 文件,就在添加编译配置的父级 build 下一个属性 server,在 server–>configurations 属性,在 production 同级属性,添加以下内容: 注意:ng serve 运行时只能一次运行一种语言的翻译,无法同时加载多种语言。 5、测试运行,查看效果到这里,我们就有了一个简体中文版本的注册页面可以看了。启动项目,在项目根目录启动终端输入 ng serve --configuration=zh-Hans -o --port 1234项目启动成功,则可以看到简体中文版注册页面了,如下图: 注意看 url,之前的英文版是直接的http://localhost:1234,现在简体中文版是http://localhost:1234/zh-Hans 如果觉得每次测试运行查看修改输入文字太长,可以修改 package.json 文件,在 script 属性下,新加一句 "zh-Hans": "ng serve --configuration=zh-Hans -o --port 1234"就可以使用npm run zh-Hans启动了。 后续将 angular 项目便已打包部署到服务器例如 nginx,则可以设计点击按钮切换 url,切换语言,就和 MDN 一样效果。 例如https://developer.mozilla.org/en-US/docs/Glossary/JavaScript是英文,直接修改 url 为https://developer.mozilla.org/zh-CN/docs/Glossary/JavaScript就变成简体了。 小结到这里,angular i18n 多国语言使用说明就基本完了,更多的细节可以去 angular 官方国际化 i18n升华。 总结一下基本步骤: 准备好需要翻译的页面模板; 根据需要翻译的页面模板,创建对应的翻译源文件; 修改翻译源文件,添加标签即内容; 修改 angular 项目的配置; 运行查看结果。 源代码已放到github,已经整理添加了中文繁体、中文简体、英语、百度翻译版本日语,可参考配置 angular i18n 多国语言。","categories":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/categories/angular/"}],"tags":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/tags/angular/"},{"name":"i18n","slug":"i18n","permalink":"http://swmlee.gitee.io/tags/i18n/"}]},{"title":"将Node.js项目打包为一个可执行文件","slug":"TechnicalEssays/pack-nodejs-project","date":"2019-12-18T14:25:53.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/18/TechnicalEssays/pack-nodejs-project/","link":"","permalink":"http://swmlee.gitee.io/2019/12/18/TechnicalEssays/pack-nodejs-project/","excerpt":"","text":"实际上,nodejs 项目本来不需要做什么打包编译动作的,因为 js 本来也就不是编译型语言,只是个解释型语言,所以只要有 nodejs 运行环境,代码拷过去就能运行。 但是难免有些机器可能并没有安装 nodejs 运行环境,或者项目代码指定了 nodejs 的运行环境版本与实际不符合,就会多一个步骤去规整化运行环境的问题。 这本来不是个问题,那么简单打包后,变成一个可执行文件,就可以不用担心运行环境的问题了。 本文目的:将 Node 项目打包为可执行文件,可以在没有安装 Node.js 运行环境的设备上运行。 主要介绍两种将 nodejs 项目打包的工具,pkg 和 nexe。 使用 pkg 打包 nodejs 项目(网上推荐较多)注意 pkg 支持的 nodejs 版本问题注意:pkg 各个版本所支持的 Node 版本有所不同(主要集中在长期支持版本)。例如最新版本之 pkg4.4 不支持 Node9 版本。 如何查看 pkg 支持的 nodejs 版本? 访问 pkg 源码 package.json,在”dependencies”属性中,查看”pkg-fetch”的版本。这个 pkg-fetch 可以获取的 nodejs 版本,就是 pkg 工具包可以打包的版本。 例如现在的 pkg 默认是 4.4.2,对应的 pkg-fetch 是 2.6.4,找到 pkg-fetch 对应版本的源文件中的 patches.json 文件,地址[https://github.com/zeit/pkg-fetch/blob/master/patches/patches.json],得到以下数据: 如果安装的 pkg 版本,和实际需要打包的 nodejs 项目的版本不同,则可能会出现类似pkg error no available node version satisfies 'node 9'的报错信息。 为了节约大家的时间,秉承用新不用旧,我简单列举以下(2019/12/17 记): nodejs 版本为 v12、v10、v8、v6、v4、v0.12,用 pkg4.4 版本;nodejs 版本为 v9,用 pkg4.3 版本;nodejs 版本为 v7,用 pkg4.2 版本; 我好像没找到 pkg-fetch 中的 patches.json 有支持 nodejs v11、v5 版本的,所有就不列示了。 一般习惯,还是使用长期支持版本的 nodejs 开发为好,这样也可以直接使用最新版本的 pkg 而忽略版本问题了。 安装配置全局安装:npm install -g pkg安装成功,输入pkg -h就可以看到指令信息了。 一般操作说明如下:pkg 的基本语法是pkg [options] <input>,那么 [option] 简单说明: -t:指定打包的目标平台和 Node 版本,如-t node12-linux-x64,node12-win-x64,node12-macos-x64.-c:指定一个 JSON 配置文件,用来配置额外的打包脚本或资源。–options:指定 Node 或 V8 运行时选项,打包后默认执行,通过–option name 取消。-o:指定输出可执行文件名称,若使用了-t 指定多个目标,则需要使用—out-path 指定输出路径。-d:输出打包日志,以便排查问题。-b:从 node 源代码编译 node 二进制文件,默认会下载官方预编译的文件,使用该选项便不会有前面说明的 Node 版本支持问题,初次编译会耗用大量时间。 <input>可通过三种方式指定: 项目的入口文件如:pkg app.js;项目的 package.json 文件,pkg 会使用 package.json 中配置 bin 属性作为入口文件。项目的路径,pkg 会寻找路径中的 package.json。 打包项目:我简单的生成一个 express 项目,然后用来说明打包过程 创建 express 项目(如果没有安装过 express-generator,需要先安装npm install express-generator -g,再使用) express node-express-demo1、首先在 package.json 中配置 bin 属性作为入口。添加以下属性(express 项目默认入口文件就是./bin/www): "bin": "./bin/www"如存在不能自动打包的文件(如:require(变量)、非 Javascript 文件),则需要通过 pkg 属性手动配置。同样在 package.json 中添加 pkg 属性,类似: "pkg": { "scripts": ["xxx","xx"], "assets": ["xxx","xx"] }再执行 pkg 打包命令(注意你的 os 平台和 nodejs 版本,例如我的是 ubuntu 下 nodejs12.16 版本): david@ubuntu:~/TTT/node-express-demo$ pkg -t node12-linux-x64 -o pkg-ned -b package.json > [email protected] > Building base binary from source: built-v12.13.1-linux-x64 > Cloning Node.js repository from GitHub... git [====================] 100% > Checking out v12.13.1 > Applying patches > Compiling Node.js from sources... make [====================] 100% david@ubuntu:~/TTT/node-express-demo$这个首次编译打包等待时间可能会有好长,好长,好长,耐心等待一下。别看着没进度就忍不住把它终止了。但这是第一次执行时,后续同样的编译需求,则直接使用缓存的编译环境资源,就几秒钟了。 pkg 无法自动打包二进制模块文件,该类文件与平台有关,若依赖中包含此类文件需手动复制到打包后可执行文件目录下如 node-java 有编译 nodejavabridge_bindings.node 文件,需从 node_modules/java/build/Release 中复制出来。 运行打包后文件,程序可正常运行。 为方便使用,将 pkg 打包命令写入到 package.json,打包时运行 npm run pkg。 在 package.json 的 script 属性下,加入一句,例如: "pkg": "pkg -t node12-linux-x64 -o pkg-ned -b package.json"打包成功后,可以在 nodejs 项目的根目录下看见生成了 pkg-ned 文件,直接运行示例如下: 待解决问题:我之前使用成品的项目打包时没有遇到,但是这次直接使用的 express-generator 生成的模板项目打包,则出现了,运行打包后可执行文件,首页访问报错的问题,如下图: 这个问题待我找到原因再补充上。 使用 nexe 打包 nodejs 项目(实际测试较好用)nexe 可以通过命令行将 Node 项目打包为可执行文件。值得一说的是,nexe 预编译之 Node 只有偶数版本,所以例如使用 v9 版本,只能自行编译。 安装 nexe:npm install -g nexe 安装成功后,终端输入 nexe -h就可以看到命令格式了。 一般使用格式是 nexe <entry-file> [options]简单说明[options]:-i:指定 Node 项目入口文件-o:指定输出可执行文件路径名称-t:指定編譯平臺 Node 版本(支援版本)-n:指定主模块名称-r:添加资源文件-a:指定已构建的 nexe 二进制文件–build:从源码编译 Node 最简单的使用 ,在 nodejs 项目下运行 nexe <入口文件>,例如我的是 .bin/www,但是 nodejs 版本是 9.x david@ubuntu:~/TTT/node-express-demo$ nvm use 9.11.2 Now using node v9.11.2 (npm v5.6.0) david@ubuntu:~/TTT/node-express-demo$ nexe ./bin/www ℹ nexe 3.3.2 ✔ Downloading pre-built Node.js ✔ Finished in 1.037s Error: https://github.com/nexe/nexe/releases/download/v3.0.0/linux-x64-9.11.2 is not available, create it using the --build flag See nexe -h for usage.. david@ubuntu:~/TTT/node-express-demo$因为没有 nodev9 不是预编译版本,所以报错。因此就需要使用–build,如下: nexe -i app.js -o nexe-ned -t linux-x64-9.11.2 --build需要等待一些时间,还需要联网。整个过程就是下载对应的 node 资源,然后编译,写文件。当然,这也是第一次执行时,后续同样的编译需求,则直接使用缓存的编译环境资源。 具体显示流程如下: david@ubuntu:~/TTT/node-express-demo$ nexe -i ./bin/www -o nexe-ned -t linux-x64-9.11.2 --build ℹ nexe 3.3.2 ✔ Node source extracted to: /home/david/.nexe/9.11.2 ✔ Node binary compiled ✔ Entry: 'bin/www' written to: nexe-ned ✔ Finished in 2057.42s david@ubuntu:~/TTT/node-express-demo$运行效果如下: 小结总结起来,感觉使用 nexe 效果更好一点:首先没有版本限制,只是看能不能预编译(LTS 版本有预编译);额外的配置需求相对较少;测试模板项目测试没有出现意外;首次编译打包的时间相对较短很多。 当然这只是我的个人看法,也有人询问过 nexe 和 pkg 的区别,也引用如下: nexe: Bundles the application if desired using webpack. Downloads node source (or a prebuilt binary) Adds your application bundle as a native module (like fs, http, path etc) Applies arbitrary source patches. Maybe compiles downloaded source Inserts bundle into pre-sized binary Code is run as main when executable is run (instead of the repl) pkg: Bundles the application with a custom v8 script compiler into a snapshot Downloads the node source (or a prebuilt binary) Applies arbitrary source patches Maybe compiles downloaded source Appends snapshotted output to the end of the binary Snapshot (cachedData from v8) is loaded/run when binary executes. (以上可窥见 pkg 打包文件首页访问出错原因) 不过两者都是不错的 nodejs 打包工具了,有需求都可能尝试一下。","categories":[{"name":"nodejs","slug":"nodejs","permalink":"http://swmlee.gitee.io/categories/nodejs/"}],"tags":[{"name":"nodejs","slug":"nodejs","permalink":"http://swmlee.gitee.io/tags/nodejs/"},{"name":"pkg","slug":"pkg","permalink":"http://swmlee.gitee.io/tags/pkg/"},{"name":"nexe","slug":"nexe","permalink":"http://swmlee.gitee.io/tags/nexe/"}]},{"title":"使用 Verdaccio 搭建 npm 私有仓储","slug":"TechnicalEssays/verdaccio-private-registry","date":"2019-12-18T14:12:44.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/18/TechnicalEssays/verdaccio-private-registry/","link":"","permalink":"http://swmlee.gitee.io/2019/12/18/TechnicalEssays/verdaccio-private-registry/","excerpt":"","text":"前言在 2018 年 1 月份的时候,我有开始搭建 npm 私有仓库,那个时候的 Sinopia 也已经停止维护 3 年了,而 verdaccio 还可能不是特别受欢迎,只有 1700 个 star,搭建 npm 私有仓库的工具还有不少。 但在我今天写此文的时候,已经有 8.5K 的 star 了,版本也从 2.7.1 更新到了 4.4,而且基本上没有别的比较好的选择可以替代了。 无营养的话,两年前 sinopia 有 4318 个 star,现在还是增加为 5.3k 了,即便一直没更新,优秀的作品依旧优秀。 安裝使用 Verdaccio(本文是在 Ubuntu 下)1、安裝 verdaccio verdaccio 是个发布在 npm 上的命令行工具。可以通过 npm 直接下载安装: npm install -g verdaccio2、開啓服務verdaccio 在文件系统上存储数据,没有额外依赖,而且提供了一套默认配置,我们可以直接启动仓储服务。 在终端直接输入: verdaccio 终端上的日志显示了默认配置文件路径和 verdaccio 工作的地址端口。 3、查看安裝成功效果浏览器打开http://localhost:4873/ ,页面如下: 不得不说,这比两年前有自我辨识度,之前看起来就像是山寨 npmjs。 4、修改 npm 默认仓库地址 不过目前直接使用的 npm install 还是会去找 npmjs 的,所以,如果需要将默认寻找的地址改为自己的私有仓库,则需要修改 npm 默认的仓库地址,修改如下: npm set registry http://localhost:4873/查看有没有修改成功,使用npm get registry,查看显示的地址即可。 5、卸载 verdaccio 当然,如果不想用了,就是一个 npm 下载的工具包而已,直接卸载即可: npm uninstall -g verdaccio常规的配置说明配置文件一般在home/{user}/.config/verdaccio/config.yaml,把{user}替换成你的主机用户名。 简单说明如下(注意与实际配置对比参看): - storage: 设置托管或缓存包的存放目录 - auth: 权限控制 - htpasswd: 启用 htpasswd 插件管理权限 - file: 制定 htpasswd 文件路径,htpasswd 中存储者用户名和加密过的秘钥 - max\\*users: 最多允许注册用户数 - uplinks: 设置外部仓储,如果 verdaccio 找不到请求的包(非 verdaccio 托管),就会查找外部仓储。常见的有 - {name}: 外部仓储名称 - url: 访问路径 - timeout: 超时 - maxage: 默认值 2m,2m 钟内不会就同样的请求访问外部仓储 - fail\\*timeout: 如果外部访问失败,在多长时间内不回重试 - headers: 添加自定义 http 头当外部仓储访问请求中,例如 authorization: "Basic YourBase64EncodedCredentials==" - cache: 是否启用缓存,默认启用。 # 常用仓储有 npmjs: url: https://registry.npmjs.org yarnjs: url: https://registry.yarnpkg.com cnpmjs: url: https://registry.npm.taobao.org - packages: 包访问或发布控制 - {regexp}: 包名匹配正则。 - access: 访问控制,可选值有 - \\$all(用户不限制), - \\$anonymous(用户不限制), - \\$authenticated(所有登录用户), username( 用户名,需指定具体用户,可指定多个用户,用户间空格隔开,如 secret super-secret-area ultra-secret-area)。 - publish: 发布控制, - proxy: 代理控制,设置的值必选现在 uplinks 中定义。 # 常用的包名正则有: \\*\\* # 匹配任意包 @\\*/\\_ # 匹配任意 scope 包 @npmuser/\\_ # 匹配 scope 为 npmuser 的包 npmuser-\\* # 匹配包名有 npmuser- 前缀的包 # 包名正则规范通 gitignore 一致,verdaccio 内部使用 minimatch 实现的,如果需要书写更复杂的正则,可以参考 [minimatch](https://www.npmjs.com/package/minimatch/) 文档。 - web: 前端展示页面控制 - title: 设置页面标题 - logo: 指定 logo 图片文件路径 - publish: 发布包是的全局配置 - allow_offline: 在外部仓储离线时是否允许发布。在发布包是 verdaccio 会检查依赖包有效性,这个过程中需要访问外部仓储。 - url_prefix: 设置资源文件路径前缀。默认不需要设置,但如果使用 nginx 代理并改写了请求路径,就需要指定了。 - listen: 设置服务运行地址端口,默认为 http://localhost:4873 支持的配置有: localhost:4873 # default value http://localhost:4873 # same thing 0.0.0.0:4873 # listen on all addresses (INADDR_ANY) https://example.org:4873 # if you want to use https [::1]:4873 # ipv6 unix:/tmp/verdaccio.sock # unix socket - https: HTTPS 证书配置 - key: path/to/server.key - cert: path/to/server.crt - ca: path/to/server.pem - log: 日志控制 - type: file, stdout, stderr, 其中 stdout 需要同时指定 path - level: trace | debug | info | http (default) | warn | error | fatal - format: json | pretty | pretty-timestamped - http_proxy: 设置以 http 形式访问外部仓储时使用的代理 - https_proxy: 设置以 https 形式访问外部仓储时使用的代理 - no_proxy: 不使用代理的请求路径 - max_body_size: 请求时上传的 json 允许的最大值 - notify: 当有包发布成功时,verdaccio 会发送通知。通知实际上是一次 http 请求。支持配置多套通知 - method: 请求方法 GET,POST 等 HTTP Method - packagePattern: 包匹配正则, 这儿为 js 正则,仅当发布的包名匹配正则时才发送通知 - packagePatternFlags: js 正则标志位,如 i 忽略大小写 - headers: 自定义请求头 - endpoint: 请求地址 - content: handlebar 格式 html 模板,可以使用变量详见 Package Metadata上传私有包1、创建包 新建一个文件夹,并npm init,再创建一个 index.js 文件,内容如下图,将此包示例为个人私有包。 2、发布包 首先在私有仓库服务器注册一个用户,此处注册成功会自动登入仓库,输入注册语句(之前访问http://localhost:4873上面有显示): david@ubuntu:~$ npm adduser --registry http://localhost:4873 Username: david Password: Email: (this IS public) 183318×××@qq.com Logged in as david on http://localhost:4873/. david@ubuntu:~$ 如果之前有注册用户,此处可以直接登入 david@ubuntu:~$ npm login --registry http://localhost:4873 Username: david Password: Email: (this IS public) 183318×××@qq.com Logged in as david on http://localhost:4873/. david@ubuntu:~$登入成功之后,在刚刚创建的私有包的根目录下,运行发布包命令 npm publish,即可将包发布到私有仓库。 如果没有设定本机默认的 npm 仓库是指定的私有仓库,最好还是指定仓库位置,否则有可能是发布到 npmjs 上去。 david@ubuntu:~/TTT/privatePackage$ npm publish --registry http://localhost:4873 npm notice npm notice 📦 [email protected] npm notice === Tarball Contents === ……省略一些npm notice…… npm notice + [email protected] david@ubuntu:~/TTT/privatePackage$发布成功之后,再访问http://localhost:4873页面,就能看到已发布的私有包了。 当然后续下载使用,就和访问 npmjs 公共包是一样的。 其他常用操作与配置使用 pm2 启动verdaccio 毕竟还是一个 nodejs 程序,直接在命令行输入verdaccio启动了,保不齐因为何种原因崩溃。这类守护型工具,常用的就例如 forever 或者 pm2。以 pm2 为例,用最简单的pm2 start {program}来启动。 全局安装: npm install pm2 -g使用 pm2 start。 start 后跟 verdaccio 全局安装的启动地址: pm2 start {/home/david/.nvm/versions/node/v12.6.0/lib/node_modules/verdaccio/bin/verdaccio}如果不知道自己 mpnjs 全局安装的 package 的位置,输入npm config ls -l;找到参数 prefix. 例如我的就是prefix = "/home/david/.nvm/versions/node/v12.6.0". 那么我的 start 的路径就是:/home/david/.nvm/versions/node/v12.6.0/lib/node_modules/verdaccio/bin/verdaccio,如下图: 浏览器显示公共包之前有说明,如果你把 verdaccio 私有仓库设为默认仓库,如果某个 package 在私有仓库没有找到,可以设定在链接的外部仓库去寻找,例如我全局安装了一个 doctoc 工具,npm install -g doctoc。 david@ubuntu:~$ npm i -g doctoc /home/david/.nvm/versions/node/v12.6.0/bin/doctoc -> /home/david/.nvm/versions/node/v12.6.0/lib/node_modules/doctoc/doctoc.js + [email protected] updated 1 package in 7.961s david@ubuntu:~$因为我最开始,已经把 npm 的默认仓库地址改为私有地址了,又在配置文件中默认连接了 npmjs,所以执行这一句之后,首先在 verdaccio 的 storage 的位置找,找不到 doctoc,然后会去 npmjs (配置文件中的 uplinks 参数)去寻找,找到之后下载下来,也存放了一份到 storage 的位置。 现在去配置文件设置的 storage 的地址,应该是可以看到 doctoc 工具包,同样可以看到之前上传的 privatepackage 私有包: 当然,从安装结果可以看到,只是个索引,真实位置还是在 npm 全局安装的位置。 但是访问http://localhost:4873默认是不会显示的,一定程度来讲,共有的包也不必要显示在自家私有仓库内。 如果非要显示,也是可以的,修改显示的列表文件。 找到.verdaccio-db.json文件,默认就在私有仓库的设定 storage 的位置,verdaccio 默认位置~/.local/share/verdaccio/storage/.verdaccio-db.json.打开之后,把需要显示的包名,添加到 list 变量的对象中,如下图: 然后在重启 verdaccio。如果使用了 pm2,按上述说明作业,直接 pm2 restart verdaccio即可。如果还没有使用 pm2,直接关闭 verdaccio 的命令窗口在启动就好。 再访问http://localhost:4873,就可以看到 doctoc 包了,如下图: 以上实践有效,如果有问题,可提出交流,谢谢。","categories":[{"name":"npm","slug":"npm","permalink":"http://swmlee.gitee.io/categories/npm/"}],"tags":[{"name":"npm","slug":"npm","permalink":"http://swmlee.gitee.io/tags/npm/"},{"name":"verdaccio","slug":"verdaccio","permalink":"http://swmlee.gitee.io/tags/verdaccio/"}]},{"title":"Angular Schematics 简明教程","slug":"TechnicalEssays/angular-schematics-tutorial","date":"2019-12-12T12:39:17.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/12/TechnicalEssays/angular-schematics-tutorial/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/angular-schematics-tutorial/","excerpt":"angular schematics 101 :基本内容说明基础三问:angular schematics 是什么?有什么用?怎么用?","text":"angular schematics 101 :基本内容说明基础三问:angular schematics 是什么?有什么用?怎么用? angular schematics 是什么?首先要知道什么是 schematics(原理图):angular 官方文档说明: ①: 原理图是一个基于模板的支持复杂逻辑的代码生成器。它是一组通过生成代码或修改代码来转换软件项目的指令。 原理图会打包成集合(collection)并用 npm 安装。②: 原理图的集合可以作为一个强大的工具,以创建、修改和维护任何软件项目,特别是当要自定义 Angular 项目以满足你自己组织的特定需求时。例如,你可以借助原理图来用预定义的模板或布局生成常用的 UI 模式或特定的组件。你也可以使用原理图来强制执行架构规则和约定,让你的项目保持一致性和互操作性。 所以 简单来讲,angular schematics 就是 angular 生态圈中,针对 angular 项目的代码生成器。 所以 angular schematics 只是模板代码生成器?不可否则,schematics 最主要的作用就是代码生成器.有很多模板代码结构内容都是一样的,例如 component,每次新建一个 component,都需要复制上一份的代码进行修改. angular-cli 默认有一些可以按照模板生成的组件,例如 componet、router、service、interceptor 等等,这也是很多插件工具一键生成模板代码的依据。但是并不是所有代码都适合所有开发者。例如有些使用者常与 CURD 打交道,所以希望生成的 service 直接包含所有的 CRUD 函数,输入不同的名称对应生成不同产品。等等各自自适应、自构建的模板。 除此之外,它还能用于按照一定规则修改程式代码、快速按照所需模块等,就如引用的 angular 官方文档说明的第 ② 点。毕竟至少对应到了 ng generate 、 ng add 和 ng update 指令。 简单上手 angular schematics:创建一个 hello1、全局安装 schematics cli(需要 node 6.9 以上版本) npm install -g @angular-devkit/schematics-cli安装完成之后,可以在终端输入schmatics命令,能显示 schematics 的指令说明则表示安装成功。 2、创建一个空的原理图 schematics blank --name=hello生成的原理图中基本内容结构可参看官网说明集合的内容和命名原理图简单说明如下: collection.json 配置一般一个原理图就只配置一次,所以大部分内容都是在 index.ts 中实现,所以,一定先清楚这个文件的内容:具体可以参看官方说明原理图的概念简单说明如下: 3、运行原理图因为创建的是一个空白原理图,运行是不会有任何输出显示的。 3.1 在当前位置运行:先构建: npm run build再运行 schematics .:hello简单说明:ts 代码在编译之后转换成 js 代码才能运行,在原理图中,在执行npm run build之后,会出现其他的 XXX.d.ts、XXX.js、XXX.js.map。运行时,.:hello的.是当前位置,hello则是在 collection.json 中配置的原理图的名字。当然,一份 collection.json 中可以配置多个原理图,所以要指定使用的是哪一个。 “Nothing to be done.”显然没有任何意义,我们可以在 index.ts 中加入一句打印,看看效果: export function hello(_options: any): Rule { return (tree: Tree, _context: SchematicContext) => { console.log("hello"); return tree; }; }那么再次编译运行之后,就可以看到输入的是: david@ubuntu:~/TTT/hello$ schematics .:hello hello Nothing to be done. david@ubuntu:~/TTT/hello$3.2 在相对路劲下运行如果现在不在 hello 下运行了,那么就不能直接.:了,需要找到该原理图的 collection.json 文件,并指定使用哪一个。例如在 hello 同级目录下新建个 test 文件夹,在 test 中运行则是: david@ubuntu:~/TTT/test$ schematics ../hello/src/collection.json:hello hello Nothing to be done. david@ubuntu:~/TTT/test$以上就是 angular schematics 的基础说明和运行,是不是毫无实际意义?没错。接下来,我们开始进入有点实际作用的内容。 angular schematics 实现 ng generator 使用模板生成组件本节会实现,在 angular 项目中,使用 ng generator 指令生成一个通用的 component 组件,和一般向后台请求数据需要包含 CRUD 函数的 service 组件。here we go。 准备工作新建一个空白 schematics,如下: schematics blank --name=angular-schematics-tutorial将原本的内容: 修改为: 为了方便,不用每次修改都运行 build,在此项目的 package.json 的 script 加入一行: "build:watch": "tsc -p tsconfig.json --watch"使得该项目一致在 watch 状态下,然后运行 npm run build:watch准备工作完成,进入正题。 实现创建通用 component 组件一般每个命令原理图都包含以下内容: index.ts: 定义命名原理图中转换逻辑的代码。schema.json: 原理图变量定义。schema.d.ts: 原理图变量。files/: 要复制的可选组件/模板文件。 我们的也不例外。 创建 component 文件模板 template一般 angular component 会包含 4 个文件:XXX.html,XXX.scss,XXX.spec.ts,XXX.ts。所以,先准备好这些模板。 在/component 文件夹下新建 files 文件夹,并创建以下 4 个文件,效果如下: 简单说明两个函数: classify() 方法接受一个值,并返回标题格式(title case)的值。比如,如果提供的名字是 my service,它就会返回 MyService。dasherize() 方法接受一个值,并以中线分隔并小写的形式返回值。比如,如果提供的名字是 MyService,它就会返回 “my-service” 的形式。 此 component 模块的效果就是,在新建一个 component 时,输入 component 的名字,例如 apple,则会在该 angular 项目的 src/app/下,新建一个文件夹 apple,下面 4 个文件 apple.component.html, apple.component.scss,apple.component.spec.ts,apple.component.ts。 模板内容分别如下:__name@dasherize__.component.html.template文件: <p> <%= dasherize(name) %> works! </p> __name@dasherize__.component.spec.ts.template文件: /* tslint:disable:no-unused-variable */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; describe('<%= classify(name) %>Component', () => { let component: <%= classify(name) %>Component; let fixture: ComponentFixture<<%= classify(name) %>Component>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [<%= classify(name) %>Component] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(<%= classify(name) %>Component); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });__name@dasherize__.component.ts.template文件: import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-<%= dasherize(name) %>', templateUrl: './<%= dasherize(name) %>.component.html', styleUrls: ['./<%= dasherize(name) %>.component.scss'] }) export class <%=classify(name)%>Component implements OnInit { constructor() { } ngOnInit() { } }scss 部分就没有必要了。此部分内容,可参考 angular-cli 源代码内容:angular-cli component 组件模板 配置 schema.json,定义该原理图可用选项在 component 文件夹下新建 schema.json,并输入以下内容: { "$schema": "http://json-schema.org/schema", "id": "componentSchema", "title": "component options schema.", "type": "object", "descripiton": "创建一个component范本", "properties": { "name": { "description": "component的名字.", "type": "string", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "你想创建的component的名字:" } }, "required": [ "name" ] }属性说明: id:这个模式定义在集合中的唯一 id。title:一个人类可读的模式描述。type:由这些属性提供的类型描述符。properties:一个定义该原理图可用选项的对象。required:必填的选项 注意属性(proerties)选项: $default 的设定,上面的表示,如果没有指定输入的选项,那么输入的第一个就是 name x-prompt:如果没有输入选项,则提示语提示输入 创建好 schema.json 之后,一定要记得在 collection.json 中配置 schema 属性 创建 schema.d.ts,定义的各个选项的值一般的,可以手动创建 schema.d.ts,如本生成 component 的原理图,它的 schema.json 中属性只有一个必填的 name,那么编写的 schema.d.ts 内容就如下: export interface Schema { name: string; }实际上,这个文件可以使用指令生成,在 schema.json 的同级目录下,开启终端输入指令,如下: npx -p dtsgenerator dtsgen schema.json -o schema.d.ts效果如下 但注意,生成之后把 declare 改成 export,因为需要把这接口导出使用。 编写规则工厂逻辑代码以上都定义完之后,便到了最重要的环节,编写逻辑代码实现 componet 组件的生成。 简单分析,至少我们有以下几步工作需要完成:第一步:判断目标项目是不是 angular 项目。如果不在 angular 项目中去生成 angular 的 componet,那就没什么意义。这一步可以提出来通用。 同理,@schematics 对 angular 有很多的已有支持,我们这里可以用到一些便捷的方法,所以需要先安装@schematics/angular,输入指令如下: npm install @schematics/angular -S第二步:读取预设的模板 template 文件,并将使用者输入选项应用到模板。第三步:合并模板文件,返回新的 tree。 修改 index.ts 代码具体如下: import { Rule, SchematicContext, Tree, apply, mergeWith, url, move, applyTemplates, SchematicsException } from '@angular-devkit/schematics'; import { strings } from '@angular-devkit/core'; import { ComponentSchema as Schema } from './schema' import { buildDefaultPath } from '@schematics/angular/utility/project'; import { parseName } from '@schematics/angular/utility/parse-name'; export function genComponent(_options: Schema): Rule { return (tree: Tree, _context: SchematicContext) => { // 获取到在angular cli工作区下的 路劲和要生成的组件 前缀name const { name, path } = getParsePath(tree, _options); // 读取模板文件 const sourceTemplates = url('./files'); // 应用模板文件 const sourceParametrizedTemplates = apply(sourceTemplates, [ applyTemplates({ ..._options, ...strings, name }), move(path) ]); // 将传入的值(option)与模板文件合并(传入值替代模板变量值) return mergeWith(sourceParametrizedTemplates)(tree, _context); }; } function getParsePath(tree: Tree, options: any): any { // 读取angular.json文件并存为buffer const workspaceConfigBuffer = tree.read("angular.json") // 判断是不是在一个angular-cli工作区 if (!workspaceConfigBuffer) { throw new SchematicsException('不在angular cli工作区,请在angular项目中执行!') } // 读取并整理angular配置 const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString()); // 有传入project属性或者是默认project const projectName = options.project || workspaceConfig.defaultProject; // 获取project定义 const project = workspaceConfig.projects[projectName]; // 获取默认project路径 const defaultProjectPath = buildDefaultPath(project); // parseName()可以把路径和文件名拆开,取得path和name // 例如 src/feartures/login,会被拆分为 path:src/features 和 name:login const parsePath = parseName(defaultProjectPath, options.name); return parsePath; }像上述的buildDefaultPath,parseName方法就是@schematics/angular 中提供的。 编写测试用例测试代码这个测试方面的内容也是个大项,这里就不多说明,简单说一下我们的测试用例。我的测试就是模拟生成一个 angular 项目,然后看看是否在里面生成了 component 的那 4 个文件,若有生成,那么通过,否则就失败。修改 index_spec.ts 代码如下: import * as path from 'path'; import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import { Schema as ApplicationOptions, Style } from '@schematics/angular/application/schema'; import { Schema as WorkspaceOptions } from '@schematics/angular/workspace/schema'; import { strings } from '@angular-devkit/core'; const collectionPath = path.join(__dirname, '../collection.json'); describe('component', () => { // 选项 --name的值 const name = 'apple'; const runner = new SchematicTestRunner('schematics', collectionPath); // 模拟ng new创建angular项目,主要对workspace和application进行配置 // angular项目的配置 const workspaceOptions: WorkspaceOptions = { name: 'workspace', // 不重要的名字,随意取,不影响测试结果 newProjectRoot: 'projects', // 项目app的根目录,可以随意取,但是验证会用到 version: '6.0.0', // 版本号,随意,不影响测试 }; const appOptions: ApplicationOptions = { name: 'component', // 项目名称 inlineStyle: false, // 以下是项目属性,随意true/false,不影响测试结果 inlineTemplate: false, routing: false, style: Style.Css, skipTests: false, skipPackageJson: false, }; // 调用 SchematicTestRunner 的 runExternalSchematicAsync 方法,并以给出的参数生成angular项目 let appTree: UnitTestTree; beforeEach(async () => { appTree = await runner.runExternalSchematicAsync( '@schematics/angular', 'workspace', workspaceOptions ).toPromise(); appTree = await runner.runExternalSchematicAsync( '@schematics/angular', 'application', appOptions, appTree ).toPromise(); }); // 最基本的判断,如果生成的文件名和预期生成的文件名一致,就验证成功 it('works', async () => { // runSchematicAsync()参数:项目名、参数、Tree const tree = await runner.runSchematicAsync('component', { name }, appTree).toPromise(); const dasherizeName = strings.dasherize(name); /* 以下其实可以直接4个toContain代替*/ // 预期生成的文件 const expectFiles = [ `/projects/component/src/app/${dasherizeName}/${dasherizeName}.component.ts`, `/projects/component/src/app/${dasherizeName}/${dasherizeName}.component.html`, `/projects/component/src/app/${dasherizeName}/${dasherizeName}.component.scss`, `/projects/component/src/app/${dasherizeName}/${dasherizeName}.component.spec.ts`, ] // 如果实际模拟的angular项目中拥有预期生成的文件,则将它从expectFiles中移除 for (const v of tree.files) { for (let i = 0; i < expectFiles.length; i++) { const e = expectFiles[i]; if (v.toString() === e) { expectFiles.splice(i, 1); } } } //如果预期生成的文件都有生成,那么预期的应该是0=0成立 expect(0).toEqual(expectFiles.length); }); });运行测试用例就很简单了,直接npm run test,如果测试成功,终端应该如下输出: david@ubuntu:~/TTT/angular-schematics-tutorial$ npm run test > [email protected] test /home/david/TTT/angular-schematics-tutorial > npm run build && jasmine src/**/*_spec.js > [email protected] build /home/david/TTT/angular-schematics-tutorial > tsc -p tsconfig.json Randomized with seed 66156 Started . 1 spec, 0 failures Finished in 0.369 seconds Randomized with seed 66156 (jasmine --random=true --seed=66156) david@ubuntu:~/TTT/angular-schematics-tutorial$实际 angular 项目运行测试用例通过可能很好,但最好还是去实际项目中测试一下,向生成一个 angular 项目: ng new angular-demo在该 angular-demo 项目中运行指令angular-demo$ schematics ../angular-schematics-tutorial/src/,可以看到结果如下: david@ubuntu:~/TTT/angular-demo$ schematics ../angular-schematics-tutorial/src/collection.json:component --debug=false ? 你想创建的component的名字: comp/apple CREATE /src/app/comp/apple/apple.component.html (23 bytes) CREATE /src/app/comp/apple/apple.component.scss (0 bytes) CREATE /src/app/comp/apple/apple.component.spec.ts (755 bytes) CREATE /src/app/comp/apple/apple.component.ts (607 bytes) david@ubuntu:~/TTT/angular-demo$简单说明:其中指令 –debug=false 是为了实际生成文件,如果不加这一句,不会生成实际的文件。因为没有直接输入--name=comp/apple或者直接comp/apple,所以出现了输入提示语。直接输入 schematics ../angular-schematics-tutorial/src/collection.json:component --name=comp/apple --debug=false则不会出现提示语。 注意:如果已有同位置同名文件,再次生成会报错,提示already exists.。 我们查看生成的 component 组件模块内容: 可以看到,文件有实际生成,模板内容有被替换。 恭喜,如果到这里都正确,那么已经完成了自定义原理图的制作和使用了。 所以,想要生成一个具有 CRUD 函数的 service 步骤效果就是一样的,不再赘述,具体可参看代码。 理论上,一个原理图项目(schematics)可以有很多个原理图(schematic),但是要在 collection 中统一指定配置。 总结制作原理图的一般步骤: 1、新建原理图使用模板文件; 2、创建该原理图需要的 schema.json 并将该原理图配置到 collection.json; 3、依照 schema.json 创建接口 schema.d.ts; 4、在 index.ts 中编写实现该原理图目的的逻辑代码; 5(非必须)、编写测试用例进行测试; 6(测试)、利用测试用例或实际项目进行测试。 angular schematics 实现 ng add 指令安装模块ng add 有什么用? 将对外部库的支持添加到您的项目中。 如何使用? ng add [options] 但是实际上,这个能够直接使用 ng add 也是应该这个需要添加的库,内部实现了对此原理图的功能。 举个例子,众所周知,angular material 可以直接使用 ng add 添加到已有的 angular 专案。执行ng add @angular/material。 效果如下: 但是,如果我想添加 Font Awesome 到 angular 项目,你就需要 npm install @fortawesome/fontawesome-svg-core npm install @fortawesome/free-solid-svg-icons npm install @fortawesome/angular-fontawesome@<version>然后手动引入模块,声明使用等等操作。 为什么不能使用 ng add?你看: font awesome 不支持直接的 ng add,因为 package 中没有实现导入等作业,所以只执行了安装。 所以本节的目标,就是编写一个原理图,实现 ng add 的时候1、一次性安装以上 3 个 package,2、在 app.module.ts 中引入 FontAwesomeModule3、在 app.component.ts 声明并在 app.component.html 实例化。 创建 ng add 的 schema.json 并配置在与之前 component 文件夹同级的路径下,创建 ng-add 文件夹。 在 ng-add 文件夹下创建 schema.json 文件,并编写如下内容: { "$schema": "http://json-schema.org/schema", "id": "NgAddSchema", "title": "Ng-Add Schema", "type": "object", "description": "给angular项目添加 Font-Awesome。", "properties": { "project": { "type": "string", "description": "给angular项目添加 Font-Awesome。" } } }在 collection.json 中的 “schematics”添加子项,内容如下: "ng-add": { "description": "给angular项目中添加 Font-Awesome库。", "factory": "./ng-add", "schema": "./ng-add/schema.json" }注意名称保持 ng-add。 生成并导出 schema.d.ts 文件schema.d.ts 文件内容大概如下: /** * Ng-Add Schema * 给angular项目添加 Font-Awesome。 */ export interface NgAddSchema { /** * 给angular项目添加 Font-Awesome。 */ project?: string; }编写规则工厂实现逻辑代码在文件夹 ng-add 下新建 index.ts 文件,并写入以下代码: import { Rule, SchematicContext, Tree, SchematicsException } from '@angular-devkit/schematics'; import { buildDefaultPath } from '@schematics/angular/utility/project'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; import { NgAddSchema } from './schema'; import { addImportToModule } from '@schematics/angular/utility/ast-utils'; import { InsertChange } from '@schematics/angular/utility/change'; import * as ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; export default function (_options: NgAddSchema): Rule { return (_tree: Tree, _context: SchematicContext) => { // 如果不是 Angular 项目则抛出错误 const workspaceConfigBuffer = _tree.read('angular.json'); if (!workspaceConfigBuffer) { throw new SchematicsException('Not an Angular CLI workspace'); } // 取得 project 的根目录 const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString()); const projectName = _options.project || workspaceConfig.defaultProject; const project = workspaceConfig.projects[projectName]; const defaultProjectPath = buildDefaultPath(project); //将 FortAwesomeModule 加入 AppModule const modulePath = `${defaultProjectPath}/app.module.ts`; const sourceFile = readIntoSourceFile(_tree, modulePath); const importPath = '@fortawesome/angular-fontawesome'; const moduleName = 'FontAwesomeModule'; const declarationChanges = addImportToModule(sourceFile, modulePath, moduleName, importPath); const declarationRecorder = _tree.beginUpdate(modulePath); for (const change of declarationChanges) { if (change instanceof InsertChange) { declarationRecorder.insertLeft(change.pos, change.toAdd); } } _tree.commitUpdate(declarationRecorder); // 将某个 icon 引入到 app.component.ts,再到 app.component.html 中使用它。(声明并实例化) // 获取 app.component.ts 的 AST const componentPath = `${defaultProjectPath}/app.component.ts`; const componentSourceFile = readIntoSourceFile(_tree, componentPath); // 取得所有的 ImpotDeclaration const allImports = componentSourceFile.statements.filter(node => ts.isImportDeclaration(node))! as ts.ImportDeclaration[]; // 找到最后一个 ImpotDeclaration let lastImport: ts.Node | undefined; for (const importNode of allImports) { if (!lastImport || importNode.getStart() > lastImport.getStart()) { lastImport = importNode; } } // 找到 ClassDeclaration const classDeclaration = componentSourceFile.statements.find(node => ts.isClassDeclaration(node))! as ts.ClassDeclaration; // 取得所有的 property const allProperties = classDeclaration.members.filter(node => ts.isPropertyDeclaration(node))! as ts.PropertyDeclaration[]; // 取得最后一个 propery let lastProperty: ts.Node | undefined; for (const propertyNode of allProperties) { if (!lastProperty || propertyNode.getStart() > propertyNode.getStart()) { lastProperty = propertyNode; } } const componentRecorder = _tree.beginUpdate(componentPath); const importFaCoffee = '\\nimport { faCoffee } from \\'@fortawesome/free-solid-svg-icons\\';'; componentRecorder.insertLeft(lastImport!.end, importFaCoffee); // 添加声明部分的代码 const faCoffeeProperty = 'faCoffee = faCoffee;' const changeText = lastProperty ? lastProperty.getFullText() : ''; let toInsert = ''; if (changeText.match(/^\\r?\\r?\\n/)) { toInsert = `${changeText.match(/^\\r?\\n\\s*/)![0]}${faCoffeeProperty}`; } else { toInsert = `\\n ${faCoffeeProperty}\\n`; } // 插入字串 if (lastProperty) { componentRecorder.insertLeft(lastProperty!.end, toInsert); } else { componentRecorder.insertLeft(classDeclaration.end - 1, toInsert); } _tree.commitUpdate(componentRecorder); //在 app.component.html 里面加上 <fa-icon [icon]="faCoffee"></fa-icon> : const htmlPath = `${defaultProjectPath}/app.component.html`; const htmlStr = `\\n<fa-icon [icon]="faCoffee"></fa-icon>\\n`; const htmlSourceFile = readIntoSourceFile(_tree, htmlPath); const htmlRecorder = _tree.beginUpdate(htmlPath); htmlRecorder.insertLeft(htmlSourceFile.end, htmlStr); _tree.commitUpdate(htmlRecorder); // 修改 package.json const dependencies = [ { name: '@fortawesome/fontawesome-svg-core', version: '~1.2.25' }, { name: '@fortawesome/free-solid-svg-icons', version: '~5.11.2' }, { name: '@fortawesome/angular-fontawesome', version: '~0.5.0' } ]; dependencies.forEach(dependency => { addPackageToPackageJson( _tree, dependency.name, dependency.version ); }); // 使用 Schematic安装3个依赖 Package 。 // 使用Angular Schematics 的 API - NodePackageInstallTask 。 _context.addTask( new NodePackageInstallTask({ packageName: dependencies.map(d => d.name).join(' ') }) ); return _tree; }; }; //读取文件 function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile { const text = host.read(modulePath); if (text === null) { throw new SchematicsException(`File ${modulePath} does not exist.`); } const sourceText = text.toString('utf-8'); return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); } // 给package.json添加依赖包 function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { if (host.exists('package.json')) { const sourceText = host.read('package.json')!.toString('utf-8'); const json = JSON.parse(sourceText); if (!json.dependencies) { json.dependencies = {}; } if (!json.dependencies[pkg]) { json.dependencies[pkg] = version; json.dependencies = sortObjectByKeys(json.dependencies); } host.overwrite('package.json', JSON.stringify(json, null, 2)); } return host; } // 对象key排序 function sortObjectByKeys(obj: any) { return Object.keys(obj).sort().reduce((result, key) => (result[key] = obj[key]) && result, {} as any); }代码不用细读,大约做了以下几件事: 1、判断是不是 angular 项目2、获取 angular.json 中参数配置3、在 app.module.ts 中引入 FontAwesomeModule4、在 app.component.ts 声明一个 Font5、在 app.component.html 实例化。6、修改模板项目 package.json 依赖列表,加入那 3 个库;7、实现那 3 个库的安装。…… 测试并使用如果需要写测试用例,那么可以 ng-add 文件夹下添加 index_spec.ts 并写代码(可参看源码) 直接在 angular 项目中实践: 如果到这里都正常,那么恭喜你,自定义原理库实现 ng add 也成功了! angular schematics 实现 ng update 更新模块时修改指定组件内容上面 2 节分别实现了 ng generator 和 ng add,那么接下来就是 ng update 了。关于 angular schematics 自定义 ng update 的实践目前网上找的示例都比较少,还不是很清晰。我们这里也会做个实例,但是不会深究。 ng update 有什么用? 更新您的应用程序及其依赖项。 如何使用? ng update [options] 一般的,如果你的 angular 项目是老旧版本的,运行ng update指令,可能就会出现类似以下的内容: Using package manager: 'npm' Collecting installed dependencies... Found 33 dependencies. We analyzed your package.json, there are some packages to update: Name Version Command to update -------------------------------------------------------------------------------- @angular/cdk 8.1.4 -> 8.2.3 ng update @angular/cdk @angular/cli 8.3.0 -> 8.3.20 ng update @angular/cli @angular/core 8.2.3 -> 8.2.14 ng update @angular/core @angular/material 8.1.4 -> 8.2.3 ng update @angular/material rxjs 6.4.0 -> 6.5.3 ng update rxjs 大家可以仔细看看,这份分析结果说明了什么?一共有 33 个依赖,但是只有 5 个可以使用 ng update 升级?从哪里看出哪些依赖能用 ng update 升级?各个依赖包的 package.json ug update 的原理逻辑还是比较复杂了,其实直接看 angular-cli 的升级,比较容易看出端倪。 访问https://github.com/angular/angular-cli/tree/master/packages/schematics/angular/migrations, 查看 migration-collection.json 文件,随意选一个,例如以下: // 上略 "migration-07": { "version": "8.0.0-beta.12", "factory": "./update-8", "description": "Update an Angular CLI project to version 8." }, // 下略看起来大概意思就是,ug update 将 angular 升级到 8.0.0-beta.12 版本时,就会执行migration-07的升级相关命令,其工厂函数内容就在 update-8 的文件夹中。 有兴致可以细研究 ng update 到 8.0.0-beta.12 具体做了什么。我们只需要简单知道migration-07他会依照相关规则去执行一些动作。 那么,这份migration-collection.json是在哪里使用的呢? 可以看到,在这里:https://github.com/angular/angular-cli/blob/master/packages/angular/cli/package.json,在 angular-cli 的 package.json 文件中被定义的。 以上只是一些我在知道结果之后抛出来给大家看的关键点,大家可以深入研究。我在这里,就简单写个示例,供分析。 注意:为了后续 ng update 实际使用效果,在此时进行后续作业前,可将现在的代码备份一下,作为 0.0.1 版本。 版本号的更改当然在 package.json 中。 本节需要实现的目标是:1、本 angular-schematics-tutorial package 在 angular 项目中使用时,可以使用 ng update 进行升级2、在升级到指定版本时,能够按照升级需求修改已有 angular 项目中部分规则内容。 这也是在使用 ng update 升级 angular 项目时,可以看到很多旧版本的写法,他会自动纠正成新版本的写法。 不过,依照我的简单总结,使用 ng update 升级,其实有几点要做: 创建 migration.json 文件在 src/文件夹下,与 collection.json 同级目录,新建 migration.json 文件(名字虽然不限制,但是以便识别),并写入以下内容: { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "migration002": { "version": "0.0.2", "description": "更新angular-schematics-tutorial到0.0.2版本时执行的更新", "factory": "./ng-update/index#update" } } }此份说明,当后续把这个 schematics 项目打包之后,其他 angular 项目中使用了这个包,升级到 0.0.2 版本时,就要执行在/ng-update/index.ts 文件中的逻辑规则更新代码了。 在 package.json 中声明 ug-update 配置在 package.json 中,添加以下项目: "ng-update": { "migrations": "./src/migration.json" },其作用,就是在执行 ng update 时,能够找到对应的配置文件 编写更新执行的规则工厂逻辑代码因为只是简单示例,我简单的实现,如果把此 angular-schematics-tutorial 包升级到了 0.0.2 版本,那么一并更新 app.component.ts 里面的 title 变量的值,为AngularSchematicsTutorial002,如果在我之前使用的测试 angular-demo 中运行,可以先看到,目前的 title 值应该是title = 'angular-demo';。 在 component 同级文件夹路径下新建 ng-update 文件夹,并添加 index.ts 文件,并添加以下代码: import { Rule, Tree, SchematicContext, SchematicsException } from '@angular-devkit/schematics'; import { buildDefaultPath } from '@schematics/angular/utility/project'; import * as ts from 'typescript'; export function update(): Rule { return (_tree: Tree, _context: SchematicContext) => { // 解析angular项目 const workspaceConfigBuffer = _tree.read('angular.json'); if (!workspaceConfigBuffer) { throw new SchematicsException('Not an Angular CLI workspace'); } const workspaceConfig = JSON.parse(workspaceConfigBuffer.toString()); const projectName = workspaceConfig.defaultProject; const project = workspaceConfig.projects[projectName]; const defaultProjectPath = buildDefaultPath(project); // 把 app.component.ts 转成 Typescript AST const componentPath = `${defaultProjectPath}/app.component.ts`; const componentSourceFile = readIntoSourceFile(_tree, componentPath); // 找出 title 变量 const classDeclaration = componentSourceFile.statements.find(node => ts.isClassDeclaration(node))! as ts.ClassDeclaration; const allProperties = classDeclaration.members.filter(node => ts.isPropertyDeclaration(node))! as ts.PropertyDeclaration[]; const titleProperty = allProperties.find(node => node.name.getText() === 'title'); // 如果有找到 title 变量,则修改它的值 if (titleProperty) { const initialLiteral = titleProperty.initializer as ts.StringLiteral; const componentRecorder = _tree.beginUpdate(componentPath); const startPos = initialLiteral.getStart(); componentRecorder.remove(startPos, initialLiteral.getWidth()); componentRecorder.insertRight(startPos, '\\'AngularSchematicsTutorial002\\''); _tree.commitUpdate(componentRecorder); } return _tree; } } function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile { const text = host.read(modulePath); if (text === null) { throw new SchematicsException(`File ${modulePath} does not exist.`); } const sourceText = text.toString('utf-8'); return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); }内容可不细看,就是找到 angular 项目中的 app.component.ts,把 title 的值改为 AngularSchematicsTutorial002。 测试并使用当然,再次在 ng-update/index.ts 下创建 index_spec.ts 编写测试用例也是 ok 的,源代码中会给出。若不用这个麻烦,可以直接在之前的 angular-demo 专案的测试,不过因为是相对路径,则不能直接使用 ng update 了,因为它不知道去哪里判断是不是新版本的更新。 在 angular-demo 中运行: schematics ../angular-schematics-tutorial/src/migration.json:migration002 --debug=false执行之后,应该会得到以下结果: david@ubuntu:~/TTT/angular-demo$ schematics ../angular-schematics-tutorial/src/migration.json:migration002 --debug=false UPDATE /src/app/app.component.ts (318 bytes) david@ubuntu:~/TTT/angular-demo$并且,app.component.ts 中title = 'angular-demo';变成了title = 'AngularSchematicsTutorial002'; 如果能够运行成功,说明 angular schematics 构建 ng update 原理图也学习完成。 将原理图 package 发布并使用以上在执行时,都是使用相对路径在项目中运行,一点都不专业?那么我们可以把这个 schematics 工具包发布,后续直接使用 ng 指令运行。 使用 npm link 本地调试可以在我们的 angular-schematics-tutorial 项目目录下,运行npm link指令。npm link 指令细节可参看https://docs.npmjs.com/cli/link.html。主要作用就是创建一个全局可访问链接符号。执行之后得到的效果应该如下: david@ubuntu:~/TTT/angular-schematics-tutorial$ npm link npm WARN [email protected] No repository field. audited 78 packages in 1.031s found 0 vulnerabilities /home/david/.nvm/versions/node/v12.6.0/lib/node_modules/angular-schematics-tutorial -> /home/david/TTT/angular-schematics-tutorial david@ubuntu:~/TTT/angular-schematics-tutorial$然后在模板 angular 项目中,链接刚刚的全局 angular-schematics-tutorial 链接。npm link angular-schematics-tutorial,相当于在当前项目安装了之前 link 的包,然后就可以在 angular 项目中直接使用 ng g 或 ng add 了。 例如在 angular-demo 项目中 link angular-schematics-tutorial ,应该如下: david@ubuntu:~/TTT/angular-demo$ npm link angular-schematics-tutorial /home/david/TTT/angular-demo/node_modules/angular-schematics-tutorial -> /home/david/.nvm/versions/node/v12.6.0/lib/node_modules/angular-schematics-tutorial -> /home/david/TTT/angular-schematics-tutorial david@ubuntu:~/TTT/angular-demo$然后使用 ng generator: david@ubuntu:~/TTT/angular-demo$ ng g angular-schematics-tutorial:component ? 你想创建的component的名字: comp/pie CREATE src/app/comp/pie/pie.component.html (21 bytes) CREATE src/app/comp/pie/pie.component.scss (0 bytes) CREATE src/app/comp/pie/pie.component.spec.ts (741 bytes) CREATE src/app/comp/pie/pie.component.ts (259 bytes) david@ubuntu:~/TTT/angular-demo$注意:现在不用设置 –debug=false 属性,一样会直接真实创建了。 同理,使用 ng add angular-schematics-tutorial: david@ubuntu:~/TTT/angular-demo$ ng add angular-schematics-tutorial Skipping installation: Package already installed UPDATE src/app/app.module.ts (407 bytes) UPDATE src/app/app.component.ts (302 bytes) UPDATE src/app/app.component.html (25506 bytes) UPDATE package.json (1439 bytes) npm WARN [email protected] requires a peer of eslint@^5 || ^6 but none is installed. You must install peer dependencies yourself. …… 省略了一些npm WARN …… npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"}) + @fortawesome/[email protected] + @fortawesome/[email protected] + @fortawesome/[email protected] added 4 packages from 25 contributors, removed 1 package and audited 18882 packages in 11.067s found 0 vulnerabilities david@ubuntu:~/TTT/angular-demo$ 注意:如果之前执行过,最好撤销后再试。看到上面显示这一句Skipping installation: Package already installed,这里 already installed 的 package 指的是 angular-schematics-tutorial,原因在于我们使用 npm link angular-schematics-tutorial 已经做了链接了。 那么,能否直接测试 ng update 呢?目前我是没有成功的。 因为 npm link 无法同时测试两个版本?这点可请大家指出。 注意,测试完了请使用 npm unlink 去清除那些链接,避免混乱等情况。 将原理图 package 发布到仓库并使用写在前面:如果觉得这样的糟粕不想自己发布用于测试,可以直接使用我已发布的进行测试。先安装 0.0.1 版本:npm i [email protected]然后执行ng update,查看是否生效。 一般如果公司有自己内部的包管理系统,那么就可以直接发布到内部去。如何搭建私有 npm 仓库?我之前有文件,使用 verdaccio。 我们这里示例将包发布到公网的 npmjs.com 去。既然是 npmjs,那可能你要去注册个帐号了。 记得前面我们有备份一个 0.0.1 版本的 angular-schematics-tutorial 吗?我们先来发布它。 添加帐号,在终端输入 adduser,按提示填写david@ubuntu:~/TTT/angular-schematics-tutorial$ npm adduser Username: davidsu Password: Email: (this IS public) 183318××××@qq.com Logged in as davidsu on https://registry.npmjs.org/. david@ubuntu:~/TTT/angular-schematics-tutorial$logged in 表示成功 在根目录执行npm publish得到结果如下: david@ubuntu:~/TTT/angular-schematics-tutorial$ npm publish npm notice npm notice 📦 [email protected] npm notice === Tarball Contents === …… 略一大部分 npm notice 内容 …… npm notice === Tarball Details === npm notice name: angular-schematics-tutorial npm notice version: 0.0.1 npm notice package size: 11.3 kB npm notice unpacked size: 41.2 kB npm notice shasum: 9f80b36542065cafb2eb06a5eceb068d5aa9db44 npm notice integrity: sha512-oZcVhqatHlU9K[...]Qz7Deicn1f3oA== npm notice total files: 34 npm notice + [email protected] david@ubuntu:~/TTT/angular-schematics-tutorial$那么没有添加 ng update 的 0.0.1 版本就发布了,可以在 npmjs 网站自己的账户下查看自己发布的包。 发布 0.0.2 版本的包重复以上动作(npm publish),把 angular-schematics-tutorial 的 0.0.2 版本的包也发布了,那么就可以看到自己两个版本的包了。 实际测试使用现在,我们的 angular-schematics-tutorial 就是一个可以被所有人访问的原理图工具包了,那么我们就可以像实际使用包一样去测试它了。 为了避免干扰,可以删除之前的 angular-demo 测试项目,新建一个 angular-test 的新项目 ng new angular-test我们先测试 ug update 的效果。 先安装 0.0.1 版本: npm i [email protected]然后运行ng update指令,应该看到以下内容 david@ubuntu:~/TTT/angular-test$ ng update Using package manager: 'npm' Collecting installed dependencies... Found 31 dependencies. We analyzed your package.json, there are some packages to update: Name Version Command to update -------------------------------------------------------------------------------- angular-schematics-tutorial 0.0.1 -> 0.0.2 ng update angular-schematics-tutorial rxjs 6.4.0 -> 6.5.3 ng update rxjs david@ubuntu:~/TTT/angular-test$现在,我们可以直接使用 ng update 去升级我们的工具包了。注意:使用 ng update 升级前,要提交所有变更。运行ng update angular-schematics-tutorial,注意查看 app.component.ts 中 title 变量是否变化: david@ubuntu:~/TTT/angular-test$ ng update angular-schematics-tutorial Using package manager: 'npm' Collecting installed dependencies... Found 31 dependencies. Fetching dependency metadata from registry... Updating package.json with dependency angular-schematics-tutorial @ "0.0.2" (was "0.0.1")... UPDATE package.json (1329 bytes) npm WARN [email protected] requires a peer of eslint@^5 || ^6 but none is installed. You must install peer …… 省略一些npm WARN …… npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"}) updated 1 package and audited 18955 packages in 8.615s found 0 vulnerabilities ** Executing migrations for package 'angular-schematics-tutorial' ** UPDATE src/app/app.component.ts (233 bytes) david@ubuntu:~/TTT/angular-test$从最后一行,看到,应该是修改了,实际也如此。 其他的 ng generator 的原理图和 ng add 的原理图也是可以的,可以自行测试。例如:ng g angular-schematics-tutorial:crudService或者ng add angular-schematics-tutorial 总结花费了如此大量的篇幅,基本上讲清楚了 angular schematics 的基本使用内容。虽然看起来很鸡肋,但是如果是 angular 技术栈并且长期有重复大量相同工作的开发,不免可以试一下使用它来重构下作业流程。虽然不一定比 ctrl+c ctrl+v 快,但是至少逼格更高了。 总结一下使用 angular schematics 的步骤重点。 制作 ng generator 或 ng add 原理图:1、新建原理图使用模板文件;2、创建该原理图需要的 schema.json 并将该原理图配置到 collection.json;3、依照 schema.json 创建接口 schema.d.ts;4、在 index.ts 中编写实现该原理图目的的逻辑代码;5(非必须)、编写测试用例进行测试;6(测试)、利用测试用例或实际项目进行测试。 添加 ng update1、创建并编写 migration.json 文件;2、在 package.json 中声明 ug-update 配置;3、编写更新执行的规则工厂逻辑代码;4、测试并使用。 以上内容亲测有效,有问题可提出交流,谢谢。 主要参考:https://ithelp.ithome.com.tw/articles/10222826https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4和 angular 官方文档:https://angular.cn/guide/schematics","categories":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/categories/angular/"}],"tags":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/tags/angular/"},{"name":"schematics","slug":"schematics","permalink":"http://swmlee.gitee.io/tags/schematics/"}]},{"title":"只言片语——angular material input输入框可以辅助下拉选择","slug":"TechnicalEssays/snippets-angular-input-with-select","date":"2019-12-12T11:50:49.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/12/TechnicalEssays/snippets-angular-input-with-select/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/snippets-angular-input-with-select/","excerpt":"在 angular 使用 material 开发时,如果需要一个输入框,既可以自行输入值,还可以点击下拉选择预设的选项输入值,可以使用 autocomplete 标签。","text":"在 angular 使用 material 开发时,如果需要一个输入框,既可以自行输入值,还可以点击下拉选择预设的选项输入值,可以使用 autocomplete 标签。 需要引入 MatAutocompleteModule 模块 import {MatAutocompleteModule} from '@angular/material/autocomplete';在 xxx.ts 中定义了下拉选项,例如: options = [ { value: '00:30:00', column: '30分钟' }, { value: '01:00:00', column: '1小时' }, { value: '01:30:00', column: '90分钟' }, { value: '02:00:00', column: '2小时' }, { value: '05:00:00', column: '5小时' } ];在 xxx.html 中使用示例如下: <span>持续时间:</span> <mat-form-field style="width:150px"> <input placeholder="自行输入或下拉选择" matInput [matAutocomplete]="auto"> <mat-autocomplete #auto="matAutocomplete"> <mat-option *ngFor="let option of options" [value]="option.value"> {{option.column}} </mat-option> </mat-autocomplete> </mat-form-field>得到的效果如下:","categories":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/categories/angular/"}],"tags":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/tags/angular/"}]},{"title":"只言片语——angular ngFor遍历显示2个数组值","slug":"TechnicalEssays/snippets-angular-ngfor","date":"2019-12-12T11:48:43.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/12/TechnicalEssays/snippets-angular-ngfor/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/snippets-angular-ngfor/","excerpt":"angular 的*ngFor 只能遍历 1 个数组,但是能够获取到 index,所以以第一个数组的 index 带出第二个数组的值显示即可。","text":"angular 的*ngFor 只能遍历 1 个数组,但是能够获取到 index,所以以第一个数组的 index 带出第二个数组的值显示即可。 例如,在 xxx.ts 中有声明两个数组: names = ['张三', '李四', '王五', '马六']; ages = [32, 34, 54, 25];在 xxx.html 中如此使用: <div> <li *ngFor="let name of names; let i=index "> <label>{{name}} ~ {{ages[i]}}岁</label> </li> </div>得到的效果图如下: 当然,如果两个数组的长度不一致,要么显示空白(ngFor 遍历的数组长度值大),要么不显示值(ngFor 遍历的数组长度值小。)","categories":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/categories/angular/"}],"tags":[{"name":"angular","slug":"angular","permalink":"http://swmlee.gitee.io/tags/angular/"}]},{"title":"Ubuntu18.04下docker基本指令和使用docker安装mysql","slug":"TechnicalEssays/ubuntu-mysql-docker","date":"2019-12-12T11:30:48.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/12/TechnicalEssays/ubuntu-mysql-docker/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/ubuntu-mysql-docker/","excerpt":"docker 基本指令安装dockersudo sh -c "$(curl -fsSL https://get.docker.com)" sudo usermod -aG docker $USER 第一行用docker官方提供的script快速安装第二行将现有的使用者加入docker群组,否则会没有权限操作docker命令。记得注销账号重登,以获取docker操作权限。","text":"docker 基本指令安装dockersudo sh -c "$(curl -fsSL https://get.docker.com)" sudo usermod -aG docker $USER 第一行用docker官方提供的script快速安装第二行将现有的使用者加入docker群组,否则会没有权限操作docker命令。记得注销账号重登,以获取docker操作权限。 执行sudo docker run hello-world查看是否安装成功。如果出现以下画面则安装成功: 常用docker基本指令1 启动/关闭 docker服务 service docker start 或者 systemctl start docker // 启动 service docker stop 或者 systemctl stop docker // 关闭2 创建一个新的容器并运行一个命令 docker run [OPTIONS] IMAGE [COMMAND] [ARG...]常用OPTIONS说明: -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项; -d: 后台运行容器,并返回容器ID; -i: 以交互模式运行容器,通常与 -t 同时使用; -P: 随机端口映射,容器内部端口随机映射到主机的高端口; -p: 指定端口映射,格式为:主机(宿主)端口:容器端口; -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用; -e <环境变量名>=”<值>”: 设置环境变量; –name=”“: 为容器指定一个名称; 例如:使用docker镜像nginx:latest以后台模式启动一个容器,并将容器命名为mynginx。 docker run --name mynginx -d nginx:latest3 只创建而不运行容器(选项同option) docker create [OPTIONS] IMAGE [COMMAND] [ARG...]4 删除一个或多个容器 docker rm [OPTIONS] CONTAINER [CONTAINER...]OPTIONS说明: -f :通过SIGKILL信号强制删除一个运行中的容器 -l :移除容器间的网络连接,而非容器本身 -v :-v 删除与容器关联的卷 例如:强制删除容器db01、db02 docker rm -f db01 db025 在运行的容器中执行命令 docker exec [OPTIONS] CONTAINER COMMAND [ARG...]OPTIONS说明: -d :分离模式: 在后台运行 -i :即使没有附加也保持STDIN 打开 -t :分配一个伪终端 6 查看已有下载镜像(images) docker images使用docker安装mysql1 下载最新mysql镜像 docker pull mysql:latest2 运行容器 docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql部分参数说明: mysql-test:容器名 -p 3306:3306 :映射容器服务的 3306 端口到宿主机的 3306 端口,外部主机可以直接通过 宿主机ip:3306 访问到 MySQL 的服务。 MYSQL_ROOT_PASSWORD=123456:设置 MySQL 服务 root 用户的密码。3 配置mysql 使用者(user) 3.1 进入容器 docker exec -it mysql-test bash 3.2 登录mysql并修改root密码 mysql -u root -p ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; 3.3 添加远程登录用户 –> ‘用户名‘@’主机’,%表任意都行 CREATE USER '<username>'@'%' IDENTIFIED WITH mysql_native_password BY '<password>'; GRANT ALL PRIVILEGES ON *.* TO '<username>'@'%';备份docker中mysql的数据 使用docker安装mysql可能遇到一个问题,就是如果不做其他处理,docker容器损坏,那么里面的数据就可能丢了再也找不回. 所以应当养成定时备份的好习惯。 使用以下指令,将docker容器中的mysql数据备份到宿主主机指定位置: docker exec [CONTAINER] //usr/bin/mysqldump -u [USER] --password=[PASSWORD] --routines --triggers test_db > /home/user/test_db_backup.sql说明: test_db是docker中mysql的数据库名 /home/user/test_db_backup.sql 备份到宿主主机的地址和文件名 将上面指令写入宿主主机的cron定时任务中就可定时将docker中mysql数据备份到本机,避免docker损坏数据就丢失了。","categories":[{"name":"docker","slug":"docker","permalink":"http://swmlee.gitee.io/categories/docker/"}],"tags":[{"name":"ubuntu","slug":"ubuntu","permalink":"http://swmlee.gitee.io/tags/ubuntu/"},{"name":"mysql","slug":"mysql","permalink":"http://swmlee.gitee.io/tags/mysql/"},{"name":"docker","slug":"docker","permalink":"http://swmlee.gitee.io/tags/docker/"}]},{"title":"对象数组按对象指定属性排序","slug":"TechnicalEssays/object-array-sort","date":"2019-12-12T11:23:30.000Z","updated":"2020-01-09T13:41:14.888Z","comments":true,"path":"2019/12/12/TechnicalEssays/object-array-sort/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/object-array-sort/","excerpt":"引入  说明:使用语言为javascript。  对象数组,即数组中存放的元素是一个个对象。例如 let objArr = [ { name: "张三", sex: 'female', age: 30, birthday: "1994/10/11" }, { name: "李四", sex: 'male', age: 20, birthday: "2001/08/11" }, { name: "王五", sex: 'female', age: 40, birthday: "2001/01/15" } ];  但是我们可能需要对该对数组中对象的指定属性进行排序,例如上述对象数组中的age,birthday。如何操作?方法之一,就是数组的sort()方法。  下面,就一步步去分析,实现一份简单通用的对象数组排序方法。","text":"引入  说明:使用语言为javascript。  对象数组,即数组中存放的元素是一个个对象。例如 let objArr = [ { name: "张三", sex: 'female', age: 30, birthday: "1994/10/11" }, { name: "李四", sex: 'male', age: 20, birthday: "2001/08/11" }, { name: "王五", sex: 'female', age: 40, birthday: "2001/01/15" } ];  但是我们可能需要对该对数组中对象的指定属性进行排序,例如上述对象数组中的age,birthday。如何操作?方法之一,就是数组的sort()方法。  下面,就一步步去分析,实现一份简单通用的对象数组排序方法。 实现对象数组排序第一步:简单排序number属性  因为Array.sort()的“默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的”。好在是sort()方法接受一个用来指定按某种顺序进行排列的函数作为可选参数,即arr.sort([compareFunction]),如果 compareFunction(a, b) 小于0,那么a会被排列到b之前;所以可以在此实现自己想要的排序方法。注意,sort方法会改变原数组。(sort() 方法用原地算法对数组的元素进行排序,并返回数组。)   话不多说,直接看。  例如上述objArr,按照age升序排序: // 指定排序的比较函数 function compare(property) { return function (object1, object2) { let value1 = object1[property]; let value2 = object2[property]; // 升序 return value1 - value2; } } let sortObj = objArr.sort(compare("age")); console.log(sortObj);   应该会得到如下结果: 第二阶段:可以排序string属性  但是这个写法只能对age这个number类型的属性其作用,如果换成name或者birthday等,就不行,因为string不能直接用’-‘比较得出大小。  例如,运行console.log('male' - 'female');,应该会看到得出的是NaN。  string,就应该使用localeCompare() 方法,它返回一个数字来指示一个参考字符串是否在排序顺序前面或之后或与给定字符串相同。完整语法:referenceStr.localeCompare(compareString[, locales[, options]])。   所以需要简单修改一下compare()方法,修改如下: function compare(property) { return function (object1, object2) { let value1 = object1[property]; let value2 = object2[property]; if (typeof (value1) == typeof (value2)) { if (typeof (value1) === 'number') { return value1 - value2; } if (typeof (value1) === 'string') { // 升序 return value1.toString().localeCompare(value2); } } } } let sortObj = objArr.sort(compare("birthday")); console.log(sortObj);   结果应当如下: 第三阶段:可以指定升序/降序  既然已经到了这个程度,那可以再加一个是按照升序或者降序排列。  简单修改如下: function compare(property, sortType = "asc") { return function (object1, object2) { let value1 = object1[property]; let value2 = object2[property]; // 判断 传入的属性值 是number还是 string if (typeof (value1) == typeof (value2)) { if (typeof (value1) === 'number') { // 如果是升序 if (sortType === "asc") { return value1 - value2; } else if (sortType === "desc") { // 如果是降序 return value2 - value1; } } if (typeof (value1) === 'string') { // 如果是升序 if (sortType === "asc") { return value1.toString().localeCompare(value2); } else if (sortType === "desc") { // 如果是降序 return value2.toString().localeCompare(value1); } } } } } // 生日,降序 let sortObj = objArr.sort(compare("birthday", 'desc')); console.log(sortObj);   得到的结果应该如下: 第四阶段:封裝成通用方法  反正都这样了,再简单封装一下,导出成一个方法,后续直接使用。   新建一个objArraySort.js,放入以下代码: // sort使用的排序方法 // 传入对象数组用于排序的对象的属性,升序/降序 function compare(property, sortType = "asc") { // 如果不是 asc,desc,不做下一步比较 if (!(sortType === "desc" || sortType === "asc")) { return; } return function (object1, object2) { // 取得对象属性值 let value1 = object1[property]; let value2 = object2[property]; // 如果该对象不存在这个属性,也不做后续比较 if (!value1 || !value2) { return; } // 如果两个属性取得的值不是一个类型的就不用比较了 if (typeof (value1) == typeof (value2)) { // 判断 传入的属性值 是number还是 string if (typeof (value1) === 'number') { // 如果是升序 if (sortType === "asc") { return value1 - value2; } else { // 如果是降序 return value2 - value1; } } else if (typeof (value1) === 'string') { // 如果是升序 if (sortType === "asc") { return value1.toString().localeCompare(value2); } else { // 如果是降序 return value2.toString().localeCompare(value1); } } else { // 其它类型就不排序了 return; } } else { return; } } } // 通用方法,需要传入 需要排序的对象数组、对象属性、排序方式 function objectArraySort(array, property, sortType) { // 如果不是对象数组用这个方法,返回的是undefined if (!(array instanceof Array)) { return; } return array.sort(compare(property, sortType)); } // 导出 module.exports = { objectArraySort: objectArraySort, }  再将刚刚的objArr按照age降序排序: // 引入模块 const oas = require("./objectArraySort"); // 调用方法,传入需要排序的对象数组、对象属性、排序方式 let sortObj = oas.objectArraySort(objArr, 'age', 'desc'); console.log(sortObj);   应该会得到以下结果:   至此,一个简单通用的对象数组按照其对象指定属性排序的模块就完成了。实践有效,如果有问题,可提出交流,谢谢。","categories":[{"name":"javascript","slug":"javascript","permalink":"http://swmlee.gitee.io/categories/javascript/"}],"tags":[{"name":"javascript","slug":"javascript","permalink":"http://swmlee.gitee.io/tags/javascript/"}]},{"title":"Express+multer 文件上传,并在 router 中指定文件存放路径","slug":"TechnicalEssays/express-multer-demo","date":"2019-12-12T11:06:12.000Z","updated":"2020-01-09T13:41:14.887Z","comments":true,"path":"2019/12/12/TechnicalEssays/express-multer-demo/","link":"","permalink":"http://swmlee.gitee.io/2019/12/12/TechnicalEssays/express-multer-demo/","excerpt":"Express+multer 文件上传,并在 router 中指定文件存放路径 内容简单说明  文件上传是 web 开发中比较常见的一个功能虽然说起来是文件上传,实际上,可以看做是对 multipart/form-data 数据的处理。在 npm 中,有很多处理类似数据的库,包括周下载量近 2kw 的 form-data,周下载量近 3mw 的 formidable。   不过,如果 nodejs 后端使用的 express 框架,其官方也有一个自己的文件上传中间件,用它自己的话来说就是:“Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。”","text":"Express+multer 文件上传,并在 router 中指定文件存放路径 内容简单说明  文件上传是 web 开发中比较常见的一个功能虽然说起来是文件上传,实际上,可以看做是对 multipart/form-data 数据的处理。在 npm 中,有很多处理类似数据的库,包括周下载量近 2kw 的 form-data,周下载量近 3mw 的 formidable。   不过,如果 nodejs 后端使用的 express 框架,其官方也有一个自己的文件上传中间件,用它自己的话来说就是:“Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。”   使用 multer 比较简单,一般就是    1、导入 multer,    2、指定文件上传地址(如果有必要的话,不指定只是写到内存中),    3、在 router 的路径后,回调函数前,写一个upload.single(photo)(单文件)或者upload.array('photos', 12)(多文件),在 router 的回调中,就可以使用req.file 或者 req.files获取文件了。   在这里,因为指定的上传地址是在 multer(opts)中的 opts 配置,所以 opts 配置号一个地址之后,后续修改就不是那么方便。如果需要对不同文件不同路由路径指定不同的文件上传地址,那应该如何处理?   multer 的简单使用后文会给个示例,但是最终的目的,是想要在 express 的 router 回调函数中,可以指定文件上传的路径,而不是所有的文件都上传到唯一指定的路径。例如,路由是“testUpload”,我在 router 处理时指定存放到测试使用的上传路径。路由是“formalUpload”,我在处理时可以指定存放到正式的上传路径。 express+multer 基本文件上传示例  因为主要是测试 multer 内容,所以一切从简,就在一个简单的 express 项目中测试就好 1、创建一个 express 项目(前提:已安装 express-generator),并安装 multerexpress --view=ejs express-mutler-demo // 进入项目根目录 npm i multer2、上传页面编写  修改 views/index.ejs 的标签内容如下: <div> <h3>Express + multer 簡陋上傳文件</h3> <form method="post" action="/upload" id="upload-form" encType="multipart/form-data"> <input id='upload' type="file" name="file" /> <input type="submit" value="上傳"> </form> <!-- 进度条 --> <progress id="uploadprogress" min="0" max="100" value="0">0</progress> <p id='msg'></p> </div> <!-- 引入jquery.js --> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script> let form = $("#upload-form"); form.on('submit', function (event) { // 清除提交结果显示信息 $("#msg").html(""); // 在原页面处理,不跳转 event.preventDefault(); // 检查是否支持FormData if (window.FormData) { let formData = new FormData(); // 建立一个file表单项,值为上传的文件 formData.append('file', $('#upload').get(0).files[0]); let xhr = new XMLHttpRequest(); xhr.open('POST', $(this).attr('action')); // 进度条占比计算 xhr.upload.onprogress = function (event) { if (event.lengthComputable) { let complete = (event.loaded / event.total * 100 | 0); $("#uploadprogress").val(complete); $("#uploadprogress").innerHTML = complete; } }; // 定义上传完成后的回调函数 xhr.onload = function (e) { if (xhr.status === 200) { $("#msg").html("上传成功!"); // alert('上传成功!'); } else { // alert('文件上传出错了!') $("#msg").html("上传失败!"); } }; // 发送表单数据 xhr.send(formData); } }); </script>   代码内容很简单,就是一个 form 用来模拟文件上传,为了最简单,直接使用的 XMLHttpRequest 实现上传,还没事整了个进度条。  本来想用原始的方法,还是引入了 jquery。更简略类似下面也 ok。 <script> function PostData() { $.ajax({ type: "POST", url: "XXX", data : "", success: function(msg) { } }); return false; } </script> <form onsubmit="return PostData()"> <input type="text" value=""> <input type="submit"> </form>  依旧以第一个为准,页面大概是这个样子(运行 express 项目,在 localhost:3000 看到): 3、multer 的简单配置  新建一个 util/Upload.js,编写 multer 配置并导出: const multer = require('multer'); // 文件上传配置 const fileStorage = multer.diskStorage({ destination: function (req, file, callback) { callback(null, "/defaultUploadDir"); }, filename: function (req, file, callback) { callback(null, file.originalname); } }); // 导出配置 module.exports = { fileUpdate: multer({ 'storage': fileStorage }), }注意:上传地址 “/defaultUploadDir”要先手动创建,否则报错。 4、在对应 router 中使用 multer  在 routes/index.js 中,添加以下 router 代码: router.post('/upload', upload.fileUpdate.single('file'), function (req, res, next) { const file = req.file; console.log(file); //如果得到了文件,就返回上传成功 if (file) { return res.status(200).json({ success: true }); } else { return res.status(500).json({ success: false }); } });  记得在最上面引入 multer 配置: const upload = require('../util/Upload');  几个简单注意点:    1、这个路由路径和路由方法,要和前台页面中的 action 和 method 一致;    2、多文件就要 upload.array(),单文件就用 upload.single()(后续都是单文件示例中说明);    3、第二点()里面的标志字符串要和前台页面中的<input id='upload' type="file" name="file" />name 属性一致。   如果步骤都正确,成功上传,应该可以看到前台页面如下:   router 的回调中取得上传文件的信息,如下:   文件上传的位置: 关于使用 multer 文本域数据  multer 的 readme 所说:”Multer 会添加一个 body 对象 以及 file 或 files 对象 到 express 的 request 对象中。 body 对象包含表单的文本域信息,file 或 files 对象包含对象表单上传的文件信息。“  实际测试,在前台页面 index.ejs 创建 formData 后,append 一个文本数据: let formData = new FormData(); // 补入此句 formData.append('dest', 'file_upload');   刷新页面之后,重新上传,可以在 multer 配置中,在 diskStorage 的 destination 的 callback 中,可以得到 req.body 包含了 dest 属性。如下图:   这是好事,很好的,这样,在前台上传文件时,就可以把需要上传的地址放到这里,那么不同的文件上传就可以存放的不同的地址了。  那么会有哪些问题呢?    1、前端需要知道后台的上传路径,不合理。    2、并不是所有使用 formData.append()添加的属性都能在文件上传 destination 生成前,在 req.body 中获取到。     这是一个实际遇到的问题,我在使用 angular 时,使用 HttpClient 实现文件上传操作,类似: upload(file: any) { // 文件使用FormData发送 const formData: FormData = new FormData(); formData.append('file', file, file.name); formData.append('file_name', file, file.name); return this.http.post(this.URL + '/upload', formData ); }  后台的 req.body 在获取到上传的文件前并不会有 file_name 属性的值,即在 multer 配置在 diskStorage 的 destination 的 callback 中,可以得到 req.body 是空,在对应 upload 的 router 回调中,才取得 req.body 的 file_name 属性。 在 router 的回调中,指定文件上传的路径。  在”关于使用 multer 文本域数据“这部分有讲到,前台直接传入文件上传的路径不合理,在接受到上传的文件前得到指定的上传路径也不一定成功,而直接使用配置好的 multer,其文件上传目的地 destination 又只有固定一个。该如何实现?   把 multer 的配置,封装到一个返回 promise 的函数,指定传入一个文件路径参数,并在 router 的回调中使用该函数,传入上传路径。   修改 utils/Upload.js 文件,补入以下内容: // multer文件上传,可指定上传路径,不在router参数里直接用 let uploadFunction = (req, res, dest) => { let storage = multer.diskStorage({ destination: function (req, file, cb) { let newDestination = dest; let stat = null; try { // 检查传入的路径是否存在,不存在则创件 stat = fs.statSync(newDestination); } catch (err) { fs.mkdirSync(newDestination); } if (stat && !stat.isDirectory()) { throw new Error('文件目录: "' + dest + '已存在!"'); } cb(null, newDestination); }, filename: function (req, file, callback) { callback(null, file.originalname); } }); let upload = multer({ storage: storage }).single('file'); return new Promise((resolve, reject) => { upload(req, res, (err) => { if (err) { return reject(err); } resolve(); }) }) };  记得导出: module.exports = { fileUpdate: multer({ 'storage': fileStorage }), uploadFunction, }  在 router 中使用,修改原 routes/index.js 的 upload 路由如下: router.post('/upload', /*upload.fileUpdate.single('file'), */ async function (req, res, next) { // 指定文件上传路径 let uploadPath = 'test_upload'; // 等到文件上传完成 await upload.uploadFunction(req, res, uploadPath); const file = req.file; console.log(req.file); //如果得到了文件,就返回上传成功 if (file) { return res.status(200).json({ success: true }); } else { return res.status(500).json({ success: false }); } });  当然,await 需要在 async 函数中使用,也最好放到 trycatch 中。   如果步骤正确,结果应该和第一步中的一样,文件上传成功。在后台的项目中会新建一个 test_upload 文件夹,并有上传的文件。   代码已放到 github,有需求可查阅。   以上内容,全部亲测有效,如果有问题,请提出交流,谢谢。","categories":[{"name":"nodejs","slug":"nodejs","permalink":"http://swmlee.gitee.io/categories/nodejs/"}],"tags":[{"name":"nodejs","slug":"nodejs","permalink":"http://swmlee.gitee.io/tags/nodejs/"},{"name":"multer","slug":"multer","permalink":"http://swmlee.gitee.io/tags/multer/"}]}]}