forked from snowdreams1006/snowdreams1006.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
/
search_plus_index.json
1 lines (1 loc) · 894 KB
/
search_plus_index.json
1
{"./":{"url":"./","title":"简介","keywords":"","body":"简介 教程分享均是笔者亲身学习经验总结,涉及到的知识点均亲身试验,但有时为了知识完整性,可能并未亲自确认,对于这部分内容会明确指出,到时由读者确认试验. 如果读者实际运行效果和教程演示效果有出入,很可能是版本问题,系统环境等原因,希望能及时反馈,避免更多人踩坑,谢谢! 国内用户推荐访问 snowdreams1006.tech ,速度非常快并且和 snowdreams1006.github.io 保持同步更新. 教程特点 面向初学者,适合零基础入门; 面向常用操作,技能实用性强; 情景教学,理清事情来龙去脉; 章节重点知识小结,精华集锦; 学习要求 保持哲学三问,是什么,为什么,怎么样?希望最终能有你自己的答案; 好记性不如烂笔头,亲自动手操作一遍,你会发现你的理解更上一层楼; 授人以鱼不如授人以渔,希望带给你不仅仅是知识更多的是学习的方法; 知识重在分享才有价值,鼓励知识传播与分享,创造收益更有价值; 因本人能力有限,如有出入,敬请指正,请联系我 snowdreams1006 怎么联系 /assets/audio/snowdreams.mp3 第三方平台 微信公众号 名称 : 雪之梦技术驿站,微信号 : snowdreams1006 关注理由: 开源的不开源的都会发布到微信公众号,不再局限于系列教程而是随性而为,展示真实的技术人生. 个人微信号 名称 : 雪之梦技术驿站,微信号 : snowdreams1109 适用场景: 如果三言两语很难阐释你遇到的问题,如果你是人见人爱的妹子,那么欢迎加我私人微信一起畅谈人生. 克隆网站 下载项目 Github 作为远程项目服务器时下载速度感人,而国内用户使用 Gitee 进行下载时速度明显提升,因此下载项目时推荐使用 Gitee 作为远程项目仓库. 核心思路 如果以 Github 作为远程仓库,全量下载项目代码时切换到 Gitee 远程仓库地址进行下载,增量更新时切换回 Github 远程仓库地址进行更新,以此保持最新代码状态. 全量下载 Gitee # 使用 https 方式下载 Gitee 源码 git clone https://gitee.com/snowdreams1006/snowdreams1006.git # 切换回 Github 远程仓库地址 git remote set-url origin https://github.com/snowdreams1006/snowdreams1006.github.io.git 增量更新 Github # 切换远程仓库地址为 Gitee 地址 git remote set-url origin https://gitee.com/snowdreams1006/snowdreams1006.git # 更新项目源码 git Pull # 切换回 Github 远程仓库地址 git remote set-url origin https://github.com/snowdreams1006/snowdreams1006.github.io.git 如果以 Gitee 作为远程仓库,无论下载还是更新均无需切换远程项目地址,正常下载更新即可. 全量下载 Gitee # 使用 https 方式下载 Gitee 源码 git clone https://gitee.com/snowdreams1006/snowdreams1006.git 增量更新 Gitee # 更新项目源码 git Pull 部署项目 不论是直接下载 zip 压缩包还是命令行克隆项目,接下来需要使用 gitbook 相关命令进行启动本地服务,正常情况下运行效果和线上环境相差无二. gitbook 环境需要本机安装 nodejs 环境,个别插件可能还需要额外环境依赖,具体情况请参考 gitbook入门教程 # 安装项目插件 gitbook install # 运行本地服务 gitbook serve 一般情况下,服务启动后会在本机 4000 端口开启服务,可以打开浏览器访问 localhost:4000 或者 127.0.0.1:4000 进行访问. 本地服务确认无误后,可使用 gitbook build 生成静态网站,然后将其上传到服务器直接部署即可,例如可以将 _book/ 文件夹下的静态网站全部上传到 Github 网站,上传成功后即可访问在线网站: https://snowdreams1006.github.io/ # 生成静态网站,生成完毕后全部网站文件位于_book文件夹中. gitbook build # 将静态网站文件夹_book全部复制到当前目录,保证首页存在index.html文件 cp -rf ./_book/* ./ # 添加到版本库等待上传 Github git add . # 添加更新说明 git commit -m \"regenerate website\" # 推送到远程仓库,推送成功后等待一段时间后在线服务即可开启. git push 推送到远程仓库要求用户对该仓库具有写权限,因此建议 fork 本项目到个人空间,这样就可以体验完整的个人博客搭建流程. 多处备份 如果远程仓库只有Github 一种,发生意外情况的话将直接影响到网站运行,所以可以考虑将网站进行多处备份,目前本项目已经备份到 Gitee 和 Gitlab 网站. 一个 pull + 多个 push 本项目以 Github 为主,Gitee 和 Gitlab 为辅,具体表现为: 当本地环境需要更新项目时,拉取自 Github 仓库源码,推送项目时,一次性同时推送到多个仓库. # 添加 Gitee 推送地址 git remote set-url --add git@gitee.com:snowdreams1006/snowdreams1006.git # 添加 Gitlab 推送地址 git remote set-url --add git@gitlab.com:snowdreams1006/snowdreams1006.gitlab.io.git 设置完成后,以后使用 git push 进行推送就会一次性同步到多个远程仓库,当然需要先确保已将该项目导入到相应的仓库并且能正常通讯. 多套 pull + push 如果希望拉取地址和推送地址是对应的,可以使用下面这种方式建立多套远程仓库地址. # 添加 Gitee 远程仓库地址,拉取或推送时需要指定仓库名称,例如: git push gitee master git remote add gitee git@gitee.com:snowdreams1006/snowdreams1006.git # 添加 Gitlab 远程仓库地址,拉取或推送时需要指定仓库名称,例如: git push gitlab master git remote add gitlab git@gitlab.com:snowdreams1006/snowdreams1006.gitlab.io.git 上述两种方式并不冲突,如果愿意的话也可以两者同时设置,好处是一般情况下拉取源码时从 Github 下载,推送源码时自动同步到多个远程仓库. 如果遇到特殊情况也可以单独指定远程仓库,例如长时间没更新项目再次更新下载速度比较慢,也可以指定远程仓库 Gitee 等等. 最后,运行 git remote -v 命令可查看远程项目关联情况,实现一份代码多处备份. $ git remote -v gitee git@gitee.com:snowdreams1006/snowdreams1006.git (fetch) gitee git@gitee.com:snowdreams1006/snowdreams1006.git (push) gitlab git@gitlab.com:snowdreams1006/snowdreams1006.gitlab.io.git (fetch) gitlab git@gitlab.com:snowdreams1006/snowdreams1006.gitlab.io.git (push) origin git@github.com:snowdreams1006/snowdreams1006.github.io.git (fetch) origin git@github.com:snowdreams1006/snowdreams1006.github.io.git (push) origin git@gitee.com:snowdreams1006/snowdreams1006.git (push) origin git@gitlab.com:snowdreams1006/snowdreams1006.gitlab.io.git (push) 相关说明 本教程源码托管在 snowdreams1006.github.io ,在线阅读请访问 snowdreams1006.github.io. 如果你觉得本教程对你有所帮助,请不吝 Star. 如果你想贡献一份力量,欢迎提交 Pull Request. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-06-03 08:44:07 "},"markdown/":{"url":"markdown/","title":"markdown 入门教程","keywords":"","body":"markdown 入门教程 markdown 不止是 HTML 的简化版,更重要的是 txt 的升级版,word 的轻量版,是笔记的最佳载体. markdown 作为一种简单的格式标记语言,不同于 txt 的无格式,不同于 HTML 的复杂标记,也不同于 word 的鼠标调整样式. markdown 通过简单的几个字符键入,就可以快捷的定义文档的样式. 掌握 markdown,你可以完全抛弃 txt 和笔记软件的编辑器,并且在大多数场景下替代掉复杂臃肿的 word.享受简洁之美、享受效率提升. 下面列举了 markdown 语法及对应的示例: 标题 在标题文字前面加#,并且加上空格分割. 一个#是一级标题,两个#是两级标题,以此类推,最多支持六级标题. 示例: # 标题1 ## 标题2 ### 标题3 #### 标题4 ##### 标题5 ###### 标题6 效果: 标题1 标题2 标题3 标题4 标题5 标题6 列表 包括有序列表和无序列表,支持列表嵌套. 有序列表 有序列表就是有顺序的列表,依靠行前的数字加.标记顺序,序号和内容之间以空格 分开. 示例: 1. 有序列表1 2. 有序列表2 3. 有序列表3 效果: 有序列表1 有序列表2 有序列表3 无序列表 无序列表就是列表不排序,支持- * + 3种前缀,可用于表示1级列表,2级列表,3级列表. 示例: - 无序列表1 * 无序列表2 + 无序列表3 效果: 无序列表1 无序列表2 无序列表3 列表嵌套 上一级和下一级列表之间空两个空格 即可表示列表嵌套. 示例: - 无序列表1 * 无序列表11 * 无序列表12 * 无序列表13 + 无序列表131 + 无序列表132 + 无序列表133 - 无序列表2 - 无序列表3 效果: 无序列表1 无序列表11 无序列表12 无序列表13 无序列表131 无序列表132 无序列表133 无序列表2 无序列表3 引用 在引用文字前加一个>即可,支持引用嵌套. 示例: > 引用1 >> 引用11 >>> 引用111 效果: 引用1 引用11 引用111 字体 粗体 要加粗的文字左右两边分别用两个 * 号或者 _ 号包围起来 斜体 要倾斜的文字左右两边分别用一个 * 号或者 _ 号包围起来 粗体+斜体 要加粗并倾斜的文字左右两边分别用三个 * 号或者 _ 号包围起来 删除线 要删除的文字左右两边分别用两个 ~ 号包围起来 示例: **粗体1** __粗体2__ *斜体1* _斜体2_ ***粗体+斜体1*** ___粗体+斜体2___ ~~删除线~~ 效果: 粗体1 粗体2 斜体1 斜体2 粗体+斜体1 粗体+斜体2 删除线 分割线 三个或三个以上的- *即可表示分割线 示例: --- *** 效果: 图片 其中,图片alt表示图片的解释文字,图片src是图片地址,支持本地路径和网络路径,图片title是图片的标题,可选. 示例: ![preview.png](images/preview.png \"preview.png\") 效果: 超链接 超链接text 其中,超链接text表示超链接的解释文字,超链接url支持本地路径和网络路径,超链接title是超链接的标题,可选. 示例: [https://snowdreams1006.github.io](https://snowdreams1006.github.io \"snowdreams1006\") 效果: https://snowdreams1006.github.io 表格 第一行定义表头,单元格内定义标题; 第二行定义样式,单元格内部至少一个-,文字默认居左对齐,单元格内部-两侧均加:表示居中,只有右侧加:表示居右对齐; 第三行定义数据; 示例: |默认居左|文字居中|文字居右| |-|:-:|-:| |居左对齐1|居中对齐1|居右对齐1| |居左对齐2|居中对齐2|居右对齐2| |居左对齐3|居中对齐3|居右对齐3| 效果: 默认居左 文字居中 文字居右 居左对齐1 居中对齐1 居右对齐1 居左对齐2 居中对齐2 居右对齐2 居左对齐3 居中对齐3 居右对齐3 代码 单行代码 代码两侧分别用一个反引号包围起来 示例: `code` 效果: code 多行代码 代码块首尾分别用三个反引号包围起来,且两边的反引号独占一行 示例: (```) function fun(){ echo \"这是一句非常牛逼的代码\"; } fun(); (```) 注:为了防止转译,前后三个反引号处加了小括号,实际是没有的. 效果: function fun(){ echo \"这是一句非常牛逼的代码\"; } fun(); 注释 示例: 效果: 看不到注释就对了! 都学会了吗? 那考考你,你猜当前文档是如何书写的,看看你的答案和我实际书写规则是否一致呢! 答案请参考snowdreams1006.github.io 参考文献 https://daringfireball.net/projects/markdown/syntax http://www.markdown.cn/ https://www.appinn.com/markdown/index.html var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/markdown/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-04-18 08:38:32 "},"git/":{"url":"git/","title":"git 入门教程","keywords":"","body":"git 入门教程 git 是分布式版本控制系统,是文本文档管理的利器,是帮助你管理文件动态的好帮手. 如果你曾经手动管理过文档,一定有这样的经历,比如你正在编辑文档,想删除某段落,又担心不久后可能会恢复,此时你可能会先备份然后再删除,或者想要修改某段落,几经修改后发现还是最初的比较好,这是就哭笑不得了... 从最初的新建文档,经过反反复复的修改,最终定稿文档的过程极其繁琐冗长,这就是手动式管理文档的痛点. 如果有这么一种工具,能帮我自动记录每次文档的改动,想要查看文档变更详情只需要打开软件就能一目了然告诉我发生了哪些改变?岂不美哉! 版本 文件 用户 说明 时间 1 README.md snowdreams1006 初始化简介文档 2019-03-01 08:00 2 README.md snowdreams1006 增加特点说明 2019-03-01 10:00 3 README.md snowdreams1006 增加要求说明 2019-03-01 12:00 事实上,还真有这样的软件,专业术语称为版本控制系统,而git就是最先进的分布式版本控制系统; 特点: 文件的变更从此有迹可循,再也不怕丢失文件; 有网无网均可工作,数据交换不需再相互拷贝; 人人平等的开放环境,有机会贡献自己的智慧; 本书发表在 https://snowdreams1006.github.io/git/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/base/about.html":{"url":"git/base/about.html","title":"初识 git","keywords":"","body":"初识 git git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. 背景 我们都知道,Linus 在1991年创建了开源的linux系统,随着不断发展壮大,目前已发展成为最大的服务器系统软件. Linus 虽然创建了 linux,但 linux 的发展壮大是靠全世界热心的志愿者参与贡献的,这么多人在世界各地为linux系统编写代码,那么linux的代码是如何管理呢? 事实上,在2002年以前,世界各地的志愿者直接将源代码通过 diff 的方式发送给Linus,然后由Linus本人通过手动方式合并代码! ... Linus花了两周时间自己用 C语言 写了一个分布式版本控制系统,这就是Git! 一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下. 分布式 和 集中式 先说集中式版本控制系统,版本库是集中存放在专门的中央服务器中,而平时使用过程中需要时刻处于联网状态才能和中央服务器保持联系.日常工作流程是这样的,上班前先从中央服务器拉取最新工作内容,本地修改完毕后推送到中央服务器,第二天上班再拉取最新内容,修改后再推送给中央服务器... 集中式版本控制系统的特点就是必须要有一个专门的中央服务器,工作中必须联网才能进行版本控制,试想一下如果正在在外地出差或者没有网络条件下,还怎么进行版本控制,岂不是又重新回到原始时代了吗? 那再说说分布式版本控制系统,版本库是存放在各自使用者的电脑的,不需要专门的中央服务器,每个人电脑中就是一份完整的版本库,因此不需要联网也能工作,工作流程和其他的版本控制系统大致相同. 由此可见,集中式的版本控制系统依赖于中央服务器,要求使用者一直保持通信,而分布式的版本控制系统并不依赖中央服务器,不必强制联网. 万一出现意外,集中式版本控制系统中充当中央服务器的电脑宕机了,那么所有人就没法工作了,再也不能享受版本控制带来的便利了! 同样的情况发生在分布式版本控制系统身上会如何呢?一台电脑宕机没关系,所有人的电脑不可能同时都宕机吧,因为每个人电脑中都是一份完整的版本控制,那么找到其中一个人的版本手动复制到宕机电脑中瞬间不久恢复运行了么?所以说分布式比集中式更安全! 可能会有疑问了,既然分布式版本控制系统中每个人都拥有完整的版本库,那么两个人到底如何交流以谁的版本为准呢?一个版本,两个版本还好,假设有100个版本库呢? 实际上,这并不重要,假设有100个人在合作开发一个项目,而你作为项目负责人,你可能并不关心100人的全部工作细节,在乎的只是最终成果,而这些成果是由10个项目组长提交维护的,所以你关心的只是10个版本,假设没有集中式的中央服务器角色,那么你需要手动合并10个版本库,最终完成项目. 这样看起来中央服务器确实还是有存在的必要,为了方便不同版本库之间进行交流,通常分布式版本控制系统也有一台充当中央服务器角色的电脑,需要理解的是,此时中央服务器的作用仅仅是方便大家交换各自的修改而已,没有它,大家还是可以照常工作的,只是彼此间交换修改不太方便而已! 不论是分布式还是集中式,存在即合理,如何取舍有着各自应用场景,分别代表民主和专制. git 和 svn git 是分布式版本控制系统的代表,除此之外还有BitKeeper,Mercurial,Bazaar 等分布式控制系统,每种分布式控制系统均有自身特点,毋容置疑的是git是最简单最流行! svn 是集中式版本控制系统的代表,是目前使用最广泛的集中式版本控制系统,cvs ClearCase等均属于集中式. 不论是分布式还是集中式,不论是免费还是收费,不一昧追求最好的,只需要最适合自己的即可. git 是分布式控制系统,svn 是集中式版本控制系统 git 将内容按元数据方式存储,svn 是按文件方式存储 git 的内容完整性优于svn,因为 git 内容存储基于sha-1哈希算法,确保内容的完整性. 小结 git 是Linus为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/base/about.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/base/install.html":{"url":"git/base/install.html","title":"安装 git","keywords":"","body":"安装 git git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行,根据自身环境选择安装. Linux 系统 linux 系统安装软件大致有两种途径,一种是利用安装包管理工具安装,另一种采用源码包安装方式. 安装前先确认下是否之前已安装过,在命令行窗口输入git --version ,如果打印出版本号则表示已安装,否则参考一下内容进行安装. 查看 git 版本 git --version Debian/Ubuntu # 安装 git 依赖 apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \\ libz-dev libssl-dev # 安装 git apt-get install git # 查看 git 版本 git --version Centos/RedHat # 安装 git 依赖 yum install curl-devel expat-devel gettext-devel \\ openssl-devel zlib-devel # 安装 git yum -y install git # 查看 git 版本 git --version git-core 和 git 历史渊源: 以前有个软件也叫GIT(GNU Interactive Tools),所以git只能叫git-core了,后来由于git名气实在太大以至于GNU Interactive Tools改名成gnuit,而git-core正式改为git. 源码安装 先从git 官网下载指定版本源码,然后解压,依次输入:./config,make, sudo make install 这几个命令安装到指定目录即可. Debian/Ubuntu # 安装 git 相关依赖 apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \\ libz-dev libssl-dev # 下载指定版本源码包 wget https://github.com/git/git/archive/v2.21.0.tar.gz # 解压 tar -zxf v2.21.0.tar.gz # 切换到 git目录 cd git-2.21.0 # 安装 make prefix=/usr/local all # 安装 sudo make prefix=/usr/local install Centos/RedHat # 安装 git 相关依赖 yum install curl-devel expat-devel gettext-devel \\ openssl-devel zlib-devel # 解压 tar -zxf v2.21.0.tar.gz # 切换到 git目录 cd git-2.21.0 # 安装 make prefix=/usr/local all # 安装 sudo make prefix=/usr/local install Windows 系统 直接从git 官网下载安装程序,然后按默认选项安装即可. 安装完成后,在开始菜单里找到Git->Git Bash,弹出命令行窗口,则说明安装成功! Mac 系统 一般有两种安装方式,一种是利用 mac 的homebrew管理工具安装git,具体安装方法参考homebrew官方文档 另一种方法安装xcode默认集成git,首先从 App Store下载 xcode ,下载完成后运行Xcode,选择菜单Xcode->Preferences,在弹出窗口中找到Downloads,选择Command Line Tools,点Install就可以完成安装了 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/base/install.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/base/config.html":{"url":"git/base/config.html","title":"配置 git","keywords":"","body":"配置 git 安装完成后,还需要最后一步配置就可以愉快使用了,在命令行输入: git config --global user.name \"your username\" git config --global user.email \"example@example.com\" 因为Git是分布式版本控制系统,所以每个机器都必须自报家门:你的名字和Email地址. 配置文件 git 提供git config工具,专门用来配置相应的工作环境变量,支持三种不同的位置. /etc/gitconfig 配置文件 (优先级最低) 系统中对所有用户都生效的配置,效果等同于git config --system ~/.gitconfig 配置文件 (优先级其次) 系统中仅仅对当前登录用户生效的配置,效果等同于git config --global $(pwd)/.git/config 配置文件 (优先级最高) 仅仅对当前项目生效,效果等同于git config 每一级别的配置都会自动覆盖上级相同配置,当前项目配置优先于其余配置 查看配置 如果要查看已有的配置信息,可以输入 git config --list 命令,如果看到重复变量名,表示来自不同配置文件(比如/etc/gitconfig 和 ~/.gitconfig),实际上git会采用最后一个! # 查看已有配置信息 git config --list # 查看当前用户配置信息 cat ~/.gitconfig # 查看系统级别配置信息 cat /etc/gitconfig 也可以直接查看某项环境变量值,比如 # 查看用户名称变量 git config user.name var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/base/config.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/usage/about.html":{"url":"git/usage/about.html","title":"实战 git","keywords":"","body":"实战 git git 是一款分布式版本控制系统,可以简单概括: 不要把鸡蛋放在一个篮子里,你的一举一动都在监视中. 实战场景 你作为某项目的其中一员或者负责人,和小伙伴们一起开发,大家既有着各自分工互不干扰,也有着相互合作,最终每个人的劳动成果汇聚成最后的项目,愉快完成项目! 要求 理解 git 的工作流程,懂得实际工作中如何交流合作 掌握 git 常用操作,工具为我所有,进而提高工作效率 独当一面,最好能够独自解决使用git 过程中遇到的问题 主动分享经验,能够教会别人如何使用 git 更上一层楼 推荐 最好的教程在官网 git 官网 在线练习常用操作 Learning Git Branching 廖雪峰的官方网站 git教程 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/about.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/usage/local-repository.html":{"url":"git/usage/local-repository.html","title":"本地仓库","keywords":"","body":"本地仓库 背景 创建工作目录 平时工作时我们习惯对文档分门别类进行管理,.doc .txt 等文本类型的文件习惯存在 doc文件下,开发java js 等源代码文件存在在 src 目录下,这一点很好理解,那么讲解 git的项目我们也要创建一个文件夹,姑且新建一个demo的文件夹吧! # 在工作空间创建指定目录 mkdir demo # 切换至工作目录 cd demo 创建本地仓库 既然已经创建了工作文件夹,那么我们自然是希望该文件下的所有文件都能被 git 管理,也就是说在当前文件下的创建新文件,修改原文件内容或者删除文件等操作都能纳入版本控制中,不然为什么要用git 呢? 下面这个命令就是告诉git 这个 demo 目录要纳入版本控制了. # 初始化本地仓库 git init 一旦运行git init 命令,细心的读者可能会发现在原来的 demo 目录下多了.git隐藏文件,正因如此,原来被我们称为工作目录的 demo 才能纳入版本控制,我们将.git目录称之为版本库. 由于当前项目 demo 只在我们自己电脑上,其他人无法访问,所以我们称这种形式的版本库为本地仓库. 添加文件到版本库 首先明确的是,所有的版本控制系统只能追踪文本文件的改动,文本文件就是平常熟悉的.txt .html .js .css .java .xml等等文件,非文本文件的其他格式有哪些? 例如二进制文件,像我们平时听音乐的.mp3,看视频的.mp4,浏览图片的.png等这些都是二进制文件,需要专门的软件才能正常打开,不信的话,你用记事本看看能不能打开视频? 了解文本文件和二进制文件的区别,那是不是说二进制文件没法进行版本控制了,刚才你不是还说demo 目录下的所有文件吗?这不是自相矛盾吗! 非也非也,git 当然也能够管理二进制文件,对于文本文件的追踪,可以细粒度到哪个文件在哪一行发生了哪些变化,而二进制文件只能粗粒度知道哪个文件变化了,并不知道具体变化. 不幸的是,Microsoft 的Word格式是二进制格式,因此,版本控制系统是没法跟踪Word文件的改动的,前面我们举的例子只是为了演示,如果要真正使用版本控制系统,就要以纯文本方式编写文件. 因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持. 言归正传,现在我们在demo 目录下创建一个test.txt 演示文件,内容如下git test # 创建新文件 touch test.txt # 编辑新文件,输入 git test echo \"git test\" > test.txt 接下来我们还需要两步操作才能将test.txt纳入git管理: 第一步,使用git add 命令将文件添加到本地仓库: # 添加到本地仓库: 第一步指定要添加的文件 git add test.txt 第二步,使用git commit -m 命令将文件提交到本地仓库: # 添加到本地仓库: 第二步指定添加文件备注 git commit -m \"add test.txt\" 经过上述两步操作,test.txt 文件已经纳入到版本控制中了,这里你可能会有疑问了为什么需要add commit两步呢? 因为commit 可以一次性提交很多文件,所以你可以多次add不同的文件,比如: # 创建三个文件file1.txt file2.txt file3.txt touch file1.txt file2.txt file3.txt # 添加一个文件file1.txt git add file1.txt # 添加两个文件file2.txt file3.txt git add file2.txt file3.txt # 一次性提交全部文件 git commit -m \"add 3 files.\" 小结 初始化本地仓库 git init 添加文件到本地仓库分两步 git add 和 git commit -m 实际工作中,大致以下流程 # 在工作空间创建指定目录 mkdir demo # 切换至工作目录 cd demo # 初始化本地仓库 git init # 创建新文件 touch test.txt # 编辑新文件,输入 git test echo \"git test\" > test.txt # 添加到本地仓库: 第一步指定要添加的文件 git add test.txt # 添加到本地仓库: 第二步指定添加文件备注 git commit -m \"add test.txt\" ... # 继续编辑目标文件,追加 git init echo \"git init\" >> test.txt # 将目标文件添加到本地仓库 git add test.txt # 添加本次新增文件的备注 git commit -m \"add git init\" var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/local-repository.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/version-manage.html":{"url":"git/usage/version-manage.html","title":"版本管理","keywords":"","body":"版本管理 背景 在上一节中我们已经成功创建版本库并且已经添加test.txt等文件,这一节我们继续讲解如何进行版本控制. 首先我们先查看test.txt 文件有什么内容吧! # 查看文件内容 $ cat test.txt git test git init git diff $ 接下来模拟正常工作,接着输入一下内容: # 追加新内容到 test.txt 文件 echo \"understand how git control version\" >> test.txt # 查看当前文件内容 $ cat test.txt git test git init git diff understand how git control version $ 紧接着运行 git status 看一下输出结果: # 查看文件状态 $ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) modified: test.txt Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt $ 从上述 git status 命令输出的结果可以看出,test.txt 已经被修改但还没提交,但是具体发生了什么变化却没能告诉我们,如果能够告诉我们具体修改细节那就好了! 运行git diff命令可以实现上述需求 $ git diff diff --git a/test.txt b/test.txt index 729112f..989ce33 100644 --- a/test.txt +++ b/test.txt @@ -1,3 +1,4 @@ git test git init git diff +understand how git control version $ git diff 命令即查看差异(difference),从输出结果可以看出我们在最后一行新增了understand how git control version 文字. 通过git status 知道文件发生了改动,git diff 让我们看到了改动的细节,现在我们提交到版本库就放心多了,还记得上节课如何添加版本库的命令吗? 分两步操作: git add 和 git commit -m 第一步: git add $ git add test.txt $ 等一下,在执行 git commit 命令之前,我们再运行 git status 命令查看一下当前仓库状态: $ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) modified: test.txt $ 此时 git status 命令告诉我们 test.txt 文件已被修改等待提交,好了,那么接着第二步的commit吧! 第二步: git commit -m # 提交到版本库并添加备注 $ git commit -m \"add understand how git control version\" [master 36f234a] add understand how git control version 1 file changed, 2 insertions(+) $ 提交后,我们此时再次运行git status 命令查看当前仓库状态: $ git status On branch master nothing to commit, working tree clean $ 输出结果显示没有需要提价的改动,工作目录是干净的. 小结 查看工作区状态 git status 比较修改差异 git diff var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/version-manage.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/reset.html":{"url":"git/usage/reset.html","title":"回到过去","keywords":"","body":"回到过去 背景 现在你已经掌握git的基本操作了,文件发生更改首先使用 git add 添加更改,然后 git commit 提交全部更改,当本地文件再次发生更改时,仍然需要git add 和 git commit 两步操作,中途如何想查看文件是否发生更改,使用git status 查看版本库状态,git diff 命令帮助我们查看更改详情. 像这样重复的操作其实每次都会产生一个快照,用于保存文件状态,只不过这个快照不是完整的文件,被称为提交或者版本commit .一旦发生意外,假如文件修改乱了或者误删了文件,我们可以从最近的一个 commit 中进行恢复,然后继续工作,这就是git 管理的好处之一. 每一次重大更新或者你认为比较重要的时刻,我们总会留作纪念,添加些什么特殊标记来区分平时的提交,还记得我们每次提交都会添加备注吗?git commit -m 这条命令现在就可以大显身手了,我们现在要做的就是找到我们提交的历史记录,而历史记录中有我们提交的详情,这样即使过了一个月或者更长时间,我们也能清楚知道当时的情景! 查看提交历史记录 git log,接下来我们赶紧试一下吧 $ git log commit 36f234a60d858871f040cb0d7ca3e78251df82f7 (HEAD -> master) Author: snowdreams1006 Date: Thu Mar 7 22:19:00 2019 +0800 add understand how git control version commit 2006f72ffe2ce2278b5974313b8598847cf445e4 Author: snowdreams1006 Date: Tue Mar 5 13:27:46 2019 +0800 add 3 files. commit eaa4850070354ae987dc5108a9fd57fda9d64730 Author: snowdreams1006 Date: Tue Mar 5 12:18:57 2019 +0800 add git init commit 6ad8956bc09a6a62c731711eabe796690aa6471c Author: snowdreams1006 Date: Tue Mar 5 12:17:51 2019 +0800 add test.txt git log 命令默认显示最近到最远的提交历史,这一点也很好理解,毕竟我们是在命令行操作,输入git log 完毕后自然先要定位到命令处,看到最新提交记录方便我们确认是否符合我们预期,还有一点就是如果提交历史过多,从头开始到最新提交记录岂不是眼花缭乱,简直不敢想象啊! 下面以最新的一次提交 commit 为例,简单解释一下输出内容: # 提交唯一标示id: 36f234a60d858871f040cb0d7ca3e78251df82f7 commit 36f234a60d858871f040cb0d7ca3e78251df82f7 (HEAD -> master) # 作者: snowdreams1006 邮箱: Author: snowdreams1006 # 日期: Thu Mar 7 22:19:00 2019 +0800 Date: Thu Mar 7 22:19:00 2019 +0800 # 提交备注: add understand how git control version add understand how git control version 默认输出内容有点多,不仅有提交 id ,提交备注还有作者时间之类的,由于每个 commit 都如此,这样一来,满屏都展示不下,那能不能简化些呢? 一行显示提交日志 --pretty=oneline ,即git log --pretty=oneline $ git log --pretty=oneline 36f234a60d858871f040cb0d7ca3e78251df82f7 (HEAD -> master) add understand how git control version 2006f72ffe2ce2278b5974313b8598847cf445e4 add 3 files. eaa4850070354ae987dc5108a9fd57fda9d64730 add git init 6ad8956bc09a6a62c731711eabe796690aa6471c add test.txt $ 相比无参数git log,是不是简短了一些呢? 和之前日志相比少了作者和时间等信息,仍然保留提交 id 和提交备注. 因为提交 commit 是 git 的基础,当然不能省略,而提交备注能够帮助我们理解commit 的含义,毕竟提交备注使我们自定义的内容,这也是我们为什么提交时要写提交备注的原因! 现在我们已经了解到版本库存放了我们的提交,接下来让我们验证一下是否能够回到过去吧! 回到上一个提交,上一个提交自然是相对当前提交而言,只有知道当前提交才能知道上一个提交以及上一个提交的上一个提交. 提交id 36f234a60d858871f040cb0d7ca3e78251df82f7,那么上一个提交HEAD^,上上一个提交是HEAD^^.如果此时我想回到往上数100个版本,那么是不是可以这么写? HEAD^^^^...^^^ 其中^ 有100个,如果需要手动打出100个^的话,那么绝对是疯了! 既然有这种相对定位方式,自然也有绝对定位方式,用绝对定位方式解决就是这样: HEAD~100 $ git log commit 36f234a60d858871f040cb0d7ca3e78251df82f7 (HEAD -> master) Author: snowdreams1006 Date: Thu Mar 7 22:19:00 2019 +0800 add understand how git control version 回到上一个版本 git reset --hard HEAD^ 在操作之前我们先看一下当前文件 test.txt 的内容: $ cat test.txt git test git init git diff understand how git control version 现在让我们开始回到过去,运行 git reset --hard HEAD^ 命令: $ git reset --hard HEAD^ HEAD is now at 2006f72 add 3 files. $ 现在让我们再看一下,test.txt 的内容有没有被还原: $ cat test.txt git test git init 果然被还原了!这就是git的神奇之处,说明我们已经能够回到过去了! 现在我们先用git log 查看下提交历史: $ git log commit 2006f72ffe2ce2278b5974313b8598847cf445e4 (HEAD -> master) Author: snowdreams1006 Date: Tue Mar 5 13:27:46 2019 +0800 add 3 files. commit eaa4850070354ae987dc5108a9fd57fda9d64730 Author: snowdreams1006 Date: Tue Mar 5 12:18:57 2019 +0800 add git init commit 6ad8956bc09a6a62c731711eabe796690aa6471c Author: snowdreams1006 Date: Tue Mar 5 12:17:51 2019 +0800 add test.txt $ 和上次相比,少了一条提交记录: commit 36f234a60d858871f040cb0d7ca3e78251df82f7 (HEAD -> master) Author: snowdreams1006 Date: Thu Mar 7 22:19:00 2019 +0800 add understand how git control version 这样是正常的,毕竟你已经处于 过去 了,当然看不到 未来 的提交记录. 正如影视穿越剧那样,主人公意外穿越过去,总是想要回到未来,怎么办,没有法器没有未来的确切目标怎么行?! git 的穿越剧也需要这样一种法器,能准确告诉时光机把我们带到具体的那个时间点,当然这个时间点不一定是未来时刻,过去时刻也行,反正就是一个准确的坐标. 聪明的你肯定已经猜测到这个任务是由commit 担任的,所有我们现在要找到未来的时间点,也就是commit id,就是那一长串 hash 字符串. 只要当前命令行窗口还没有关闭,慢慢往上翻,总是能找到当初我们的穿越点commit的,即36f234a60d858871f040cb0d7ca3e78251df82f7 回到当初提交 git reset --hard 万事俱备只欠东风,已经成功定位到未来坐标,等待穿越到未来! $ git reset --hard 36f234a60d858871f040cb0d7ca3e78251df82f7 HEAD is now at 36f234a add understand how git control version $ 现在我们再次查看 test.txt 内容: $ cat test.txt git test git init git diff understand how git control versi 果然成功穿越回到未来! 上述穿越回到未来的场景是我们知道目标 commit ,也就是在当前命令行窗口没有关闭的情况下,手动查找穿越点 commit.那如果命令行窗口已关闭或者没办法通过查阅历史命令来定位穿越点 commit 情况下怎么办呢? 这种情况下也是有补救措施的,git 提供了命令历史 git reflog,记录了我们操作的命令历史. 翻阅历史命令 git reflog $ git reflog 36f234a (HEAD -> master) HEAD@{0}: reset: moving to 36f234a60d858871f040cb0d7ca3e78251df82f7 2006f72 HEAD@{1}: reset: moving to HEAD^ 36f234a (HEAD -> master) HEAD@{2}: commit: add understand how git control version 2006f72 HEAD@{3}: commit: add 3 files. eaa4850 HEAD@{4}: commit: add git init 6ad8956 HEAD@{5}: commit (initial): add test.txt 确实记录了我们操作的关键命令,从上述输出结果可以看出,穿越点 commit 正是36f234a60d858871f040cb0d7ca3e78251df82f7,剩下的事情应该不必多说了吧! 小结 HEAD 是当前提交的指针,指向的提交就是当前提交,上一个提交是 HEAD^,上上个提交是 HEAD^^,前100个提交是HEAD~100. git log 查看提交历史,git log --pretty=oneline 简短化输出提交历史. git reflog 查看命令历史,以便我们重拾关键步骤信息. git reset --hard 穿越到指定提交,比如上一个提交就是 git reset --hard HEAD^ . var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/reset.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/concept.html":{"url":"git/usage/concept.html","title":"基本概念","keywords":"","body":"基本概念 了解工作区,暂存区和版本库的区别和联系有助于我们更好理解 git 的工作流程,了解命令的操作意图. git 和其他版本控制系统如 svn 的不同之处就是有暂存区的概念. 基本概念 工作区 | Working Directory 正常情况下能看到的目录(不包括隐藏文件),也就是用户主动创建的目录 暂存区 | Stage 工作区下的隐藏.git目录下的.index文件,因此也称为索引. 版本库 | Repository 工作区下的隐藏目录.git目录 通过前几节我们知道,将文件纳入版本控制,需要分两步操作: 第一步 git add 添加文件,实际上是将文件更改添加到暂存区. 第二步 git commit 提交更改,实际上是将暂存区所有内容提交到当前分支. 我们使用 git init 命令初始化创建 git 仓库时,git 会自动创建唯一一个 master 分支,默认所有操作是在 master 分支上进行的,所以 git commit 就是徃 master 分支上提交更改的. 通俗地讲,文件更改可以多次添加到暂存区,即允许多次执行 git add 命令,然后一次性提交暂存区的全部更改到版本库,即只需要执行一次 git commit 命令即可. 说说个人理解 git 为何分成三部分进行版本控制操作,二部分行不行? 答案是肯定的,没有暂存区概念的 svn 同样可以进行版本控制,所以 git 增加暂存区必然是有存在的意外也就是所谓的好处的. 第一,暂存区的概念允许将本地文件的更改添加进来,也就是说本地文件的更改只有添加到暂存区才能进行下一步的提交更改,所以说那些更改添加到暂存区是由开发者本人决定的,这其实有了一定灵活性,并不是所有的更改都需要被记录! 第二,暂存区作为中间过程,暂存区的内容是打算提交更改的内容,也就是说暂存区可以视为一种临时缓存,用来记录预提交更改.实际工作中,新功能的开发并不是一蹴而就的,是由一系列的更改一起组成的,如果将这些更改分散开来单独提交,那势必会产生很多commit,如果等待全部工作完成再提交的话,解决了过多commit的问题,但是又遇到新问题就是你可能很长时间才能提交一次更改,失去了版本控制的意义.综上所述,暂存区的出现一种很好的解决方案,它允许将相关性代码添加在一起,方便后续提交更改时提交的都是相关性代码! 第三,作为分布式版本控制系统,不像集中式控制系统那样,对网络强相关,失去网络的 svn 是没办法再进行版本控制的,但失去网络的 git 仍然可以进行版本控制,只不过不能远程操作了而已,不过这部分也是无可厚非的,正所谓\"巧妇难为无米之炊\",你总不能要求断网下继续访问百度吧! 好了,我们继续回到 git 常用操作上,看一下工作区,暂存区和版本库三者如何协同工作的. 首先,先修改test.txt文件. # 查看 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version # 追加 how git work 到 test.txt 文件 $ echo \"how git work\" >> test.txt # 再次查看 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work $ 紧接着新建newFile.txt 并随便输入内容: # 查看当前文件夹下全部文件 $ ls . file1.txt file2.txt file3.txt test.txt # 创建新文件 newFile.txt $ touch newFile.txt # 再次查看当前文件夹下全部文件 $ ls file1.txt file2.txt file3.txt newFile.txt test.txt # 输入 add newFile.txt 文件内容 到 newFile.txt 文件 $ echo \"add newFile.txt\" > newFile.txt # 查看 newFile.txt 文件内容 $ cat newFile.txt add newFile.txt $ 现在运行git status 命令查看当前文件状态: $ git status On branch master Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store newFile.txt no changes added to commit (use \"git add\" and/or \"git commit -a\") $ 从输出结果中得知,test.txt 文件已修改(modified),还没添加到暂存区,而newFile.txt 文件还没被跟踪(Untracked). 现在我们使用git add 命令将 test.txt 和 newFile.txt 都添加到暂存区,再用 git status 查看文件状态: # 添加 test.txt 文件 git add test.txt # 添加 newFile.txt 文件 git add newFile.txt # 查看文件状态 git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) new file: newFile.txt modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store $ 现在输出结果和上次就不一样了,显示的是即将被提交文件,其中newFile.txt 是新文件(new file),test.txt 是修改文件(modified). 所以,git add 命令作用是将需要提交的更改文件临时放到暂存区中,然后执行git commit 命令就可以一次性将暂存区的所有内容提交到当前分支. $ git commit -m \"understand how stage works\" [master a5cd3fb] understand how stage works 2 files changed, 2 insertions(+) create mode 100644 newFile.txt $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 暂存区的所有内容提交到版本库,所以运行git status 时,工作区是干净的,即此时暂存区没有内容了! .DS_Store 是 mac 电脑自动生成的文件,可以暂不理会,等到后面的.gitignore 文件时再处理. 图解 下图展示了工作区,暂存区,版本库之间的关系: 图中左侧是工作区,右侧是版本库,版本库中标记index 的区域是暂存区,标记 master 的是 master 分支所代表的目录树. HEAD 是指向 master 分支的指针,标记 objects 的区域是 git 的对象库,真实路径位于.git/objects目录下,用于表示创建的对象和内容. 意图说明 git add 添加文件 工作区的修改或者新增的文件执行git add 命令后,暂存区(index)的目录树会自动更新,同时引发这次变化的文件内容会被记录下来,即生成对象库(objects)中的新对象,而对象的 id会被记录到暂存区的文件索引(index)中. git commit 提交文件 暂存区的目录树写入到对象库(objects),master 分支的目录树自动更新. git reset HEAD 撤销文件 暂存区的目录树被重写,被master 分支的目录树所替换,但是工作区不受影响. git rm --cached 删除缓存文件 删除暂存区文件,工作区不受影响. git checkout . 检出文件 暂存区的文件替换工作区文件,注意:当前尚未添加到暂存区的改动会全部丢失! git checkout HEAD . 检出文件 HEAD 指针指向的 master 分支中的文件替换暂存区以及工作区文件,注意:不仅清除工作区未提交的改动,连暂存区未提交的改动也会被清除! 小结 以上就是常用命令的背后意图,主要是工作区,暂存区和版本库之间文件同步策略的关系. git add 是工作区更新到暂存区 git commit 是暂存区更新到版本库 git reset HEAD 是版本库更新到暂存区 git checkout -- 是暂存区更新到工作区 git checkout HEAD 是版本库同时更新暂存区和工作区 git rm --cached 清空暂存区 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/concept.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/version-control.html":{"url":"git/usage/version-control.html","title":"版本控制","keywords":"","body":"版本控制 我们知道 git 是分布式版本控制系统,所以称被控制对象是版本本身没错,但是从git 命令中发现,并没有版本这个名词,有的只是commit,所以前几节我一直称其为提交. 为了避免后续教程引发歧义,特意说明,无论是版本也好,提交也罢,都是中文翻译而已,不必太过较真,直接原汁原味称commit也可以啊! 假设你已掌握暂存区的相关概念,简单来说,暂存区就是更改文件的缓存集合,等待一次性全部提交到版本库,正因如此,方便我们批量操作相关性文件,打包提交到版本库,这正是暂存区的独特魅力. 我们反复在说 git 是分布式版本控制系统,分布式的概念已经粗略讲过多次了,下面我们讲一下版本控制,谈谈 git 的版本控制和其他系统的版本控制有什么不同,为什么 git 这么优秀,如此流行? git 跟踪并管理的是更改,而非文件本身.正如linux 一切皆文件,java 一切皆对象一样,git 一切皆更改.新增文件是一个更改,新增文件内容是一个更改,修改文件内容是一个更改,删除文件内容也是一个更改,换言之,git 管理的正是这一个个的更改,并不是文件本身. 下面我们用事实说话,证明 git 管理的是更改而不是文件本身: 第一步,追加 git tracks changes 到 test.txt 文件 # 查看 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work # 追加 git tracks changes 文件内容到 test.txt 文件 $ echo \"git tracks changes\" >> test.txt # 再次查看 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes $ 第二步,添加test.txt 文件到暂存区并查看文件状态 $ git add test.txt sunpodeMacBook-Pro:demo sunpo$ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store $ 对于上述内容应该不必再解释了吧,无外乎说test.txt 文件已修改(modified),即将被提交(to be committed). 但是,此时偏偏不提交,继续修改 test.txt 文件:(这种情况实际工作中也有可能出现,比如你正在研发某功能,本以为已经开发完毕,满心欢喜添加到暂存区,然后意外发现一个小bug,分分钟就修复了,时间间隔很短以至于你根本不记得还需要再次添加到暂存区.) 第三步,继续修改文件内容,忘记再次添加到暂存区 # 编辑 test.txt 文件,将 git tracks changes 更改为 git tracks changes of files vim test.txt # 查看 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files $ 第四步,正常提交暂存区的全部更改到版本库 $ git commit -m \"git tracks changes\" [master 2daa74a] git tracks changes 1 file changed, 1 insertion(+) 此次提交后,我们再看一下文件状态: $ git status On branch master Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store no changes added to commit (use \"git add\" and/or \"git commit -a\") $ 发现有什么不同吗?以往提交后再次查看文件状态,工作区都是干净的,这次居然提示我们 test.txt 文件已经修改但未添加到暂存区?! 等一下,我们先回忆一下我们的操作流程: 第一次修改(git tracks changes) -> git add -> 第二次修改(git tracks changes of files) -> git commit 这样就很好理解了,git 管理的是更改而不是文件本身,如果是文件本身的话,应该将文件的内容全部提交才对,所以管理的是更改. 第一次修改过后使用 git add 命令将工作区的第一次修改内容放到暂存区准备提交,但是此时工作区发生了第二次修改,注意,这次修改并没有放到暂存区,所以下一步的git commit 命令提交的暂存区内容中自然也就没有第二次修改的内容了!所以git commit 完毕后运行git status命令才会发现此时工作区和暂存区还存在版本差异,即此时工作区不是干净的! 这一次的实验很好理解,工作区的修改需要主动告诉暂存区,暂存区的全部更改再提交到版本库.所以版本库的提交取决于暂存区,而暂存区又取决工作区是否主动将更改添加进去了吗! 理论再多不如亲身体验,让我们直接比较一下工作区和版本库的差异吧! # 比较 test.txt 文件在工作区和版本库的差异 $ git diff HEAD -- test.txt diff --git a/test.txt b/test.txt index d31bdd2..56c76b7 100644 --- a/test.txt +++ b/test.txt @@ -3,4 +3,4 @@ git init git diff understand how git control version how git work -git tracks changes +git tracks changes of files $ 由此可见,工作区比版本库多了git tracks changes of files,少了git tracks changes,所以说第二次修改内容 git tracks changes of files 并没有被提交. 现在我们再解释一下-git tracks changes 和 +git tracks changes of files 的问题: 首先查看工作区 test.txt 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files $ 根据上述分析,我们知道第一次的修改git tracks changes 已被提交到版本库,第二次的修改git tracks changes of files 没有被提交而是继续留在工作区. 因此,可以推断出目前版本库的文件应该是这样的: git test git init git diff understand how git control version how git work git tracks changes 既然如何,工作区和版本库相比岂不刚好是少了一个git tracks changes,多了git tracks changes of files,其余文件内容完全相同! 透过现象看本质,已经分析了现象也解释了产生现象的原因,是时候分析一下本质了. 抛出问题:因为git tracks changes of fiels 和 git tracks changes 被视为不同的更改,所以才会造成上述现象.如果git tracks changes of fiels 被认为是git tracks changes + of fiels 两者叠加产生的更改,还会产生上述现象吗? 答案是否定的,如果两个更改可以叠加的话,按照版本控制的思路,第二次的修改即便没有提交也只是 of fiels 没有加入到版本库而已,如此一来,工作区和版本库的差异将不再是少了一个git tracks changes,多了git tracks changes of files,而仅仅是多了of files! 由此可见,git 版本控制系统其实是全量更新的思维模式,并不是差量更新模式. 小结 工作区的更改需要git add 添加到暂存区,git commit 将暂存区的全部更改提交到版本库. 工作区,暂存区,版本库三者既相关独立又密切关联,三者是传递性依赖的关系. git 版本控制的是文件的更改,而不是文件本身,是全量更新模式,而不是差量更新模式. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/version-control.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/checkout-reset.html":{"url":"git/usage/checkout-reset.html","title":"撤销更改","keywords":"","body":"撤销更改 相信你已经了解了 git 的基本概念,也清楚了工作区,暂存区和版本库的关系,现在让我们用所学的知识继解决实际问题吧! 背景 正常看得见的目录是我们最为熟悉的工作区,在工作中不可能总是100%的精力,难免会犯错,尤其是下午犯困,晚上加班更是如此.下面列举了常见的一些场景 场景一: 工作区出现意外更改且尚未添加到暂存区 北京时间现在是晚上10点钟,你正在赶制一份工作报告,尽管心中一万个不愿意,还是不得不做. 开始模拟意外更改前,先查看一下 test.txt 文件相关信息: # 列出当前目录的文件 $ ls file1.txt file2.txt file3.txt newFile.txt test.txt # 查看 `test.txt` 文件内容 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files # 查看 `test.txt` 文件状态 $ git status On branch master Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store no changes added to commit (use \"git add\" and/or \"git commit -a\") # 查看 `test.txt` 文件差异 $ git diff diff --git a/test.txt b/test.txt index d31bdd2..56c76b7 100644 --- a/test.txt +++ b/test.txt @@ -3,4 +3,4 @@ git init git diff understand how git control version how git work -git tracks changes +git tracks changes of files $ 还记得在上一节中我们讲解 git 版本控制的到底是什么,为了证明 git 管理的是更改而不是文件本身,我们特意在第二次更改时没有添加到暂存区,现在我们先把这个遗留问题解决掉. # 工作区更改添加到暂存区 $ git add test.txt # 暂存区内容提交到版本没哭 $ git commit -m \"git tracks changes of files\" [master b7bda05] git tracks changes of files 1 file changed, 1 insertion(+), 1 deletion(-) # 查看文件状态 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 现在正在加班加点干活,一不小心将心中的不满表露出来了,于是有了下面的内容: # 意外更改正是这么犯傻的一句话 $ echo \"My stupid boss still prefers svn\" >> test.txt # 当前文件内容 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files My stupid boss still prefers svn $ 虽然强打精神,可还是很困,于是打算喝杯咖啡提提神,猛然发现 stupid boss 可能会让你丢掉这个月的奖金! 暗自庆幸,咖啡果然是个好东西,既然发现了问题,那就事不宜迟赶紧修复,因为不适宜的话正是 stupid boss ,所以你完全可以手动删除,但是假如你说了一大堆不合适的话,或者复制粘贴时弄错了,这就不是删除一两行那么简单了! 既然手动解决比较麻烦,那git 有没有什么好方法来解决这类问题呢?在寻求git 帮助前,首先再看一下当前文件状态(git status).正所谓\"知己知彼方能百战百胜\",还是看一眼吧! # 查看文件状态 $ git status On branch master Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store no changes added to commit (use \"git add\" and/or \"git commit -a\") $ git 不负众望,果然给了我们希望,(use \"git checkout -- ...\" to discard changes in working directory) 这句话的告诉我们可以丢弃工作区的更改! 脑海中在快速回忆一下工作区,暂存区,版本库三者之间的关系,其实git checkout -- 命令的意思是用暂存区的内容替换掉工作区内容,因此也就是丢弃掉工作区的更改了. 事不宜迟,运行 git checkout -- 命令试试看吧: # 丢弃工作区的更改 $ git checkout -- test.txt # 查看文件内容: My stupid boss still prefers svn 终于不见了 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files # 查看文件状态 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 一顿操作猛如虎,撤销掉意外更改,回到上一次版本控制状态,世界如此美好... 注意: git checkout -- 中的 -- 至关重要,没有它就是切换分支了! 场景二: 工作区出现意外更改且已经添加到暂存区,但尚未提交到版本库 时间一分一秒过去了,转眼间已经11点了,假设你不但写了一些胡话,还添加到暂存区了(git add).可想而知,这次意外比场景一要糟糕. # 模拟正常提交(不然岂不是从场景一到场景二你什么都没做,那还能叫做赶制工作报告吗?!) $ echo \"someone prefers svn,but i don't care it\" >> test.txt $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it $ git add test.txt $ git commit -m \"normal commit\" [master ab1cbd2] normal commit 1 file changed, 1 insertion(+) # 意外更改的前夕 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it # 意外更改内容: my teammate is stupid too. $ echo \"my teammate is stupid too.\" >> test.txt $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it my teammate is stupid too. # 意外操作: 将意外更改内容提交到暂存区 $ git add test.txt 不过庆幸的是,在提交到版本库(git commit)之前及时发现问题,还是看一下现在的文件状态(git status)吧! # 查看文件状态: 救命稻草 (use \"git reset HEAD ...\" to unstage) $ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store $ git 同样告诉我们,可以使用 git reset HEAD 命令撤销暂存区更改. 其实 git reset HEAD 命令是用版本库的内容替换掉暂存区的内容,也就是说原来暂存区的内容已被丢弃了! 所以说这个命令并不会影响工作区内容,不如我们现在再看一眼工作区内容,方便执行 git reset HEAD 命令后证实我们的结论. # 查看文件内容: my teammate is stupid too. $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it my teammate is stupid too. $ 迫不及待执行 git reset HEAD 命令,先睹为快! # 救命稻草: 版本库内容替换掉暂存区内容 $ git reset HEAD test.txt Unstaged changes after reset: M test.txt # 效果: 目标文件已修改但未添加到暂存区 $ git status On branch master Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store no changes added to commit (use \"git add\" and/or \"git commit -a\") # 目标文件内容: 仍然保持不变 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it my teammate is stupid too. $ 现在场景二已经退化成场景一了,目标文件发生意外更改但还没添加到暂存区,如何撤销工作区更改,请参考场景一方法. 提示: git checkout -- test.txt 场景三: 工作区出现意外更改不仅已添加到暂存区,还已提交到版本库,但尚未推送到远程仓库 时间不紧不慢地已经到凌晨了,困意越来越浓,洋洋洒洒写下几千字的工作报告,总算是写完了,添加到暂存区(git add),提交到版本库(git commit)一气呵成,等等,好像有什么不对劲,难免会犯糊涂,这不又发生意外了! # 衔接场景二 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it # 正常提交一 $ echo \"i love working,work makes me happy\" >> test.txt $ git add test.txt $ git commit -m \"encourage myself\" [master a44cf7a] encourage myself 1 file changed, 1 insertion(+) # 正常提交二 $ echo \"fix 110 bugs,so happy\" >> test.txt $ git add test.txt $ git commit -m \"fix bugs\" [master c66399d] fix bugs 1 file changed, 1 insertion(+) sunpodeMacBook-Pro:demo sunpo$ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) # 意外更改: hate to work overtime $ echo \"hate to work overtime\" >> test.txt $ git add test.txt $ git commit -m \"test.txt\" [master c965724] test.txt 1 file changed, 1 insertion(+) $ 天妒英才,加班加点做事情,本想赢得老板的赏识,没想到最后一句话\"hate to work overtime\"让所有的努力都付之一炬,怎么办? 死马当活马医,还是照例看看git status 能提供什么建议吧! $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 没有提供任何意见能帮助我们撤销意外更改,先别慌,容我深思三秒钟... 既然意外更改已经提交到版本库,那么应该用什么内容替换版本库内容呢?有了,既然最新版本库不可用,那上一个版本库内容可用的啊,完全可以用上一个版本库内容替换最新版本库内容,真乃\"天生我材必有用\"! # 当前文件内容: 闯祸的\"hate to work overtime\" $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it i love working,work makes me happy fix 110 bugs,so happy hate to work overtime # 版本回退: 回到过去假装什么都没发生过 $ git reset --hard HEAD^ HEAD is now at c66399d fix bugs sunpodeMacBook-Pro:demo sunpo$ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) # 岁月静好,一切似乎都没发生过 $ cat test.txt git test git init git diff understand how git control version how git work git tracks changes of files someone prefers svn,but i don't care it i love working,work makes me happy fix 110 bugs,so happy # 当前文件状态 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 详情请参考回到过去,时空穿越之旅就是这么方便哈! 提示: git reset --hard HEAD^ 场景四: 工作区出现意外更改不仅已添加到暂存区,还提交到版本库,还已推送到远程仓库 场景一到场景三都是本地仓库,所有的文件更改只能本机访问,小伙伴也好,上级领导也罢都无法查看到你本地更改,但是一旦你推送到远程仓库了,那么其他人就能查看你的更改了! 正常的提交更改还好,怕就怕这种\"stupid boss\"被领导看到就不好了,那应该怎么办?暂时还是自求多福吧! 小结 丢弃工作区更改: git checkout -- 丢弃暂存区更改: git reset HEAD 丢弃本地版本库更改: git reset --hard HEAD^ 丢弃远程版本库更改: 自求多福 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/checkout-reset.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/delete.html":{"url":"git/usage/delete.html","title":"删除文件","keywords":"","body":"删除文件 回忆一下文件的常见操作,新增文件,修改文件,删除文件等,新增和修改文件都单独讨论过,现在我们来研究一下如何删除文件. 你可能会说删除文件还不简单啊,直接 rm -rf 即可,但是这仅仅是本地文件被删除了,对于 git 来说,文件并没有被删除. 还记得我们开篇介绍git 时就说过,一切操作皆版本 ,对于新增是一个版本,修改也是一个版本,就连删除都是一个版本. 下面让我们看一下 git 中如何删除文件吧! 背景 # 查看当前文件列表 $ ls file1.txt file2.txt file3.txt newFile.txt test.txt # 新建待删除文件 $ touch delete.txt # 再次查看当前文件列表,确保新建文件成功 $ ls delete.txt file2.txt newFile.txt file1.txt file3.txt test.txt # 查看当前文件状态: 新文件 `delete.txt` 还没被跟踪 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store delete.txt nothing added to commit but untracked files present (use \"git add\" to track) # 添加新文件 `delete.txt` $ git add delete.txt # 查看文件状态: 已添加到暂存区,待提交到版本库 $ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) new file: delete.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store # 提交新文件 `delete.txt` $ git commit -m \"add delete.txt\" [master 7df386a] add delete.txt 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 delete.txt # 再次查看文件状态: 已经没有新文件 `delete.txt` 的更改信息 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 以上操作,我们简单创建 delete.txt 文件,添加(git add)并提交(git commit) 该文件,完成准备工作后,开始删除文件! # 删除前文件列表 $ ls delete.txt file2.txt newFile.txt file1.txt file3.txt test.txt # 删除刚刚创建的文件 `delete.txt` $ rm delete.txt # 删除后文件列表 $ ls file1.txt file2.txt file3.txt newFile.txt test.txt # 当前文件状态: `delete.txt` 文件已被删除,且未添加到暂存区 $ git status On branch master Changes not staged for commit: (use \"git add/rm ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) deleted: delete.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store no changes added to commit (use \"git add\" and/or \"git commit -a\") $ 本地删除 delete.txt 文件后,再次查看文件状态 git status 发现 git 给了我们两条建议,其中一条 git checkout -- 我们很熟悉,就是丢弃工作区的更改,此时此景下如果丢弃删除操作,相当于撤销删除,难怪说删除也是一个版本呢! 现在我们重点来看第一条建议 git add/rm ,rm 是 remove 单词的缩写,即删除文件. # 删除文件 $ git rm delete.txt rm 'delete.txt' # 查看文件状态: `delete.txt` 文件待提交 $ git status On branch master Changes to be committed: (use \"git reset HEAD ...\" to unstage) deleted: delete.txt Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store # 提交文件 $ git commit -m \"remove delete.txt\" [master 6298070] remove delete.txt 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 delete.txt # 再次查看文件状态 $ git status On branch master Untracked files: (use \"git add ...\" to include in what will be committed) .DS_Store nothing added to commit but untracked files present (use \"git add\" to track) $ 删除文件和添加文件类似,都是一次commit ,本地文件的任何更改都要添加到暂存区,然后提交到版本库. 小结 删除文件和新增文件类似逻辑,git rm 删除文件后,依然需要 git commit 提交版本. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/delete.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/remote-repository.html":{"url":"git/usage/remote-repository.html","title":"远程仓库","keywords":"","body":"远程仓库 如果说本地仓库已经足够个人进行版本控制了,那么远程仓库则使多人合作开发成为可能. 如果你只是打算自己使用git,你的工作内容不需要发布给其他人看,那就用不到远程仓库的概念. git 是分布式版本控制系统,分布式意味着同一个git 仓库 可以部署在不同的机器上,正如\"鸡生蛋蛋生鸡\"问题一样,不论如何,先要有一个原始仓库,然后才能分布到其他机器上去. 充当原始仓库的机器要有一个特点那就是24h 开机且大家都能访问到,这个概念类似于\"中央服务器\".这样一来大家都可以从\"中央服务器\"下载最新代码,克隆到本地,本地发生更改后再推送给\"中央服务器\".如此一来,大家交流方便很多,轻松实现文件内容的共享. 这种\"中央服务器\"比较有名的是国外的网站 github,当然国内也有不少类似服务.像这种\"中央服务器\"也可以自己搭建,现阶段搭建的话简直就是\"杀鸡焉用牛刀\"! 背景 关于如何注册配置相关请参考 github 教程 为了和上述教程保持一致,项目名git-demo,先看一下当前工作区状态: # 查看文件列表 $ ls LICENSE README.md test.txt # 查看文件内容 $ cat test.txt add test.txt 现在测试一下本地更改能否推送到远程仓库,先在本地文件 test.txt 随便写点东西,然后添加(git add),提交(git commit),最后推送到远程仓库(git push origin master). # 写入新的内容并提交到本地仓库 $ echo \"see https://snowdreams1006.github.io/git/usage/remote-repository.html\" >> test.txt $ git add test.txt $ git commit -m \"see https://snowdreams1006.github.io/git/usage/remote-repository.html\" [master b3d8193] see https://snowdreams1006.github.io/git/usage/remote-repository.html 1 file changed, 1 insertion(+) # 推送到远程仓库 $ git push origin master Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 359 bytes | 359.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: Resolving deltas: 100% (1/1), completed with 1 local object. To github.com:snowdreams1006/git-demo.git 8e62564..b3d8193 master -> master $ 命令行没有报错证明我们已经成功推送到 github,现在登录 github 看一下有没有刚才我们提交的新内容. 现在本地版本库和远程版本库已经能够正常建立关联了,此刻起将不再是独自一人在战斗! 小结 创建已有本地仓库和远程仓库的关联 # 添加远程仓库关联 git remote add origin git@github.com:username/repos.git # 首次推送 master 分支的全部内容 git push -u origin master # 后续推送 master 分支的最新更改 git push origin master 从已有远程仓库克隆到本地仓库 # 克隆远程仓库到本地仓库 git clone git@github.com:username/repos.git # 推送 master 分支的最新更改 git push origin master var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/remote-repository.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-manage.html":{"url":"git/usage/branch-manage.html","title":"分支管理","keywords":"","body":"分支管理 背景 什么是分支?简单地说,分支就是两个相对独立的时间线,正常情况下,独立的时间线永远不会有交集,彼此不知道对方的存在,只有特定情况下,两条时间线才会相遇,因为相遇,所以相知,因为相知,所以改变! 正如分支对于科幻电影来说是一个很好的卖点,关于分支的话题完全可以开启新的题材,对于这点相信不少科幻迷都深有体会,不必赘述. 回归正题,分支对于版本控制系统又意味着什么呢?实际工作中,我们大多作为一个团队一起合作开发项目,如果是独立开发者,只有一个人的话,其实用不到分支的概念,甚至远程仓库也用不到.所以下述情况针对的都是团队开发情况. 作为团队中的一员不论是项目领导还是项目成员,都需要了解并掌握分支的一般概念和常用操作.如果你刚好是实际开发的程序猿,上级领导分派一个新功能,预期两个星期内才能完成,其他同事也是如此,每个人都有自己的任务.接收任务就要开始干活,第一天工作开了一个头,还留下一大堆的 TODO 标记,此时你照例运行 git add ,git commit 等命令,学会上节的git push origin master 你知道了本地仓库和远程仓库的概念,你想将你的工作成果分享给其他人就要推送到远程仓库,这样其他人才能可见,等一等,别急! 首先明确的是,这个完整功能至少需要2个星期才能基本完成啊,你现在刚刚起了个头还没完成呢!你要是真的推送到远程仓库了,那其他人是不是有理由认为你这部分功能已完成?那你可能会反驳说,我可以在工作群吼一声,说这个功能还没完成,大家别着急使用哈!这样确实可以,很长一段时间内其他人必须无视你的代码,只有等你的功能基本可用时,等你再吼一声,别人才会去使用你的代码.粗略一看,好像并没有什么问题?! 实际上这种情况是存在很大风险的,因为未完成未经过测试的代码可能会产生大量意外 bug,严重的话,甚至影响整个系统,到时候由于你的未完成代码导致别人项目都无法运行,那别人还怎么工作,这个责任是谁负责? 所以,为了不给其他人造成麻烦,最好不要把未完成工作直接暴露到别人面前,那长时间提交又可能会造成丢失更改的风险,此时此景,平行时间线应用而生! 从接手新功能的时间点开始,创建一条新的时间线,于是新功能的开发完全在新的时间线上进行,至于其他人是否开启新的时间线那就不是我们能控制得了,我们能做到的就是不给其他人制造麻烦,如果其他人给我们制造麻烦的话,那我们就去上级领导那告他一状,哈哈! 等功能开发差不多时,你再想办法切换到原来的时间线上并将开发时间线的更改顺便都带过来,这样一来,别人虽然看不到你的开发时间线,但是看到了你离开的这段时间原来做了这么多的更改啊! 现在用git的专业术语再解释一遍上述场景: 接手新功能的时刻开始,创建一个开发分支(既可以是本地分支也可以是远程分支),以后新功能的开发全部在开发分支上完成,处于开发分支上你可以照常运行 git add ,git commit 等命令,不用担心丢失更改.等工作一段时间后,终于完成了新功能,是时候让新功能展示给其他同事了.此时再切换到原来的主干分支,在主干分支上合并开发分支,现在主干分支上已经有新功能了,这样一来,其他同事突然发现你已经偷偷地完成了新功能的开发! 不仅 git 有分支概念,其他版本控制系统比如 svn 也有分支概念,基本概念和常用操作类似,只不过 git 更强大,创建分支,切换分支,合并分支等功能十分强大,效率太高! (svn 创建分支,切换分支等操作简直慢到可以喝一杯茶了,分支管理都快成摆设了!) 建议 开发新功能时尽量创建自己的分支,不要给其他人造成麻烦 分配任务时要求项目成员创建各自分支,等时机成熟时再合并到主干分支 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-manage.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-overview.html":{"url":"git/usage/branch-overview.html","title":"分支总览","keywords":"","body":"分支总览 分支就是一条独立的时间线,既有分支,必有主干,正如一棵树谈到树枝,必有树干一样的道理.我们先前对git 的全部操作默认都是在主干上进行的,这个主干也是一种特殊的分支,名为 master 分支. 无论是穿越历史还是撤销更改,我们都或多或少接触过时间线,git 管理的版本串在一起就组成了这个时间线,其中master 分支是当前分支,HEAD 指向master ,因此HEAD 相当于指向了最新的版本. 基于分支上的操作,每一次 commit 都会提交一个新版本,并且新的 commit 指向原来的 commit,这来最新的 commit 就可以往前找,直到找到最初的commit.这就是 git 的时间线. 当我们打算开辟新的时间线时,git 在当前 HEAD 指向的 master 分支的 commit 处新建一个 dev 分支.如果主角没有主动进入时间线的话,那么仍然处于 master 分支,进入的方法就是 HEAD指向新建的 dev 分支. 不考虑孙悟空的分身特效,主角不能同时处于不同的时空下,git 也是如何,HEAD 只能指向某一个 commit ,既然刚刚已经指向了 dev 分支,所以原来的 master 分支就没有 HEAD 了,因为相当于master 分支静止了. 当主角在 dev 分支独自闯荡干出一番事业时,决定回到故乡 master 分支,并将出门在外所学的本领带回家乡,建设美好家园.master 分支因为合并了 dev 分支,所以一下子增添了很多内容,家乡焕然一新! 主角这次携带 dev 分支归来,HEAD 分支自然又回到了 master 分支上,年轻的心向往外面的世间,相信不久后还会有同样的故事发生... 下面详解分支相关命令 创建分支 创建 dev 分支,列出分支已验证是否创建成功 # 创建分支 $git branch dev # 列出分支 $ git branch dev * master $ * master 前面的 * 标记表明当前仍然处于 master 分支 切换分支 切换到新分支以便在分支上开展工作 # 切换分支 $ git checkout dev Switched to branch 'dev' # 列出分支 $ git branch * dev master $ 现在,我们在 dev 分支上奋笔疾书,先后提交两个版本后完成分支开发工作: # 查看当前文件列表 $ ls LICENSE README.md test.txt # 查看目标文件内容 $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html # 第一个版本: learn git branch $ echo \"learn git branch\" >> test.txt $ git add test.txt $ git commit -m \"learn git branch\" [dev 9c30e50] learn git branch 1 file changed, 1 insertion(+) # 第二个版本: see https://snowdreams1006.github.io/git/usage/branch-overview.html $ echo \"see https://snowdreams1006.github.io/git/usage/branch-overview.html\" >> test.txt $ git add test.txt sunpodeMacBook-Pro:git-demo sunpo$ git status On branch dev Changes to be committed: (use \"git reset HEAD ...\" to unstage) modified: test.txt $ git commit -m \"see https://snowdreams1006.github.io/git/usage/branch-overview.html\" [dev 413a4d1] see https://snowdreams1006.github.io/git/usage/branch-overview.html 1 file changed, 1 insertion(+) 此时,再从 dev 分支切换回 master 分支,合并dev分支前看一下当前文件内容: # 切换回 master 分支 $ git checkout master Switched to branch 'master' Your branch is up to date with 'origin/master'. sunpodeMacBook-Pro:git-demo sunpo$ git status On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean # 查看当前文件列表 $ ls LICENSE README.md test.txt # 查看文件内容: 无 dev 分支更改 $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html $ 合并分支 切换回 master 分支并没有我们在 dev 分支的更改,因为两条时间线是独立的,现在合并 dev 分支,再看一下当前文件内容: # 合并 dev 分支 $ git merge dev Updating b3d8193..413a4d1 Fast-forward test.txt | 2 ++ 1 file changed, 2 insertions(+) # 查看文件内容: 已经存在 dev 分支的更改! $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/ 删除分支 合并分支后,dev 分支的历史使命已经完成,应该及时清空不必要分支. # 删除 dev 分支 $ git branch -d dev Deleted branch dev (was 413a4d1). # 列出当前分支: 只剩下 master 分支 $ git branch * master $ 以上场景包括了分支的常用操作,创建分支(git branch ),切换分支(git checkout ),删除分支(git branch -d )一系列操作十分流畅,因此 git 鼓励我们大量使用分支! 小结 列出分支 git branch 创建分支 git branch 切换分支 git checkout 创建并切换分支 git checkout -b 合并指定分支到当前分支 git merge 删除分支 git branch -d var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-overview.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-merge-with-conflict.html":{"url":"git/usage/branch-merge-with-conflict.html","title":"冲突合并","keywords":"","body":"冲突合并 如果足够幸运的话,团队成员互不影响,彼此相安无事,大家各自基于 master 分支的某个 commit 创建自己的分支,平时在分支上独立工作,等到一段时间后再合并 merge 到 master 分支,这样一样 master 作为各个功能的集大成者,最终完成项目. 然而事情总不是一帆风顺的,团队协作时由于意见不同,遇到冲突简直是家常便饭,既然无法回避冲突,当冲突发生时如何应该呢? 背景 基于 master 分支上的某个 commit ,新功能由此继续开发: echo \"git commit c1\" >> test.txt $ git add test.txt $ git commit -m \"git commit c1\" 新功能分支命名为 feature ,使用git checkout -b 创建分支并切换: $git checkout -b feature Switched to a new branch 'feature' $ 在新功能 feature 分支上开发新功能,并提交: $ echo \"git commit c2\" >> test.txt $ git add test.txt $ git commit -m \"git commit c2\" [feature 0fe95f8] git commit c2 1 file changed, 1 insertion(+) $ 无论新功能 feature 是否开发完毕,团队的其他成员均有可能处于 master 分支并做相应更改: $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 3 commits. (use \"git push\" to publish your local commits) 其他成员对新功能有着自己的看法,于是也提交了版本,由于我们之前提交的是 git commit c2,而此时master 分支提交的是git commit c3,显然我们两个人的意见不一致! $ echo \"git commit c3\" >> test.txt $ git add test.txt $ git commit -m \"git commit c3\" [master 0949cc3] git commit c3 1 file changed, 1 insertion(+) $ 正在此时,feature 分支的新功能已开发完毕并主动切换回 master 分支,准备合并 feature 分支. # 合并 feature 分支 $ git merge feature Auto-merging test.txt CONFLICT (content): Merge conflict in test.txt Automatic merge failed; fix conflicts and then commit the result. $ 由于项目成员沟通不畅或者意见不一致,导致了代码冲突,git 作为版本控制系统,自然无法解决这类问题,总不能擅自做主抛弃后来的更改吧或者抛弃分支更改?所以 git 只负责抛出问题,等待我们程序员去解决问题. 既然是人的问题,那我们看一下我们到底是哪里不一致,为什么会产生冲突? # 查看状态 $ git status On branch master Your branch is ahead of 'origin/master' by 4 commits. (use \"git push\" to publish your local commits) You have unmerged paths. (fix conflicts and run \"git commit\") (use \"git merge --abort\" to abort the merge) Unmerged paths: (use \"git add ...\" to mark resolution) both modified: test.txt no changes added to commit (use \"git add\" and/or \"git commit -a\") # 比较差异 $ git diff diff --cc test.txt index 6e00f87,0f95fd7..0000000 --- a/test.txt +++ b/test.txt @@@ -3,4 -3,4 +3,8 @@@ see https://snowdreams1006.github.io/gi learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 ++>>>>>> feature 和我们预期一样,test.txt 文件产生了冲突,当前 HEAD 指向的提交即 master 分支是 git commit c3 ,而 feature 分支是 git commit c2,对于同一个文件的同一行内容发生不同的更改,git 不知道也不应该知道如何处理. # 查看内容 $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 >>>>>> feature git 用 标记一个分支冲突开始,======= 标记分支分割线,>>>>>>> 标记另一个分支结束. 经过冲突双方的讨论后,彼此间达成妥协,决定修改成git commit c2 and c3 ,修改后继续提交: # 编辑冲突文件,按照协商一致的内容修改文件 $ vim test.txt # 将冲突内容更改为 git commit c2 and c3 $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 $ git add test.txt $ git commit -m \"fix conflict\" [master 3b8f434] fix conflict 冲突已经解决,现在回顾一下提交历史,使用git log --graph 图形化展示提交历史: # 查看提交日志 $ git log --pretty=oneline --graph * 3b8f434013caa8c27fade4c59d7aa2ee2c079636 (HEAD -> master) fix conflict |\\ | * 0fe95f871b371834d30ea17faa82f84b7d67672b (feature) git commit c2 * | 0949cc319e099d554795d03c69ee38923af00d6c git commit c3 |/ * 5c482cd9965b9dfd4f273b43b240ed7db66167a8 git commit c1 * 413a4d1d2aab5ab85b6097d4b9f81cb5601c3b26 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50248b773e38b032477a859e87abe7c1bb0 learn git branch * b3d8193bbcb9f76c47e831e3e212f2405ae09f93 (origin/master, origin/HEAD) see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e625640348a47ac922409a1ecb4c844385582aa add test.txt * 9b196aab5bc87eeb11709c9eef35fca283e05c61 Initial commit $ 最后,删除新功能分支 feature ,不用的分支及时清理干净,需要时再创建分支. $ git branch -d feature 小结 无法杜绝冲突的发生,代码上的冲突本质上是人为因素造成的冲突. 解决冲突需要有关双方协商解决,不可能独自解决冲突,除非你抛弃自我,完全以对方为准. 使用 git log --graph 命令可以图表化查看提交历史,抑或 git log --pretty=oneline --graph var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-merge-with-conflict.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-strategy.html":{"url":"git/usage/branch-strategy.html","title":"分支策略","keywords":"","body":"分支策略 默认情况下合并分支常常直接使用 git merge 命令,是最方便快速的合并方法.其实这种情况下 git 采用的是 fast forward 模式,特点是删除分支后,会丢失分支信息,好像从来没存在该分支一样,而我们推荐的是recursive 模式,能够保留分支的版本记录. 递归模式(recursive) 创建并切换 dev 分支,提交版本后切换回 master 分支,然后再合并 dev 分支,这不过这一次不再使用 git merge dev 命令: # 创建并切换 dev 分支 $ git checkout -b dev Switched to a new branch 'dev' # 提交版本 $ echo \"git checkout -b dev\" >> test.txt $ git add test.txt $ git commit -m \"git checkout -b dev\" [dev 44d68f6] git checkout -b dev 1 file changed, 1 insertion(+) # 切换回 master 分支 $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use \"git push\" to publish your local commits) $ 现在添加 --no-ff 参数禁用 fast forward 模式,即git merge --no-ff: $ git merge --no-ff -m \"git merge --no-ff dev\" dev Merge made by the 'recursive' strategy. test.txt | 1 + 1 file changed, 1 insertion(+) $ 上述内容显示,这次使用的不再是 fast forward 模式,而是 recursive 模式,那让我们看一下提交历史有什么不同吧! $ git log --pretty=oneline --graph * 22fbef71b7575cd7eb7911079551618667f9f38f (HEAD -> master) git merge --no-ff dev |\\ | * 44d68f674bc85bc972426c572b78915e850e476c (dev) git checkout -b dev |/ * 3b8f434013caa8c27fade4c59d7aa2ee2c079636 fix conflict |\\ | * 0fe95f871b371834d30ea17faa82f84b7d67672b git commit c2 * | 0949cc319e099d554795d03c69ee38923af00d6c git commit c3 |/ * 5c482cd9965b9dfd4f273b43b240ed7db66167a8 git commit c1 * 413a4d1d2aab5ab85b6097d4b9f81cb5601c3b26 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50248b773e38b032477a859e87abe7c1bb0 learn git branch * b3d8193bbcb9f76c47e831e3e212f2405ae09f93 (origin/master, origin/HEAD) see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e625640348a47ac922409a1ecb4c844385582aa add test.txt * 9b196aab5bc87eeb11709c9eef35fca283e05c61 Initial commit $ 这种递归模式(recursive) 有一个明显的特点就是会产生一个新的 commit ,并不会像之前快速前进模式(fast forward)那样单纯更改 HEAD 的指向. 秉承着阅后即焚的习惯,分支一旦合并后就立即删除,现在删除 dev 分支,看一下会发生什么: # 删除 dev 分支 $ git branch -d dev Deleted branch dev (was 44d68f6). # 查看提交历史 $ git log --pretty=oneline --graph * 22fbef71b7575cd7eb7911079551618667f9f38f (HEAD -> master) git merge --no-ff dev |\\ | * 44d68f674bc85bc972426c572b78915e850e476c git checkout -b dev |/ * 3b8f434013caa8c27fade4c59d7aa2ee2c079636 fix conflict |\\ | * 0fe95f871b371834d30ea17faa82f84b7d67672b git commit c2 * | 0949cc319e099d554795d03c69ee38923af00d6c git commit c3 |/ * 5c482cd9965b9dfd4f273b43b240ed7db66167a8 git commit c1 * 413a4d1d2aab5ab85b6097d4b9f81cb5601c3b26 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50248b773e38b032477a859e87abe7c1bb0 learn git branch * b3d8193bbcb9f76c47e831e3e212f2405ae09f93 (origin/master, origin/HEAD) see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e625640348a47ac922409a1ecb4c844385582aa add test.txt * 9b196aab5bc87eeb11709c9eef35fca283e05c61 Initial commit $ 由此可见,删除 dev 分支后仅仅少了 dev 的引用而已,原来 dev 分支所做的更改全部保留下来了! 快速前进模式(fast forward) 创建并切换 dev 分支,提交版本后切换回 master 分支,然后再合并 dev 分支,使用 git merge dev 命令: # 创建并切换 dev 分支 $ git checkout -b dev Switched to a new branch 'dev' # 提交版本 $ echo \"fast forward\" >> test.txt $ git add test.txt $ git commit -m \"fast forward\" [dev 3fe94c0] fast forward 1 file changed, 1 insertion(+) $ 现在切换回 master 分支,采用默认的git merge 命令合并 dev 分支: $ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 8 commits. (use \"git push\" to publish your local commits) sunpodeMacBook-Pro:git-demo sunpo$ git merge dev Updating 22fbef7..3fe94c0 Fast-forward test.txt | 1 + 1 file changed, 1 insertion(+) $ 上述内容显示这次合并采用的是快速前进模式(fast forward),让我们看一下提交历史: $ git log --pretty=oneline --graph * 3fe94c0088cae526eda1fb2ffa303001b1eb42ba (HEAD -> master, dev) fast forward * 22fbef71b7575cd7eb7911079551618667f9f38f git merge --no-ff dev |\\ | * 44d68f674bc85bc972426c572b78915e850e476c git checkout -b dev |/ * 3b8f434013caa8c27fade4c59d7aa2ee2c079636 fix conflict |\\ | * 0fe95f871b371834d30ea17faa82f84b7d67672b git commit c2 * | 0949cc319e099d554795d03c69ee38923af00d6c git commit c3 |/ * 5c482cd9965b9dfd4f273b43b240ed7db66167a8 git commit c1 * 413a4d1d2aab5ab85b6097d4b9f81cb5601c3b26 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50248b773e38b032477a859e87abe7c1bb0 learn git branch * b3d8193bbcb9f76c47e831e3e212f2405ae09f93 (origin/master, origin/HEAD) see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e625640348a47ac922409a1ecb4c844385582aa add test.txt * 9b196aab5bc87eeb11709c9eef35fca283e05c61 Initial commit $ 上述内容表明,此次合并并没有产生新的 commit ,只是更改下 HEAD 指向而已(HEAD -> master, dev). 同样,现在删除 dev 分支,再看一下提交历史: # 删除 dev 分支 $ git branch -d dev Deleted branch dev (was 3fe94c0). # 查看提交历史 $ git log --pretty=oneline --graph * 3fe94c0088cae526eda1fb2ffa303001b1eb42ba (HEAD -> master) fast forward * 22fbef71b7575cd7eb7911079551618667f9f38f git merge --no-ff dev |\\ | * 44d68f674bc85bc972426c572b78915e850e476c git checkout -b dev |/ * 3b8f434013caa8c27fade4c59d7aa2ee2c079636 fix conflict |\\ | * 0fe95f871b371834d30ea17faa82f84b7d67672b git commit c2 * | 0949cc319e099d554795d03c69ee38923af00d6c git commit c3 |/ * 5c482cd9965b9dfd4f273b43b240ed7db66167a8 git commit c1 * 413a4d1d2aab5ab85b6097d4b9f81cb5601c3b26 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50248b773e38b032477a859e87abe7c1bb0 learn git branch * b3d8193bbcb9f76c47e831e3e212f2405ae09f93 (origin/master, origin/HEAD) see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e625640348a47ac922409a1ecb4c844385582aa add test.txt * 9b196aab5bc87eeb11709c9eef35fca283e05c61 Initial commit $ 由此可见,快速前进模式一旦删除分支后就彻底丢失了分支的信息,即便是从提交历史中也找不到曾经存在的痕迹! 分支策略 git 是分布式版本控制系统,同时鼓励大量使用分支,如此一来大量的分支该如何管理? 实际开发中,建议准从以下原则进行分支管理: master 分支作为主干分支,负责对外提供服务,要求稳定可靠,因为应该专人负责更新维护. dev 分支作为开发分支,取代 master 分支的开发地位,积累到一定产出时再合并到 master 分支. feature 分支作为新功能分支,根据实际情况动态创建,删除分支,并适时合并到 dev 分支. bugFixed 分支作为修复特定 bug 分支,可能由 master 分支衍生而来,也可能由 dev 分支衍生等等,修复后及时合并到原分支. custom 自定义分支,项目成员私有分支,由上级领导分配任务后各开发人员自行选择创建自己的分支,并根据实际情况决定合并到 dev 分支或 feature 等分支. 小结 快速前进模式(git merge )不保留分支合并历史,递归模式(git merge --no-ff -m )保留分支合并历史. 制定大家都认同的分支管理原则,并严格准守规则. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-strategy.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-emergency-fixbug.html":{"url":"git/usage/branch-emergency-fixbug.html","title":"紧急修复","keywords":"","body":"紧急修复 和往常一样,每个人团队开发者都在自己的本地分支上进行日常工作,相互独立又相互联系,一直以来相安无事,可是某天下午,上级领导突然急冲冲的打电话告诉你线上出bug了,需要你紧急修复,下班之前必须解决! 我们天生就是创造 bug 的特殊群体,每天都在和各种各样的 bug 打交道,早已经习惯了这样的工作节奏,再也没有当初刚刚遇到紧急问题的手足无措,先喝杯茶,冷静一下,然后汇报领导说:放心吧!保证30min 内解决问题! 背景 学习了分支操作的相关知识,团队内部就基本的开发流程达成一致: 假设线上是主干 master 分支,开发是 dev 分支,团队成员是自定义 custom 分支,平时开发时在大家在各自 custom 分支上工作,完成分配任务后再合并到开发 dev 分支,等到开发分支功能稳定后,由项目领导负责合并到主干分支 master . 上述流程只是开发流程的简化版,实际情况更加复杂,后续再介绍 gitflow 工作流相关知识. 由于是线上出现 bug,理所当然是基于 master 分支检出临时分支,修复分支代号为 issue-110,然后定位 bug 并提交,最后再合并到 master 分支,如此一来成功修复 bug,完成既定任务,心安理得准备下班回家! 如果真的向上述步骤那样操作,显然还不够冷静,刚才那一杯茶算是白喝了!因为这样操作可能会丢失现场数据,那很多工作岂不是白做了,下面简单演示一下: 错误示例 (一). 事发前正在自定义的 snow 分支上愉快编码中... # 线上分支 `master`,开发分支 `dev`,自定义分支 `snow`,当前正处于自定义分支 $ git branch dev master * snow # 接到领导电话前正在自定义 `snow` 分支上进行愉快编码中... $ echo \"Happy coding\" >> test.txt $ git add test.txt $ git commit -m \"Happy coding\" (二). 事发时直接检出主分 master 分支,并紧急修复 bug . (2.1) 基于 master 分支检出 issue-110 分支,并修复提交. # 注意: 事发时正在思考人生,此时更改尚未添加到暂存区! $ echo \"who am i\" >> test.txt # 当前情况下,默认不允许直接切换到其他分支,因为工作区更改会被重写,这里为了演示错误示例,强制切换! $ git checkout -f master # 基于主干 `master` 分支检出修复 `issue-110`分支 $ git checkout -b issue-110 Switched to a new branch 'issue-110' # 定位线上 `bug`并修复,假设将 `fast forward` 更改为 `fast forward not recommend`,瞬间修复 `bug`有没有! $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward $ vim test.txt $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward not recommend # 修复 `bug` 后,提交更改并备注已修复 $ git add test.txt $ git commit -m \"fix bug about issue-110\" [issue-110 e60c8ad] fix bug about issue-110 1 file changed, 1 insertion(+), 1 deletion(-) sunpodeMacBook-Pro:git-demo sunpo$ git status On branch issue-110 nothing to commit, working tree clean $ (2.1) 切换到主干 master 分支,并合并修复 issue-110 分支 # 切换回 `master` 分支,合并修复 `issue-110` 分支 $ git checkout master Switched to branch 'master' Your branch is up to date with 'origin/master'. $ git merge issue-110 Updating 3fe94c0..e60c8ad Fast-forward test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) # 验证 `bug` 已修复: 更改为 `fast forward not recommend` $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward not recommend $ (三). 事发后切换回自定义 snow 分支,打算下班回家. # 切换回 `snow` 分支,发现丢失了事发前的未保存更改:`who am i` $ git checkout snow Switched to branch 'snow' $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward Happy coding $ 现在还打算下班吗?你所做的更改因为没有提交或者不能提交造成全部丢失! 结果 因为手头工作进行到一半无法提交或者忘记提交等原因,为了临时修复紧急 bug 而直接切换到目标分支再回来时发现更改全部丢失,相当于那部分工作白忙活了! 正确示例 经过上述错误示例的惨痛教训后,再也不敢轻易切换分支了,原因在于工作区更改并没有被提交,或者说不能提交,如果能够有一种机制来保护案发现场,这样我们就能放心切换到其他分支工作,回来时一切如初,那该多好? 幸运的是,git 确实提供这么一种机制,git stash 命令临时存储工作区,类似\"草稿箱\"作用. (一). 恢复工作区丢失更改,并使用 git stash 命令保存现场. # 修复工作区丢失更改: 同样未添加到暂存区 $ echo \"learn git stash\" >> test.txt $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward Happy coding learn git stash # 保护现场: 存储到\"草稿箱\" $ git stash Saved working directory and index state WIP on snow: 93227ba Happy coding (二). 切换到开发 dev 分支并合并修复 issue-110 分支. # 切换到开发 `dev` 分支 $ git checkout dev Switched to branch 'dev' sunpodeMacBook-Pro:git-demo sunpo$ git status On branch dev nothing to commit, working tree clean # 合并修复 `issue-110` 分支 $ git merge issue-110 Updating 3fe94c0..e60c8ad Fast-forward test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) sunpodeMacBook-Pro:git-demo sunpo$ git status On branch dev nothing to commit, working tree clean $ (三). 切换回自定义 snow 分支,并恢复工作现场. # 切换回自定义 `snow` 分支 $ git checkout snow Switched to branch 'snow' sunpodeMacBook-Pro:git-demo sunpo$ git status On branch snow nothing to commit, working tree clean $ git status 命令返回结果怎么显示工作区是干净的,好不容易才将丢失的更改找回来怎么又不见了?!逗我玩? 冷静,冷静,不要慌,既然工作现场已经保存到\"草稿箱\",那我们想要找回总要去\"草稿箱\"才能取出来吧?现在让我们看一下\"草稿箱\"有没有我们的工作现场? # 查看存储的\"草稿箱\"列表 $ git stash list stash@{0}: WIP on snow: 93227ba Happy coding $ 这里的 stash@{0} 是草稿 id,因为\"草稿箱\"允许保存多条草稿! 现在放心了吧,保存的\"草稿\"安然无恙躺在未知的某个地方,现在我们想办法恢复回工作区即可! git stash apply 恢复草稿,然后 git stash drop 删除草稿 git stash pop 恢复并删除草稿 # 恢复工作现场 $ git stash pop On branch snow Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt no changes added to commit (use \"git add\" and/or \"git commit -a\") Dropped refs/stash@{0} (b0c8ddc034d21f31204c82e9838fc5d4c01a49a8) # 工作现场已恢复,更改未添加到暂存区,`learn git stash` 又恢复了! $ git status On branch snow Changes not staged for commit: (use \"git add ...\" to update what will be committed) (use \"git checkout -- ...\" to discard changes in working directory) modified: test.txt no changes added to commit (use \"git add\" and/or \"git commit -a\") $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward Happy coding learn git stash 结果 不论手头工作有没有提交,一旦工作区保存到\"草稿箱\"后,就放心大胆切换分支进行工作,回来时岁月静好,一切如初! 小结 紧急修复 bug 时,可以通过 git stash 保护工作现场,然后再切换到目标分支,检出修复分支,完成修复后切换到目标分支,合并修复分支,最后删除修复分支,此时再切换回本地分支后一切如初! 工作区更改添加到\"草稿箱\" : git stash,支持多次添加到\"草稿箱\" 列出\"草稿箱\"内容 : git stash list 恢复\"草稿箱\"内容 : git stash apply 删除\"草稿箱\"内容 : git stash drop 恢复并删除\"草稿箱\"内容 : git stash pop 恢复|删除指定\"草稿箱\"内容 : git stash ,例如 git stash apply stash@{0} var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-emergency-fixbug.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/usage/branch-rebase.html":{"url":"git/usage/branch-rebase.html","title":"变基合并","keywords":"","body":"变基合并 git 鼓励大量使用分支---\"早建分支!多用分支!\",这是因为即便创建再多的分支也不会造成存储或内存开销,并且分支的作用有助于我们分解逻辑工作,这样一样其实比维护单一臃肿分支要简单得多! 正因如此,每个新功能会创建合并分支,修复 bug 会创建合并分支等等,一段时间后再次回顾整个版本库的提交历史就会发现分支错综复杂,难以理清! 虽然\"条条大路通罗马\",但错综复杂的道路容易让人迷失方向,如果不使用分支,当然就不存在\"分叉问题\",所以在某些情况下我们希望寻求一种替代方案来解决分支合并带来的\"分叉问题\"! 回顾提交历史 查看提交历史: git log --pretty=oneline --graph --abbrev-commit # 查看提交历史 $ git log --pretty=oneline --graph --abbrev-commit * e60c8ad (HEAD -> dev, origin/master, origin/HEAD, master) fix bug about issue-110 * 3fe94c0 fast forward * 22fbef7 git merge --no-ff dev |\\ | * 44d68f6 git checkout -b dev |/ * 3b8f434 fix conflict |\\ | * 0fe95f8 git commit c2 * | 0949cc3 git commit c3 |/ * 5c482cd git commit c1 * 413a4d1 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50 learn git branch * b3d8193 see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e62564 add test.txt * 9b196aa Initial commit 仅仅是简单的演示项目的提交历史都已经出现\"分叉问题\",更何况真实的企业级开发项目呢?如果真的是多分支多人合作开发的话,\"分叉现象\"将更加明显,模拟效果图大概长这样: 整理提交历史 如果想要一条直路直达罗马,那我们必须规划好路径,摒弃小道,坚持主干道.git 的各种 dev,feature等分支就是需要治理的一条条分叉小道,而 master 主分支就是我们的大道. 演示项目有三个分支,主干master,开发dev,自定义snow,目标是将自定义 snow 分支的工作成功整理合并到主干分支,从而解决\"分叉问题\",dev 分支与项目演示无关,无需更改. (1). 切换到 snow 分支并提交一个版本(learn git rebase) # 切换到 `snow` 分支 $ git checkout snow Switched to branch 'snow' # 追加新内容到 `test.txt` 文件 $ echo \"learn git rebase\" >> test.txt # 提交到版本库 $ git commit -am \"learn git rebase\" [snow 7d21e80] learn git rebase 1 file changed, 1 insertion(+) $ (2). 切换到 master 分支也提交一个版本(modify README) # 切换回 `master` 分支 $ git checkout master Switched to branch 'master' Your branch is up to date with 'origin/master'. # 追加新内容到 `README.md` 文件 $ echo \"learn git ,share git\" >> README.md # 提交到版本库 $ git add README.md $ git commit -m \"modify README\" [master 3931d48] modify README 1 file changed, 1 insertion(+) $ (3). 切换回 snow 分支,整理提交历史(git rebase)到 master 分支 # 切换到 `snow` 分支 $ git checkout snow Switched to branch 'snow' # 改变基础版本(父版本),简称\"变基\" $ git rebase master HEAD is up to date. # 当前提交历史线 $ git log --pretty=oneline --graph --abbrev-commit * e92f068 (HEAD) rebase * 72f4c01 fix confict about happy coding * 3931d48 (master) modify README * e60c8ad (origin/master, origin/HEAD, dev) fix bug about issue-110 * 3fe94c0 fast forward * 22fbef7 git merge --no-ff dev |\\ | * 44d68f6 git checkout -b dev |/ * 3b8f434 fix conflict |\\ | * 0fe95f8 git commit c2 * | 0949cc3 git commit c3 |/ * 5c482cd git commit c1 * 413a4d1 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50 learn git branch * b3d8193 see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e62564 add test.txt * 9b196aa Initial commit $ (4). 切换回 master 主干分支再次变基合并 snow 分支 # 切换回 `master` 分支 $ git checkout master Warning: you are leaving 2 commits behind, not connected to any of your branches: e92f068 rebase 72f4c01 fix confict about happy coding If you want to keep them by creating a new branch, this may be a good time to do so with: git branch e92f068 Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit. (use \"git push\" to publish your local commits) # 改变父版本为 `snow` 分支指向的版本 $ git rebase snow First, rewinding head to replay your work on top of it... Applying: modify README $ (5). 整理分支完成,最终主干分支是一条直线 # 查看提交历史线 $ git log --pretty=oneline --graph --abbrev-commit # `modify README` 是 `master` 分支提交的版本 * dcce09c (HEAD -> master) modify README # `learn git rebase` 是 `snow` 分支提交的版本 * 7d21e80 (snow) learn git rebase * a06a866 fix conflict |\\ | * e60c8ad (origin/master, origin/HEAD, dev) fix bug about issue-110 * | ab846f9 learn git stash * | 93227ba Happy coding |/ * 3fe94c0 fast forward * 22fbef7 git merge --no-ff dev |\\ | * 44d68f6 git checkout -b dev |/ * 3b8f434 fix conflict |\\ | * 0fe95f8 git commit c2 * | 0949cc3 git commit c3 |/ * 5c482cd git commit c1 * 413a4d1 see https://snowdreams1006.github.io/git/usage/branch-overview.html * 9c30e50 learn git branch * b3d8193 see https://snowdreams1006.github.io/git/usage/remote-repository.html * 8e62564 add test.txt 这一次我们没有使用 git merge 而是采用 git rebase 方式完成了分支的合并,优点是提交历史更清晰,缺点是丢失了分支信息. 小结 git rebase 变基合并分支,实际上就是取出一系列的提交版本并“复制”到目标版本,从而形成一条新的提交历史线. 比如我们想要把 bugFix 分支里的工作直接移到 master 分支上,移动以后会使得两个分支的功能看起来像是按顺序开发,但实际上它们是并行开发的,这就是 git rebase 的作用. git rebase 的优势是创造更线性的提交历史,使得代码库的提交历史变得异常清晰,劣势是缺失了分支信息,好像从没存在过该分支一样. 将目标分支上的工作成果转移到到主干分支 : git rebase master 主干分支接收已转移好的目标分支工作成果 : git rebase var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-rebase.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/branch-remote.html":{"url":"git/usage/branch-remote.html","title":"协同开发","keywords":"","body":"协同开发 前面我们已经介绍过远程仓库的相关概念,不过那时并没有深入探讨,只是讲解了如何创建远程仓库以及推送最新工作成果到远程仓库,实际上远程仓库对于团队协同开发很重要,不仅仅是团队协同开发的基础,也是代码备份的保障手段,现在我们先简单回忆下相关概念,以便为接下来的协同开发做好铺垫! 远程仓库和远程分支 远程仓库 远程仓库其实并不复杂,实际上只是本地电脑上的本地仓库在另一台远程电脑的备份而已. 相对本地仓库来说远程电脑上的版本库自然就是远程仓库,远程仓库使得我们的版本库更加安全,毕竟远程电脑可不是一般的电脑,出错的概率比我们平时工作所使用的电脑概率要小得多,这样一来即使不小心丢失了本地仓库的全部数据,只要远程仓库没有丢失,那我们就可以通过远程仓库重新取回最新数据! 还有一点,远程仓库让代码社交化,因为大家有了一致途径来访问远程仓库,团队也好或者陌生人也罢,只有你愿意,他们就可以获取远程仓库的最新代码并参与开发,这也是 github 的一大亮点! 远程分支 回顾好远程仓库的概念后,我们再来讲一下本地仓库的远程分支是什么意思? 当前你正在工作的电脑上存储的是本地仓库,如果没有远程仓库的支持,只能一个人鼓捣,别人无法共享你的工作成果,现在加入了团队开发流程,自然不再一个人独自开发,需要和团队其他人协同开发,共享开发成果. 所以本地仓库必然保存着远程仓库的基本信息,只有区分好自己的工作成果和公共成果,才能不乱套,又能做到信息及时共享. 实际上,在项目初期刚刚拷贝远程仓库(git clone)时,git 已经默认在本地仓库创建一个远程分支(origin/master),本地修改提交首先都是在本地仓库完成的,比如 git add,git commit 等命令,如果需要发布你的工作成果,那么就需要使用 git push origin 命令推送到远程仓库,这里的 origin 指的就是远程仓库名称(因为最初大家都是先从远程仓库克隆下来的,所以远程仓库存储的项目相当于原始项目,故而叫origin). git clone 命令帮助本地仓库的 master 分支和远程仓库的 master 分支建立了关联,一般称远程仓库名称为 origin. 查看远程仓库信息 : git remote 或 git remote -v # 查看远程仓库名称 $ git remote origin # 查看远程仓库详情 : 拉取和推送链接 $ git remote -v origin git@github.com:snowdreams1006/git-demo.git (fetch) origin git@github.com:snowdreams1006/git-demo.git (push) $ 本地分支推送到远程仓库 : git push origin 本地仓库和远程仓库的分支理论上应该一一对应,本地仓库的主干分支叫做 master ,而远程仓库也有相应的分支叫做 master ,这种映射关系是使用 git clone 命令时默认生成的,也是推荐的做法. 一般来说,本地仓库的分支推送到远程仓库指的就是推送到远程仓库同名的分支上,例如 git push origin master 意思是: 推将本地仓库的 master 分支推送到远程仓库的 master分支,当然你也可以推送其他分支到相应的远程分支上. 按照之前约定的分支管理策略来说,master 分支用于生产环境部署,dev 分支用于收集开发成果,feature 分支用于开发具体功能分支,既然如此,那这些本地分支哪些需要同步推送到远程仓库就比较清晰了! 推送本地 master 分支到远程仓库的 master 分支 : git push origin master 推送本地 dev 分支到元层仓库的 dev 分支 : git push origin dev # 查看当前分支 : `master` 主分支 $ git branch dev * master snow # 推送本地 `master` 分支到远程仓库 `origin` 上相应的 `master` 分支 $ git push origin master Counting objects: 15, done. Delta compression using up to 4 threads. Compressing objects: 100% (15/15), done. Writing objects: 100% (15/15), 1.31 KiB | 1.31 MiB/s, done. Total 15 (delta 9), reused 0 (delta 0) remote: Resolving deltas: 100% (9/9), completed with 3 local objects. To github.com:snowdreams1006/git-demo.git e60c8ad..dcce09c master -> master $ 正常来说,本地仓库的 master 分支应该领先远程仓库 origin 上的 master 分支若干个版本. 一旦我们已经将本地分支上的工作成果推送到远程仓库上相应分支时,本地仓库和远程仓库这时候就保持一致了. $ git status On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean $ 远程仓库下载到本地分支 : git fetch 远程仓库的操作可以简单归纳为两部分: 上传和下载. 本地仓库推送到远程仓库是上传,而远程仓库拉取到本地仓库就是下载. 团队多人协作开发时,大家都会定期或不定期往 master 或 dev 等分支上推送各自的更改,相应的我们就需要下载别人的最新工作成果. 现在模拟其他伙伴正在往 master 分支上推送更改,最好在另一个电脑另一个账户,当然模拟的话也可以是同一个电脑下其他目录,或者最简单的方式,直接登录 github 更改 master 分支上某个文件内容,简单起见,我们采用最后一种方式. 其他伙伴已往远程仓库上的 master 分支提交了新的版本: 创建 git-remote.txt 文件 现在我们想要下载其他人的最新工作成果,接下来让我们看看本地仓库的 master 还能和远程仓库的 master 分支保持一致吗? # 下载远程仓库的 `master` 分支 $ git fetch origin master remote: Enumerating objects: 4, done. remote: Counting objects: 100% (4/4), done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:snowdreams1006/git-demo * branch master -> FETCH_HEAD dcce09c..10942ff master -> origin/master $ 执行 git fetch 命令后,远程仓库上的最新提交记录已经下载到本地仓库,同时更新了本地仓库的远程分支origin/master ,值得注意的是本地仓库的 master 分支并没有更新! 那你可能会有疑问了,我想要的结果是下载其他人的最新工作成果,怎么我本地仓库的 master 分支并没有更新呢? # 查看工作区 $ ls LICENSE README.md test.txt # 查看版本库状态 $ git status On branch master Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded. (use \"git pull\" to update your local branch) nothing to commit, working tree clean $ 既然 git fetch 并没有更新本地仓库的 master 分支,那它到底做了哪些工作呢? git fetch 会做的事情 实际上, git fetch 完成了仅有的但是很重要的两步操作: 从远程仓库下载本地仓库中缺失的提交记录 更新本地仓库的远程分支(比如origin/master) 通过上述两步操作完成的效果是: 将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态. 远程分支实际上是反映了远程仓库在你最后一次与它通信时的状态,而git fetch 就是你与远程仓库通信的方式了! git fetch 不会做的事情 git fetch 并不会改变你本地仓库的状态,所以也就不会更新你的 master分支,自然也不会修改你磁盘上的文件. 理解这一点很重要,因为许多开发人员误以为执行了 git fetch 以后,他们本地仓库就与远程仓库同步了. 实际上它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件. 既然本地仓库的远程分支已更新,那么想要更新本地仓库的 master 分支该如何做呢?很简单,可以 git merge 啊! 远程仓库更新到本地分支 : git pull 其实通过 git fetch 命令我们已经下载了远程仓库的最新版本,只不过还没有合并到本地仓库而已,如何合并分支相信大家已经轻车熟路了,有很多方法: git merge origin/master git rebase origin/master git cherry-pick origin/master 实际上,先抓取更新(git fetch)再合并(git merge)这个流程很常用,因此 git 是有专门的命令来完成这两步操作的,这就是拉取更新git pull --- 刚好与推送更新 git push 相反! # 拉取最新版本 $ git pull Updating dcce09c..10942ff Fast-forward git-remote.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 git-remote.txt # 查看版本库状态 $ git status On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean # 查看工作区内容: 文件已更新 $ ls LICENSE README.md git-remote.txt test.txt $ 团队协作 掌握了远程仓库和远程分支的相关概念后,现在开始真正模拟团队协作开发了,为了简单起见,仍然以直接操作 github 上的 master 分支为例说明如何协同开发. (1). 其他人已往远程仓库推送2个版本 (2). 你正在本地仓库提交1个版本 $ echo \"learn teamwork\" >> test.txt $ git commit -am \"learn teamwork\" [master f971647] learn teamwork 1 file changed, 1 insertion(+) $ (3). 你推送到远程仓库前先拉取最新版本 # 拉取最新版本,并尝试合并 $ git pull remote: Enumerating objects: 8, done. remote: Counting objects: 100% (8/8), done. remote: Compressing objects: 100% (5/5), done. remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (6/6), done. From github.com:snowdreams1006/git-demo 10942ff..612e08a master -> origin/master Merge made by the 'recursive' strategy. git-remote.txt | 2 ++ 1 file changed, 2 insertions(+) # 查看版本库状态 $ git status On branch master Your branch is ahead of 'origin/master' by 2 commits. (use \"git push\" to publish your local commits) nothing to commit, working tree clean # 查看其他人工作成果 $ cat git-remote.txt git remote git clone git commit -am \"fake second teamwork\" # 查看自己即将推送的工作成果 $ cat test.txt add test.txt see https://snowdreams1006.github.io/git/usage/remote-repository.html learn git branch see https://snowdreams1006.github.io/git/usage/branch-overview.html git commit c1 git commit c2 and c3 git checkout -b dev fast forward not recommend Happy coding learn git stash learn git rebase learn teamwork $ (4). 你将本地仓库更改内容推送到远程仓库 # 推送到远程仓库 $ git push origin master Counting objects: 5, done. Delta compression using up to 4 threads. Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 564 bytes | 564.00 KiB/s, done. Total 5 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), completed with 3 local objects. To github.com:snowdreams1006/git-demo.git 612e08a..8fe5aba master -> master $ 现在前往 github 网站确认我们已经推送成功,我们的工作成果和其他人的工作成果同时存在于远程仓库中,这样就完成了一次团队协同开发的案例. 现在简单回顾一下整个协同开发流程: 其他人先于我们提交2个版本 我们本地提交1个版本 本地版本推送前拉取远程仓库 本地仓库推送到远程仓库 小结 查看远程仓库信息: git remote -v 本地仓库推送到远程仓库: git push origin 远程仓库抓取到本地仓库: git fetch 远程仓库拉取到本地仓库: git pull 相当于 git fetch 和 git merge 本地创建和远程仓库一致的分支: git checkout -b origin/,本地和远程分支名称最好一直,比如本地 master 和 远程 origin/master,本地 dev 和远程 origin/dev 本地分支和远程分支建立关联: git branch --set-upstream origin/ ,足够任性的话,本地 dev 可以关联远程 remote-dev 等,不过建议名称最好一致. 团队协同开发时,不仅平时要定期拉取(git pull),推送到远程仓库前更应先拉取(git pull)再推送(git push),如出现冲突,解决冲突后再推送. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/branch-remote.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/local-remote-repository.html":{"url":"git/usage/local-remote-repository.html","title":"本地和远程仓库的本质","keywords":"","body":"本地和远程仓库的本质 本地仓库和远程仓库在本质上没有太大区别,只不过一个是本地电脑,一个是远程电脑. 远程仓库不一定非得是 github 那种专门的\"中央服务器\",甚至局域网的另外一台电脑也可以充当\"中央服务器\"的角色,因为它存在的最初目的只是方便大家交换彼此的提交记录而已! 所以本地仓库和远程仓库的基本行为应该是一致的,约定俗成的规定是远程仓库一般不直接参与日常开发工作,主要作为项目托管中心. 某些自动化持续集成环境中也可能会直接操作远程仓库,这时远程仓库就真的和本地仓库没什么区别了! 个人开发常用命令 个人开发看重的是效率,同时兼顾下版本控制的话算是是锦上添花,git 的本地仓库是本地备份,而远程仓库则是网盘备份. git init : 初始化本地项目 将本地项目初始化 git 项目,直观表现是在该项目同级目录下多了 .git 隐藏目录,其存储着 git 版本库相关信息. 此后当前项目便具备了本地管理的能力,可以与 git 进行交互. git clone : 克隆远程项目 同 git init 一样的作用,也是创建本地仓库,只不过 git init 是直接将本地项目作为本地仓库,而git clone 是将远程项目克隆到本地并作为本地仓库. 由此可见,git clone 比 git init 多了一层远程仓库的概念. git add : 添加文件 将工作区的提交记录添加到暂存区,暂存区是工作区和版本库交互的桥梁,暂存区积累到一定量的提交记录时可以批量提交到版本库,这一点暂存区有点像缓存. git commit : 提交文件 将暂存区的版本提交到版本库,从而形成工作区->暂存区->版本库的基本链路,本地工作区的版本控制流程大致如此. git push : 推送文件 如果是使用 git clone 命令克隆的本地项目,当工作到一定程度时可能需要将这部分工作成果推送到远程仓库,这时候使用 git push 命令完成本地版本的推送流程. 如果是使用 git init 命令初始化的本地项目,可能没有远程仓库,自然也就不需要推送.如果后来创建了远程仓库,那么你自然是想要将本地仓库推送到远程仓库的,因此你需要准确告诉 git 你要推送到哪个远程仓库. 使用 git remote add origin git@github.com:username/repos.git 命令添加远程仓库信息,这样就建立了本地仓库和远程仓库的关联,以后就可以正常推送到远程仓库了. 团队开发常用命令 团队开发注重的不仅是个人效率还有团队的整体进度,随着企业级开发的日趋复杂化,不再是一个人能够独立完成的,更何况时间也不允许慢慢完成,大多数公司采用的是人力换时间的方式,团队并行开发来缩短整个项目周期,这种复杂需求下正是 git 大展拳脚的好机会. 项目整体采用并行开发模式,拆解成不同的功能模块,每个人负责各自模块,模块之间相对独立但也不排除存在交集的可能性.对于每一个个体开发者来说,既需要版本控制又需要团队交流.这时候分支的作用就凸显出来了. 根据项目的业务特点将其拆解成不同的功能模块,这些功能模块分别代表不同的分支,而这些功能模块又组成了完整的项目,这就是主干和分支的关系. 初始时项目是一个整体,中间拆解成不同功能模块,最后再合并成一个整---\"分久必分合久必分\". git branch : 创建分支 每一个独立的功能模块被定义成一个单独分支,创建分支的过程其实是拆解项目的过程,创建本地分支后就在分支上开发特有功能,不再关心其他功能分支. git checkout : 切换分支 模块拆解完成并创建了相应的分支后,需要切换到既定分支上才能开展自己的工作. git merge : 合并分支 没有绝对的独立,项目再怎么拆分也是整体的一部分,肯定需要和其他功能模块发生关系,某些情况下需要其他分支的工作成果合并到自己的本地仓库中,这样才能完成一次小规模的组装. 可以预期的是,当这种组装足够多的时候,最终便会演变成项目的终极形态,形成一个整体. git fetch : 抓取远程分支 合并目标分支首先需要能够获取到目标分支的提交记录,既然每个功能模块都是不同的项目成员负责开发的,也就不在我们电脑上,所以我们先要将目标分支下载到我们本地电脑,然后才能合并该分支到本地分支. git pull : 拉取远程分支 \"先下载目标分支再合并到本地分支,从而小规模组成更复杂更强大的功能\",每一次的组装过程都需要两步操作者显然不符合懒人思维啊,git pull 就是这两步操作的简化命令,先下载再合并就是这么简单! 本地和远程仓库的碰撞 不论是个人开发还是团队开发,我们几乎习惯惯站在主动方的角度来思考问题,有没有想过当远程仓库接收到我们的git push 或 git pull 请求时,远程仓库发什么了什么改变,这种改变对本地仓库又有什么影响? 远程仓库(远程电脑上的本地仓库)只是众多分布式电脑上本地仓库中的一员,说它特殊也很特殊,充当着\"中央服务器\"作用,其余人统一从这里下载或推送;说它普通也很普通,和本地电脑上的本地仓库没有什么不同,因为它随时可被任意电脑上的本地仓库所取代! 揭开远程仓库的神秘面纱后,现在我们只需要将其视为普通的本地仓库一样对待即可,然而我们本地电脑上已经有了本地仓库,故而需要将远程仓库做一下简单标识区分(origin)称之为远程分支. 先说说 git push 命令做了什么? 对于本地来说,git 将本地仓库的指定分支推送到远程仓库的相应分支,同时更新了本地仓库的远程分支. 对于远程来说,git 接收到本地仓库的推送请求时应该在相应分支上合并本地分支,同时更新远程仓库的相应分支. 只要本地的指定分支成功推送到远程的相应分支时,对于本地来说,不论是指定分支还是远程分支(origin/master)都应该是最新状态,因为已经与服务器同步了. 而远程接收到此次推送请求时,应该尝试合并此次推送请求,再更新自己的相应分支,远程合并完成后再通知本地此次推送结果,如此一来,三端同步,皆大欢喜! 再讲讲 git pull 命令发生了什么? 对于远程来说,接收到本地的拉取请求时,因为没有新版本需要处理,所以无需任何操作. 对于本地来说,当远程仓库的相应分支下载到本地时应该更新远程分支状态,再尝试合并到本地的相应分支. git pull 命令或者说是 git fetch 命令是本地和远程通信的方式,所以 origin/master 会自动更新! 小结 本地仓库和远程仓库本质上没有太大区别, git fetch 是本地仓库和远程仓库之间的通信途径,本地仓库中的远程分支(origin/master)保存着它们之间最后一次的通信状态. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/local-remote-repository.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/usage/tag.html":{"url":"git/usage/tag.html","title":"里程碑式标签","keywords":"","body":"里程碑式标签 \"春风得意马蹄疾,一日看尽长安花\",对于项目也是如此,最值得期待的恐怕就要数新版本发布的时刻了吧?每当发布新版本时要么是版本号命名(比如v0.0.1)或者代号命名(比如Chelsea),不管怎么说这种里程碑阶段总是要留下些许纪念意义. 既然想要纪念这种特殊的历史时刻,自然是希望它能够固定下来,不要发生随意移动,产生不可预期后果. 这种需求其实和我们前面说的分支概念很相似,均是源于特殊的版本号,逐渐收集起一系列版本,最终形成一条相对独立的历史线,但分支并不是实现里程碑概念的最佳选择,为什么? 分支适合多人协作开发时互不影响,适当时机主动合并他人工作成果这种模式,而这种模式是由不同的功能模块进行驱动的,正所谓\"天下大势分久必合,合久必分\",当功能模块开发完毕后自然也就没有分支存在的必要性,更何况分支在收集版本的过程中会一直移动,并没有特殊的固定版本,显然分支不是最佳选择! 但是,分支确定一定程度上和里程碑概念很相似,源于特定版本,自主命名,收集版本等,那么何必重头再来,为何不复用已有概念呢? 实际上,git 中的标签(tag) 就是实现里程碑概念的方式,它可以永久性指向特定的提交并将命名,然后就可以将其理解成分支一样引用了! 但标签(tag)不是分支(branch),标签是一个点的话,分支就是若干点连接而成的线,标签是静态的,分支是动态的,标签是只读的,分只是可读可写的. 创建标签 git tag # 方式一: 默认 `HEAD` 指向的版本 git tag v0.0.1 # 方式二: 指定 `commit_id` 表示的版本 git tag v0.0.2 f971647 # 方式三: 指定 `commit_id` 表示的版本,同时创建标签说明信息 git tag -a v0.0.3 -m \"v0.0.3\" f971647 列出标签 git tag git tag 显示标签 git show git show v0.0.1 删除标签 git tag -d git tag -d v0.0.1 推送标签 git push origin git push origin v0.0.1 推送全部标签 git push origin --tags git push origin --tags 删除远程标签 git tag -d git push origin :refs/tags/ # 删除本地标签 git tag -d v0.0.1 # 推送删除标签(删除也是推送) git push origin :refs/tags/v0.0.1 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/usage/tag.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"git/custom/about.html":{"url":"git/custom/about.html","title":"私人定制","keywords":"","body":"个性化 git 前情概要 初识 git 时,我们就已经接触过 git 的基本配置,使用 git config 命令配置用户名和邮箱: # 配置当前项目(`local`)的用户名(`snowdreams1006`) git config --local user.name \"snowdreams1006\" # 配置当前项目(`local`)的邮箱(`snowdreams1006@163.com`) git config --local user.email \"snowdreams1006@163.com\" 快速回忆一下配置的相关语法: # 查看默认全部配置: `local>global>system` git config --list # 查看当前项目配置,等同于 `.git/config` 文件 git config --local --list # 查看当前用户配置,等同于 `~/.gitconfig` 文件 或 `~/.config/git/config` 文件 git config --global --list # 查看当前系统配置,等同于 `/etc/gitconfig` 文件 git config --system --list man git-config 查看帮助文档,git 的配置文件是普通文本,也可以直接编辑. 高频配置 总体来说,git 的配置项基本分为两类: 客户端和服务端.其中大部分属于客户端配置, 除非使用自己搭建私服,否则没机会手动配置服务端(第三方服务器基本都支持可视化配置,比如禁止强制推送等配置). alias 别名 熟悉 linux 操作的小伙伴对 ll 这个命令可能再熟悉不过了,是 ls -l 的缩写,称之为别名. git 也支持别名,有个别名我们可以将常用的命令都缩短,大大降低出概率,提高工作效率. # `git checkout` 缩写成 `git co` git config --global alias.co checkout # `git commit` 缩写成 `git ci` git config --global alias.ci commit # `git branch` 缩写成 `git br` git config --global alias.br branch 如此一来,以后再也不用担心打错字了,简化命令,懒人至上! core.editor 编辑器 默认情况下,git 使用的是 $VISUAL 或 $EDITOR 配置的文本编辑器,如果没有设置,则调用 vi 编辑器创建和编辑文本信息. 查看当前编辑器配置项: # 查看编辑器配置项: 若没配置过,则无内容输出,已配置过的话,会输出相应编辑器信息 git config core.editor 假设使用 sublime 作为默认编辑器,那么便可如下设置: # `Mac` 系统如下设置: 设置成自己的 `Sublime` 的安装路径 git config --local core.editor \"'/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl' -n -w\" # `Windows` 系统如下设置: 设置成自己的 `Sublime` 的安装路径 git config --local core.editor \"'F:\\Sublime Text 3 sublime text.exe' -n -w\" 此时再次查看编辑器配置项应该会输出刚才配置信息,接下来我们验证下编辑器的效果: 查看提交历史,已经提交成功(之前备注信息是在命令行中直接输入的,而现在是在编辑器中编辑) $ git log --pretty=oneline --abbrev-commit 43fa8aa (HEAD -> master) validate sublime successfully 00e16d7 ok 2400f11 git config --local core.editor \"'/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl' -n -w\" 0d60cb8 ok 8fe5aba (origin/master, origin/HEAD) Merge branch 'master' of github.com:snowdreams1006/git-demo $ 如果只是输入简单备注,根本用不到编辑器,若提交备注有格式化要求时再手动输入就显得力不从心了! core.template 提交模板 如果你需要格式化提交备注,那么这种情况下模板文件最好不过了,和自定义的编辑器一起搭配,这样就能约束自己和他人按照既定格式规范填写提交备注,方便以后统一管理. 查看当前提交模板配置: git config commit.template 假设你在当前项目下创建 commit-template.txt 模板文件,内容如下: # This is commit template # snowdreams1006 # git-demo 将编辑好的模板文件设置成提交默认信息,需要如下设置: git config --local commit.template commiit-template.txt 此时再次运行 git config commit.template 查看已配置提交模板,现在看一下实际效果: 查看提交历史,当然也提交成功啦,可根据实际需求定制适合自己的提交模板. $ git log --abbrev-commit commit a2ca3f0 (HEAD -> master) Author: snowdreams1006 Date: Wed Mar 27 16:22:18 2019 +0800 ok myself yes commit 43fa8aa Author: snowdreams1006 Date: Wed Mar 27 14:58:36 2019 +0800 validate sublime successfully commit 00e16d7 Author: snowdreams1006 Date: Wed Mar 27 14:56:20 2019 +0800 ok commit 2400f11 git 还支持其他配置,暂时不一一介绍了,详情请参考在线帮助文档: man git-config var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/custom/about.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/custom/ignore.html":{"url":"git/custom/ignore.html","title":"忽略文件","keywords":"","body":"忽略文件 \"并不是所有的牛奶都叫特仑苏\",在版本控制系统中也有相似的表达,那就是\"并不是所有的文件都需要提交\". 有的是因为没必要提交,比如日志文件,系统缓存文件等,有的是因为不能提交,比如个人隐私文件,付费文档等. 正常来说,这些文件都是不应该被提交到版本库,倘若一不留神提交到版本库,要么泄露机密信息,要是造成经济损失,要么对团队其他人工作造成不便. 有鉴于此,我们应该寻求一种机制来规避事故的发生,在 git 版本控制系统中一般有三种不同的解决方案. 最常用也是最简单的当属 .gitignore 文件,不过先不要着急,我们先了解一下忽略原则和配置规则. 忽略文件的基本原则 忽略操作系统自动生成的文件,保持不同操作系统的纯粹性和整洁度. 忽略工具软件自动生成的文件,避免因个性化配置而产生的工作障碍. 忽略个人隐私配置文件,除非你愿意承担公开隐私所带来的潜在风险. 目标: 只提交必要文件,忽略无用文件,尽可能考虑多种情况,不给他人制造麻烦. 忽略文件的配置规则 一行记录代表一条规则,配置规则仅针对尚未被跟踪的文件清单. # 忽略 `*.a` 文件 *.a # 忽略 `*.A` 文件,但 `somefile.A` 除外. *.A !somefile.A # 忽略 `*.b` 和 `*.B` 文件 *.[bB] # 忽略 `*.c` 和 `*.C` 文件,但 `somefile.C` 除外. *.[cC] !somefile.C # 只忽略 `somepath/` 目录(包括该目录下所有文件),但不忽略 `somepath` 文件 somepath/ # 只忽略 `somepath/` 一级子目录下 `*.txt`,但不忽略 `somepath/sub/*.txt` 文件 somepath/*.txt # 忽略 `somepath` 文件和 `somepath` 目录 somepath # 只忽略 `somepath` 文件,但不忽略 `somepath/` 目录 somepath !somepath/ # 只忽略当前目录下的 `somepath` 文件和目录,但不忽略子目录的 `somepath` /somepath 说明: # 开头表示注释,! 紧跟某规则之后表示增加例外情况 在线示例和帮助文档 提供两个不错的在线示例,可以参考下在什么场景应该忽略哪些文件以及如何编写忽略规则. https://www.gitignore.io/ https://github.com/github/gitignore 运行 git help ignore 命令查看帮助文档 三种设置方式 git 设置忽略文件有三种方式,如下: 全局配置文件(~/.gitignore),执行 git config --global core.excludesfile ~/.gitignore 命令后适用于所有的版本库. 远程配置文件($PWD/.gitignore),编辑 .gitignore 文件后适用于远程和本地版本库. 本地配置文件($PWD/.git/info/exlude),编辑 $PWD/.git/info/exlude 文件后适用于本地版本库. 最常用方式 三种设置方式中,第二种最为常见,另外两种大致一样,重点在于配置文件如何编写. 创建 .gitignore 文件 参考在线示例以及基本语法编写自定义忽略规则 # General .DS_Store .AppleDouble .LSOverride # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db 提交 .gitignore 文件 忽略文件规则配置完毕后,需要将该文件提交到版本库,这样在其他电脑上也能应用相同的忽略规则. # 添加 `.gitignore` git add .gitignore # 提交 `.gitignore` git commit -m \"add .gitignore\" # 上传 `.gitignore` git push origin master 验证忽略效果 新建 .gitignore 文件中已忽略的文件,运行 git status 命令,如果提示 working directory clean,那么说明忽略文件的配置已经生效,如果工作区不干净,很遗憾,忽略文件配置可能并未生效,需要检查下哪里配置错了. 运行 git check-ignore 命令检查是哪个配置规则写错了,从而我们能够更正相应的配置规则. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/custom/ignore.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/custom/sync-remote-repo.html":{"url":"git/custom/sync-remote-repo.html","title":"同步推送多Git仓库","keywords":"","body":"同步推送多Git仓库 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/custom/sync-remote-repo.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/server/private.html":{"url":"git/server/private.html","title":"搭建私服","keywords":"","body":"git 私服搭建教程 前几节我们的远程仓库使用的是 github 网站,托管项目大多是公开的,如果不想让任何人都能看到就需要收费,而且 github 网站毕竟在国外,访问速度太慢,基于上述两点原因,我们有必要搭建自己的 git 服务器. 虽然我们能搭建基本的 git 服务器,但是想要做到 github 网站那种规模还不是目前能够探讨的,本节的主要目标是使用我们私有服务器对我提供类似于github的远程仓库托管服务,以下示例以centos 服务器为例说明: 安装 git 服务 运行以下命令安装 git 服务 # 安装 git 相关依赖 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel # 安装 git yum install git # 查看 git 版本 git --version 详情请参考安装 git 配置 git 用户 创建 git 用户组和 git 用户,以便对外提供 git 服务 # 新增 git 用户组 groupadd git # 新增 git 用户并归属于 git 用户组 useradd git -g git # 禁用 git 用户登录 shell 编辑 /etc/passwd git:x:1001:1001:,,,:/home/git:/bin/bash 更改为 git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell 收集 git 公钥 回忆一下,在我们使用 github 网站时,我们是不是曾经将本地电脑生成的公钥~/.ssh/id_rsa.pub 复制到 Account -> Settings -> SSH and GPG keys -> New SSH key,而我们现在搭建的git 服务还是简单,但是这步骤必不可少,因此只能手动收集素有需要访问我们服务器的公钥文件. 我们知道需要登录我们服务器的用户公钥一般是存放在~/.ssh/id_rsa.pub ,那当前服务器作为远程服务器将这些公钥存放到哪里呢?还记得上一步我们创建了 git 用户吗? 因为 linux 系统支持多用户操作,而 git 用户就用于专门运行 git 服务,负责所有和 git 有关的事宜.因此,导入公钥文件的目录就是/home/git/.ssh/authorized_keys文件.一个用户公钥占用一行,几个用户就有几行. # 切换到 git 用户主目录 cd /home/git/ # 创建.ssh 目录 mkdir .ssh # 赋予标准目录权限 chmod 755 .ssh # 创建authorized_keys文件 touch .ssh/authorized_keys # 赋予标签文件权限 chmod 744 .ssh/authorized_keys 如果团队规模不大,那么上述方案完全可行,如果团队规模几百上千人,通过手动收集每个人的公钥再上传到服务器统一管理就有点力不从心了,这时候推荐 gitosis 决这一问题. 初始化 git 仓库 同样我们和github 网站类比,在 github 创建仓库时都会在当前账号下创建项目,完整的访问路径大概是这样的: git@github.com:snowdreams1006/git-demo.git,从中我们可以看出项目仓库都有一个前缀即命名空间,这和上一步操作是不是很类似,上一步收集 git 公钥时我们也有统一的目录,这次也不例外. 假设 git 仓库存放目录在 /home/git/repos/,同样的先创建该目录并赋予响应权限. # 切换到 git 用户主目录 cd /home/git/ # 创建 repos 目录 mkdir repos # 更改 repos 目录属主 chown git:git repos/ # 切换到 repos 目录 cd repos # 初始化 git 裸仓库 git init --bare git-demo.git # 更改 git-demo.git 仓库属主 chown -R git:git git-demo.git 这里搭建git服务器仅为了共享,不考虑用户直接登录该服务器上使用 git 将其作为工作区这一情况 经过上述操作,我们成功在远程服务器部署了 git 服务,并且创建了 git-demo 测试项目,实际访问路径大概是这样的 git@snowdreams1006.cn:/home/git/repos/git-demo.git 访问授权 总是存在一些公司不仅视源代码为生命,还视员工为窃贼,抑或是深受svn毒害,要求在版本控制系统中设置一套完善的权限控制体系,具体到每个账号对每个项目的每个目录是否有读写权限. 然而 git 天生并不支持权限控制,这一点和其出身有关,本来就是为了开源而生,并不关心所有人的提交. 不过这并不意味着 git 无法实现权限控制功能,因为 git 支持钩子函数(hook) ,所以在服务器端编写一系列的脚本控制提交行为,从而实现权限控制.详情请参考 gitolite 本地克隆远程仓库 身份回到本地电脑,假设本地已搭建好 git 环境,并且生成的ssh 公钥上传到远程服务器,那么我们接下来就可以和之前远程服务器是 github 网站那样的方式开发我们的项目了,唯一不同的是,接下来我们推送的远程服务器均是我们刚搭建好的主机. 需要做好心里准备,我们搭建的服务器还很简单,没有 github 网站那样可以直观操作远程仓库,但是这并不影响我们的 pull push merge 等操作哟! git clone git@snowdreams1006.cn:/home/git/repos/git-demo.git git-指的是 git 用户,snowdreams1006.cn-指的是远程主机域名或ip,/home/git/repos-指的是 git 仓库的目录,git-demo.git-指的是项目名称 现在我们已经成功搭建好自己的 git私服了,是不是很简单呢?有没有对 git 和 github 进一步理解?欢迎大家一起探讨! 小结 git 私服就是无 web 界面的简化版 github 小团队人工收集用户公钥,大团队使用 gitosis 实现类似 svn 那样的权限控制请使用 gitolite var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/server/private.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/about.html":{"url":"git/tools/about.html","title":"扩展工具","keywords":"","body":"扩展工具 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/about.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-command.html":{"url":"git/tools/git-bash-command.html","title":"git bash 常见命令","keywords":"","body":"git bash 常见命令 已投稿给脚本之家公众号,如需查看请访问: 从 git bash 命令行中窥探人生 https://mp.weixin.qq.com/s/5bSogfIMqmhgMcZ5NoYNlA var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-command.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-relationship.html":{"url":"git/tools/git-bash-relationship.html","title":"git bash 朋友圈","keywords":"","body":"git bash 朋友圈 已投稿给脚本之家公众号,如需查看请访问: 看过git bash的朋友圈才知道cmd为啥会呵呵一笑 https://mp.weixin.qq.com/s/4t2OPNtlVL12AQjrqAjuHg var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-relationship.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-tree.html":{"url":"git/tools/git-bash-tree.html","title":"git bash 扩展tree命令","keywords":"","body":"git bash 没有tree命令? 开门见山 git bash 是 Windows 用户安装 git 时默认安装的命令行工具,不仅界面漂亮功能也不错,大多数情况下可以替代 Windows 原生的 cmd 命令行. 然而,git bash 命令行不是万金油,并不能完全替代 cmd ,详情请参考 mintty 官网的相关说明. mintty is not a full replacement for the Windows Console window git bash 命令行默认使用 mintty 作为终端模拟器,而 mintty 官宣表示自己不能完全替代 cmd,也就是说 git bash 可能不具备某些 cmd 命令. 举个简单的例子,如果想要查看当前目录的文件结构,最好是以目录树的形式展现,聪明的你获取已经猜到了tree 命令. git bash 命令行中输入 tree 命令发现并无此命令. snowdreams1006@home MINGW64 /g/sublime/test $ tree bash: tree: command not found 为了验证,确实没有 tree 命令,我们直接打开 git bash 支持的命令文件目录,查看到底有没有 tree.exe 文件. 在 git bash 桌面快捷方式右键,选择打开文件位置,当前正处于 git 的安装目录,进入.\\usr\\bin 文件夹. 经过验证,git bash 支持的命令文件确实没有发现 tree.exe 文件,因此真的不支持 tree 命令. 然而,cmd 自带的命令行中输入 tree 中竟然发现能够输出目录树,原来 cmd 支持 tree 命令. G:\\sublime\\test>tree 卷 工作 的文件夹 PATH 列表 卷序列号为 00000081 CC3C:50D0 G:. ├─cmd └─git tree 命令其实调用的是 tree.com 并不是常见的 tree.exe 格式. 科普时间 现在我们已经知道 git bash 提供的终端模拟器不支持某些 linux 命令,但是为什么不支持以及如何才能支持这些命令呢? 这些问题必须等我们弄清楚 mintty 的朋友圈关系才能更好地解决上述问题,接下来简单科普下 mintty 的朋友圈. 关于科普知识的来源,请参考上一篇文章: > mintty 是什么 Mintty 是 Cygwin,MSYS 或 Msys2 的终端模拟器,派生项目和 WSL. mintty 开源终端模拟器,基于 putty 的终端仿真和 Windows 前端页面. mintty 作为一款优秀的终端模拟器,不仅是其他系统上默认的终端,也是 git bash 的默认终端. $ mintty --help Usage: mintty [OPTION]... [ PROGRAM [ARG]... | - ] Start a new terminal session running the specified program or the user's shell. If a dash is given instead of a program, invoke the shell as a login shell. Options: -c, --config FILE Load specified config file -e, --exec Treat remaining arguments as the command to execute -h, --hold never|start|error|always Keep window open after command finishes -i, --icon FILE[,IX] Load window icon from file, optionally with index -l, --log FILE|- Log output to file or stdout -o, --option OPT=VAL Override config file option with given value -p, --position X,Y Open window at specified coordinates -s, --size COLS,ROWS Set screen size in characters -t, --title TITLE Set window title (default: the invoked command) -u, --utmp Create a utmp entry -w, --window normal|min|max|full|hide Set initial window state --class CLASS Set window class name (default: mintty) -H, --help Display help and exit -V, --version Print version information and exit 如果想要自定义 mintty 终端,在 git bash 命令行界面右键选择选项设置即可打开设置页面. mingw 是什么 mingw 是 Minimalist GNU for Windows 的缩写,是 Microsoft Windows 应用程序的极简主义开发环境. msys 是什么 MSYS 是 Minimal SYStem 的缩写,是 Bourne Shell 命令行解释器系统.作为 Microsoft 的 cmd.exe 的替代品,它提供了一个通用的命令行环境,特别适合与 MinGW 一起使用,用于将许多开源应用程序移植到 MS-Windows平台; 它是 Cygwin-1.3 的轻量级分支,它包含一小部分 Unix 工具,可以帮助实现这一目标. cygwin 是什么 大量 GNU 和开源工具,提供类似于 Windows 上的 Linux 发行版的功能. gnu 是什么 GNU 是 GNU's Not Unix 的递归缩写,是自由软件操作系统. 朋友圈关系梳理 git bash 采用 mintty 作为终端模拟器,而 mintty 终端是 mingw ,msys2 和 cygwin的默认终端,这些\"操作系统\"或多或少都是 GNU 的一部分. GNU 是自由软件运动的成果,提出自由软件以及自由软件操作系统概念,源码开源发布. 正因如此,江山代有才人出,前人栽树后人乘凉,Cygwin 作为 GNU 一员,提出了要在 Windows 打造出 Linux 的感觉,开发出一套完整的解决方案. 或许由于这套方案太完整,功能齐全因而软件包体积庞大,因此 MinGW 和 MSYS 分别在其基于上进行精简重构,保留最简功能,发展出\"极简主义的GNU\". 开源的力量是可持续的,慢慢的,这些操作系统的内置终端功能也被单独提取出来,mintty 作为他们的默认终端也逐渐独立提供终端模拟器服务,轻松和各个系统进行安装集成. 说到开源,自然是少不了 git 的身影,分布式版本控制系统这种优秀工具应该造福全人类,然而 git 本身仅支持类 Unix 系统,并不提供 WIndows 系统的支持. Git For Windows 组织出手增加了 git 对 Windows 系统的支持,背后的技术多半离不开上述介绍的 GNU 操作系统. Git Bash Here 则是 Git For Windows 的命令行工具,使用的终端模拟器就是明星模拟器 mintty. 回到正题 弄清楚事情的来龙去脉后,对我们解决问题有什么帮助呢? 帮助可大了去了,刨根问题找到了源头,问题自然迎刃而解! 我劝少年放弃吧 你确定不是在逗我?让我直接放弃? 亲,真的抱歉呢,放弃是解决问题的最快途径! ... 谈一谈为什么要放弃? git bash 命令行使用的是 mintty 终端,而 mintty 终端并不能完全替代 cmd ,也没有提供包管理工具供我们扩展第三方命令. 所以默认情况下,如果没有提供某些命令,那我们只好放弃在 git bash 命令行中使用这些命令. 此路不通,自然会寻求其他解决途径,比如可以借助 cmd 命令行或者其他第三方软件等,没必要非要坚持使用git bash! 既然本文是 tree 命令引发的讨论,那就谈点和主题有关的技能点,不然岂不是跑题了? tree 命令虽然 git bash 不提供,但是 cmd 却已经内置了. 值得注意的是,cmd 提供的 tree 命令比较特殊,并不是常见的 .exe 结尾文件,而是 .com 结尾的文件. 所以 git bash 中输入 tree 命令时,自身 /usr/bin 中没有 tree.exe 文件,而系统中也没有 tree.exe 命令,那么就会提示找不到命令了啊! 手动补全命令 那么第一种调用方法便是补全后缀名,这样自然能够调用 cmd 的 tree.com 命令. git bash 并不识别 .com 后缀的命令,输入 tree 命令时以为是 tree.exe ,实际上 tree 命令应该是 tree.com 的简写. 我擦,竟然出现乱码,根据我多年的开发经验来看,乱码问题多半是编码问题导致的,那么修改下终端的编码设置应该就能解决问题. 在命令行窗口内右键弹出 mintty 终端的设置页面,选择文本(Text)中本地化(Locale)配置,选择中文简体(zh_CN),字符集(Character set)设置为UTF-8. 瞬间被打脸,无论是原来的git bash 命令行窗口还是新开的命令行窗口,仍然还是乱码! 然而,我是不会承认被打脸的,世人皆醉我独醒,肯定是 bug,哈哈! 其实,中文乱码真的是 bug ,还是官方认可的 bug 哟,我并没有被打脸呢,后续会介绍. 调用 cmd 程序 既然git bash 没有提供 tree 命令,而恰巧 cmd 提供了 tree 命令,那为何不假借他人之手实现自己的目的呢? 在 cmd 中使用 tree 命令直接输入即可得到目录树结构,但是现在需要在 git bash 中调用 cmd 中的 tree 命令. # 直接输入 `tree` 命令,正确响应并且无中文乱码. G:\\sublime\\test>tree 卷 工作 的文件夹 PATH 列表 卷序列号为 00000093 CC3C:50D0 G:. ├─cmd └─git # 输入 `cmd tree` 命令,虽无报错,但也没有正确响应. G:\\sublime\\test>cmd tree Microsoft Windows [版本 6.3.9600] (c) 2013 Microsoft Corporation。保留所有权利。 cmd 中直接输入 tree 命令即可,无需通过 cmd tree 这种方式,还以为你要调用 cmd 命令呢! 因此,我们需要告诉git bash 要通过 cmd 去调用下 tree 命令. # 注意看前缀是 `snowdreams1006@home MINGW64` 表明当前处于 `git bash` 环境 snowdreams1006@home MINGW64 /g/sublime/test # 输入 `cmd tree` 命令,虽无报错,但也没有正确响应,并且仍然有中文乱码. $ cmd tree Microsoft Windows [▒汾 6.3.9600] (c) 2013 Microsoft Corporation▒▒▒▒▒▒▒▒▒▒Ȩ▒▒▒▒ # 输出 `tree` 命令,正确响应但有中文乱码.此时命令行前缀已经更改为 `G:\\sublime\\test>` 表明当前不再处于`git bash` 环境! G:\\sublime\\test>tree tree ▒▒ ▒▒▒▒ ▒▒▒ļ▒▒▒ PATH ▒б▒ ▒▒▒▒▒к▒Ϊ 0000006B CC3C:50D0 G:. ▒▒▒▒cmd ▒▒▒▒git 通过上述操作结果来看,不难发现以下问题. cmd tree 命令切换到 cmd 环境,并且tree 命令并没有执行. 换句话说,cmd tree 和 cmd dir 或者 cmd 的作用相同,都是切换了当前 bash 环境. 进入 cmd 命令行运行 tree 能够得到正确响应,但存在中文乱码. 想要退出 cmd 环境,Ctrl + C 组合即可重新回到 git bash 环境. 由此可见,不加任何参数冒昧进入到 cmd 环境还是比较麻烦的,因此下面提供带参数的命令帮助我们阅后即焚. cmd //c tree 命令,阅后即焚,表示执行完立即退出. snowdreams1006@home MINGW64 /g/sublime/test $ cmd //c tree ▒▒ ▒▒▒▒ ▒▒▒ļ▒▒▒ PATH ▒б▒ ▒▒▒▒▒к▒Ϊ 00000008 CC3C:50D0 G:. ▒▒▒▒cmd ▒▒▒▒git snowdreams1006@home MINGW64 /g/sublime/test $ 执行命令前后我们都在 git bash 环境并且在 cmd 中得到正确响应结果,唯一的区别就是多加了 //c 参数,表示执行完命令立即退出 cmd 环境. 优雅调用 cmd 简单总结下,如何在 git bash 中借助 cmd 实现 tree 命令. tree.com : 补全调用命令后缀名,直接调用系统命令. cmd //c tree : 借助 cmd 运行 tree 命令,从而实现调用 tree 的目的. 这两种方式都存在中文乱码问题,即使设置了终端的编码方式也没有解决乱码. 调用 tree 命令的目的已经达到,没有解决的问题是中文乱码. 解决问题最快速的方式是百度一下或者从官网寻求帮助,这次我选择后者,因为百度一下人人都会,不用我再讲了吧! 简单解释下这段话的意思: 如果在 mintty 终端调用原生 cmd 程序,简单的输出指令没有什么问题,交互指令可能存在问题. 因此建议使用 winpty 进行包装再调用原生 cmd 程序. winpty 是一种提供与cmd 通信的软件包,详情请参考https://github.com/rprichard/winpty 有什么神奇之处?不妨加上 winpty 试试看! snowdreams1006@home MINGW64 /g/sublime/test # `winpty` + `tree.com` : 正常输出且无中文乱码 $ winpty tree.com 卷 工作 的文件夹 PATH 列表 卷序列号为 00000074 CC3C:50D0 G:. ├─cmd └─git snowdreams1006@home MINGW64 /g/sublime/test # `winpty` + `cmd //c tree` : 正常输出且无中文乱码 $ winpty cmd //c tree 卷 工作 的文件夹 PATH 列表 卷序列号为 000000B3 CC3C:50D0 G:. ├─cmd └─git snowdreams1006@home MINGW64 /g/sublime/test $ 果然是神药,一下子就治好了我多年的老寒腿啊! winpty tree.com : 不叫小名而叫全称,直接调用系统命令 winpty cmd //c tree : 假借他人之手,变相调用系统命令 上文中说设置文件编码应该能够解决中文乱码问题结果仍然有乱码,猜测是 bug ,现在没有打脸吧? 既然已经提供了解决方案,那文章是不是应该到此为止了呢? 不不不,远远还没结束,这只是开胃小菜,好戏还在后头呢. 更何况这命令也忒长了,记不住啊! 很简单,可以设置别名啊,把常用命令设置成别名,这样就记住啦! 输入 alias 命令没有报错,说明目前环境是支持设置别名的. snowdreams1006@home MINGW64 /g/sublime/test $ alias alias ll='ls -l' alias ls='ls -F --color=auto --show-control-chars' alias node='winpty node.exe' 按照 linux 的操作习惯,命令行设置的一般都是临时性的,想要永久生效,都要写入到文件中,别名这种当然要一劳永逸设置成永久文件. snowdreams1006@home MINGW64 /g/sublime/test $ cat /etc/bashrc cat: /etc/bashrc: No such file or directory snowdreams1006@home MINGW64 /g/sublime/test $ cat ~/.bashrc cat: /c/Users/snowdreams1006/.bashrc: No such file or directory 竟然配置文件都不存在? 当然不存在了啊!快醒醒,你是在 Windows 系统上并不是 Linux 系统,上哪给你弄这些配置文件去? 脑海中迅速闪现哲学基本问题: 我是谁,我在那,我在干什么? 我是 Windows 系统用户,正在 git bash 命令行中试图设置别名,没有找到类似于 linux 配置文件. 既然你明白你何出来,那你去那里看看有没有什么发现? snowdreams1006@home MINGW64 /g/sublime/test $ cd /e/git snowdreams1006@home MINGW64 /e/git $ winpty tree.com 卷 软件 的文件夹 PATH 列表 卷序列号为 00000063 223E:7300 E:. ├─bin ├─cmd ├─dev │ ├─mqueue │ └─shm ├─etc │ ├─pkcs11 │ ├─pki │ │ └─ca-trust │ │ ├─extracted │ │ │ ├─java │ │ │ ├─openssl │ │ │ └─pem │ │ └─source │ │ └─anchors │ ├─profile.d │ └─ssh ├─mingw64 │ ├─bin │ ├─doc │ │ └─git-credential-manager │ ├─etc │ │ ├─pkcs11 │ │ └─pki │ │ └─ca-trust │ │ └─extracted │ │ ├─java │ │ ├─openssl │ │ └─pem │ ├─lib │ │ ├─dde1.4 │ │ ├─engines │ │ ├─itcl4.0.4 │ │ ├─p11-kit │ │ ├─pkcs11 │ │ ├─reg1.3 │ │ ├─sqlite3.11.0 │ │ ├─tcl8 │ │ │ ├─8.4 │ │ │ │ └─platform │ │ │ ├─8.5 │ │ │ └─8.6 │ │ │ └─tdbc │ │ ├─tcl8.6 │ │ │ ├─encoding │ │ │ ├─http1.0 │ │ │ ├─msgs │ │ │ ├─opt0.4 │ │ │ └─tzdata │ │ │ ├─Africa │ │ │ ├─America │ │ │ │ ├─Argentina │ │ │ │ ├─Indiana │ │ │ │ ├─Kentucky │ │ │ │ └─North_Dakota │ │ │ ├─Antarctica │ │ │ ├─Arctic │ │ │ ├─Asia │ │ │ ├─Atlantic │ │ │ ├─Australia │ │ │ ├─Brazil │ │ │ ├─Canada │ │ │ ├─Chile │ │ │ ├─Etc │ │ │ ├─Europe │ │ │ ├─Indian │ │ │ ├─Mexico │ │ │ ├─Pacific │ │ │ ├─SystemV │ │ │ └─US │ │ ├─thread2.7.3 │ │ └─tk8.6 │ │ ├─demos │ │ │ └─images │ │ ├─images │ │ ├─msgs │ │ └─ttk │ ├─libexec │ │ └─git-core │ │ └─mergetools │ ├─share │ │ ├─antiword │ │ ├─doc │ │ │ ├─connect │ │ │ ├─git-doc │ │ │ │ ├─howto │ │ │ │ └─technical │ │ │ └─nghttp2 │ │ ├─gettext-0.19.7 │ │ │ └─its │ │ ├─git │ │ │ ├─bindimage.txt │ │ │ └─completion │ │ ├─git-core │ │ │ └─templates │ │ │ ├─hooks │ │ │ └─info │ │ ├─git-gui │ │ │ └─lib │ │ ├─gitweb │ │ │ └─static │ │ ├─licenses │ │ │ ├─bzip2 │ │ │ ├─expat │ │ │ ├─gcc-libs │ │ │ ├─gettext │ │ │ │ ├─gettext-runtime │ │ │ │ │ ├─intl │ │ │ │ │ └─libasprintf │ │ │ │ ├─gettext-tools │ │ │ │ │ └─gnulib-lib │ │ │ │ │ └─libxml │ │ │ │ └─gnulib-local │ │ │ │ └─lib │ │ │ │ └─libxml │ │ │ ├─libffi │ │ │ ├─libiconv │ │ │ │ └─libcharset │ │ │ ├─libssh2 │ │ │ ├─libsystre │ │ │ ├─libtasn1 │ │ │ ├─libtre │ │ │ ├─libwinpthread │ │ │ │ └─mingw-w64-libraries │ │ │ │ └─winpthreads │ │ │ ├─openssl │ │ │ ├─wineditline │ │ │ └─zlib │ │ ├─nghttp2 │ │ ├─p11-kit │ │ │ └─modules │ │ ├─perl5 │ │ │ └─site_perl │ │ │ └─Git │ │ │ └─SVN │ │ │ └─Memoize │ │ └─pki │ │ └─ca-trust-source │ └─ssl │ └─certs ├─tmp └─usr ├─bin │ ├─core_perl │ └─vendor_perl ├─lib │ ├─awk │ ├─coreutils │ ├─gawk │ ├─gnupg │ │ └─gnupg │ ├─openssl │ │ └─engines │ ├─p11-kit │ ├─perl5 │ │ ├─core_perl │ │ │ ├─auto │ │ │ │ ├─arybase │ │ │ │ ├─attributes │ │ │ │ ├─B │ │ │ │ ├─Compress │ │ │ │ │ └─Raw │ │ │ │ │ ├─Bzip2 │ │ │ │ │ └─Zlib │ │ │ │ ├─Cwd │ │ │ │ ├─Data │ │ │ │ │ └─Dumper │ │ │ │ ├─DB_File │ │ │ │ ├─Devel │ │ │ │ │ ├─Peek │ │ │ │ │ └─PPPort │ │ │ │ ├─Digest │ │ │ │ │ ├─MD5 │ │ │ │ │ └─SHA │ │ │ │ ├─Encode │ │ │ │ │ ├─Byte │ │ │ │ │ ├─CN │ │ │ │ │ ├─EBCDIC │ │ │ │ │ ├─JP │ │ │ │ │ ├─KR │ │ │ │ │ ├─Symbol │ │ │ │ │ ├─TW │ │ │ │ │ └─Unicode │ │ │ │ ├─Fcntl │ │ │ │ ├─File │ │ │ │ │ ├─DosGlob │ │ │ │ │ └─Glob │ │ │ │ ├─Filter │ │ │ │ │ └─Util │ │ │ │ │ └─Call │ │ │ │ ├─GDBM_File │ │ │ │ ├─Hash │ │ │ │ │ └─Util │ │ │ │ │ └─FieldHash │ │ │ │ ├─I18N │ │ │ │ │ └─Langinfo │ │ │ │ ├─IO │ │ │ │ ├─IPC │ │ │ │ │ └─SysV │ │ │ │ ├─List │ │ │ │ │ └─Util │ │ │ │ ├─Math │ │ │ │ │ └─BigInt │ │ │ │ │ └─FastCalc │ │ │ │ ├─MIME │ │ │ │ │ └─Base64 │ │ │ │ ├─mro │ │ │ │ ├─NDBM_File │ │ │ │ ├─ODBM_File │ │ │ │ ├─Opcode │ │ │ │ ├─PerlIO │ │ │ │ │ ├─encoding │ │ │ │ │ ├─mmap │ │ │ │ │ ├─scalar │ │ │ │ │ └─via │ │ │ │ ├─POSIX │ │ │ │ ├─re │ │ │ │ ├─SDBM_File │ │ │ │ ├─Socket │ │ │ │ ├─Storable │ │ │ │ ├─Sys │ │ │ │ │ ├─Hostname │ │ │ │ │ └─Syslog │ │ │ │ ├─threads │ │ │ │ │ └─shared │ │ │ │ ├─Tie │ │ │ │ │ └─Hash │ │ │ │ │ └─NamedCapture │ │ │ │ ├─Time │ │ │ │ │ ├─HiRes │ │ │ │ │ └─Piece │ │ │ │ ├─Unicode │ │ │ │ │ └─Collate │ │ │ │ ├─Win32 │ │ │ │ ├─Win32API │ │ │ │ │ └─File │ │ │ │ └─Win32CORE │ │ │ ├─B │ │ │ ├─Compress │ │ │ │ └─Raw │ │ │ ├─CORE │ │ │ ├─Data │ │ │ ├─Devel │ │ │ ├─Digest │ │ │ ├─Encode │ │ │ │ ├─CN │ │ │ │ ├─JP │ │ │ │ ├─KR │ │ │ │ ├─MIME │ │ │ │ │ └─Header │ │ │ │ └─Unicode │ │ │ ├─File │ │ │ │ └─Spec │ │ │ ├─Filter │ │ │ │ └─Util │ │ │ ├─Hash │ │ │ │ └─Util │ │ │ ├─I18N │ │ │ ├─IO │ │ │ │ └─Socket │ │ │ ├─IPC │ │ │ ├─List │ │ │ │ └─Util │ │ │ ├─Math │ │ │ │ └─BigInt │ │ │ ├─MIME │ │ │ ├─PerlIO │ │ │ ├─Scalar │ │ │ ├─Sub │ │ │ ├─Sys │ │ │ ├─threads │ │ │ ├─Tie │ │ │ │ └─Hash │ │ │ ├─Time │ │ │ ├─Unicode │ │ │ │ └─Collate │ │ │ └─Win32API │ │ └─vendor_perl │ │ ├─auto │ │ │ ├─HTML │ │ │ │ └─Parser │ │ │ ├─Net │ │ │ │ └─SSLeay │ │ │ ├─SVN │ │ │ │ ├─_Client │ │ │ │ ├─_Core │ │ │ │ ├─_Delta │ │ │ │ ├─_Fs │ │ │ │ ├─_Ra │ │ │ │ ├─_Repos │ │ │ │ └─_Wc │ │ │ └─Term │ │ │ └─ReadKey │ │ ├─HTML │ │ ├─Net │ │ │ └─SSLeay │ │ ├─SVN │ │ └─Term │ ├─pkcs11 │ ├─sasl2 │ ├─ssh │ ├─tar │ └─terminfo │ ├─63 │ ├─64 │ └─78 ├─libexec ├─share │ ├─bash-completion │ │ └─completions │ ├─cygwin │ ├─git │ ├─gnupg │ ├─licenses │ │ ├─curl │ │ ├─dos2unix │ │ ├─expat │ │ ├─file │ │ ├─gcc-libs │ │ ├─libffi │ │ ├─libsasl │ │ ├─libsqlite │ │ ├─libssh2 │ │ ├─mintty │ │ ├─ncurses │ │ ├─openssh │ │ ├─openssl │ │ ├─p11-kit │ │ ├─perl-Net-SSLeay │ │ ├─perl-TermReadKey │ │ ├─unzip │ │ ├─vim │ │ └─zlib │ ├─misc │ ├─p11-kit │ │ └─modules │ ├─perl5 │ │ ├─core_perl │ │ │ ├─App │ │ │ │ └─Prove │ │ │ │ └─State │ │ │ │ └─Result │ │ │ ├─Archive │ │ │ │ └─Tar │ │ │ ├─Attribute │ │ │ ├─autodie │ │ │ │ ├─exception │ │ │ │ └─Scope │ │ │ ├─B │ │ │ ├─Carp │ │ │ ├─Class │ │ │ ├─Compress │ │ │ ├─Config │ │ │ │ └─Perl │ │ │ ├─CPAN │ │ │ │ ├─Exception │ │ │ │ ├─FTP │ │ │ │ ├─HTTP │ │ │ │ ├─Kwalify │ │ │ │ ├─LWP │ │ │ │ ├─Meta │ │ │ │ └─Plugin │ │ │ ├─DBM_Filter │ │ │ ├─Devel │ │ │ ├─Digest │ │ │ ├─Encode │ │ │ ├─encoding │ │ │ ├─Exporter │ │ │ ├─ExtUtils │ │ │ │ ├─CBuilder │ │ │ │ │ └─Platform │ │ │ │ │ └─Windows │ │ │ │ ├─Command │ │ │ │ ├─Constant │ │ │ │ ├─Liblist │ │ │ │ ├─MakeMaker │ │ │ │ │ └─version │ │ │ │ ├─ParseXS │ │ │ │ └─Typemaps │ │ │ ├─File │ │ │ ├─Filter │ │ │ ├─Getopt │ │ │ ├─HTTP │ │ │ ├─I18N │ │ │ │ └─LangTags │ │ │ ├─IO │ │ │ │ ├─Compress │ │ │ │ │ ├─Adapter │ │ │ │ │ ├─Base │ │ │ │ │ ├─Gzip │ │ │ │ │ ├─Zip │ │ │ │ │ └─Zlib │ │ │ │ ├─Socket │ │ │ │ └─Uncompress │ │ │ │ └─Adapter │ │ │ ├─IPC │ │ │ ├─JSON │ │ │ │ └─PP │ │ │ ├─Locale │ │ │ │ ├─Codes │ │ │ │ └─Maketext │ │ │ ├─Math │ │ │ │ ├─BigFloat │ │ │ │ └─BigInt │ │ │ ├─Memoize │ │ │ ├─Module │ │ │ │ ├─CoreList │ │ │ │ └─Load │ │ │ ├─Net │ │ │ │ └─FTP │ │ │ ├─overload │ │ │ ├─Params │ │ │ ├─Parse │ │ │ │ └─CPAN │ │ │ ├─Perl │ │ │ ├─PerlIO │ │ │ │ └─via │ │ │ ├─Pod │ │ │ │ ├─Perldoc │ │ │ │ ├─Simple │ │ │ │ └─Text │ │ │ ├─Search │ │ │ ├─TAP │ │ │ │ ├─Formatter │ │ │ │ │ ├─Console │ │ │ │ │ └─File │ │ │ │ ├─Harness │ │ │ │ └─Parser │ │ │ │ ├─Iterator │ │ │ │ ├─Result │ │ │ │ ├─Scheduler │ │ │ │ ├─SourceHandler │ │ │ │ └─YAMLish │ │ │ ├─Term │ │ │ ├─Test │ │ │ │ ├─Builder │ │ │ │ │ ├─IO │ │ │ │ │ └─Tester │ │ │ │ ├─Tester │ │ │ │ └─use │ │ │ ├─Text │ │ │ ├─Thread │ │ │ ├─Tie │ │ │ ├─Time │ │ │ ├─Unicode │ │ │ │ └─Collate │ │ │ │ └─CJK │ │ │ ├─unicore │ │ │ │ ├─lib │ │ │ │ │ ├─Age │ │ │ │ │ ├─Alpha │ │ │ │ │ ├─Bc │ │ │ │ │ ├─BidiC │ │ │ │ │ ├─BidiM │ │ │ │ │ ├─Blk │ │ │ │ │ ├─Bpt │ │ │ │ │ ├─Cased │ │ │ │ │ ├─Ccc │ │ │ │ │ ├─CE │ │ │ │ │ ├─CI │ │ │ │ │ ├─CompEx │ │ │ │ │ ├─CWCF │ │ │ │ │ ├─CWCM │ │ │ │ │ ├─CWKCF │ │ │ │ │ ├─CWL │ │ │ │ │ ├─CWT │ │ │ │ │ ├─CWU │ │ │ │ │ ├─Dash │ │ │ │ │ ├─Dep │ │ │ │ │ ├─DI │ │ │ │ │ ├─Dia │ │ │ │ │ ├─Dt │ │ │ │ │ ├─Ea │ │ │ │ │ ├─Ext │ │ │ │ │ ├─Gc │ │ │ │ │ ├─GCB │ │ │ │ │ ├─GrBase │ │ │ │ │ ├─Hex │ │ │ │ │ ├─Hst │ │ │ │ │ ├─Hyphen │ │ │ │ │ ├─IDC │ │ │ │ │ ├─Ideo │ │ │ │ │ ├─IDS │ │ │ │ │ ├─In │ │ │ │ │ ├─Jg │ │ │ │ │ ├─Jt │ │ │ │ │ ├─Lb │ │ │ │ │ ├─LOE │ │ │ │ │ ├─Lower │ │ │ │ │ ├─Math │ │ │ │ │ ├─NChar │ │ │ │ │ ├─NFCQC │ │ │ │ │ ├─NFDQC │ │ │ │ │ ├─NFKCQC │ │ │ │ │ ├─NFKDQC │ │ │ │ │ ├─Nt │ │ │ │ │ ├─Nv │ │ │ │ │ ├─PatSyn │ │ │ │ │ ├─PatWS │ │ │ │ │ ├─Perl │ │ │ │ │ ├─QMark │ │ │ │ │ ├─SB │ │ │ │ │ ├─Sc │ │ │ │ │ ├─Scx │ │ │ │ │ ├─SD │ │ │ │ │ ├─STerm │ │ │ │ │ ├─Term │ │ │ │ │ ├─UIdeo │ │ │ │ │ ├─Upper │ │ │ │ │ ├─WB │ │ │ │ │ ├─XIDC │ │ │ │ │ └─XIDS │ │ │ │ └─To │ │ │ ├─User │ │ │ ├─version │ │ │ ├─warnings │ │ │ └─Win32API │ │ │ └─File │ │ └─vendor_perl │ │ ├─Authen │ │ │ └─SASL │ │ │ └─Perl │ │ ├─Convert │ │ ├─Date │ │ │ └─Language │ │ ├─Encode │ │ ├─Error │ │ ├─File │ │ ├─HTML │ │ ├─HTTP │ │ │ ├─Cookies │ │ │ ├─Headers │ │ │ └─Request │ │ ├─IO │ │ │ └─Socket │ │ │ └─SSL │ │ ├─LWP │ │ │ ├─Authen │ │ │ └─Protocol │ │ ├─Mail │ │ │ ├─Field │ │ │ └─Mailer │ │ ├─MIME │ │ │ ├─Decoder │ │ │ ├─Field │ │ │ └─Parser │ │ ├─Net │ │ │ ├─HTTP │ │ │ └─SMTP │ │ ├─Time │ │ ├─URI │ │ │ ├─file │ │ │ └─urn │ │ └─WWW │ │ └─RobotRules │ ├─pki │ │ └─ca-trust-source │ ├─tabset │ ├─terminfo │ │ ├─63 │ │ ├─64 │ │ └─78 │ └─vim │ └─vim74 │ ├─autoload │ │ └─xml │ ├─colors │ ├─compiler │ ├─doc │ ├─ftplugin │ ├─indent │ ├─keymap │ ├─macros │ │ ├─hanoi │ │ ├─life │ │ ├─maze │ │ └─urm │ ├─pack │ │ └─dist │ │ └─opt │ │ ├─dvorak │ │ │ ├─dvorak │ │ │ └─plugin │ │ ├─editexisting │ │ │ └─plugin │ │ ├─justify │ │ │ └─plugin │ │ ├─matchit │ │ │ ├─doc │ │ │ └─plugin │ │ ├─shellmenu │ │ │ └─plugin │ │ └─swapmouse │ │ └─plugin │ ├─plugin │ ├─print │ ├─spell │ ├─syntax │ ├─tools │ └─tutor └─ssl ├─certs └─misc 看到熟悉的 ./etc/bash.bashrc 文件,顿时亲切不少,设置一下别名再说. snowdreams1006@home MINGW64 /e/git $ echo \"# Set alias for tree command\" >> ./etc/bash.bashrc snowdreams1006@home MINGW64 /e/git $ echo \"alias tree='winpty tree.com'\" >> ./etc/bash.bashrc snowdreams1006@home MINGW64 /e/git $ source ./etc/bash.bashrc 现在测试一下能否正确打印出目录树: snowdreams1006@home MINGW64 /e/git $ cd /g/sublime/test snowdreams1006@home MINGW64 /g/sublime/test $ tree 卷 工作 的文件夹 PATH 列表 卷序列号为 000000A3 CC3C:50D0 G:. ├─cmd └─git 亲测有效,通过设置别名的方式可以简化命令,从而实现在 git bash 中优雅调用 tree 命令. 固执少年一意孤行 少年既然不听劝,那我只好和你一起一意孤行. git bash 不支持 tree 命令,意味着 mintty 终端不支持 tree 命令,但 mintty 既然作为一款优秀的终端模拟器不可能不支持 tree 命令,否则 cygwin ,msys2 和 mingw 等系统不可能将其作为默认终端. 回想起 linux 系统,最小化安装版也不支持 tree 命令,通过包管理工具自行扩展即可支持 tree 命令. 因此,思路有两种,像 linux 那样通过包管理工具安装 tree 命令,或者通过源码编译方式扩展 tree 命令. 第一种需要包管理工具,而git bash 使用的是 mintty 终端,并没有提供相应的包管理工具. 所以想要通过包管理工具进行安装 tree 命令也是无路可走. 包管理工具安装 如果能够提供包管理工具,那么我们就可以像 linux 系统那样安装第三方命令一样,安装 tree 命令了. 首先想到的是 mintty 官网有没有相关说明,遗憾的是,mintty 本身一般是通过包管理工具安装的,单独的终端并没有包管理的环境,因此无法调用相关命令. Administrator@snowdreams1006 MINGW64 /f/workspace/test $ mingw-get bash: mingw-get: command not found Administrator@snowdreams1006 MINGW64 /f/workspace/test $ pacman bash: pacman: command not found 然而,小小的挫折是不会轻易放弃的,既然 mintty 官网不能提供有效的帮助,那我们回到最初安装 git 的地方,看一下 git 能否提供相关的包管理工具. 频繁出现 Git For Windows 名词,根据软件的命令规则,Git for Windows 可能是独立的软件,更何况 git bash 集成的终端也不是自身研发的终端而是第三方的 mintty 终端. 所以,我们有理由相信 Git for Windows 是另外的团队在维护,而不是 Git 团队. git-for-windows 官网: https://gitforwindows.org/ 根据官方说明,安装后正在下载相关依赖,下载速度比较慢的话,请自行解决. 下载完成后,原来的 cmd 窗口会自动关闭并且打开新的 git bash 窗口. Administrator@snowdreams1006 MINGW64 / (master) $ sdk help The 'sdk' shell function helps you to get up and running with the Git for Windows SDK. The available subcommands are: create-desktop-icon: install a desktop icon that starts the Git for Windows SDK Bash. cd : initialize/update a worktree and cd into it. Known projects: git git-extra msys2-runtime installer build-extra MINGW-packages MSYS2-packages mingw-w64-busybox mingw-w64-curl mingw-w64-cv2pdb mingw-w64-git mingw-w64-git-credential-manager mingw-w64-git-lfs mingw-w64-git-sizer mingw-w64-wintoast bash curl gawk git-flow gnupg heimdal mintty nodejs openssh openssl perl perl-HTML-Parser perl-Locale-Gettext perl-Net-SSLeay perl-TermReadKey perl-XML-Parser perl-YAML-Syck subversion tig init : initialize and/or update a worktree. Known projects are the same as for the 'cd' command. build : builds one of the following: git-and-installer git git-extra msys2-runtime installer mingw-w64-busybox mingw-w64-curl mingw-w64-cv2pdb mingw-w64-git mingw-w64-git-credential-manager mingw-w64-git-lfs mingw-w64-git-sizer mingw-w64-wintoast bash curl gawk git-flow gnupg heimdal mintty nodejs openssh openssl perl perl-HTML-Parser perl-Locale-Gettext perl-Net-SSLeay perl-TermReadKey perl-XML-Parser perl-YAML-Syck subversion tig edit : edit a well-known file. Well-known files are: git-sdk.sh sdk.completion ReleaseNotes.md install.iss reload: reload the 'sdk' function. 现在安装完成后,我们再次打开 Git for Windows 的开发文档简介,从中不难发现该项目使用了 MSYS2 项目,那么问题迎刃而解. 根据科普知识,我们知道 MSYS2 和 MinGW 都是操作系统,而 Git For Windows 将两者结合在一起,默认使用 MSYS2 的包管理工具. Administrator@snowdreams1006 MINGW64 / (master) $ Pacman -h 用法: Pacman [...] 操作: Pacman {-h --help} Pacman {-V --version} Pacman {-D --database} Pacman {-F --files} [选项] [软件包] Pacman {-Q --query} [选项] [软件包] Pacman {-R --remove} [选项] Pacman {-S --sync} [选项] [软件包] Pacman {-T --deptest} [选项] [软件包] Pacman {-U --upgrade} [选项] 使用 'Pacman {-h --help}' 及某个操作以查看可用选项 激动人心的时刻就要来临,在正式使用 Pacman 安装 tree 命令外,我们再次检查当前系统环境以确保没有 git bash 无法调用 tree 命令. Administrator@snowdreams1006 MINGW64 / (master) $ tree bash: tree: 未找到命令 调用 Pacman -S tree 命令安装 tree 命令. Administrator@snowdreams1006 MINGW64 / (master) $ Pacman -S tree 正在解析依赖关系... 正在查找软件包冲突... 软件包 (1) tree-1.8.0-1 下载大小: 0.05 MiB 全部安装大小: 0.07 MiB :: 进行安装吗? [Y/n] y 警告:没有 /var/cache/pacman/pkg/ 缓存存在,正在创建... :: 正在获取软件包...... tree-1.8.0-1-x86_64 51.1 KiB 211K/s 00:00 [#####################] 100% (1/1) 正在检查密钥环里的密钥 [#####################] 100% (1/1) 正在检查软件包完整性 [#####################] 100% (1/1) 正在加载软件包文件 [#####################] 100% (1/1) 正在检查文件冲突 [#####################] 100% (1/1) 正在检查可用存储空间 [#####################] 100% :: 正在处理软件包的变化... (1/1) 正在安装 tree 验证安装成功,切换到测试目录调用 tree 命令真的打印出了目录树结构. Administrator@snowdreams1006 MINGW64 / (master) $ pwd / Administrator@snowdreams1006 MINGW64 / (master) $ cd /f/workspace/test Administrator@snowdreams1006 MINGW64 /f/workspace/test $ tree . ├── cmd └── gitbash 2 directories, 0 files tree.exe 文件确实已经存在,通过这样方式当然可以安装任意第三方命令了呢! Administrator@snowdreams1006 MINGW64 / (master) $ pwd / Administrator@snowdreams1006 MINGW64 / (master) $ ls usr/bin/tree.exe usr/bin/tree.exe* Administrator@snowdreams1006 MINGW64 / (master) $ ls usr/bin/tree.exe usr/bin/tree.exe* 然而,事情还没有结束,虽然打印当前路径显示的是在 / ,但是如果从普通的 git bash 命令行窗口进入 /,发现他们并不一致! 开发版左上角文字: SDK-64,普通版左上角: MinGW64. 事情应该不至于这么复杂,我猜测如果进入到 git sdk 的安装目录,应该是一样的! 源码编译安装 还有一种源码编译安装方式,可以猜想到的是将会比较麻烦,不仅要安装 c 编译环境,还可能会面临如何移植到 Windows 环境的问题. 恕再下先行一步,告辞! 但是老司机怎么能收走就走,不是说好一起闯天下得嘛? 然而,心有余而力不足,编译安装再到测试确实是不少挑战,最重要的是,电脑太卡了等不了. 所以,收集到了一些资料方便有条件的小伙伴去研究吧! 下载链接: ftp://mama.indstate.edu/linux/tree/tree-1.8.0.tgz 在 mingw ,msys2 或者 cygwin 系统上编译安装 tree 的 c 文件,最终生成 tree.exe 可执行文件. 独立安装 c 编译环境,生成的 tree.exe 可执行文件再想办法兼容到 git bash 所支持的 .exe 类型,或许也不用转换. 请参考 linux 系统的 tree 命令源码: http://mama.indstate.edu/users/ice/tree/ 我觉得我还可以再坚持一会,虽然不能从头开始编译生成 tree.exe 可执行文件,但是研究了这么多朋友圈关系,足够我找到解决方案了. tree 命令的源码文件编译成 tree.exe 可执行文件比较费劲,但是可以找到已经编译好的文件啊. 说道这里,不得不提一下神奇的 sourceforge 网站,提供源码和下载网站. 前面我们一直在说 mintty 终端模拟器也好,或者 mingw ,msys2 和 cygwin 操作系统也罢,他们或多或少和 GNU 有一些联系,而 GNU 是自由软件操作系统,源码会随程序一同发布. 所以我们应该可以从 sourceforge 网站上找到些蛛丝马迹,说不定还有打包好的可执行文件呢,如果那样的话就不用我们手动编译安装了! 功夫不负有心人,竟然真的找到了,搜索 GNU 发现了 gnuwin32 项目,完整提供了原生命令,其中就有我们需要的 tree 命令. 下载链接: https://sourceforge.net/projects/gnuwin32/files/tree/1.5.2.2/tree-1.5.2.2-bin.zip/download 于是下载二进制文件找到其中的 /bin/tree.exe 并将其复制到 /git/usr/bin 目录下,这样 git bash 本身就支持 tree 命令了. 首先清除掉上一步设置的别名,防止干扰以确保此二进制文件真实有效. snowdreams1006@home MINGW64 /g/sublime/test # 切换到 `git` 安装目录 $ cd /e/git snowdreams1006@home MINGW64 /e/git # 编辑 `bash.bashrc` 配置文件,移除别名 $ vim ./etc/bash.bashrc snowdreams1006@home MINGW64 /e/git # 查看配置文件内容,别名设置已移除 $ tail ./etc/bash.bashrc [[ \"$-\" != *i* ]] && return # Set a default prompt of: user@host, MSYSTEM variable, and current_directory #PS1='\\[\\e]0;\\w\\a\\]\\n\\[\\e[32m\\]\\u@\\h \\[\\e[35m\\]$MSYSTEM\\[\\e[0m\\] \\[\\e[33m\\]\\w\\[\\e[0m\\]\\n\\$ ' # Uncomment to use the terminal colours set in DIR_COLORS # eval \"$(dircolors -b /etc/DIR_COLORS)\" # Fixup git-bash in non login env shopt -q login_shell || . /etc/profile.d/git-prompt.sh snowdreams1006@home MINGW64 /e/git # 刷新配置文件,使其立即生效 $ source ./etc/bash.bashrc snowdreams1006@home MINGW64 /e/git # 移除 `tree` 别名,适用于命令行方式设置而不是文件设置 $ unalias tree snowdreams1006@home MINGW64 /e/git # 运行 `tree` 命令,确保已经无法通过别名方式调用系统的 `tree.com` 命令 $ tree bash: tree: command not found 真的成功添加了 tree.exe 命令,明显和 cmd 自带的 tree.com 命令不一致. snowdreams1006@home MINGW64 /e/git $ cd /g/sublime/test snowdreams1006@home MINGW64 /g/sublime/test # 扩展命令 `tree` 帮助信息 $ tree.exe --help usage: tree [-adfghilnpqrstuvxACDFNS] [-H baseHREF] [-T title ] [-L level [-R]] [-P pattern] [-I pattern] [-o filename] [--version] [--help] [--inodes] [--device] [--noreport] [--nolinks] [--dirsfirst] [--charset charset] [--filelimit #] [] -a All files are listed. -d List directories only. -l Follow symbolic links like directories. -f Print the full path prefix for each file. -i Don't print indentation lines. -q Print non-printable characters as '?'. -N Print non-printable characters as is. -p Print the protections for each file. -u Displays file owner or UID number. -g Displays file group owner or GID number. -s Print the size in bytes of each file. -h Print the size in a more human readable way. -D Print the date of last modification. -F Appends '/', '=', '*', or '|' as per ls -F. -v Sort files alphanumerically by version. -r Sort files in reverse alphanumeric order. -t Sort files by last modification time. -x Stay on current filesystem only. -L level Descend only level directories deep. -A Print ANSI lines graphic indentation lines. -S Print with ASCII graphics indentation lines. -n Turn colorization off always (-C overrides). -C Turn colorization on always. -P pattern List only those files that match the pattern given. -I pattern Do not list files that match the given pattern. -H baseHREF Prints out HTML format with baseHREF as top directory. -T string Replace the default HTML title and H1 header with string. -R Rerun tree when max dir level reached. -o file Output to file instead of stdout. --inodes Print inode number of each file. --device Print device ID number to which each file belongs. --noreport Turn off file/directory count at end of tree listing. --nolinks Turn off hyperlinks in HTML output. --dirsfirst List directories before files. --charset X Use charset X for HTML and indentation line output. --filelimit # Do not descend dirs with more than # files in them. snowdreams1006@home MINGW64 /g/sublime/test # 原生 `tree.com` 帮助信息 $ winpty tree.com /? 卷 软件 的文件夹 PATH 列表 卷序列号为 000000CA 223E:7300 E:\\GIT\\? 无效的路径 - \\GIT\\? 没有子文件夹 虽然原生 cmd 自带的 tree.com 命令也能打印出目录结构树,但是和扩展的第三方 tree.exe 命令相比,可配置的选项实在太少,难怪固执少年会执意扩展 tree 命令. 懒人直达 如果想要在 git bash 命令行中调用 tree 命令,总结了下列几种方法. 注意: 从上到下逐渐复杂,根据自己的情况自行选择. winpty tree.com : 直接调用 cmd 内置 tree.com 命令. winpty cmd //c tree : 通知 cmd 调用 tree 命令. 安装 Git For Windows 开发版,Pacman -S tree 安装 tree 命令,然后执行 tree 调用. 下载 已编译好的 tree.exe 文件到 git bash 安装目录下的 /usr/bin 目录,然后执行 tree 命令. 回顾总结 Git 本身并不支持 Windows 系统,Git For Windows 团队为了移植到 Windows 平台,付出了很多努力,最直观的感受就是提供了 Git For Windows 软件. 由于 Git for Windows 的出色工作得到 Git 的官方认可,现在默认下载的 Git 就是来源于 Git for Windows. 但 Git 底层是运行在类 linux 系统的,这种差异必然需要 Windows 到 Linux 的中间处理层进行转换. 因此,Git for Window 背后的技术中涉及到大量的 GNU 自由软件操作系统和 WSL (Windows Subsystem for Linux) 的相关代码. 正是由于背后千丝万缕的联系,为我们扩展 git bash 命令行提供了一些思路. 基于 Cygwin 系统进行扩展,安装完整的操作系统,模拟出 linux 运行环境,在 Windows 上也能找到 linux 的感觉. 当然,Cygwin 由于比较完整,体积也相当大,所以在此衍生出 MSYS2 和 MinGW 操作系统. 殊途同归,他们的底层架构有些不同,但目的是一致的,都是在 Windows 上找到 Linux 的感觉,并且他们的终端命令行几乎都是 mintty . 在终端中调用各自的包管理工具即可轻松扩展第三方命令,此外,由于他们大多数都有 c 编译环境,因此也可以选择重新编译安装. 所以 git bash 命令行看起来下像是 git 官方支持,其实却是由 Git for Windows 组织独立维护的开源项目. 默认 Git for Windows 没有提供包管理工具,开发版却默认集成了 MSYS2 环境.也就是说我们可以用 Pacman 来安装 tree 命令. 最后稍微总结本文知识要点: git bash 命令行并不能完全替代 cmd 命令,两者互补才能相得益彰. git bash 命令行中调用 cmd 程序需要通过 winpty 调用,比如 winpty tree.com git bash 默认安装程序无包管理工具等高级功能,如需扩展自定义命令应该下载Git for Windows SDK. git bash 环境兼容 MSYS2 ,MinGW ,Cygwin 等系列自由软件,其他类似系统的 tree.exe 可能并不支持. 好了,说了这么多,小伙伴们 get 到如何在 git bash 使用(扩展) tree 命令了吗? 如有疑问,欢迎留言告诉我! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-tree.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-extend-up.html":{"url":"git/tools/git-bash-extend-up.html","title":"git bash 扩展命令(上)","keywords":"","body":"git bash 扩展命令(上) 已投稿给脚本之家公众号,如需查看请访问: 三招教你轻松扩展 git bash 命令(上) https://mp.weixin.qq.com/s/29laLQ9k1YAPS_Rx3IAeQQ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-extend-up.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-extend-middle.html":{"url":"git/tools/git-bash-extend-middle.html","title":"git bash 扩展命令(中)","keywords":"","body":"git bash 扩展命令(中) 已投稿给脚本之家公众号,如需查看请访问: 三招教你轻松扩展 git bash 命令(中) https://mp.weixin.qq.com/s/qN4KbT8Lc1pQhnDdSeHcEQ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-extend-middle.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-bash-extend-down.html":{"url":"git/tools/git-bash-extend-down.html","title":"git bash 扩展命令(下)","keywords":"","body":"git bash 扩展命令(下) 已投稿给脚本之家公众号,如需查看请访问: 三招教你轻松扩展 git bash 命令(下) https://mp.weixin.qq.com/s/yAtGh4FiGXbzoCeKnhZ2ag var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-bash-extend-down.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/tools/git-submodule-quickstart.html":{"url":"git/tools/git-submodule-quickstart.html","title":"git submodule 父子模块","keywords":"","body":"git submodule 父子模块 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/tools/git-submodule-quickstart.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-19 11:39:10 "},"git/summary/about.html":{"url":"git/summary/about.html","title":"沙海拾贝","keywords":"","body":"沙海拾贝 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/summary/about.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/summary/common.html":{"url":"git/summary/common.html","title":"知识速查","keywords":"","body":"知识速查 创建版本库 初始化项目 git init 从零开始创建项目 示例 git init 克隆项目 git clone 将已有项目拷贝到本地 示例 git clone git@github.com:snowdreams1006/snowdreams1006.github.io.git 添加文件 git add 将新文件或已修改文件添加到缓存区 示例 git add README.md 查看状态 git status 查看当前文件是否和上次提交内容是否有修改 示例 git status README.md 比较差异 git diff 查看当前文件和上次提交内容的具体差异 尚未缓存的修改: git diff 查看已缓存修改: git diff --cached 查看已缓存与未缓存的所有修改: git diff HEAD 显示摘要而非整个差异: git diff --stat 示例 git diff README.md 提交文件 git commit 将缓存区内容添加到版本库 示例 git commit -m \"remark\" 取消已缓存内容 git reset HEAD 将缓存区内容添加到版本库 示例 git reset HEAD 删除文件 git rm 从暂存区中移除且不保留在工作目录: git rm 强制从暂存区中移除且不保留在工作目录: git rm -f 从暂存区中移除但保留工作目录: git rm --cached 示例 git rm README.md 移动文件 git mv 移动或重命名文件,目录,软连接 示例 git mv README.md README_NEW.md commit push pull fetch merge 的区别与含义: git commit : 将本地修改过的文件提交到本地仓库中 git push : 将本地仓库的最新版本推送到远程库中 git pull : 从远程库获取最新版本到本地,并自动merge git fetch : 从远程库获取最新版本到本地,不会自动merge git merge : 将指定版本合并到当前分支 替换本地改动 丢弃当前文件修改内容,已添加到暂存区以及新文件都不会受到影响 示例 git checkout -- 丢弃本地所有改动 示例 git reset --hard 分支管理 创建分支 git branch 创建本地分支,但不自动切换新分支 示例 git branch dev 切换分支 git checkout 切换到指定分支 示例 git checkout dev 创建并切换分支 git checkout -b 创建本地分支并自动切换到新分支 示例 git checkout -b feature 合并分支 git merge 将指定分支合并到当前分支 示例 git merge dev 删除分支 git branch -d 删除指定分支 示例 git branch -d dev 列出分支 git branch 列出本地全部分支 示例 git branch 提交日志 git log 查看纳入版本库的提交日志 示例 git log 标签管理 创建标签 git tag -a 创建标签并提交备注 示例 git tag -a v1.0.0 追加标签 git tag -a 追加标签并更新备注 示例 git tag -a v0.9.0 6ad8956bc09a6a62c731711eabe796690aa6471c 删除标签 git tag -d 删除指定标签 示例 git tag -d v1.0.0 查看标签 git show 查看指定标签 示例 git show v1.0.0 列出标签 git tag 列出本地全部标签 示例 git tag var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/summary/common.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"git/summary/cheatsheet-translation.html":{"url":"git/summary/cheatsheet-translation.html","title":"备忘录[译]","keywords":"","body":"备忘录[译] 创建 | Create 克隆一个已存在的仓库 | Clone an existing repository git clone git@github.com:snowdreams1006/snowdreams1006.github.io.git 创建一个新的本地仓库 | Create a new local repository git init 本地更改 | Local Changes 工作目录中已更改文件 | Changed files in your working directory git status 已追踪文件的更改 | Changes to tracked files git diff 添加当前全部更改到下次提交版本 | Add all current changes to next commit git add . 添加文件中某些更改到下次提交版本 | Add some changes in to next commit git add -p 提交已追踪文件的全部本地更改 | Commit all local changes in tracked files git commit -a 提交上一次暂存区更改 | Commit previously staged changes git commit 更改上次提交 | Change the last commit 没有更改已发布的提交 | Don't amend publishd commits! git commit --amend 提交历史 | Commit history 显示全部提交,以最新的开头 | Show all commits,starting with newest git log 显示某个文件一段时间内的更改 | Show changes over time for a specific file git log -p 某文件是谁在什么时候更改了什么内容 | Who changed what and when in git blame 分支和标签 | Branches & Tags 列出全部已存在的分支 | List all existing branches git branch -av 切换到 HEAD 分支 | Switch HEAD branch git checkout 基于当前 HEAD 创建新分支 | Create a new branch based on your curent HEAD git branch 基于远程分支创建新的正在追踪分支 | Create a new tracking branch based on a remote branch git checkout --track 删除一个本地分支 | Delete a local branch git branch -d 为当前提交打上标签 | Make the current commit with a tag git tag 更新和发布 | Update & Publish 列出当前全部已配置的远程仓库 | List all currently configured remotes git remote -v 显示远程仓库信息 | Show information about a remote git remote show 添加的远程仓库 | Add new remote repository named git remote add 下载来自远程仓库的所有更改但是不合并到 HEAD | Download all changes from but don't integrate into HEAD git fetch 下载来自远程仓库指定分支的所有更改并且自动合并到 HEAD | Download changes and directly merge/integrate into HEAD git pull 在远程仓库上发布本地更改 | Publish local changes on a remote git push 在远程仓库上删除分支 | Delete a branch on the branch git branch -dr 发布你的标签 | Publish your tags git push --tags 合并和变基 | MERGE & REBASE 合并指定分支到你的 HEAD | Merge into your current HEAD git merge 变基到当前HEAD | Rebase your current HEAD onto 不要变基已发布的提交 | Don't rebase published commits! git rebase 取消变基 | Abort a rebase git rebase --abort 使用已配置的冲突工具去解决冲突 | Use your configured merge tool to solve conflicts git mergetool 使用编辑器手工解决冲突然后(解决之后)标记文件已解决冲突 | Use your editor to manually solve conflicts and (after resolving) mark file as resolved git add git rm 撤销 | UNDO 丢弃工作区全部更改 | Discard all local changes in your working directory git reset --hard HEAD 丢弃指定文件的本地更改 | Discard local changes in a specific file git checkout HEAD 抵消一个提交(通过产生一个新的相反的提交) | Revert a commit (by producing a new commit with contrary changes) git revert 重置当前 HEAD 指针到上一个提交...然后丢弃自那以后的全部更改 | Reset your HEAD pointer to a previous commit ... and discard all changes since then git reset --hard ...然后作为未缓存更改保存全部更改 | ... and preserve all changes as unstaged change git reset ...然后保存未提交的本地更改 | ... and preserve all changes as unstaged change git reset --keep 建议 | SUGGESTION 提交相关更改 | COMMIT RELATED CHANGES 提交应该是相关更改的包装,例如,修复两个不同的 bug 应该产生两个单独的提交. 小的提交让其他开发者更容易理解此次更改,并且万一出错方便回滚. 在暂存区这类工具以及暂存部分文件的能力下,git 很容易创建细粒度的提交. A commit should be a wrapper for related changes, For example,fixing two different bugs should produce two separete commits. Small commits make it easier for other developers to understand the changes and roll them back if something went wrong. With tools like the staging area and the ability to stage only parts of a file. Git makes it easy to create very granular commits. 经常提交 | COMMIT OFTEN 经常提交使得你的提交很小并且有助于仅提交相关更改. 此外,这样允许你更频繁地和其他人分享你的代码,对于每个人来说更容器定期合并更改,避免了遭遇合并冲突. ,很少的大提交,很少分享它们.相反很难解决冲突. Commiting often keeps your commits small and again helps you commit only related changes. Moreover,it allows you to share your code more frequently with others. That way it's easier for everyone to integrate changes regularly and avoid having merge conflicts.Having few large commits and sharing them rarely.in contrast,makes it hard to solve conflicts. 不要提交未完成工作 | DON'T COMMIT HALF-DONE WORK 你应该仅提交已完成代码,这并不意外着提交前你不得不完成一个完整的,很大的功能分支.恰恰相反,将功能分支划分成很多逻辑块并且记得早一点,频繁些提交. 如果仅仅是为了下班前仓库该有点什么就不要提交,如果你尝试提交仅仅是因为你需要一个干净的工作副本(检出分支,拉取更改),考虑使用 git 的 stash 特性. You should only commit code when it's completed. This doesn't mean you have to complete a whole ,large feature before commiting. Quite the contrary:split the feature's implementatiion into logical chunks and remember to commit early and often. But don't commit just to have something in the repository before leaving the ofice at the end of the day. If you're tempted to commit just because you need a clean working copy (to check out a branch,pull in changes ,etc.) consider using Git's feature instead. 提交前测试代码 | TEST CODE BEFORE YOU COMMIT 抵制自以为已完成的提交. 直接测试来确保它真的已完成并且没有副作用(显而易见的). 当提交半成品到本地仓库时要求你不得不自我谅解,让你的代码进过测试对发布或者分享你的代码也很重要. Resist the temptation to commit something that you think is completed. Test it thoroughly to make sure it really is completed and has no side effect (as far as one can tell). While committing half-baked thing in your local repository only requires you to forgive yourself,having your code tested is even more important when it comes to publishing/sharing your code with others. 编写代码提交信息 | WRITE CODE COMMIT MESSAGE 对你的更改以简短总结进行描述(达到50字符作为准则). 以包括空白行作为分割下述内容. 提交信息体应该提供下述问题的详细答案: 此次更改的动机是什么? 和上一个实现有什么不同? 使用必要的现在时语态(更改,不是已更改,或者变更)和使用形如 git merge 命令生成的信息保持一致. Begin your message with short summary of your changes(up to 50 characters as a guideline). Separate it from the following body by including a blank line. The body of your message should provide detailed answers to the following questions: What was the motivation for the change? How does it differ from the previous implementation? Use the imperative ,present tense(change,not changed or changes) to be consistent with generated messages from commands like git merge. 版本控制不是一个备份系统 | VERSION CONTROL IS NOT A BACKUP SYSTEM 在远程服务器存有文件的备份是版本控制系统的一个很好副作用.但是你不应该将VCS 视为一个备份系统. 当做版本控制时,你应该注意语义化提交,而不是死记硬背文件. Having your files backed up on a remote server is a nice side effect of having a version control system. But you should not use your VCS like it was a backup system. When doing version control,you should pay attention to committing semantically(see related changes) - you shouldn't just cram in files. 利用分支 | USE BRANCHES 分支是 git 最强大的特性之一,这不是偶然. 从第一天开始快速而简单的分支就是一个核心需求. 分支是帮助你避免弄混不同开发线的完美工具. 在你的开发流程中应该广泛使用分支,像新功能,修复 bug,新想法... Branching is one of Git's most powerful features-and this is not by accident:quick and easy branching was a central requirement from day one. Branches are the perfect tool to help you avoid mixing up different lines of development. You should use branches extensively in your development workflows:for new features,bug fixes,ideas... 认同工作流 | AGREE ON A WORKFLOW Git 允许你从大量不同的工作流中选择一个:长期运行的分支,主题分支,合并或变,基工作流... 具体选择哪一个取决于一系列因素:你的项目,你的总体开发和部署工作流和(可能是最重要的)你和你的团队的个人偏好. 不论你选择哪一个去工作,你需要确保准守一个大家都认同的工作流. Git lets you pick from a lot of different workflows:long-running branches,topic branches,merge or rebase,git-flow... Which one you choose depends on a couple of factors:your project,your overall development and deployment workflows and (maybe most importantly ) on your and your teammate's personal preferences. However you choose to work,just make sure to agree on a common workflow that everyone follows. 帮助和文档 | HELP & DOCUMENTATION 命令行下获取 git 帮助 git help Git help on the command line git help 免费在线资源 | FREE ONELINE RESOURCES http://www.git-tower.com/learn http://rogerdudler.github.io/git-guide/ http://www.git-scm.org/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/git/summary/cheatsheet-translation.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"github/":{"url":"github/","title":"github 入门教程","keywords":"","body":"github 入门教程 github 是一个基于 git 的代码托管平台,是平时工作学习的好帮手,学会如何用好 github 网站能够帮助我们更好分享代码或者与其他开发人员合作. 注册 github 账号 首先准备好邮箱和密码,然后在 github 官网注册新账号,和大多数网站类似的注册流程,唯一注意的是你要想好注册类型,针对个人用户来说,一般无外乎个人账号和项目账号两种,比如 snowdreams1006 就认为是个人账号,而这种 security-plus 认为是项目账号. 其实这两种账号对于 github 来说是一样的,不像是个人账号同企业账号的差异那么大,那为什么称个人账号和项目账号呢? 是因为,大多数个人开发者名下会有多款开源作品,这些作品既可以全部挂载在某一个开发者账号下面,也可以单独挂载某一个开发者账号下面,如果此时的账号名恰好是项目名岂不是清晰多了? 因为个人刚开始可能并没多大名气,如果一个产品直接挂载在个人名下,那么这个产品很大程度上就依赖于个人名气了,所以不妨反过来,用产品说话,事实胜于雄辩,这种做法也是一种常用的宣传手段,很多个人开源产品正是这么做的! 除此之外项目账号还有一个好处,利用 github 的静态网站托管服务可以免费快速搭建项目官网,只要创建一个snowdreams1006.github.io 的项目,那么这个项目就可以作为静态网站的源码项目了,访问 https://snowdreams1006.github.io 就能看到项目官网了! 注意: snowdreams1006仅仅是笔者用户名,实际需要替换成读者的用户名 配置 github 既然项目已经托管到 github 网站,那本地如何访问到远程仓库呢?常用的方式有两种,一种是 https 方式,每次都需要输入密码,另外一种是 ssh 方式,只需要一次配置ssh 密钥对. 这里我们重点介绍最常用也是最方便的第二种 ssh 方式访问 github ,大致思路是本地生成密钥对,然后将公钥上传给 github 表明身份,之后本地再次推送给远程仓库时,github 自然就能识别到我们身份了. 第一步: 生成密钥对 默认情况下,会在当前用户目录下生成一对密钥对. ssh-keygen -t rsa -C \"youremail@example.com\" 这里的邮箱 youremail@example.com 需要填写自己的 github 邮箱,之后会提示输入路径和密码,一路回车采用默认值即可,运行结束后会在当前用户目录下 生成一对密钥对,包括公钥和私钥.其中公钥可以发送给任何人,而私钥千万不可泄露. 第二步: 复制公钥 在当前用户根目录下打开 .ssh 目录,其中包括两个文件,一个是公钥 id_rsa.pub ,另一个是私钥 id_rsa,用记事本或者其他方式打开公钥文件,复制其中内容,准备粘贴到github 相关设置项. # 查看当前用户下的 ssh 目录 ls ~/.ssh # 查看生成的公钥内容 cat ~/.ssh/id_rsa.pub 第三步: 设置 github 回到 github,点击头像(Acount),选择设置(Settings),再选择左侧的 SSH and GPG keys,点击右侧的NEW SSH Key,然后填写标题(Title),最好是有意义的名称,比如youremail@example.com for github,密钥(Key)填写上一边生成的公钥,一般是以ssh-rsa 开头的一大串字符,最后保存(Add SSH Key). 第四步: 验证 ssh 利用 ssh 协议测试一下是否能够正常访问 github 网站,如果出现成功提示,那就证明我们的配置没问题. ssh -T git@github.com 创建远程仓库 登录 github 网站新建远程仓库(New Repository),例如git-demo,默认权限是公开的(public),也可以选择私有的(private),初始化 README.md 文件和 .gitignore 文件以及选择开源协议这些都是可选的,视具体情况而定. 刷新当前页面,应该能到看到已创建好的git-demo 项目,接下来准备将其克隆到本地电脑. 克隆到本地仓库 将远程项目克隆到本地工作空间,和之前本地仓库的开发流程一样,例如add commit status 等等,唯一不同的是,多了一步 push 命令,即本地仓库的最新版本需要推送给远程仓库中,只有这样其他小伙伴才能从远程仓库拉取最新版本,进而才能看到你的代码,因而打破各自为政局面,实现团队协同开发. # 克隆到本地仓库 git clone git@github.com:snowdreams1006/git-demo.git # 切换到当前项目 cd git-demo # 创建新文件 touch test.txt echo \"add test.txt\" > test.txt # 添加文件到暂存区 git add test.txt # 提交文件到本地仓库 git commit -m \"add test.txt\" # 推送到远程仓库 git push origin master 提交完成后,登录 github 网站,刷新当前项目 git-demo ,应该能看到我们刚刚提交的新文件test.txt. 添加仓库关联 添加本地仓库和远程仓库之间关联,默认本地仓库分支名和远程仓库分支名相同 git remote add origin2 git@github.com:snowdreams1006/git-demo.git 查看远程仓库 查看当前配置有哪些远程仓库 git remote 执行时加上-v 参数能够查看别名关联的具体地址,即 git remote -v 下载远程仓库 从远程仓库下载最新分支数据 git fetch 注意: 该命令并不会自动合并当前分支,如需要合并,需手动执行git merge 命令 拉取远程仓库 从远程仓库拉取最新分支数据,自动尝试合并到当前分支,如有冲突,需先解决冲突再合并到当前分支. git pull git pull 相当于 git fetch + git merge 推送远程分支 将本地最新版本推送到远程仓库 git push origin master 以上命令将本地 master 分支推送到 origin 远程仓库的 master 分支 删除远程仓库 git remote rm origin var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/github/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"github/speedup.html":{"url":"github/speedup.html","title":"github 访问速度太慢","keywords":"","body":"github 访问速度太慢 github 是全世界最流行的开源项目托管平台,其代表的开源文化从根本上改变了软件开发的方式. 基本上所有的需求都能从 github 上或多或少找到现成的实现方案,再也不用重头开始造轮子而是自定义轮子! 然而,有时候国内访问 https://github.com/ 速度太慢,如何加速访问 github.com 网站就成了刚需. 由于 github.com 网站位于美国旧金山,所以初始访问 github.com 时网络寻址会比较耗费时间,这也是网站打开速度慢的其中一个原因. 国外在线检测网站: https://www.ipaddress.com/,无法访问的话,请另辟蹊径. 最初用户从浏览器中输入 github.com 网址时,浏览器并不知道这个域名对应的真实 ip 地址,先问问自己电脑认识不认识这个域名的门牌号,如果本机不认识会接着往上问,当地运行商也不认识这个域名的话,继续问上级,直到问出来 github.com 的门牌号是 192.30.253.113 为止! 如此繁琐的问路过程被称之为 DNS 寻址,如果问路的时间都占用很久,那么访问网站的速度自然会很慢. 所以,如果我们直接告诉浏览器目的地,那么浏览器也就不会一步一步去费劲问路了,这在一定程度上也就优化了访问网站的速度. $ ping github.com -c 3 PING github.com (192.30.253.113): 56 data bytes 64 bytes from 192.30.253.113: icmp_seq=0 ttl=41 time=405.924 ms 64 bytes from 192.30.253.113: icmp_seq=1 ttl=41 time=346.654 ms 64 bytes from 192.30.253.113: icmp_seq=2 ttl=41 time=345.485 ms --- github.com ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 345.485/366.021/405.924/28.220 ms ping github.com -c 3 查看 github.com 网站的门牌号 正常来说,网站的主域名下会存在多个子域名,由这些域名组合在一起提供完整的服务. 而 github.com 也不例外,其中 github.com是一级域名,也是主域名,其他的域名基本上都是二级域名或者说次域名. 所以我们不仅要告诉本机 github.com 的主域名,还要把相关的子域名也告诉本机,帮人帮到底,送福送到西! 那到哪里去查询域名和 ip 的对应关系呢? 想一想现实生活中,每个人都有自己的家,而这个家有具体的地址,也就是平时说的门牌号. 当然,有些人名下不只有一个家,有钱人的世界可以有很多家,毕竟狡兔还有三窟呢! 在这个家中既可以是单身窝,也可以是情侣房,或者是家庭房,具体容纳几个人是由房屋大小决定的. 对应到计算机世界中,如果域名是用户,那么 ip 地址就是用户的家. 同一个域名可以对应多个 ip 地址,同一个 ip 地址也可以有多个域名. 如果有人想要拜访您,肯定要有具体的地址才能到你家里做客,从你家到你家的地址这个过程可能是你告诉他的,也可能是他自己找别人打听到的消息. 域名到 ip 地址的过程同样也需要找人询问,这个信息一般会存在 dns 服务商那里,就像我们的地址登记到相关政府机构一样. 虽然相关机构的信息比较权威及时,但门槛有点高,所以不访问一下当地的\"消息通\". 互联网上的\"消息通\"更是数不胜数,这里推荐两个查询域名解析的网站. https://www.ipaddress.com/ http://tool.chinaz.com/dns/ 子域名有哪些 下面以 ipaddress.com 网站为例,查询下 github.com 网站的相关信息. 在输入框中输入 github.com 域名后开始解析该域名的相关信息,不仅找到了域名对应的 ip 地址还查询到相关网站的域名信息. 亲自去体验一下: 域名查询 根据查到的相关域名信息,再次查询出这些域名对应的 ip 地址,于是整理出以下内容. # github related website 192.30.253.113 github.com 151.101.185.194 github.global.ssl.fastly.net 192.30.253.118 gist.github.com 192.30.253.120 codeload.github.com 185.199.108.153 desktop.github.com 185.199.108.153 guides.github.com 185.199.108.153 blog.github.com 18.204.240.114 status.github.com 185.199.108.153 developer.github.com 185.199.108.153 services.github.com 192.30.253.175 enterprise.github.com 34.195.49.195 education.github.com 185.199.108.153 pages.github.com 34.196.237.103 classroom.github.com 就近 cdn 加速 大型网站服务器都不会是只有一台服务器,而是多台服务器组成的集群一起对外提供服务. 全世界都在使用 github ,如果每一次访问网站时走的都是美国服务器,即使浏览器知道目的地,但是距离太多遥远还是会很慢. 因此,如果能够就近访问 github 网站就能大幅提高访问速度了,幸运的是,网络上同样有现成的工具来帮助我们查看就近的网站地址. 亲自去体验一下: DNS查询 从上图中我们可以看出,同一个域名有很多不同的 ip 地址,从中选择 TTL 值最小的作为优化标准. 于是,将上述清单继续优化成以下内容: # github related website 192.30.253.113 github.com 151.101.185.194 github.global.ssl.fastly.net 203.98.7.65 gist.github.com 13.229.189.0 codeload.github.com 185.199.109.153 desktop.github.com 185.199.108.153 guides.github.com 185.199.108.153 blog.github.com 18.204.240.114 status.github.com 185.199.108.153 developer.github.com 185.199.108.153 services.github.com 192.30.253.175 enterprise.github.com 34.195.49.195 education.github.com 185.199.108.153 pages.github.com 34.196.237.103 classroom.github.com 最好亲自测试一下就近站点以求获得最佳体验,不过推测应该差异不是很大,所以直接复制也无妨. 告诉本机新地址 现在我们已经弄清楚域名和 ip 的映射关系,接下来要做的事情就是告诉本机,不同的操作系统具体文件存放的地址可能有些不同,下面我们以 Windows 和 Mac 举例说明. # github related website 192.30.253.113 github.com 151.101.185.194 github.global.ssl.fastly.net 203.98.7.65 gist.github.com 13.229.189.0 codeload.github.com 185.199.109.153 desktop.github.com 185.199.108.153 guides.github.com 185.199.108.153 blog.github.com 18.204.240.114 status.github.com 185.199.108.153 developer.github.com 185.199.108.153 services.github.com 192.30.253.175 enterprise.github.com 34.195.49.195 education.github.com 185.199.108.153 pages.github.com 34.196.237.103 classroom.github.com windows 映射文件存放于: C:\\Windows\\System32\\drivers\\etc\\hosts 打开 hosts 文件,将上述映射关系追加到文件末尾,保存并退出. 如果由于权限不足,无法保存,可以复制到桌面再编辑文件,最后移动并替换到 hosts 文件. 运行 ipconfig /flushdns 刷新 dns 缓存. mac 映射文件存放于: /etc/hosts 编辑 hosts 文件并追加上述映射关系. $ cat /etc/hosts # jetbrains 0.0.0.0 account.jetbrains.com 0.0.0.0 www.jetbrains.com # github related website 192.30.253.113 github.com 151.101.185.194 github.global.ssl.fastly.net 203.98.7.65 gist.github.com 13.229.189.0 codeload.github.com 185.199.109.153 desktop.github.com 185.199.108.153 guides.github.com 185.199.108.153 blog.github.com 18.204.240.114 status.github.com 185.199.108.153 developer.github.com 185.199.108.153 services.github.com 192.30.253.175 enterprise.github.com 34.195.49.195 education.github.com 185.199.108.153 pages.github.com 34.196.237.103 classroom.github.com 运行 sudo dscacheutil -flushcache 刷新 dns 缓存. $ ping github.com -c 3 PING github.com (192.30.253.113): 56 data bytes 64 bytes from 192.30.253.113: icmp_seq=0 ttl=41 time=395.808 ms 64 bytes from 192.30.253.113: icmp_seq=1 ttl=41 time=306.919 ms 64 bytes from 192.30.253.113: icmp_seq=2 ttl=41 time=298.188 ms --- github.com ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 298.188/333.638/395.808/44.105 ms $ 又到总结时间 本文讲述了如何解决 github.com 网站访问速度慢的问题,通过修改本机的 hosts 文件来绕过 dns 解析,这种方法仅仅适用于能够访问网站只不过是访问速度慢这一现象. 如果本身无法访问国外网站,那么这种方法就不适用,可能需要另辟蹊径! 最后再贴一下 hosts 文件内容: # github related website 192.30.253.113 github.com 151.101.185.194 github.global.ssl.fastly.net 203.98.7.65 gist.github.com 13.229.189.0 codeload.github.com 185.199.109.153 desktop.github.com 185.199.108.153 guides.github.com 185.199.108.153 blog.github.com 18.204.240.114 status.github.com 185.199.108.153 developer.github.com 185.199.108.153 services.github.com 192.30.253.175 enterprise.github.com 34.195.49.195 education.github.com 185.199.108.153 pages.github.com 34.196.237.103 classroom.github.com var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/github/speedup.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"github/badge.html":{"url":"github/badge.html","title":"github 徽章从何而来","keywords":"","body":"github 徽章从何而来 前言 平时大家在在逛 github 时或多或少都看到过项目首页各式各样的小徽章,不知道你是否和我一样好奇这些小徽章都是哪来的呢? 首先我们先来一睹为快目前前端开发的三大主流框架: var ,看一看他们的 github 项目首页有哪些小徽章吧! Vue : https://github.com/vuejs/vue Angular : https://github.com/angular/angular React : https://github.com/facebook/react 小结: 前端三大框架的徽章均不相同,由此可见,这应该不是 github 统一分发而是自定义行为! 虽然不是统一分配的,但也不是毫无规律可寻,想要制作专属的小徽章,其实真的很简单! 什么是徽章 徽章是一种小巧精美的小图标,一般配有相关文字进行辅助说明,富有表现力. 不仅出现于 github 项目主页,凡是能够表现图片的地方都可以出现徽章,本质上是一种 svg 格式的矢量图标. 下面以自定义 github-snowdreams1006-brightgreen.svg 徽章为例,简单认识一下徽章. 在线链接 在线链接: github-snowdreams1006-brightgreen.svg https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg 浏览器效果 打开在线链接,并检查当前网页,豁然开朗,徽章是一种 svg 实现的矢量图标. svg VS png 如果说 svg 是矢量图形而 png 却不是,所以不妨将 png 姑且称之为标量图形. svg 是矢量图形,png 是标量图形,两者均能实现类似效果,只不过矢量图形不论怎么方法都能保持原样,并不会像 png 那样会失真而已. 既然两种均能表现相同的效果,现在我们就来演示一下 png 的实现效果. svg 转 png 在线网站: https://cloudconvert.com/svg-to-svg 左侧的 svg 无论放大多少倍,依然保持原样,清晰度保持不变.右侧的 png 一旦放大,立马变得模糊不清. 如何使用徽章 大多数徽章都是 svg 格式,当然也不排除某些徽章是 png 格式,不论怎么说,一律当成图标使用就可以了. 如果你和我一样,希望在 markdown 文件中使用徽章,那么建议使用在线链接,或者引入本地 svg 相关文件. 徽章格式 : [![图片文字说明](图片源地址)](超链接地址) 即超链接内部嵌套图片 [![github](https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg)](https://github.com/snowdreams1006) 如果你是在 html 文件使用徽章,同样先取得在线徽章地址,然后按照 html 语法插入图片即可. 徽章格式 : 即超链接内部嵌套图片 不论是什么语法,最核心最根本的获得到徽章链接,至于不同语言有着各自的语法,按照语言规则手动拼接就好. Badge URL https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg Markdown [![github](https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg)](https://github.com/snowdreams1006) HTML Textile !https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg!:https://github.com/snowdreams1006 RDOC {}[https://github.com/snowdreams1006] AsciiDoc image:https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg[\"github\", link=\"https://github.com/snowdreams1006\"] RST .. image:: https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg :target: https://github.com/snowdreams1006 徽章分类 如果以徽章的格式为标准,那么可以分为svg 和 png 两类. svg https://badge.fury.io/js/gitbook-plugin-mygitalk.svg png https://badge.fury.io/js/gitbook-plugin-mygitalk.png 如果以徽章的样式为标准,那么可以分为默认样式和自定义样式两类. 默认样式 https://img.shields.io/github/stars/snowdreams1006/snowdreams1006.github.io.svg?style=social 自定义样式 https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg 如果以徽章的内容数据是否动态为标准,那么可以分为静态数据和动态数据两类. 静态数据意味着数据本身是不变的,只要在线链接不变,那么生成的徽章永远不会改变,而动态数据意味着生成徽章的数据是动态变化的,即使在线链接不变,当数据本身发现变化时,徽章自然随之更新. 静态数据 https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg 动态数据 https://badge.fury.io/js/gitbook-plugin-mygitalk.svg 静态数据示例中 github-snowdreams1006-brightgreen.svg 数据不会更改,自然生成的徽章也不会变.动态数据示例中 gitbook-plugin-mygitalk.svg 是 npm 的版本号,当项目升级后,版本号会发生更改,那么生成的徽章也会随之更新. 如果以徽章的内容数据来源为标准,那么可以有无数多的分类. GitHub https://badgen.net/github/stars/snowdreams1006/gitbook-plugin-mygitalk Npm https://badgen.net/npm/dt/gitbook-plugin-mygitalk Docker https://badgen.net/docker/stars/library/centos 如果以徽章的内容数据用途为标准,那么也可以有无数多的分类. 构建状态 https://img.shields.io/travis/GitbookIO/gitbook.svg 代码覆盖率 https://img.shields.io/codecov/c/github/vuejs/vue.svg 代码分析 https://img.shields.io/github/languages/top/snowdreams1006/snowdreams1006.github.io.svg 徽章来源 徽章有不同的分类,不管是哪种分类,在线徽章最为简单便捷,下面就简单介绍下提供在线生成徽章的网站. https://shields.io/ https://badgen.net/ https://forthebadge.com/ https://badge.fury.io/ https://github.com/boennemann/badges https://shields.io/ 适用于绝大多数情况,默认按照徽章内容分类,Build,Code Coverage,Analysis 等多主题,同时支持自定义徽章和动态徽章. 如果徽章的主题明确,那么根据网站提供的主题对号入座即可在线生成徽章,下面以 gitbook-plugin-mygitalk 为例,简要说明如何获得相应徽章链接. gitbook-plugin-mygitalk 是 gitbook 的一款评论插件. 打开网站后按照分类,选择其中一个主题,点击进去后填写目标信息,即可在线生成徽章. 浏览已支持的主体,选择 License 许可证主题. 浏览已支持的 License 许可证列表,选择 NPM 许可证. 填写好正确的 npm 包信息并实时预览,然后点击按钮复制徽章链接或者或者特定格式的徽章. ![NPM](https://img.shields.io/npm/l/gitbook-plugin-mygitalk.svg) 按照主题生成徽章真的很简单,首先对号入座,然后按需生成相应徽章即可,唯一的要求就是对号入座! 如果默认提供的徽章主题没有适合自己的徽章,或者想要自定义徽章效果,那么也可以在线制作私人订制徽章. 打开网站后往下拉,找到 Your Badge 区域,准备制作专属徽章. 填写(Label)标签-(Message)信息-(Color)颜色等信息后,点击(Make Badge)生成徽章. 点击生成徽章后默认会在当前标签页面打开该链接,手动复制链接并调整成目标格式即可. ![微信公众号-雪之梦技术驿站-brightgreen.svg](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg) https://badgen.net/ 徽章内容来源种类较多,默认按照平台分类,按照特定规则生成徽章,需要手动拼接在线链接,略显繁琐. https://badgen.net/badge/:subject/:status/:color?icon=github ──┬── ───┬─── ──┬─── ──┬── ────┬────── │ │ │ │ └─ Extra Options (label, list, icon, color) │ │ │ │ │ TEXT TEXT RGB / COLOR_NAME ( optional ) │ \"badge\" - default (static) badge generator 虽然支持颜色,图标以及查询参数等高级用法,但是还是习惯性采用默认设置,下面动手开始制作徽章吧! 切换到默认动态徽章选项卡,选择 GitHUb 徽章. 选择 stars 徽章,将 micromatch 替换成目标信息. /github/stars/micromatch/micromatch 替换成 /stars/snowdreams1006/snowdreams1006.github.io 预览徽章效果并手动修改成目标格式. ![snowdreams1006.github.io](https://badgen.net/github/stars/snowdreams1006/snowdreams1006.github.io) 除了支持动态徽章,同样也支持静态徽章,切换到 STATIC BADGES 选项卡,一起来生成静态徽章吧! ![★★★★☆](https://badgen.net/badge/stars/%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%86) 按照徽章的在线链接规则,应该也支持自定义徽章,再次回顾一下链接规则: 规则 : https://badgen.net/badge/:subject/:status/:color ,如果是自定义动态链接,估计不支持吧! ![微信公众号-雪之梦技术驿站](https://badgen.net/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99) https://forthebadge.com/ 扁平化的徽章,支持的徽章数量有限,不支持自定义徽章. 网站首页默认提供了一些预览徽章,左侧是复制 image 链接,右侧是复制 markdown 链接. [![forthebadge](https://forthebadge.com/images/badges/fuck-it-ship-it.svg)](https://forthebadge.com) 网站首页默认展示的徽章毕竟有限,如果找不到理想徽章,岂不是白介绍了这个网站,当然不能够! VIEW ALL 查看目前支持的全部徽章,如果还是找不到徽章,那就真的没有. https://badge.fury.io/ 版本徽章,支持各类平台版本,包括 npm ,Ruby,Python,Go 等平台. 选择目标平台并输入包管理信息,即可在线生成各个类型的徽章版本. [![npm version](https://badge.fury.io/js/gitbook-plugin-mygitalk.svg)](https://badge.fury.io/js/gitbook-plugin-mygitalk) 排版布局 默认 markdown 实现的图片是依次排开的,无法自定义样式,而 markdown 语法同时也兼容 html 语法,因此我们可以用 html 语法实现居中对齐. 抛砖引玉 社交化徽章 ![GitHub followers](https://img.shields.io/github/followers/snowdreams1006.svg?style=social) ![GitHub forks](https://img.shields.io/github/forks/snowdreams1006/snowdreams1006.github.io.svg?style=social) ![GitHub stars](https://img.shields.io/github/stars/snowdreams1006/snowdreams1006.github.io.svg?style=social) ![GitHub watchers](https://img.shields.io/github/watchers/snowdreams1006/snowdreams1006.github.io.svg?style=social) 自定义徽章 [![github](https://img.shields.io/badge/github-snowdreams1006-brightgreen.svg)](https://github.com/snowdreams1006) [![wechat](https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](http://weixin.qq.com/r/cy5CWvvE5Kabrb8593th) [![慕课网](https://img.shields.io/badge/%E6%85%95%E8%AF%BE%E7%BD%91-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://www.imooc.com/u/5224488/articles) [![简书](https://img.shields.io/badge/%E7%AE%80%E4%B9%A6-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://www.jianshu.com/u/577b0d76ab87) [![csdn](https://img.shields.io/badge/csdn-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://blog.csdn.net/weixin_38171180) [![博客园](https://img.shields.io/badge/%E5%8D%9A%E5%AE%A2%E5%9B%AD-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://www.cnblogs.com/snowdreams1006/) [![掘金](https://img.shields.io/badge/%E6%8E%98%E9%87%91-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://juejin.im/user/582d5cb667f356006331e586) [![思否](https://img.shields.io/badge/%E6%80%9D%E5%90%A6-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://segmentfault.com/u/snowdreams1006) [![开源中国](https://img.shields.io/badge/%E5%BC%80%E6%BA%90%E4%B8%AD%E5%9B%BD-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://my.oschina.net/snowdreams1006) [![腾讯云社区](https://img.shields.io/badge/%E8%85%BE%E8%AE%AF%E4%BA%91%E7%A4%BE%E5%8C%BA-%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99-brightgreen.svg)](https://cloud.tencent.com/developer/user/2952369/activities) 进度条徽章 [![progress](http://progressed.io/bar/25?title=progress)](https://github.com/fehmicansaglam/progressed.io) [![progress](http://progressed.io/bar/50?title=progress)](https://github.com/fehmicansaglam/progressed.io) [![completed](http://progressed.io/bar/75?title=completed)](https://github.com/fehmicansaglam/progressed.io) [![done](http://progressed.io/bar/100?title=done)](https://github.com/fehmicansaglam/progressed.io) 参考文档 GitHub 项目徽章的添加和设置 玩转 Github 徽章 为你的Github README生成漂亮的徽章和进度条 给python项目在github贴上build和pypi小徽章 https://github.com/igrigorik/ga-beacon https://github.com/boennemann/badges https://ellerbrock.github.io/open-source-badges/ http://githubbadges.com/ 在线网站 https://shields.io/ https://badgen.net/ https://forthebadge.com/ https://badge.fury.io/ https://ellerbrock.github.io/open-source-badges/ http://githubbadges.com/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/github/badge.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/":{"url":"myGitbook/","title":"gitbook 入门教程","keywords":"","body":"gitbook 入门教程 gitBook 是一个基于node.js的命令行工具,使用 github/git 和 markdown/asciiDoc 构建精美的电子书. gitbook 支持输出静态网页和电子书等多种格式,其中默认输出静态网页格式. gitbook 不仅支持本地构建电子书,而且可以托管在 gitbook 官网上,并享受在线发布和托管图书的便利,完整的文档请参考 gitbook 新版文档(需FQ) 或 gitbook 旧版文档(不需FQ) 目前 gitbook 旧版文档已经不可访问,特提供压箱底存货邀君共享 适用场景 不仅适用于软件说明文档的发布更新,同样适用于文本文档的连载更新. 既适合具有一定编程经验的软件开发从业者,也适用于不满足传统书写方式的文学创作者. 简而言之,gitbook 可以条理清晰地整理出零碎知识,打造专属你自己的电子书,漂亮的主题,丰富的插件让你的知识变得从此与众不同! git + markdown = gitbook,其中 git 可以管理书籍内容的变更,并将其托管到云端实现团队协作,而 markdown 简洁的语法特点,使得我们不必关心布局排版问题,专注创作,重拾写作乐趣! 如果你还不了解 git 和 markdown 相关知识,赶紧去学习 markdown 快速入门 和 git 入门教程 吧! 先睹为快 gitbook 教程 gitbook 官网 gitbook 文档 参考文档 gitbook 官网(新) gitbook 官网(旧) gitbook 文档(新) gitbook 文档(旧) gitbook 文档(存货) git 官网 github 官网 gitbook 新版需要FQ,旧版不需要FQ,旧版暂不可用,可访问压箱底存货. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/preparation/prepare.html":{"url":"myGitbook/preparation/prepare.html","title":"准备阶段","keywords":"","body":"准备阶段 主要包括两部分: 前置知识和操作工具. 前置知识主要是涉及到 git 以及 markdown 的相关知识,其中 git 是分布式版本控制系统,方便管理我们的电子书,备份到云端,方便团队共享合作,而 markdown 则是一种简单标记语言,简单情况下可以替代 word 进行排版布局,能完全替换 txt 文本,最终实现媲美 html 的输出效果,简洁高效的书写体验,深受广大软件开发者的喜爱. 正是由于 git + markdown 的搭配组合,使得上手 gitbook 相当简单,带给一种全新的体验. 操作工具主要是环境准备,主要是指 gitbook 环境,因为涉及到 git ,所以也包括 git 环境,至于markdown 语法支持,不一定非要安装相应软件,按照规定的格式书写文档即可,不过新手可能更愿意使用可视化软件,毕竟所见即所得,写着放心,看着舒心! 知识准备 markdown 快速入门 git 入门教程 上述教程中有详细的安装以及使用方法,最好能够完全掌握,如果时间有限,不愿意细读的话,请看下一节,我会挑选出常用命令进行讲解! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/preparation/prepare.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/preparation/front-knowledge.html":{"url":"myGitbook/preparation/front-knowledge.html","title":"前置知识","keywords":"","body":"前置知识 markdown 基本知识 markdown 是一种简化的 html 语法,相比于 txt 无格式文本更强大. 你可以用专门的软件去编辑 markdown 文件,就像需要使用软件编辑 txt 文件一样,当然也可以什么软件也不用,甚至直接在记事本或命令行书写,只不过这样的缺点就是无法实时预览输出效果,安全依赖个人经验和想象力了. markdown 文件后缀名是.md,安装了相应插件的浏览器或专门软件能够看到输出效果. 标题 语法格式: # + 空格 + 文本 大多数markdown编辑器支持 h1~h6 级标题,而富文本编辑器一般仅支持到二级标题. 示例: # 标题1 ## 标题2 效果: 标题1 标题2 列表 列表包括有序列表,无序列表和任务列表,并支持列表嵌套. 大多数 markdown 编辑器和富文本编辑器均支持有序列表和无序列表,而任务列表和列表嵌套支持度就不是很好,存在平台兼容性问题. 有序列表 语法格式:数字 + . + 空格 + 文本 示例: 1. 有序列表1 2. 有序列表2 3. 有序列表3 效果: 有序列表1 有序列表2 有序列表3 无序列表 语法格式:'- 或 * 或 +' + 空格 + 文本 示例: - 无序列表1 * 无序列表2 + 无序列表3 效果: 无序列表1 无序列表2 无序列表3 链接和图片 markdown 编辑器和富文本编辑器均支持链接和图片,值得注意的是有些平台限制或禁止外链. 链接 语法格式:[显示文本] + (链接地址) 示例: [https://snowdreams1006.github.io](https://snowdreams1006.github.io/) 效果: https://snowdreams1006.github.io 图片 语法格式:! + [图片标题] + (图片地址) 示例: ![雪之梦技术驿站的头像](https://upload.jianshu.io/users/upload_avatars/16648241/57aebe62-b5b5-491a-a9fd-f994d5be7dda.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240) 效果: 代码 代码分为单行代码和多行代码,其中多行代码也叫做代码块. 大多数 markdown 编辑器均支持代码,富文本编辑器支持度不一样,有的支持单行代码有的支持代码块. 单行代码 语法格式:` + 单行代码 + ` 示例: `code` 效果: code 多行代码 语法格式:``` + 多行代码 + ``` 示例: ``` function fun(){ echo \"这是一句非常牛逼的代码\"; } fun(); ``` 效果: function fun(){ echo \"这是一句非常牛逼的代码\"; } fun(); 这里的富文本支持语法指的是 markdown 渲染后的内容能否正常显示,并不是指 markdown语法本身能够正常渲染,更多详情请参考 markdown 快速入门 git 基本知识 git 是全世界最先进的分布式版本控制系统,帮助项目更好地进行管理,支持版本历史管理和多人写作管理等功能. 简单地说,可以理解为一种优雅的文档备份方式,支持云端备份,多人协作等特点. 初始化项目 语法格式: git init 适合从零开始的本地项目,初始化后的项目才是能够被 git 管理的项目. 示例: git init 克隆项目 语法格式: git clone 适合已有远程项目需要下载到本地,作用是将远程项目克隆到本地,和 git init 实现类似的功能. 示例: git clone git@github.com:username/username.github.io.git 添加文件 语法格式: git add 将文件添加到暂存区,支持多次添加文件,相当于写入缓存区. 示例: git add . 提交文件 语法格式: git commit 将暂存区内容提交到版本库,完成一次历史版本. 示例: git commit -m \"写入提交备注,简短说明下提交意图和目标\" 推送文件 语法格式: git push 将本地版本库推送到远程版本库,相当于本地文件备份到云端服务器. 示例: git push origin master 拉取文件 语法格式: git pull 将远程版本库拉取到本地版本库,相当于云端服务器文件恢复到本地. 示例: git pull 查看状态 语法格式: git status 查看当前文件状态,包括文件被新增,被修改,被删除,未提交等等. 示例: git status 比较差异 语法格式: git diff 查看两个文件之间的具体差异 示例: git diff 历史日志 语法格式: git log 查看版本库的提交历史日志 示例: git log 上述仅介绍了 git 的简单命令,实际使用情况远不止这些,更多详情请参考 git 入门教程 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/preparation/front-knowledge.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/preparation/environmental-requirements.html":{"url":"myGitbook/preparation/environmental-requirements.html","title":"环境要求","keywords":"","body":"环境要求 gitbook 是基于 node.js 的命令行工具,首先需要安装并配置好 node.js 环境,然后才能安装gitbook 相关工具. 由于安装工具全部都是国外网站,因此速度可能会很慢,也可能需要FQ,请耐心等待或者学会科学上网. 当然如果安装过程中遇到任何问题,也可以找我要一下安装包或者我帮你免费解决下. 环境预检查 检查 git 环境[可选] git 是免费开源的分布式版本控制系统,主要用于电子书的更新管理和团队协作,如果不需要将电子书托管到github 网站上,则可以不安装 git . 如果打印出 git 版本信息,则表示本机已安装 git 环境,跳过此步骤. $ git --version git 安装配置教程请参考初识 git 检查 node.js 环境[必须] node.js 是 js 在服务端运行的环境基础,从而使得 js 从浏览器端延伸到服务端领域,而 gitbook 则是运行在 node.js 基础之上的命令行工具,因此必须先安装好 node.js 开发环境. 如果打印出 node.js 版本信息,则表示本机已安装 node.js 环境,跳过此步骤. $ node --version nodejs 默认的包安装工具 npm 国内访问速度有点慢,安装完毕后建议 npm install cnpm -g --registry=https://registry.npm.taobao.org 使用淘宝镜像源代替默认的 npm ,详细教程请参考官方 https://nodejs.org/ 检查 gitbook 环境[必须] gitbook-cli 是 gitbook 的脚手架工具,帮助我们更方便构建 gitbook 应用,当然也可以直接安装 gitbook ,只不过那样的话,略显麻烦,不推荐. 如果打印出 gitbook 和 cli 版本信息,则表示本机已安装 gitbook 环境,跳过此步骤. $ gitbook --version 否则的话,本机可能并没有安装 gitbook 环境,则需要安装 gitbook 相关工具. 因为 gitbook 是基于 node.js 环境,而安装好 node.js 后默认提供了 npm 包管理工具,而我们则是通过 npm 来安装其他工具. 安装 gitbook-cli 工具[必须] 假设你已经搭建好 node.js 环境,现在我们开始安装 gitbook 相关工具了! $ sudo npm install -g gitbook-cli 如果使用 cnpm 安装的话,使用 cnpm install -g gitbook-cli 命令. 安装成功后会带有 gitbook 命令,现在再次运行下 gitbook --version 查看版本信息. # 打印出 `CLI` 和 `GitBook` 版本信息即可,安装版本可能已经大于 `2.3.2` $ gitbook --version CLI version: 2.3.2 GitBook version: 3.2.3 $ 安装 GitBook Editor 编辑器[可选] gitbook 官方客户端编辑器,支持 windows, mac 和 linux ,主要用于可视化编辑文档,组织文档结构. 下载相应平台的 GitBook Editor,正常安装即可. gitbook 的使用方法大致可以有三种,而 GitBook Editor 编辑器只是其中一种,所以这一步是可选的. 使用 gitbook-cli 脚手架提供的各种命令直接在命令行管理 gitbook,适合一定编程经验的软件从业人员. 使用 GitBook Editor 编辑器管理 gitbook ,适合无任何编程的文学创作者. 使用 gitbook.com 官网在线管理 gitbook ,适合不具备本地开发环境的萌新体验者. 小结 gitbook 基于 node.js 开发环境,因此首先要安装好 nodejs 环境,其次再使用 node.js 提供的 npm 包管理工具来安装 gitbook. 只需运行 sudo npm install -g gitbook-cli 即可安装,接着运行 gitbook -V 查看安装版本信息确认已经安装成功. 至此 gitbook 的必要开发环境已经准备妥当,接下来让我们赶紧体验一下 gitbook 的魅力吧! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/preparation/environmental-requirements.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/experience/preview.html":{"url":"myGitbook/experience/preview.html","title":"快速体验","keywords":"","body":"快速体验 本文主要介绍三种使用 gitbook 的方式,分别是 gitbook 命令行工具,Gitbook Editor 官方编辑器和 gitbook.com 官网. 总体来说,三种途径适合各自不同的人群,找到适合自己的方式就好,基本操作流程都是一样的. 命令行工具更适合具备编程经验开发者,具有简单高效易整合等特点. 编辑器更适合无任何编程经验的文学创作者,不熟悉 markdown 语法,不熟悉 git 工作流,这种情况下也推荐使用图形化操作的编辑器. 官网适合想要快速体验 gitbook 效果的萌新,只有觉得物超所值才能有动力搭建 gitbook 开发环境,不是吗? 当然,如果你想访问官网的话,你可能需要学会科学上网,网址见文章结尾. gitbook 命令行 首先需要创建存放书籍的目录,然后对该目录进行初始化,最后启动本地服务即可体验效果. 初始化项目 语法格式: gitbook init 如果是空目录会自动创建 README.md 和 SUMMARY.md 两个文件,当然也可以手动创建再初始化. 示例: # 创建 `gitbook` 演示项目 $ mkdir gitbook-demo # 初始化项目 $ gitbook init warn: no summary file in this book info: create README.md info: create SUMMARY.md info: initialization is finished # 当前目录结构 $ tree . ├── README.md └── SUMMARY.md 0 directories, 2 files $ gitbook init 命令可能会自动生成 README.md 和 SUMMARY.md 两个文件,如已存在则更新. 运行项目 语法格式: gitbook serve 将初始化后的项目启动成为一个本地服务,我们可以直接在浏览器访问项目,预览书籍效果. 示例: # 启动本地服务器 $ gitbook serve Live reload server started on port: 35729 Press CTRL+C to quit ... info: 7 plugins are installed info: loading plugin \"livereload\"... OK info: loading plugin \"highlight\"... OK info: loading plugin \"search\"... OK info: loading plugin \"lunr\"... OK info: loading plugin \"sharing\"... OK info: loading plugin \"fontsettings\"... OK info: loading plugin \"theme-default\"... OK info: found 1 pages info: found 0 asset files info: >> generation finished with success in 1.2s ! Starting server ... Serving book on http://localhost:4000 如果要停止服务器,只需同时按住 CTRL+C 即可,现在再看一下 gitbook-demo 目录结构. $ tree . ├── README.md ├── SUMMARY.md └── _book ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-highlight │ │ ├── ebook.css │ │ └── website.css │ ├── gitbook-plugin-livereload │ │ └── plugin.js │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── index.html └── search_index.json 11 directories, 27 files $ gitbook serve 命令可能会自动生成 _book 目录,如已存在则更新. gitbook editor 编辑器 下载 gitbook editor 并安装,如果下载遇到困难,可以找我来帮忙哟! 如果你没有梯子,可以暂不登录(Do that Later),只不过无法与 gitbook.com 保持同步. 更改图书路径 更改默认图书存放位置(Gitbook Editor => Change Library Path...),以后图书目录都在该目录下,比如设置的是 .../gitbook-editor/ 图书目录. 新建图书 新建图书项目,名字仍然是 gitbook-demo,这样方便比较和命令行创建的 gitbook-demo 区别. 图形化操作界面总体来说还是很容易上手的,自己好好研究一下即可,这里仅仅演示默认效果. 启动项目 现在先找到新建图书的具体目录,然后再启动本地服务器,同样地,我们在浏览器中体验电子书效果. 图书项目路径: /workspace/gitbook-editor/Import/gitbook-demo,其中 /workspace/gitbook-editor/ 是上一步更改的图书路径. # 启动本地服务器 $ gitbook serve 这里不再需要运行 gitbook init 命令了,因为已经创建过 README.md 和 SUMMARY.md 这两个文件. Gitbook Editor 编辑器新建的图书项目和 gitbook-cli 创建的图书项目本质上并没有什么不同,只不过编辑器集成了常用功能而已! gitbook.com 网站 由于受网络因素所限,暂时不分享这部分知识了,简单来说就是在线编辑并发布电子书,这一点和 github 的代码托管服务类似. gitbook 新版官网(需要FQ) : https://www.gitbook.com/ gitbook 旧版官网(无需FQ) : https://legacy.gitbook.com 小结 初始化项目 : gitbook init 启动项目 : gitbook serve 默认访问地址: http://localhost:4000 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/experience/preview.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/experience/gitbook-cli.html":{"url":"myGitbook/experience/gitbook-cli.html","title":"gitbook-cli 命令行操作","keywords":"","body":"gitbook-cli 命令行操作 gitbook 生成电子书主要有三种方式: gitbook-cli 命令行操作,简洁高效,适合从事软件开发的相关人员. gitbook-editor 编辑器操作,可视化编辑,适合无编程经验的文学创作者. gitbook.com 官网操作,在线编辑实时发布,适合无本地环境且科学上网的体验者. 本文主要讲解第一种 gitbook-cli 命令行操作流程,其他两种见另外两篇教程. gitbook 的一些常用命令 安装 gitbook-cli 脚手架工具 本机已安装 node.js 开发环境,安装完成后运行 gitbook -V 能够打印出版本信息,则表示安装成功. $ sudo npm install -g gitbook-cli 关于安装配置相关问题请参考 环境要求 初始化 gitbook 项目 初始化项目,按照 gitbook 规范会自动创建 README.md 和 SUMMARY.md 两个文件,具体用途见下文. 其实 SUMMARY.md 是电子书的章节目录,gitbook 会初始化相应的文件目录结构,所以主要是用于开发初始阶段. $ gitbook init 启动 gitbook 项目 启动本地服务,程序无报错则可以在浏览器预览电子书效果: http://localhost:4000 由于能够实时预览电子书效果,并且大多数开发环境搭建在本地而不是远程服务器中,所以主要用于开发调试阶段. $ gitbook serve 构建 gitbook 静态网页 构建静态网页而不启动本地服务器,默认生成文件存放在 _book/ 目录,当然输出目录是可配置的,暂不涉及,见高级部分. 输出静态网页后可打包上传到服务器,也可以上传到 github 等网站进行托管,因而主要用于发布准备阶段. $ gitbook build 章节小结 gitbook init 初始化 README.md 和 SUMMARY.md 两个文件. gitbook build 本地构建但不运行服务,默认输出到 _book/ 目录. gitbook serve 本地构建并运行服务,默认访问 http://localhost:4000 实时预览. # 创建 `gitbook` 演示项目 $ mkdir gitbook-demo # 初始化项目 $ gitbook init warn: no summary file in this book info: create README.md info: create SUMMARY.md info: initialization is finished # 启动本地服务器 $ gitbook serve Live reload server started on port: 35729 Press CTRL+C to quit ... info: 7 plugins are installed info: loading plugin \"livereload\"... OK info: loading plugin \"highlight\"... OK info: loading plugin \"search\"... OK info: loading plugin \"lunr\"... OK info: loading plugin \"sharing\"... OK info: loading plugin \"fontsettings\"... OK info: loading plugin \"theme-default\"... OK info: found 1 pages info: found 0 asset files info: >> generation finished with success in 1.2s ! Starting server ... Serving book on http://localhost:4000 # 查看当前目录结构 $ tree . ├── README.md ├── SUMMARY.md └── _book ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-highlight │ │ ├── ebook.css │ │ └── website.css │ ├── gitbook-plugin-livereload │ │ └── plugin.js │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── index.html └── search_index.json 11 directories, 27 files $ gitbook 的目录结构说明 既然要书写一本电子书,那么起码的章节介绍和章节详情自然是必不可少的. 当然还有标题,作者和联系方式等个性化信息需要指定,如果不指定的话,一旦采用默认配合,八成不符合我们的预期,说不定都会变成匿名电子书?所以配置文件一般也是需要手动设置的! 真正可选的文件要数词汇表了,毕竟不是每一本电子书都有专业词汇需要去解释说明.如果在章节详情顺便解释下涉及到的专业词汇,那么自然也就不需要词汇表文件了. 简单解释下各个文件的作用: README.md 是默认首页文件,相当于网站的首页 index.html ,一般是介绍文字或相关导航链接. SUMMARY.md 是默认概括文件,主要是根据该文件内容生成相应的目录结构,同 README.md 一样都是被gitbook init 初始化默认创建的重要文件. _book 是默认的输出目录,存放着原始 markdown 渲染完毕后的 html 文件,可以直接打包到服务器充当静态网站使用.一般是执行 gitbook build 或 gitbook serve 自动生成的. book.json 是配置文件,用于个性化调整 gitbook 的相关配置,如定义电子书的标题,封面,作者等信息.虽然是手动创建但一般是必选的. GLOSSARY.md 是默认的词汇表,主要说明专业词汇的详细解释,这样阅读到专业词汇时就会有相应提示信息,也是手动创建但是可选的. LANGS.md 是默认的语言文件,用于国际化版本翻译,和 GLOSSARY.md 一样是手动创建但是可选的. README.md 首页文件[必须] 编辑 README.md 文件,随便写点内容并启动本地服务(gitbook serve)实时预览效果. SUMMARY.md 概括文件[必须] 先停止本地服务,编辑章节目录结构,然后重新再初始化(gitbook init)自动创建相应目录. _book 输出目录[可选] 执行 gitbook build 或 gitbook serve 命令后会自动生成静态网页. # 构建电子书 $ gitbook build info: 7 plugins are installed info: 6 explicitly listed info: loading plugin \"highlight\"... OK info: loading plugin \"search\"... OK info: loading plugin \"lunr\"... OK info: loading plugin \"sharing\"... OK info: loading plugin \"fontsettings\"... OK info: loading plugin \"theme-default\"... OK info: found 5 pages info: found 0 asset files info: >> generation finished with success in 0.7s ! # 查看输出目录 $ tree _book/ _book/ ├── first │ ├── 01.html │ └── 02.html ├── first.html ├── gitbook │ ├── fonts │ │ └── fontawesome │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-fontsettings │ │ ├── fontsettings.js │ │ └── website.css │ ├── gitbook-plugin-highlight │ │ ├── ebook.css │ │ └── website.css │ ├── gitbook-plugin-lunr │ │ ├── lunr.min.js │ │ └── search-lunr.js │ ├── gitbook-plugin-search │ │ ├── lunr.min.js │ │ ├── search-engine.js │ │ ├── search.css │ │ └── search.js │ ├── gitbook-plugin-sharing │ │ └── buttons.js │ ├── gitbook.js │ ├── images │ │ ├── apple-touch-icon-precomposed-152.png │ │ └── favicon.ico │ ├── style.css │ └── theme.js ├── index.html ├── search_index.json └── second.html 10 directories, 28 files $ book.json 配置文件[可选] 在根目录下新建 book.json 配置文件,完整的支持项请参考官方文档,下面仅列举常用的一些配置项. title 标题 书籍的标题 示例: \"title\": \"雪之梦技术驿站\" author 作者 书籍的作者 示例: \"author\": \"snowdreams1006\" description 描述 书籍的简要描述 示例: \"description\": \"雪之梦技术驿站又名snowdreams1006的技术小屋.主要分享个人的学习经验,一家之言,仅供参考.\" isbn 国际标准书号 书籍的国际标准书号 示例: \"isbn\": \"978-0-13-601970-1\" 选填,请参考 ISBN Search language 语言 支持语言项: 默认英语(en),设置成简体中文(zh-hans) en, ar, bn, cs, de, en, es, fa, fi, fr, he, it, ja, ko, no, pl, pt, ro, ru, sv, uk, vi, zh-hans, zh-tw 示例: \"language\": \"zh-hans\" direction 阅读顺序 阅读顺序,支持从右到左(rtl)或从左到右(ltr),默认值取决于语言值. 示例: \"direction\" : \"ltr\" gitbook 版本 指定 gitbook 版本,支持SemVer规范,接受类似于 >=3.2.3 的条件. 示例: \"gitbook\": \"3.2.3\" root 根目录 指定存放 gitbook 文件(除了book.json文件本身)的根目录 示例: \"root\": \".\" links 侧边栏链接 左侧导航栏添加链接,支持外链 示例; \"links\": { \"sidebar\": { \"我的网站\": \"https://snowdreams1006.cn/\" } } styles 自定义样式 自定义全局样式 示例: \"styles\": { \"website\": \"styles/website.css\", \"ebook\": \"styles/ebook.css\", \"pdf\": \"styles/pdf.css\", \"mobi\": \"styles/mobi.css\", \"epub\": \"styles/epub.css\" } plugins 插件 配置额外的插件列表,添加新插件项后需要运行 gitbook install 安装到当前项目. gitbook 默认自带5个插件,分别是: highlight 语法高亮插件 search 搜索插件 sharing 分享插件 font-settings 字体设置插件 livereload 热加载插件 后续会介绍一些常用插件,如需获取更多插件请访问官网插件市场 示例: \"plugins\": [ \"github\", \"pageview-count\", \"mermaid-gb3\", \"-lunr\", \"-search\", \"search-plus\", \"splitter\", \"-sharing\", \"sharing-plus\", \"expandable-chapters-small\", \"anchor-navigation-ex\", \"edit-link\", \"copy-code-button\", \"chart\", \"favicon-plus\", \"donate\" ] pluginsConfig 插件配置 安装插件的相应配置项,具体有哪些配置项是由插件本身提供的,应访问插件官网进行查询. \"pluginsConfig\": { \"github\": { \"url\": \"https://github.com/snowdreams1006/snowdreams1006.github.io\" }, \"sharing\": { \"douban\": true, \"facebook\": false, \"google\": false, \"hatenaBookmark\": false, \"instapaper\": false, \"line\": false, \"linkedin\": false, \"messenger\": false, \"pocket\": false, \"qq\": true, \"qzone\": true, \"stumbleupon\": false, \"twitter\": false, \"viber\": false, \"vk\": false, \"weibo\": true, \"whatsapp\": false, \"all\": [ \"facebook\", \"google\", \"twitter\", \"weibo\", \"instapaper\", \"linkedin\", \"pocket\", \"stumbleupon\" ] }, \"edit-link\": { \"base\": \"https://github.com/snowdreams1006/snowdreams1006.github.io/blob/master\", \"label\": \"编辑本页\" }, \"chart\": { \"type\": \"c3\" }, \"favicon\": \"/images/favicon.ico\", \"appleTouchIconPrecomposed152\": \"/images/apple-touch-icon-precomposed-152.png\", \"output\": \"_book\", \"donate\": { \"wechat\": \"/images/wechat.jpg\", \"alipay\": \"/images/alipay.jpg\", \"title\": \"赏\", \"button\": \"捐赠\", \"alipayText\": \"支付宝\", \"wechatText\": \"微信\" } } structure 目录结构配置 指定README.md,SUMMARY.md,GLOSSARY.md 和 LANGS.md 文件名称. 配置项 描述 structure.readme readme 文件名(默认值是 README.md) structure.summary summary 文件名(默认值是 SUMMARY.md) structure.glossary glossary 文件名(默认值是 GLOSSARY.md) structure.languages languages 文件名(默认值是 LANGS.md) pdf 配置 定制 pdf 输出格式,可能需要安装 ebook-convert 等相关插件 配置项 描述 pdf.pageNumbers 添加页码(默认值是 true ) pdf.fontSize 字体大小(默认值是 12 ) pdf.fontFamily 字体集(默认值是 Arial ) pdf.paperSize 页面尺寸(默认值是 a4 ),支持a0,a1,a2,a3,a4,a5,a6,b0,b1,b2,b3,b4,b5,b6,legal,letter pdf.margin.top 上边界(默认值是 56 ) pdf.margin.bottom 下边界(默认值是 56 ) pdf.margin.left 左边界(默认值是 62 ) pdf.margin.right 右边界(默认值是 62 ) 电子书封面照片 cover.jpg 和 cover_small.jpg,后续会详细说明. GLOSSARY.md 词汇表文件[可选] 词汇表文件,用于全书的专业词汇解释说明,比如鼠标悬停在专业词汇上会有相应提示. 语法格式: ## + ` +专业词汇` 学习 gitbook 前最好先学习下markdown和git,你知道他们的用途吗? 示例: ## markdown 简洁优雅的排版语言,简化版的 `HTML`,加强版的 `TXT`,详情请参考 [https://snowdreams1006.github.io/markdown/](https://snowdreams1006.github.io/markdown/) ## git 分布式版本控制系统,详情请参考 [https://snowdreams1006.github.io/git/](https://snowdreams1006.github.io/git/) LANGS.md 语言文件[可选] 支持国际化编写图书,一种语言一个单独子目录,同样地,将语言文件放到根目录下. 示例: * [English](en/) * [French](fr/) * [Español](es/) 章节小结 开发初始阶段运行 gitbook init 命令按照 SUMMARY.md 文件内容自动创建对应目录结构,编写各自文件内容后运行 gitbook serve 启动本地服务实时预览效果. 开发到一定程度后打算发布服务,再运行 gitbook build 输出到 _book/ 目录,别忘了配置 book.json 文件,然后就可以将 _book/ 文件夹整个扔到 nginx 等静态服务器上,这样就能联网访问你的电子书了. 是不是很简单,后续还会有如何发布与导出等相关教程,今天先到这里,下次见! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/experience/gitbook-cli.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/experience/gitbook-editor.html":{"url":"myGitbook/experience/gitbook-editor.html","title":"gitbook-editor 编辑器操作","keywords":"","body":"gitbook-editor 编辑器操作 亲测,目前已不再支持旧版 gitbook-editor 编辑器,而官网也没有相应的新版编辑器,如果哪位找到了新版编辑器,还望告知! 现在注册 gitbook 账号会默认重定向到 新版官网,而 旧版官网 的账号应该是可以正常使用的,前提是你必须之前注册过. 遗憾的是,最新注册的账号是无法使用 gitbook-editor 编辑器,不能登录到 gitbook ,也无法同步 github ,充其量只能算本地的 markdown 编辑器,所以这一节不再介绍了. 如果有兴趣了解 gitbook-editor 编辑器的基本使用,请参考 gitbook editor 编辑器. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/experience/gitbook-editor.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/experience/gitbook-com.html":{"url":"myGitbook/experience/gitbook-com.html","title":"gitbook.com 官网操作","keywords":"","body":"gitbook.com 官网操作 gitbook 官网是官方提供的图书托管的在线平台,分为新版官网(需要FQ) https://www.gitbook.com/ 和旧版官网(无需FQ) https://legacy.gitbook.com 两个网站. 目前均正常提供服务,但令人遗憾的是,两个网站的信息相互独立,而且现在注册的账号默认只能在新版官网中使用,而新版官网的访问速度简直比 github 还要慢,所以国内用户在线访问你的电子书真的需要点技术手段了! 本文主要介绍 www.gitbook.com 官网的基本使用,而 legacy.gitbook.com 网站我就算是想介绍也没有账号测试啊. \"巧妇难为无米之炊\",明明你就在那里,可我却什么也做不了. 先大概说一下 gitbook.com 网站的一些个人总结吧. gitbook.com 提供收费和免费服务,有点像早期的 github ,免费账号只能创建一个私有的命名空间,其他命名空间只能是公开的,这里的命名空间可以理解为一本书. 这一点是不是有点像早期的 github.com?免费账号无法创建私有仓库,只能是公开仓库. (现在 github.com 已被微软收购,目前可以创建无限量的私有仓库了!) 再说 gitbook 的账号问题,像 github 一样提供用户名和邮箱登录方式,他们的用户名都可以作为二级域名,比如我的用户名是snowdreams1006,那么我的 gitbook 第一本电子书网址就是 https://snowdreams1006.gitbook.io/index/ ,再看一下我的 github 个人网址 https://snowdreams1006.github.io/ ,这两个是不是很类似?! 如果不仔细看的话,八成你会觉得一样,一个是gitbook.io,另一个是github.io. 所以我严重怀疑他俩是不是有着不为人知的私密关系,太多的相似性,鼓励分享,限制私有等等特点. 无图无真相,趁着这次教程顺便将 github 个人网站项目同步到 gitbook 电子书项目了,这样的好处是本地只需要推送到 github ,自动更新 github.io 网站(利用的是github 静态网站托管服务) ,然后再自动同步到 gitbook.io 网站. 是不是很神奇,一份源码,两个官网! gitbook : https://snowdreams1006.gitbook.io/ github : https://snowdreams1006.github.io/ 注册并登陆 gitbook.com 注册信息主要包括用户名和邮箱,还有一些其他信息,没什么特殊的注意事项. 访问 https://www.gitbook.com/ 需要 FQ 新建命名空间(电子书) 注册账后后会默认生成一个私有的命名空间,因为并不打算将私有电子书托管到 gitbook,所以接下来直接将其转变成公开电子书进行演示. 个性性配置 标题和图片 主题颜色和页面反馈 观众 观众指的是当前电子书面向的受众是谁,公开的和私有的的区别以及设置是否被谷歌搜索收录. 域名 默认域名是 https://snowdreams1006.gitbook.io/,如果需要自定义域名,请保证 dns 能够正确解析到该网站. url 设置的命名空间是 index,因此最终访问路径是 https://snowdreams1006.gitbook.io/index/ 整合 gitbook 默认提供4种整合方式,在下孤陋寡闻只了解 github ,其余三种没接触过,暂不涉及. 选择 github 进行整合 登录 github 并授权 选择列出公开的仓库,然后输入用户名和密码进行登录并授权. 选择目标仓库 授权成功后会列出当前 github 账号下全部的公开仓库,选择目标仓库并点击下一步. 这里以 snowdreams1006.github.io 公开仓库为例,因为该仓库是本人官网源码项目. 同步内容 选择同步分支 根据实际情况选择同步分支,因为我一般是直接推送到 master 分支,所以 master 分支是个人网站的维护分支,因此这一步我选择的是 master. 选择同步内容 选择同步内容的方式,是从 github 同步到 gitbook,还是从 gitbook 同步到 github,因为我的项目已托管到 github ,所以初次同步内容选择的是 github --> gitbook. 显示 github 按钮 生成的电子书网站是否显示 github 按钮,作用是点击该按钮会跳转到关联的github 仓库上. 此时心里在想,万一点进 github ,随手就是一个 star 呢?哈哈! 等待内容导入 根据目标仓库的大小不同,导入内容是的时长自然也不一样,耐心等待... 上线 导入完成,电子书终于正式上线了! 现在赶紧分享一下好消息吧,访问 https://.gitbook.io/ 在线阅读! 小结 本文以如何集成 github 为例,演示了 gitbook.com 发布电子书的基本流程,由于 gitbook 电子书内容来自于 github 项目,因此我们只要更新 github 仓库,我们的 gitbook 电子书网站自然也就相应更新了! gitbook 是 markdown 和 github 的完美结合体,借助 gitbook.com 官网我们很容易发布并托管电子书. 美中不足的是,国内无法正常访问 gitbook.com ,因此并不是很推荐将电子书发布到 gitbook.com 网站. 现在国内也有类似的产品,有一种产品叫做 看云,还不错! 后续还会介绍 gitbook 如何结合 github 发布个人网站,欢迎继续关注 gitbook 系列教程! 如何打造免费的个人官网,想了解 https://snowdreams1006.github.io/ 背后的故事吗? var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/experience/gitbook-com.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/advance/advance.html":{"url":"myGitbook/advance/advance.html","title":"高级进阶","keywords":"","body":"高级进阶 不论是 gitbook-cli 命令行还是 gitbook editor 编辑器都离不开 gitbook 命令的操作使用,所以再次了解下常用命令. 注意 gitbook-cli 是 gitbook 的脚手架工具,是 gitbook 的扩展功能,同时着管理 gitbook. 查看 gitbook 帮助信息 语法格式: gitbook --help 示例: $ gitbook --help Usage: gitbook [options] [command] Options: -v, --gitbook [version] specify GitBook version to use -d, --debug enable verbose error -V, --version Display running versions of gitbook and gitbook-cli -h, --help output usage information Commands: ls List versions installed locally current Display currently activated version ls-remote List remote versions available for install fetch [version] Download and install a alias [folder] [version] Set an alias named pointing to uninstall [version] Uninstall a version update [tag] Update to the latest version of GitBook help List commands for GitBook * run a command with a specific gitbook version $ gitbook ls 列出本地安装版本 语法格式: gitbook ls 示例: # 列出本地已安装 `gitbook` 版本 $ gitbook ls gitbook current 列出当前使用版本 语法格式: gitbook current 示例: # 列出当前正在使用的 `gitbook` 版本 $ gitbook current gitbook ls-remote 列出远程可用版本 语法格式: gitbook ls-remote 示例: # 列出远程可用的 `gitbook` 版本 $ gitbook ls-remote gitbook fetch 安装指定版本 语法格式: gitbook fetch [version] 示例: # 下载并安装指定的 `gitbook` 版本 $ gitbook fetch 2.6.9 gitbook alias 指定文件夹别名 语法格式: gitbook alias [folder] [version] 示例: # 下载并安装指定的 `gitbook` 版本 $ gitbook alias /Users/sunpo/Desktop/book/gitbook/ 1.0.0 gitbook uninstall 卸载指定版本 语法格式: gitbook uninstall [version] 示例: # 卸载指定的 `gitbook` 版本 $ gitbook uninstall 2.6.9 gitbook update 更新指定版本 语法格式: gitbook update [tag] 示例: # 默认更新到最新的 `gitbook` 版本 $ gitbook update # 更新到指定的 `gitbook` 版本 $ gitbook update 2.6.9 列出 gitbook 可用命令 语法格式: gitbook help 示例: $ gitbook help build [book] [output] build a book --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) --format Format to build to (Default is website; Values are website, json, ebook) --[no-]timing Print timing debug information (Default is false) serve [book] [output] serve the book as a website for testing --port Port for server to listen on (Default is 4000) --lrport Port for livereload server to listen on (Default is 35729) --[no-]watch Enable file watcher and live reloading (Default is true) --[no-]live Enable live reloading (Default is true) --[no-]open Enable opening book in browser (Default is false) --browser Specify browser for opening book (Default is ) --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) --format Format to build to (Default is website; Values are website, json, ebook) install [book] install all plugins dependencies --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) parse [book] parse and print debug information about a book --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) init [book] setup and create files for chapters --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) pdf [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) epub [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) mobi [book] [output] build a book into an ebook file --log Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled) $ gitbook build 构建电子书 语法格式: gitbook build [book] [output] 示例: # 默认输出到 `_book/` 目录 $ gitbook build # 指定输出目录 `/Users/sunpo/Desktop/book/` $ gitbook build ./ /Users/sunpo/Desktop/book/ # 指定输出格式 `json` $ gitbook build --format=json gitbook serve 启动本地服务器 语法格式: gitbook serve [book] [output] 示例: # 默认服务端口: `4000`,热部署端口: `35729` $ gitbook serve # 指定输出目录 `/Users/sunpo/Desktop/book/` $ gitbook serve ./ /Users/sunpo/Desktop/book/ # 指定服务端口: `5000` 和热部署端口: `45729` $ gitbook serve --port=5000 --lrport=45729 gitbook install 安装插件 语法格式: gitbook install [book] 示例: # 安装当前项目所需插件 $ gitbook install # 安装指定项目所需插件 `/Users/sunpo/Desktop/gitbook-demo/` $ gitbook install /Users/sunpo/Desktop/gitbook-demo/ # 安装当前项目所需插件且指定日志输出级别: `debug` $ gitbook install --log=debug gitbook parse 解析电子书 语法格式: gitbook parse [book] 示例: # 解析并输出当前项目的 `debug` 级别日志信息 $ gitbook parse # 解析并输出指定项目的 `/Users/sunpo/Desktop/gitbook-demo/` 的 `debug` 级别日志信息 $ gitbook parse /Users/sunpo/Desktop/gitbook-demo/ # 解析并输出当前项目的 `info` 级别日志信息 $ gitbook parse --log=info gitbook pdf 输出 PDF 电子书 语法格式: gitbook pdf [book] [output] 示例: # 默认输出到当前项目 $ gitbook pdf # 指定输出文件 `/Users/sunpo/Desktop/book.pdf` $ gitbook pdf ./ /Users/sunpo/Desktop/book.pdf # 指定输出日志级别: `debug` $ gitbook pdf --log=debug 可能需要安装 ebook-convert 相关插件,详情见相关系列教程. gitbook epub 输出 epub 电子书 语法格式: gitbook epub [book] [output] 示例: # 默认输出到当前项目 $ gitbook epub # 指定输出文件 `/Users/sunpo/Desktop/book.epub` $ gitbook epub ./ /Users/sunpo/Desktop/book.epub # 指定输出日志级别: `debug` $ gitbook epub --log=debug 可能需要安装 ebook-convert 相关插件,详情见相关系列教程. gitbook mobi 输出 mobi 电子书 语法格式: gitbook mobi [book] [output] 示例: # 默认输出到当前项目 $ gitbook mobi # 指定输出文件 `/Users/sunpo/Desktop/book.mobi` $ gitbook mobi ./ /Users/sunpo/Desktop/book.mobi # 指定输出日志级别: `debug` $ gitbook mobi --log=debug 可能需要安装 ebook-convert 相关插件,详情见相关系列教程. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/advance.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/advance/plugin.html":{"url":"myGitbook/advance/plugin.html","title":"插件介绍","keywords":"","body":"插件介绍 插件是 gitbook 的扩展功能,很多炫酷有用的功能都是通过插件完成的,其中插件有官方插件和第三方插件之分. 推荐官方插件市场 https://plugins.gitbook.com/ 寻找或下载相应的插件. 当然也可以去 npm 市场搜索 gitbook 插件,根据 gitbook 插件规范, gitbook-plugin- 是功能插件,gitbook-theme- 是主体插件. 如果没有按照规范命名,还是直接百度搜索吧! npm 安装后再 gitbook 安装 语法格式: npm install gitbook-plugin- 安装到本地: npm install gitbook-plugin-advanced-emoji 激活安装插件: 配置 book.json 中 plugins 节点 安装到项目: gitbook install 启动并测试测试: gitbook serve 示例: # 安装 gitbook-plugin-advanced-emoji 插件 $ npm install gitbook-plugin-advanced-emoji # 安装 gitbook-plugin-advanced-emoji 插件 $ gitbook install npm 安装速度慢的话,可以使用 cnpm 加速安装(npm install cnpm),表情插件下载地址 Advanced Emoji gitbook 直接安装 语法格式: gitbook install 激活安装插件: 配置 book.json 中 plugins 节点 安装到项目: gitbook install 启动并测试测试: gitbook serve 示例: # 安装 gitbook-plugin-advanced-emoji 插件 $ gitbook install 表情插件下载地址 Advanced Emoji 插件示例 Advanced Emoji表情列表 Advanced Emoji下载地址 book.json 配置文件: \"plugins\": [ \"advanced-emoji\" ] 安装插件: $ gitbook install 使用示例: :bowtie: :laughing: :relaxed: var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/plugin.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/advance/plugin-practical.html":{"url":"myGitbook/advance/plugin-practical.html","title":"实用插件","keywords":"","body":"实用插件 tbfed-pagefooter 页脚插件 激活插件配置 安装 tbfed-pagefooter 插件 测试 tbfed-pagefooter 插件 disqus 评论插件 注册 disqus.com 账号 注册并绑定域名 选择服务类型 安装并配置 disqus 到网站 安装并配置 disqus 插件 激活插件配置 安装 disqus 插件 测试 disqus 插件 gitalk 评论插件 申请 GitHub Application 授权 安装并集成到网站 测试集成效果 进一步思考 小结 mygitalk 评论插件 激活插件配置 安装 mygitalk 插件 测试 mygitalk 插件 copyright 版权保护插件 激活插件配置 安装 copyright 插件 测试 copyright 插件 readmore 阅读更多插件 激活插件配置 安装 readmore 插件 测试 readmore 插件 github 插件 激活插件配置 安装 github 插件 测试 github 插件 edit-link 编辑链接插件 激活插件配置 安装 edit-link 插件 测试 edit-link 插件 github 插件 激活插件配置 安装 github 插件 测试 github 插件 search-plus 中文搜索插件 激活插件配置 安装 search-plus 插件 测试 search-plus 插件 diff 代码差异插件 激活插件配置 安装 diff 插件 测试 diff 插件 其余插件列表 tbfed-pagefooter 页脚插件 如果希望将网页源码暴露出去并接受公众的监督校准的话,使用edit-link插件可以直接链接到源码文件. 链接地址: https://plugins.gitbook.com/plugin/tbfed-pagefooter 激活插件配置 在 book.json 中配置 tbfed-pagefooter 插件,详细说明请参考 tbfed-pagefooter 插件. 示例: { \"plugins\": [\"tbfed-pagefooter\"], \"pluginsConfig\": { \"tbfed-pagefooter\": { \"copyright\":\"© snowdreams1006\", \"modify_label\": \"文件修订时间:\", \"modify_format\": \"YYYY-MM-DD HH:mm:ss\" } } } 安装 tbfed-pagefooter 插件 示例: $ gitbook install 测试 tbfed-pagefooter 插件 启动本地服务后,每个页面的页脚处都会自动生成版权信息以及当前文件的最后更新时间. 功能慎用: 如果文档频繁更新适合生成最后更新时间,如果长时间不更新文档,岂不是最后更新时间还是几年前,给读者的感觉像是不再维护了一样! 示例: $ gitbook serve disqus 评论插件 discus 是一款集成评论的插件,可以为静态网站添加动态评论,让你的网站动起来! 遗憾的是,discus 插件只有 FQ 才能正常使用,暂时没找到其他较好的替代方案. 注册 disqus.com 账号 gitbook 集成 disqus 插件中最重要的配置项就是注册 disqus.com 网站唯一标识. 注册并绑定域名 如果没有注册账号请先注册,否则直接登录,当然也支持第三方账号登录(我使用的是谷歌账号). 人机验证时,选出符合条件的全部图形,直到没有新的图形为止,这一点和国内的静态图片验证是不同的! 选择安装 disqus 插件(I want to install Disqus on my site),接下来会绑定集成网站的域名. 接下来设置网站的相关信息,其中网站名称(snodreams1006)是唯一标示,接下来集成到 gitbook 用的就是这个简短名称,而分类和语言按照实际情况选择即可. 选择服务类型 disqus 网站提供的服务类型,有基础班(basic),加强版(plus),专业版(pro)和免费版(free). 每个版本计划有不同的收费标准以及相应的服务,可以根据实际情况选择适合自己的服务类型. 接下来以免费版为例进行有关演示 安装并配置 disqus 到网站 估计是这些网站提供了默认的集成方式,这里并没看到 gitbook 相关的网站,因此选择最后一个自定义网站. 填写网站的基本信息,其中网站缩写名称仍然是 snowdreams1006,网址填写 https://snowdreams1006.github.io/ ,至于其他信息根据实际情况填写即可. 至此 disqus.com 网站配置完成,接下来我们配置 gitbook 集成 disqus 插件. 安装并配置 disqus 插件 上一步我们已经获取到唯一的标识: snowdreams1006 ,接下来可以继续配置 disqus 插件了. 链接地址: https://plugins.gitbook.com/plugin/disqus 激活插件配置 在 book.json 中配置 disqus 插件,根据实际情况修改成自己的缩写名称(shortName). 示例: { \"plugins\": [\"disqus\"], \"pluginsConfig\": { \"disqus\": { \"shortName\": \"snowdreams1006\" } } } 安装 disqus 插件 示例: $ gitbook install 测试 disqus 插件 示例: $ gitbook serve 正常情况下(FQ),disqus 插件已经成功集成到 gitbook 网站了,因此推送到实际服务器上时看到的效果是这样的. 如果你不具备条件(FQ),那么你看到的仍然是这样的. gitalk 评论插件 本篇文章发表在开源中国后得到网友 @八一菜刀 的评论,让我推荐了gitalk 评论插件,初始使用了一下,确实不错,因此在这里更新下. 上述 disqus 评论插件虽然比较好用,但是注册是在 disqus.com 官网,需要特殊手段才能访问,即便成功配置了国内一般也是访问不到的,因此功能相当鸡肋. gitalk 评论插件解决了这一痛点,利用 github 的开发者接口授权,将讨论区的 issue 变成评论区,和 github 结合的如此紧密,适合用源码托管到 github 这类情况. 先混个脸熟,看一下 gitalk 官网 是如何介绍自己的呢. 看着效果确实不错,并且评论区的内容直接作为 github 仓库的 issue,这么好的想法我咋没想到呢! 好了,现在让我们开始集成到我们自己的项目中,遇到新鲜事物,当然先要参考官网介绍了. 申请 GitHub Application 授权 登录 github 账号,点击 在线申请 授权应用. 看到这一步,想必读者已经有个大概印象了,gitalk 插件是利用 github 的开发者服务,进行授权进而调用 issue 相关接口从而显示评论功能. 这种由官网提供的开发者服务还是比较好的,至少感觉比手动模拟提交要靠谱些,更何况走的是 OAuth 授权模式. 比如第三方应用提供微信登录,走的也是 OAuth 协议,这里的第三方应用当然就是现在说的 Gitalk 插件,微信就是我们的 github . 新建应用,首页 url 和授权回调 url 填写相同的首页链接即可,其他情况自定义填写. 应用登记成功后会生成 token 令牌,clientId 和 clientSecret 需要重点保存下来,待会需要用到. 安装并集成到网站 在需要添加评论的页面,添加下述内容引入 gitalk 插件,其中参数来自我们上一步获取的 clientId 和 clientSecret . 默认应该添加到 .html 页面,当然也可以添加到 .md 页面,毕竟 markdown 语法也支持 html 标签. var gitalk = new Gitalk({ \"clientID\": \"clientId\", \"clientSecret\": \"clientSecret\", \"repo\": \"GitHub repo\", \"owner\": \"GitHub repo owner\", \"admin\": [\"GitHub repo admin\"], \"id\": location.pathname, \"distractionFreeMode\": false }); gitalk.render(\"gitalk-container\"); 稍微解释下参数的含义: \"clientID\" : [必选] GitHub Application Client ID \"clientSecret\" : [必选] GitHub Application Client Secret \"repo\" : [必选] GitHub repository \"owner\" : [必选] GitHub repository 所有者,可以是个人或者组织 \"admin\" : [必选] GitHub repository 的所有者和合作者 (对这个 repository有写权限的用户) \"id\" : [可选] 页面的唯一标识,默认值: location.href, 长度必须小于50,否则会报错! \"distractionFreeMode\": [可选] 类似 Facebook 评论框的全屏遮罩效果,默认值: false 上述配置只是最简配置,如果想要了解更多高级配置,请参考 官方文档 测试集成效果 按照上述安装步骤,将代码复制到首页(README.md)文件中,然后推送到 github ,体验下集成效果. 注意: 这里必须推送到服务器,因为申请应用时填写的域名是线上地址,因而本地测试是不会成功的,会报错,这一点和微信支付的回调地址类似. 示例: var gitalk = new Gitalk({ \"clientID\": \"3f62415a283d19cbd696\", \"clientSecret\": \"aed0e1db0620bf5d0e3a3f0225f801997ad74e58\", \"repo\": \"snowdreams1006.github.io\", \"owner\": \"snowdreams1006\", \"admin\": [\"snowdreams1006\"], \"id\": location.pathname, \"distractionFreeMode\": false }); gitalk.render(\"gitalk-container\"); 上述参数仅供参考,实际使用中请替换成自己的配置,不然你也没有我仓库的权限,肯定会报错的啊! 心心相念的 gitalk 评论区呢?是不是哪里配置错了,为啥没有出来? 别急,要淡定,看一下提示说\"未找到的 Issue 进行评论,请联系 @snowdreams1006 初始化创建\",既然如此,那我们就操作一下吧! 点击下方的按钮 使用 Github登录 ,会跳转到相应的仓库,然后按照提示确定. 再次返回首页,刷新一下看看发生什么神奇的事情了? 终于集成了评论功能,而且还支持 markdown 格式的评论呢! 进一步思考 确实不错,心中自然是欣喜万分,但别高兴太早了,因为你会发现其他页面并没有评论区,也很好理解,我们目前仅仅在首页(README.md) 集成了 gitalk 插件,也就是说使用 gitbook build 输出的 index.html 首页才支持评论区,其他页面没有插入上述代码,自然是没有评论区功能的啊! 那如果想要实现全网站的所有页面都集成评论区功能,应该怎么办呢? 百度搜索了一下,并没有找到优雅的解决方案,如果有人能够提供更好的解决方案,还望不吝赐教,在此谢过. 既然网上找不到优雅的解决方案,那寻求专业人士的帮助也是一种好办法,我去哪找 gitalk 的使用者呢? 聪明的你或许已经想到了,解铃还须系铃人,当然是向推荐给我插件的大牛提问了! 他确实提供了一种思路,以下是网友@八一菜刀原话: 文档里面我用的是tbfed-pagefooter插件,不过我是在本地使用gitbook install后重写了该插件的js,无非就是在js里面加一段Gitalk的调用代码,这样使用gitbook build命令的时候,所有的页面都会有Gitalk的评论调用 人家既然已经提供了思路,不太好意思继续麻烦人家要源码,既然如此,那就自己动手吧! tbfed-pagefooter 插件很熟悉,一般是用于注明版权以及文章的修订时间的,而且作用于每个页面,这一点就满足了集成 gitalk 相关代码的基本要求. 大体方向确定后,目前就是解决如何在 tbfed-pagefooter 插件构建的相关生命周期内顺便执行我们的代码? 正常当前项目安装 tbfed-pagefooter 插件后应该存放于 /node_modules/gitbook-plugin-tbfed-pagefooter 目录,大致看一下插件的项目结构. gitbook-plugin-tbfed-pagefooter ├── LICENSE ├── README.md ├── assets │ └── footer.css ├── index.js └── package.json 1 directory, 5 files $ 为了基本看懂项目文件作用,特意去看了下 gitbook 插件开发文档,目标锁定在 index.js . 截取重要片段,原来是电子书构建前动态增加了 html 片段啊,这就好办了! hooks: { 'page:before': function(page) { var _label = '最后更新时间: ', _format = 'YYYY-MM-DD', _copy = 'powered by snowdreams1006' if(this.options.pluginsConfig['tbfed-pagefooter']) { _label = this.options.pluginsConfig['tbfed-pagefooter']['modify_label'] || _label; _format = this.options.pluginsConfig['tbfed-pagefooter']['modify_format'] || _format; var _c = this.options.pluginsConfig['tbfed-pagefooter']['copyright']; _copy = _c ? _c + ' all right reserved,' + _copy : _copy; } var _copy = ''+_copy+''; var str = ' \\n\\n' + _copy + '' + _label + '\\n{{file.mtime | date(\"' + _format + '\")}}\\n'; str += '\\n\\n'+ '\\n\\n'+ '\\n\\n'+ '\\n\\n'; page.content = page.content + str; return page; } } 看懂基本原理后顺便修改了版权说明以及修订时间格式,然后追加了集成 gitalk 的相关代码. 这里为了方便修改 gitalk 配置,特意将相关配置项单独托管到 github 专门的 gitalk-config.js 文件. 至于配置文件的内容,并没什么特殊之处,还是顺便贴一下吧! var gitalk = new Gitalk({ \"clientID\": \"3f62415a283d19cbd696\", \"clientSecret\": \"aed0e1db0620bf5d0e3a3f0225f801997ad74e58\", \"repo\": \"snowdreams1006.github.io\", \"owner\": \"snowdreams1006\", \"admin\": [\"snowdreams1006\"], \"id\": window.location.pathname, \"distractionFreeMode\": false }); gitalk.render(\"gitalk-container\"); 至此,之后再本地构建电子书时(gitbook build),gitbook-plugin-tbfed-pagefooter 自然会顺便帮我们运行集成 gitalk 的相关代码,这才是相对来说比较优雅的做法. 当然也不一定非要借助 gitbook-plugin-tbfed-pagefooter 插件帮忙,也可以借助别的插件进行集成,甚至自己写个更好的插件. 小结 gitalk 插件相对 disqus 插件来说,更符合基本国情,只不过默认的集成方式只能一个页面一个页面去集成,当数量比较多时,工作量不敢想象. 因此,通过 gitbook 插件开发的方式,在源码文件输出为目标文件时加入相关集成代码,相当于手写100条输出语句和循环写100条输出语句. 其实本质上并没有改变什么,仍然是集成到每个页面中,但是简化了人工操作的工作量就是效率的提升. 如果有更高效更优雅的集成方式,欢迎大家一起探讨. mygitalk 评论插件 如果你正在苦恼于 Gitbook 静态博客无法添加动态交互功能,如果你渴望接收用户的评论反馈,如果你看过 gitalk 插件却苦于没有现成的 Gitbook 插件,那么 mygitalk 插件值得一试! 链接地址: https://snowdreams1006.github.io/gitbook-plugin-mygitalk/ gitbook-plugin-mygitalk 是全网最早发布的基于 gitalk 实现评论插件,用于给 Gitbook 博客网站集成评论功能. 激活插件配置 在 book.json 中配置 mygitalk 插件,详细说明请参考 mygitalk 插件. 示例: { \"plugins\" : [\"mygitalk\"], \"pluginsConfig\": { \"mygitalk\": { \"clientID\": \"GitHub Application Client ID\", \"clientSecret\": \"GitHub Application Client Secret\", \"repo\": \"GitHub repo\", \"owner\": \"GitHub repo owner\", \"admin\": [\"GitHub repo owner and collaborators, only these guys can initialize github issues\"], \"distractionFreeMode\": false } } } 安装 mygitalk 插件 示例: $ gitbook install 测试 mygitalk 插件 启动本地服务器后可能会提示联系管理员,只需要 gitbook build 上传到目标服务器上即可正常开启评论功能. 示例: $ gitbook serve copyright 版权保护插件 如果你的博客不希望被别人随意转载或者文章希望保留首发网站信息,那么推荐使用copyright插件帮助你进行版权保护. 链接地址: https://snowdreams1006.github.io/gitbook-plugin-copyright/ gitbook-plugin-copyright 版权保护插件实现复制文章时自动追加版权保护信息,并在文章结尾处追加来源信息. 激活插件配置 在 book.json 中配置 copyright 插件,详细说明请参考 copyright 插件. 示例: { \"plugins\": [\"copyright\"], \"pluginsConfig\": { \"copyright\": { \"site\": \"https://snowdreams1006.github.io/gitbook-plugin-copyright\", \"author\": \"雪之梦技术驿站\", \"website\": \"雪之梦技术驿站\", \"image\": \"https://snowdreams1006.github.io/snowdreams1006-wechat-open.png\" } } } 安装 copyright 插件 示例: $ gitbook install 测试 copyright 插件 默认情况下,版权保护信息是英文,如果 book.json 配置文件中指定中文语言 \"language\": \"zh-hans\" 时,内容复制以及文章末尾均为中文. 示例: $ gitbook serve readmore 阅读更多插件 如果 Gitbook 个人博客流量不错的话,可以考虑转化成公众号流量,readmore 插件是集成OpenWrite提供引流工具,通过关注公众号解锁博客文章,实现粉丝转换! 链接地址: https://snowdreams1006.github.io/gitbook-plugin-readmore/ 激活插件配置 在 book.json 中配置 readmore 插件,详细说明请参考 readmore 插件. 示例: { \"plugins\": [\"readmore\"], \"pluginsConfig\": { \"readmore\":{ \"blogId\": \"15702-1569305559839-744\", \"name\": \"雪之梦技术驿站\", \"qrcode\": \"https://snowdreams1006.github.io/snowdreams1006-wechat-public.jpeg\", \"keyword\": \"vip\" } } } 安装 readmore 插件 示例: $ gitbook install 测试 readmore 插件 readmore 插件暂未验证绑定域名,本地测试也能正常运行,如果后续开启了域名验证,只有部署到线上服务器才能生效,这一点和 mygitalk 插件原理类似. 示例: $ gitbook serve github 插件 添加 github 图标链接,方便直接跳转到 github 指定仓库. 链接地址: https://plugins.gitbook.com/plugin/github 激活插件配置 在 book.json 中配置 github 插件,详细说明请参考 github 插件. 示例: { \"plugins\": [\"github\"], \"pluginsConfig\": { \"github\": { \"url\": \"https://github.com/snowdreams1006/snowdreams1006.github.io\" } } } 安装 github 插件 示例: $ gitbook install 测试 github 插件 示例: $ gitbook serve edit-link 编辑链接插件 如果希望将网页源码暴露出去并接受公众的监督校准的话,使用edit-link插件可以直接链接到源码文件. 链接地址: https://plugins.gitbook.com/plugin/edit-link 激活插件配置 在 book.json 中配置 edit-link 插件,详细说明请参考 edit-link 插件. 示例: { \"plugins\": [\"edit-link\"], \"pluginsConfig\": { \"edit-link\": { \"base\": \"https://github.com/snowdreams1006/snowdreams1006.github.io/blob/master\", \"label\": \"编辑本页\" } } } 安装 edit-link 插件 示例: $ gitbook install 测试 edit-link 插件 如果不能正常跳转到源码文件,多次试验后重新更改 edit-link.base 节点内容,重新 gitbook serve 即可正常跳转源码文件. 示例: $ gitbook serve github 插件 添加 github 图标链接,方便直接跳转到 github 指定仓库. 链接地址: https://plugins.gitbook.com/plugin/github 激活插件配置 在 book.json 中配置 github 插件,详细说明请参考 github 插件. 示例: { \"plugins\": [\"github\"], \"pluginsConfig\": { \"github\": { \"url\": \"https://github.com/snowdreams1006/snowdreams1006.github.io\" } } } 安装 github 插件 示例: $ gitbook install 测试 github 插件 示例: $ gitbook serve search-plus 中文搜索插件 默认的 search 搜索插件是不支持中文搜索的,而 search-plus 则功能更强大些,两者不能共存,需要禁用或移除 search 插件. 链接地址: https://plugins.gitbook.com/plugin/search-plus 激活插件配置 在 book.json 中配置 github 插件,详细说明请参考 github 插件. 示例: { \"plugins\": [ \"-lunr\", \"-search\", \"search-plus\" ] } 安装 search-plus 插件 示例: $ gitbook install 测试 search-plus 插件 测试是否能够进行中文搜索,如果不能,请确保已移除默认的 \"lunr\" 和 \"search\" 插件. 示例: $ gitbook serve diff 代码差异插件 在写教程文档时有时会遇到这种场景,需要将前后两次代码进行差异化展示,通常有两种做法,一种是 PS 截图标注好修改内容,另一种就是手动计算出差异性代码,然后使用 diff 代码块展示前后差异. diff 插件采用的就是后一种方式,不同之处在于自动计算差异而非手动计算,同时支持多种方式来计算前后差异,下面是使用效果. 链接地址: https://snowdreams1006.github.io/gitbook-plugin-diff/ 激活插件配置 在 book.json 中配置 diff 插件,详细说明请参考 diff 插件. 示例: { \"plugins\": [\"diff\"] } 安装 diff 插件 示例: $ gitbook install 测试 diff 插件 diff 插件采用自定义 tag 语法方式获取前后代码块从而计算出代码块差异,为了更好地计算出代码差异,建议使用时指定计算方式,常见的计算方式有以下几种: diffChars 逐字符比较,适合比较单词字符改动情况. {% diff method=\"diffChars\" %} ```js cat ``` ```js cap ``` {% enddiff %} ca - t + p diffWords 逐单词比较,适合比较单行单词改动情况 {% diff method=\"diffWords\" %} ```bash beep boop ``` ```bash beep boob blah ``` {% enddiff %} beep - boop + boob + blah diffLines 逐行比较,适合比较多行文本改动情况 {% diff method=\"diffLines\" %} ```bash beep boop the cat is palying with cap what ``` ```bash beep boob blah the cat is palying with cap who ``` {% enddiff %} - beep boop + beep boob blah the cat is palying with cap - what + who diffJson json 对比,适合比较 json 对象改动情况 {% diff method=\"diffJson\" %} ```json { \"name\": \"gitbook-plugin-simple-mind-map\", \"version\": \"0.2.1\", \"description\": \"A gitBook plugin for generating and exporting mind map within markdown\" } ``` ```json { \"name\": \"gitbook-plugin-diff\", \"version\": \"0.2.1\", \"description\": \"A gitbook plugin for showing the differences between the codes within markdown\" } ``` {% enddiff %} { - \"description\": \"A gitBook plugin for generating and exporting mind map within markdown\", - \"name\": \"gitbook-plugin-simple-mind-map\", + \"description\": \"A gitbook plugin for showing the differences between the codes within markdown\", + \"name\": \"gitbook-plugin-diff\", \"version\": \"0.2.1\" } diffArrays 数组对比,适合比较数组对象改动情况 {% diff method=\"diffArrays\" %} ```json [ \"Vue\", \"Python\", \"Java\", \"flutter\", \"springboot\", \"docker\", \"React\", \"小程序\" ] ``` ```json [ \"Vuejs\", \"Nodejs\", \"Java\", \"flutter\", \"springboot\", \"docker\", \"React\" ] ``` {% enddiff %} [ - Vue - Python + Vuejs + Nodejs Java flutter springboot docker React - 小程序 ] 示例: $ gitbook serve 其余插件列表 外链视频 : gitbook-plugin-chinese-video 视频播放 : gitbook-plugin-html5-video 音频播放 : gitbook-plugin-audio_image 文件压缩 : gitbook-plugin-minifier 隐藏元素 : gitbook-plugin-hide-element 百度统计 : gitbook-plugin-baidu-tongji-with-multiple-channel 谷歌分析 : gitbook-plugin-google-tongji-with-multiple-channel var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/plugin-practical.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-05-10 23:51:11 "},"myGitbook/advance/plugin-theme.html":{"url":"myGitbook/advance/plugin-theme.html","title":"主题插件","keywords":"","body":"主题插件 Book 文档 theme-default 主题 theme-comscore 主题 API 文档 theme-api 插件 FAQ 文档 theme-faq 插件 小结 目前 gitbook 提供三类文档: Book 文档,API 文档和 FAQ 文档. 其中,默认的也是最常使用的就是 Book 文档,如果想要了解其他两种文档模式,需要引入相应的主题插件. 官方主题插件文档: https://toolchain.gitbook.com/themes/ Book 文档 theme-default 主题 插件地址: https://plugins.gitbook.com/plugin/theme-default theme-default 是 3.0.0 引入的默认主题,大多数插件针对的都是默认主题,如果切换到其他主题或者自定义主题,可能会造成某些情况下不兼容,甚至报错. 默认情况下,左侧菜单不显示层级属性,如果将 showLevel 属性设置为 true 可以显示层级数字. 示例: \"pluginsConfig\": { \"theme-default\": { \"showLevel\": true } } 效果: 默认情况下左侧菜单树不显示目录层级 开启层级显示设置后,左侧菜单树显示当前目录层级 theme-comscore 主题 插件地址: https://plugins.gitbook.com/plugin/theme-comscore default 默认主题是黑白的,而 comscore 主题是彩色的,即标题和正文颜色有所区分. 示例: \"plugins\": [ \"theme-comscore\" ] 效果: 默认情况下各级标题颜色均是黑色,不同级别的标题仅仅是大小区别. 设置 comscore 主题后,各级标题颜色不同,不仅仅是大小不同. API 文档 theme-api 插件 插件地址: https://plugins.gitbook.com/plugin/theme-api 如果文档本身是普普通文档模式,切换成 api 文档模式后并不会有太大变化,除非一开始就是接口文档,那样使用 theme-api 插件才能看出效果. 示例: { \"plugins\": [\"theme-api\"], \"pluginsConfig\": { \"theme-api\": { \"theme\": \"dark\" } } } 语法: 方法区 语法区 示例: 效果: 添加 api 相关方法后的文档效果,正常会两列显示并在右上角增加语言切换工具. FAQ 文档 theme-faq 插件 插件地址: https://plugins.gitbook.com/plugin/theme-faq theme-faq 可以帮助我们构建问答中心,预设好常见问题以及相应答案模式,同时为了方便搜索到问题或答案,一般需要搜索插件的配合. 示例: { \"plugins\": [ \"theme-faq\", \"-fontsettings\", \"-sharing\", \"-search\", \"search-plus\" ] } 帮助中心没有工具栏,因此涉及到工具类的插件一律失效或主动移除,同时默认搜索插件也会失效. 语法: 增加文章间的关联 --- related: - some/other/page.md - another_related_article.md --- Content of my article! 在当前页面底部显示延伸阅读,支持 yaml 语法关联到其他页面. 增加头部 logo 新建 _layouts/website/page.html 文件,用于扩展当前主题插件来增加自定义 logo. 增加导航栏链接 新建 _layouts/website/page.html 文件,用于扩展当前主题插件来增加自定义导航栏链接. 示例: 新建 _layouts/website/page.html 文件,增加自定义 logo 和导航栏链接. 效果: 小结 本节主要讲解了常用的三种文档模式,其中 default 主题插件,适合一般的博客类网站或静态网站,api 主题插件适合接口文档的编写,faq 主题插件则适合帮助中心. 三种主题插件分别对应不同的应用场景,默认情况下使用的是 default 主题插件,平时介绍的大多数功能插件也大多适合这种主题,另外两种主题可能就不能很好兼容第三方插件,需要亲身体验. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/plugin-theme.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/advance/plugin-develop.html":{"url":"myGitbook/advance/plugin-develop.html","title":"开发插件","keywords":"","body":"开发插件 什么是插件 Gitbook 插件是扩展 GitBook 功能(电子书和网站)的最佳方式. 只要是 Gitbook 默认没有提供的功能,基于插件机制都可以自行扩展,是插件让 Gitbook 变得更加强大. 本文将全面介绍插件的相关知识并重点介绍插件开发的全流程,只有熟悉插件开发流程才能做到有的放矢,心中有数,进而开发出自己的插件. 关于插件请参考 Gitbook 入门教程高级进阶系列文章,本文重点讲解开发 Gitbook 的基本流程. gitbook 入门教程之插件介绍 gitbook 入门教程之实用插件 gitbook 入门教程之主题插件 如何发现插件 您可以在Gitbook官网轻松搜索插件,也可以在npmjs 官网搜索 gitbook-plugin- 插件. 目前 Gitbook 官方已不再为维护插件网站,只能通过 npmjs 发现 Gitbook 插件. 如何安装插件 一旦你找到你想要安装的插件,你需要将它添加到你的 book.json 配置文件,如果没有该文件则自行创建. { \"plugins\": [\"myPlugin\", \"anotherPlugin\"] } 您还可以使用以下命令指定特定版本: myPlugin@0.3.1 . 默认不填写版本的情况下,GitBook 使用最新版本(兼容版本)的插件. 安装插件 如果是官网在线环境,网站会自动帮你安装插件. 如果是在本地环境,直接运行 gitbook install 来安装插件. $ gitbook install 或者使用 npm 提前下载插件再安装到本地项目: $ npm install gitbook-plugin- $ gitbook install 配置插件 插件的配置在 book.json 配置文件中的 pluginsConfig 属性中(如果没有该属性请自行创建), 安装插件时,最好浏览插件的文档了解相关选项的详细信息. { \"plugins\": [\"github\"], \"pluginsConfig\": { \"github\": { \"url\": \"https://github.com/snowdreams1006/snowdreams1006.github.io\" } } } 有些插件并未提供插件配置项,可以省略该步骤,有的插件会提供配置项,以插件介绍文档为准. 如何开发插件 GitBook 插件是在 npm 上发布的遵循传统定义的 node 包,除了标准的 node 规范外还有一些 Gitbook 自身定义的相关规范. 目录结构 Gitbook 插件最基本的项目结构至少包括配置文件 package.json 和入口文件 index.js ,其他目录文件根据插件用途自行增减. . ├── index.js └── package.json 实际插件项目略有不同,可能还会有 _layouts 布局目录, asset 资源目录以及自定义 example 示例目录和 docs 文档目录等等. package.json package.json 是nodejs的配置文件,Gitbook 插件同样遵循该规范,配置文件声明了插件的版本描述性信息,除此之外还有 Gitbook 相关字段,遵循schema准则,基本示例如下: { \"name\": \"gitbook-plugin-mytest\", \"version\": \"0.0.1\", \"description\": \"This is my first GitBook plugin\", \"engines\": { \"gitbook\": \">1.x.x\" }, \"gitbook\": { \"properties\": { \"myConfigKey\": { \"type\": \"string\", \"default\": \"it's the default value\", \"description\": \"It defines my awesome config!\" } } } } 值得注意的是,包名称必须以 gitbook-plugin-开头,包引擎应该包含gitbook.如需了解 package.json 的规范,可参考官方文档 index.js index.js 是插件运行时的入口,基本示例如下: module.exports = { // 钩子函数 hooks: {}, // 代码块 blocks: {}, // 过滤器 filters: {} }; 发布插件 GitBook 插件可以在npmjs官网上发布. 如需发布插件,首先需要在npmjs官网上注册帐户,然后通过命令行发布. $ npm publish 专用插件 专用插件可以托管在 GitHub 上,并使用 git urls: { \"plugins\": [ \"myplugin@git+https://github.com/MyCompany/mygitbookplugin.git#1.0.0\" ] } 本地测试插件 使用 npm link 可以在发布之前测试你的插件,命令详情参考官方文档 在插件的文件夹中,运行: $ npm link 然后在您的书或者文档的文件夹中执行: $ npm link gitbook-plugin- 单元测试插件 gitbook-tester可以方便地为你的插件编写Node.js/Mocha单元测试. 使用Travis.可以对每个提交/标签运行测试. 插件总结 Gitbook 插件是扩展 Gitbook 功能的不二之选,如果熟悉 nodejs 项目的开发流程,只要稍微熟悉下 Gitbook 提供的接口文档,开发出自己的插件应该不是难事! 希望本文能够对你理解 Gitbook 插件有所帮助,了解并熟练掌握插件开发的全流程,如果本文对你有所帮助,别忘了给我一个正面反馈以鼓励我继续创作哟! 阅读延伸 什么是Gitbook插件 如何创建Gitbook插件 如何测试Gitbook插件 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/plugin-develop.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/advance/export.html":{"url":"myGitbook/advance/export.html","title":"导出电子书","keywords":"","body":"导出电子书 gitbook 既可以将源码文件单独输出,也可以仅输出单个文件,常见的导出电子书格式主要有三种(ePub, Mobi, PDF),而这三种格式都依赖于系统本身提供的 ebook-convert 工具. 安装依赖 如果直接运行 gitbook pdf 相关命令,可能会报错,提示需要安装 ebook-convert 插件,根据提示本地需要安装 calibre 软件,这样 gitbook 才能正常导出电子书. calibre 官网: https://calibre-ebook.com/ linux 系统 下载地址: https://calibre-ebook.com/download_linux 下载应用 $ sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin 配置软链接 $ sudo ln -s /usr/bin/nodejs /usr/bin/node mac 系统 下载地址: https://calibre-ebook.com/download_osx 下载应用 将 calibre.app 移动到应用程序文件,然后尝试是否能正常打开应用. 配置软链接 $ sudo ln -s /Applications/calibre.app/Contents/MacOS/ebook-convert /usr/local/bin 测试命令 $ ebook-convert --version 如果没有输出 ebook-convert 版本信息,可能需要配置环境变量. windows 系统 下载地址 : https://calibre-ebook.com/download_windows 和一般的应用下载安装方式一样,无外乎选择一下软件安装位置和书籍存放目录,安装过程略过,动图演示. 软件安装前如果使用命令行运行 ebook-convert 相关命令会提示无法查找该命令,安装后需要重新打开新的命令行工具再次运行 ebook-convert --version 就能输出版本信息,表示安装成功. 只有新打开的命令行窗口运行 ebook-convert 才会生效1,原来的命令行窗口依旧没有会报错的呢,记住啦! 示例: Administrator@snowdreams1006 MINGW64 /f/workspace/snowdreams1006.github.io (master) $ ebook-convert --version ebook-convert.exe (calibre 3.46.0) Created by: Kovid Goyal 配置封面 所有格式的电子书都可以配置自定义封面,在项目的根目录下提供 cover.jpg 和 cover_small.jpg 两种封面图片时,生成电子书会自动增加封面页. 当然你也可以使用 autocover 插件 自动生成封面,不过本人才疏学浅,几经尝试始终没有成功,如果有人成功了记得给我留言下哈! 封面的基本要求: cover.jpg 尺寸大小: 1800X2360 px,cover_small.jpg 尺寸大小: 200x262 px; 无边界 清晰可见的书名 任何重要的文字在小版本封面图片中也要清晰可见 更多封面相关规范请参考 https://toolchain.gitbook.com/ebook.html 基本命令 语法格式: gitbook pdf 或 gitbook epub 或 gitbook mobi 示例: # 生成 `pdf` 文件并输出 `debug` 级别日志 $ gitbook pdf ./ ./myBook.pdf --log=debug # 生成 `epub` 文件并输出 `debug` 级别日志 $ gitbook epub ./ ./myBook.epub --log=debug # 生成 `mobi` 文件并输出 `debug` 级别日志 $ gitbook mobi ./ ./myBook.mobi --log=debug 相信大家对 PDF 格式比较熟悉,其余两种格式只是不同电子书格式,因而需要相应软件支持. 生成 PDF 文件 示例: $ gitbook pdf 默认在当前项目的根目录下生成 book.pdf 文件名,如果配有封面,则首页显示封面,否则无封面. 生成 ePub 文件 示例: $ gitbook epub 默认在当前项目的根目录下生成 book.epub 文件名,如果配有封面,则首页显示封面,否则无封面. 生成 mobi 文件 示例: $ gitbook mobi 默认在当前项目的根目录下生成 book.mobi 文件名,如果配有封面,则首页显示封面,否则无封面. 小结 本节主要介绍了如何导出电子书,概括来说,首先系统需要安装 ebook-convert 工具,然后配置电子书封面,最后直接导出为目标格式(ePub, Mobi, PDF)进行输出. 随着电子书内容越来越多,生成电子书所花费的时间也越来越久,实属正常,耐心等待即可. 输出 PDF 文件并输出 debug 日志: gitbook pdf --log=debug var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/export.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-03-25 15:30:56 "},"myGitbook/advance/publish.html":{"url":"myGitbook/advance/publish.html","title":"发布电子书","keywords":"","body":"发布电子书 输出目标文件 语法格式: gitbook build [book] [output] 默认情况下,gitbook 输出方式是静态网站,其实 gitbook 的输出方式有三种: website, json,和 ebook. 只不过另外两种不是很常用,更多情况下我们是使用静态网页搭建个人官网,或托管到第三方平台,或部署到私有云服务器,但不管怎么样,还是离不开生成这一步. 示例: # 默认输出格式: `website` $ gitbook build --format=website # 更改输出格式: `json` $ gitbook build --format=json # 更改输出格式: `ebook` $ gitbook build --format=ebook 默认情况下输出目录: _book/,整个项目的入口文件是: index.html 集成 github 网站 本教程的电子书源码和输出文件均托管到 github 网站,所以这里介绍下如何利用 Github Pages 静态网页服务与 gitbook 进行集成. 什么是 GitHub Pages ? Github Pages 是 github 网站推出的一种免费的静态网页托管服务,适合搭建静态的项目主页或个人官网. 其中,网站项目的源码直接托管在 github 仓库中,当仓库文件更新后,该仓库所关联的网站自动更新,从而实现了源码与官网的联动更新. 如果想了解更多详情,请参考官网: https://pages.github.com/ 怎么做 GitHub Pages ? 每个账号有且只有一个主页站点,但允许无限制多的项目站点. 啥是主页站点,项目站点又是啥? 别急,让我先举个例子看一下最终效果. 假如用户名: zhangsan 名下有四个公开仓库,一个仓库名叫做: zhangsan.github.io,另外三种分别是: project01,project02,project03 . 如果想要对外暴露上述四个仓库作为我们的静态网站,那么最终效果就是下面这样的. 主页站点: https://zhangsan.github.io 项目01站点: https://zhangsan.github.io/project01 项目02站点: https://zhangsan.github.io/project02 项目03站点: https://zhangsan.github.io/project03 注意将 zhangsan 替换成自己的 github 用户名,否则八成是打不开网站,除非真的有 zhangsan 这个用户. 其实上述规则很好理解,github 网站作为一个托管中心,有成千上万的用户在使用 github 并且每个用户的用户名都是唯一并且不同的,因此 *.github.io 通配符域名刚好充当命名空间. 可以预料的是,不仅仅有 .github.io 这种二级域名,说不定还有 api.github.io,docs.github.io 等等,毕竟只需要购买 *.github.io 通配符域名证书就可以支持任意多的二级域名了,感谢 github 赠送我们免费的 https 网站. 说到这里,不得不吐槽下 gitbook 的命名空间策略了,gitbook 也有自己的电子书托管服务,但访问地址是 .gitbook.io/ . 很显然,gitbook 没有区分主页站点和项目站点,相当于全部都是项目站点,缺少主次之分. 闲言少叙,既然知道了输入内容和输出效果,那么接下来的任务就是了解中间过程了,让我们一起探讨下怎么发布网站吧! 主页站点 创建 .github.io 公开仓库 前往 https://github.com/ 网站创建名为 .github.io 的公开仓库. 比如我的用户名是: snowdreams1006 ,那么我的主页站点仓库就是: snowdreams1006.github.io 创建首页 index.html 文件 不管是在线直接创建 index.html 还是克隆到本地创建 index.html ,最终的 .github.io 仓库一定要有 index.html 首页文件. 示例: # 克隆到本地 $ git clone https://github.com/username/username.github.io # 切换到项目 $ cd username.github.io # 创建 `index.html` 文件 $ echo \"Hello World\" > index.html # 推送到远程仓库 $ git add --all $ git commit -m \"Initial commit\" $ git push -u origin master 访问主页站点 https://username.github.io 打开浏览器,输入网址: https://username.github.io 访问主页站点,显示的内容正是我们刚刚提交的 index.html 文件内容. 如果没有正常显示,清除浏览器缓存强制刷新试试看! 项目站点 相比主页站点来说,项目站点命名比较随意了,作为静态网站不可或缺的文件仍然是 index.html. 创建首页 index.html 文件 创建首页文件并添加测试内容,方便待会在线访问项目站点测试是否部署成功. 设置 GitHub Pages 选项 点击仓库首页右上方设置(Settings)选项卡,往下翻到 GitHub Pages 选项,选择源码目录,根据实际情况选择源码来源于 master 分支还是其他分支或者docs/ 目录. 方便起见,选择第一个 master 分支即可,注意下面的主题和这一步的来源只能两者选其一,否则主题优先级更高! 访问主页站点 https://username.github.io/ 打开浏览器,输入网址: https://username.github.io/repository 访问项目站点,显示的内容正是我们刚刚提交的 index.html 文件内容. 如果没有正常显示,清除浏览器缓存强制刷新试试看! 如何集成 gitbook ? 我们已经知道 Github Pages 是提供静态网站的免费托管,而 gitbook 默认生成的内容就是静态网站,两者如何结合自然不用我多说了吧? gitbook 默认输出目录 _book/ 包括了静态网站所需的全部资源,其中就包括 index.html 首页文件. 因此我们只需要每次生成后将 _book/ 整个目录复制到项目根目录,那么推送到远程仓库时自然就是输出后静态网站了啊! 示例: # 生成静态网站 $ gitbook build # 复制到项目根目录 $ cp -r _book/* . # 添加到本地版本库 $ git add . $ git commit -m \"publish\" # 推送到远程仓库 $ git push origin master 现在登录 github 网站看一下静态网站是否成功上传以及访问主页站点或项目站点看一下最新内容是否成功渲染吧! 小结 本节我们学习 gitbook 有三种输出方式,其中默认的网页输出最为常用. 除此之外,还讲解了如何与 github pages 进行结合,从而实现源码和网站的自动更新维护. 如果源码没有托管到 github 这种第三方服务商,你也可以搭建自己的服务器,比如将 _book/ 目录全部扔到 nginx 服务器做静态资源服务器等. 毕竟,源码和输出内容都在你手中,想怎么玩还不是自己说了算? var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/advance/publish.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/openwrite/":{"url":"myGitbook/openwrite/","title":"公众号引流","keywords":"","body":"公众号引流 相信大多数博客作者都或多或少有过这样想法: 现在各种平台这么多,想要实现全平台发布就要到处复制粘贴,等我有空一定做统一平台一次性全部解决! 不知道正在阅读文章的你,有没有这样的想法? 反正我确实这么想过,甚至 github 上相关项目早已创建,可一直迟迟没有下一步,要么是工作忙,要么是技术储备不够,总有一大堆借口自我安慰! 如果只是专注于某一两家平台,这种需求可能不会那么强烈,可是如果你和我一样曾经手动复制粘贴过下面这么多平台,那么我相信你一定可以体会一文多发的迫切性! 幸运的是,在一文多发探索的路上并不孤单,不堪其扰的大佬们早已说干就干动手解决了这个问题,有的是开源平台,有的是 SAAS 服务,大家都在努力... 其中,SAAS 服务可能是最简单上手的方式了,这就是今天的主角: OpenWrite 一文多发平台! 如果你热衷于写文记录点滴、分享心得 如果你钟情于 markdown 的简洁、流畅与纯粹 如果你专注于内容创作,而对很多网络抄袭无可奈何 那么,希望 https://openwrite.cn/ 可以帮助你! 一文多发 OpenWrite 提供的众多功能中最吸引我的地方莫过于一文多发功能了: 我在用的平台它都有,我没用的平台它也有! 还是熟悉的 markdown 编辑器,便捷的自动认证功能,发布文章再也不用一处编辑,到处复制了,顺便解决了一直令人困扰的图片上传问题. 目前已提供的平台中涵盖了绝大部分技术博客平台,相信以后会支持更多平台的吧,再也不用复制粘贴那么多次了呢! 平台整体上使用体验非常不错,大致步骤是先提前登陆各大目标平台,然后通过 OpenWrite 提供的插件自动进行渠道认证,配置各大渠道后就可以愉快发文啦! 稍微摸索下就能很快上手,在这里不再赘述了,不了解的小伙伴们可以看看 技术文章博客,互联网运营平台 OpenWrite 公众号引流 如果你有自己独立博客,也在运营者微信公众号,但是苦于没有很好的手段引导读者关注公众号,那么Openwrite 推出的 ReadMore 工具绝对可以解决燃眉之急,真的可以说是良心之作! 效果怎么样看了就知道 静态博客网站集成 ReadMore 工具后,全站博客文章内容自动隐藏一半,同时浮现出阅读全文的按钮引导读者点击解锁. 一旦读者想要阅读全文就会主动点击按钮,此时就会自动弹出引导用户关注公众号的弹窗. 此时,用户有三种选择,要么扫码关注公众号解锁全站文章,要么掉头走人不再阅读,或者以其人之道还治其人之身,技术绕过直接解锁! 当然,我们自然是希望所有的读者都可以转换成公众号粉丝,所以接下来读者应该是关注公众号回复关键字获取验证码进而解锁文章. 读者关注公众号后,发送关键字获取文字链接并点击该链接,此时就会获取验证码,离成功只差一步! 再次回到博客平台的受限文章,输入刚刚获取到的验证码,不仅解锁了当前文章,博客内的其他文章也全部自动解锁,并不会造成不好体验,完美! 从陌生读者成公众号粉丝,整个操作流程一气呵成,没有丝毫卡顿也没有任何门槛,一切都是这么自然! 所以,如果你有自主运营的个人博客,想要转换成公众号粉丝,那么 ReadMore 工具简直就是躺增粉丝利器啊! 自主集成 ReadMore 工具集成步骤比较简单,按照相关官方教程说明,大致可以分为两步: 如果博客文章比较少的话,这么设置是任何没有问题的,大不了多复制一下就可以了. 但是如果博客文章比较多,肯定不能手动复制粘贴了,此时应该将该规则自动应用到全部文章中,如此一来,个人博客文章全部拥有该功能. 插件集成 熟悉了自主集成的基本思路后,不难发现,集成 ReadMore 工具只需要保证个人博客支持运行 Js 代码即可! 这个要求确实不高,哪怕是 Gitbook + Github Pages 搭建的静态网站也是支持运行 Js 代码的,更何况整合 Github 后还提供了免费域名,刚好满足 ReadMore 的条件. 说干就干,于是乎,花了一整晚的时间弄了 gitbook 插件来集成 ReadMore 工具. 安装 openwrite 插件 在 book.json 配置文件中,添加 openwrite 插件到 plugins 数组中,示例如下: { \"plugins\" : [\"openwrite\"] } 声明插件后需要添加相关配置信息,来源于 OpenWrite 后台,务必修改成自己真正的配置信息! { \"pluginsConfig\":{ \"openwrite\":{ \"blogId\": \"15702-1569305559839-744\", \"name\": \"雪之梦技术驿站\", \"qrcode\": \"https://snowdreams1006.github.io/snowdreams1006-wechat-public.jpeg\", \"keyword\": \"vip\" } } } 插件声明并配置后,通过 gitbook 或 npm 命令行方式安装 openwrite 插件到本地. $ gitbook install 或者 $ npm install gitbook-plugin-openwrite 运行 openwrite 插件 本地运行 gitbook serve 命令后,赶快验证下是否成功通过 gitbook-plugin-openwrite 插件集成 ReadMore 工具吧! 如果没有问题的话,运行 gitbook build 生成的目标文件上传到 github 或其他静态服务器就能轻松集成 ReadMore 工具! 如果可以的话,欢迎给 https://github.com/snowdreams1006/gitbook-plugin-openwrite 一个 Star ,告诉我的确有人在用! 集成思路 按照 OpenWrite 官方 ReadMore工具 集成指南,关于博客设置部分只需要将自己的专属配置信息插入到具体博客文章中即可实现集成. 但是,大多数博客平台编写博客文章时都是编写 markdown 而不是 html,因而 不太方便直接插入 js 代码,比较方便的做法是修改全局性质的模板文件. 而关于 gitbook 的模板文件位于根目录下的 _layouts/website/page.html ,所以要么直接修改模板,要么通过插件方式自定义模板! 本来打算直接修改 gitbook 模板文件,但是独乐乐不如众乐乐,所以还是采用插件的方式扩展吧! 核心代码如下,修改模板文件,在文章内容外面包裹一层 div 作为目标区域并运行集成 Js 代码片段. {% extends template.self %} {% block page %} {{ page.content|safe }} {% endblock %} {% block javascript %} {{ super() }} const btw = new BTWPlugin(); btw.init({ \"id\": \"vip-container\", \"blogId\": \"{{ config.pluginsConfig.openwrite.blogId }}\", \"name\": \"{{ config.pluginsConfig.openwrite.name }}\", \"qrcode\": \"{{ config.pluginsConfig.openwrite.qrcode }}\", \"keyword\": \"{{ config.pluginsConfig.openwrite.keyword }}\" }); {% endblock %} 实现思路还是比较简单明确的,这里简单对其中的细节做些解释说明. 构建目标区域 {% block page %} {{ page.content|safe }} {% endblock %} page.content 是每个页面当前的文件内容,不再是原生的 markdown 类型而是 html 类型,这一点非常重要,因为 gitbook 并不会处理 div 内嵌的 markdown 内容! 之前一直尝试想通过 Js 方式直接嵌套一层目标区域 div,但是只找到 markdown 异步转 html 的 api 导致无法集成,最后只能采用修改模板的方式. 而 {{ page.content|safe }} 表示的是当前页面的 html 内容,最后在原始内容外面嵌套一层 div 充当目标区域,其中 id=\"vip-container\". 插入集成代码 const btw = new BTWPlugin(); btw.init({ \"id\": \"vip-container\", \"blogId\": \"{{ config.pluginsConfig.openwrite.blogId }}\", \"name\": \"{{ config.pluginsConfig.openwrite.name }}\", \"qrcode\": \"{{ config.pluginsConfig.openwrite.qrcode }}\", \"keyword\": \"{{ config.pluginsConfig.openwrite.keyword }}\" }); {{ config.pluginsConfig.openwrite.blogId }} 表示读取的是 gitbook 关于 openwrite 插件的配置信息,这样一来集成代码就会自动插入到每一个页面中,从而省去了手动插入的麻烦,达到了自动化集成的目的. 上一步构建目标区域时设置了 id=\"vip-container\" ,在这一步直接使用了该区域唯一标示,所以该配置项不必暴露给外部用户,因此配置项中没有 id . 使用者集成参考 关于插件原理部分的相关介绍,如果不懂的话也没有关系,直接上手能够用就好,下面提供非常基础的示例: { \"title\": \"雪之梦技术驿站\", \"author\": \"snowdreams1006\", \"description\": \"雪之梦技术驿站又名snowdreams1006的技术小屋.主要分享个人的学习经验,一家之言,仅供参考.\", \"language\": \"zh-hans\", \"gitbook\": \"3.2.3\", \"plugins\": [ \"openwrite\" ], \"pluginsConfig\": { \"openwrite\":{ \"blogId\": \"15702-1569305559839-744\", \"name\": \"雪之梦技术驿站\", \"qrcode\": \"https://snowdreams1006.github.io/snowdreams1006-wechat-public.jpeg\", \"keyword\": \"vip\" } } } book.json 配置文件中的其他项可能省略了,这里只保留关于插件部分相关代码,完整示例参考: https://github.com/snowdreams1006/gitbook-plugin-openwrite/tree/master/example 虽然本教程基于 gitbook 提供的插件机制进行集成 ReadMore 工具,但基本思路也适合其他平台: 最方便的做法是基于模板固定目标区域,然后配置相应的 Js 集成代码. 当然,直接集成是非常简单的,如果是基于插件等集成形式以提供给更多人使用的话,那可能就要研究一下博客平台的接口文档了. 懒人直达 声明并配置 openwrite 插件 { \"plugins\": [ \"openwrite\" ], \"pluginsConfig\": { \"openwrite\":{ \"blogId\": \"your blogId\", \"name\": \"your name\", \"qrcode\": \"your qrcode\", \"keyword\": \"your keyword\" } } } 注意修改成自己的配置信息,来源于 OpenWrite 后台,点击使用后第二步关于博客设置! 安装 openwrite 插件到本地 $ gitbook install 运行本地服务 $ gitbook serve 如果本地运行发现没有问题的话,恭喜你成功集成了公众号引流功能,稍后运行 gitbook build 命令后就可以上传静态网站到目标服务器了呢! 总结寄语 总体来说,OpenWrite 作为一文多发平台是非常优秀的,对于多平台发布的小伙伴来说简直就是福音,但是假如你并没有一文多发的需求,那可能对你的帮助并不是很大,因为连复制粘贴都不需要了啊! 但是,作为一个有追求的技术分享者,个人博客用户转换成微信公众号粉丝这一需求应该说更加普遍,再次感谢 OpenWrite 提供的 ReadMore 工具,原以为只有动态博客才能玩的套路没想到也可以用到静态博客身上! 最后考虑到基于 Gitbook 搭建的静态博客市面上并不少,其他小伙伴可能也有类似需求,所以做成了 gitbook 插件共享给大家,gitbook-plugin-openwrite 希望对大家有所帮助! 项目地址: https://github.com/snowdreams1006/gitbook-plugin-openwrite 演示地址: https://github.com/snowdreams1006/gitbook-plugin-openwrite/tree/master/example 参考链接 https://openwrite.cn/ 还在搞公众号互推涨粉?OpenWrite推出增长神器,助你实现粉丝躺增! WordPress 博客整合导流工具,博客导流到微信公众号 谈一谈博客的关注解锁文章功能 牛掰!我是这么把博客粉丝转到公众号的 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/openwrite/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/seo/":{"url":"myGitbook/seo/","title":"优化搜索","keywords":"","body":"优化搜索 gitbook-plugin-baidu-ziyuan require(['gitbook'], function(gitbook){ gitbook.events.bind(\"start\", function(e,config){ (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName(\"script\")[0]; s.parentNode.insertBefore(bp, s); })(); }); }); gitbook-plugin-siteverification var tester = require('gitbook-tester'); tester.builder() .withContent('Test baidu and google') .withBookJson({ plugins: ['siteVerification'] }) .withBookJson({ pluginsConfig: { siteVerification: { baidu: '222' } } }) .create() .then(function(result) { console.log(result[0].content) }); 站点地图 npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save gitbook-plugin-sitemap-general gitbook-plugin-siteverification gitbook-plugin-push-bd hexo-generator-sitemap hexo-generator-baidu-sitemap https://github.com/robots.txt 搜索网站提交入口 百度搜索 谷歌搜索 360搜索 搜狗搜索 必应搜索 神马搜索 独立博客提交入口 百度搜索 谷歌搜索 站长平台 搜狗站长平台 360站长平台 百度搜索资源平台 谷歌 阅读更多 如何做好个人博客的seo呢 酒香也怕巷子深,教你一招,轻松让百度收录你的个人站点 hexo干货系列:(六)hexo提交搜索引擎(百度+谷歌) 让GitHub Pages博客支持百度搜索引擎收录 博客推广——提交搜索引擎 各大搜索引擎提交入口 搜索引擎提交入口 http://www.soshoulu.com/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/seo/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/issue/":{"url":"myGitbook/issue/","title":"常见问题","keywords":"","body":"常见问题 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/issue/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"myGitbook/issue/rm-output-directory.html":{"url":"myGitbook/issue/rm-output-directory.html","title":"热加载失败治标之法","keywords":"","body":"热加载失败治标之法 破镜如何贴花黄 gitbook 在 Windows 系统无法热加载,总是报错! gitbook 是一款文档编写利器,可以方便地 markdown 输出成美观优雅的 html ,gitbook serve 启动服务器后,原来相貌平平的 markdown 丑小鸭摇身一变就成了倾国倾城的 html 绝色佳人. 如果源文件发生更改,Windows 却无法按照预期那样重启服务器,直接抛出一个异常,立即终止了 markdown 的化妆. Restart after change in file README.md Stopping server events.js:183 throw er; // Unhandled 'error' event ^ Error: EPERM: operation not permitted, lstat 'F:\\workspace\\private-cloud-backup\\gitbook-test\\_book' 对镜贴花黄 现在看一下 markdown 灰姑娘变身 html 小姐姐的神奇过程吧! $ gitbook serve --log=debug Live reload server started on port: 35729 Press CTRL+C to quit ... debug: readme found at README.md debug: summary file found at SUMMARY.md debug: cleanup folder \"G:\\sublime\\gitbook-test\\_book\" info: 7 plugins are installed info: loading plugin \"livereload\"... OK ... info: loading plugin \"theme-default\"... OK info: found 1 pages info: found 0 asset files debug: calling hook \"config\" debug: calling hook \"init\" debug: copy assets from theme C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-theme-default\\_assets\\website ... debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-livereload\\book debug: generate page \"README.md\" debug: calling hook \"page:before\" debug: calling hook \"page\" debug: index page README.md debug: calling hook \"finish:before\" debug: calling hook \"finish\" debug: write search index info: >> generation finished with success in 1.5s ! Starting server ... Serving book on http://localhost:4000 根据上述输出日志,我们可以分析出 gitbook 的基本运行流程. 加载 readme 和 summary 文件,若存在 glossary 文件也会加载,并删除 _book 目录 debug: readme found at README.md debug: summary file found at SUMMARY.md debug: cleanup folder \"G:\\sublime\\gitbook-test\\_book\" 加载依赖插件,若没有找到相应插件会报错,提示运行 gitbook install 安装插件. info: 7 plugins are installed info: loading plugin \"livereload\"... OK info: loading plugin \"highlight\"... OK info: loading plugin \"search\"... OK info: loading plugin \"lunr\"... OK info: loading plugin \"sharing\"... OK info: loading plugin \"fontsettings\"... OK info: loading plugin \"theme-default\"... OK 扫描页面和静态资源文件 info: found 1 pages info: found 0 asset files 读取配置文件并初始化 debug: calling hook \"config\" debug: calling hook \"init\" 拷贝样式资源和插件资源 debug: copy assets from theme C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-theme-default\\_assets\\website debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-fontsettings\\assets debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-sharing\\assets debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-lunr\\assets debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-search\\assets debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-highlight\\css debug: copy resources from plugin C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\gitbook-plugin-livereload\\book 开始生成单独页面,依次执行 page:before ,page 回调函数,全部页面执行完毕后执行 finish:before 和 finish 回调函数. debug: generate page \"README.md\" debug: calling hook \"page:before\" debug: calling hook \"page\" debug: index page README.md debug: calling hook \"finish:before\" debug: calling hook \"finish\" 生成搜索文件 debug: write search index 启动完毕,输出成功信息 Starting server ... Serving book on http://localhost:4000 默认情况下服务器启动后会占用两个端口,一个是对外暴露的 4000 端口,用于浏览器访问项目. 另外一个是 35729 端口,用于监听本地文件变化,重启服务器进而实现热加载功能. 本地服务器启动后我们就可以访问 http://localhost:4000 预览静态网站效果,markdown 源文件华丽演变成 html 富文本文件. 破镜怎化妆 不幸的是,Windows 热加载可能会有问题,也就是说如果启动服务器后,本地文件发生改变,此时会触发热加载功能而报错 Error: EPERM: operation not permitted ,这样一来浏览器又无法访问了. 刚刚变身的 markdown 瞬间又被打回原形,无法欣赏化妆后的容颜了,这样的体验相当不好! 边化妆边照镜子才是做到心中有谱,随时调整,如果不照镜子而直接化妆,那不是一般人能做到的. gitbook 启动本地服务器给我们提供了镜子,但热加载失败又把镜子摔碎了,还怎么愉快的化妆? Restart after change in file README.md Stopping server debug: readme found at README.md debug: summary file found at SUMMARY.md debug: cleanup folder \"G:\\sublime\\gitbook-test\\_book\" events.js:174 throw er; // Unhandled 'error' event ^ Error: EPERM: operation not permitted, lstat 'G:\\sublime\\gitbook-test\\_book' Emitted 'error' event at: at FSWatcher._handleError (C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\index.js:236:10) at ReaddirpReadable.emit (events.js:189:13) at Immediate. (C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\node_modules\\readdirp\\stream-api.js:82:32) at runCallback (timers.js:705:18) at tryOnImmediate (timers.js:676:5) at processImmediate (timers.js:658:5) 寻医问诊修破镜 现在问题已经复现,接下来就要开始寻医问诊,试图让破镜重圆,好让 markdown 灰姑娘变成人见人爱的 html 小姐姐. 根据报错信息描述,定位到删除 _book 目录再次创建该目录时,提示 EPERM: operation not permitted ,即无权操作. 柯南附体 既然说是操作权限的问题,那我们看一下 _book 目录现在是怎样状态吧! $ ls gitbook-errorforwindows-preview.png README.md SUMMARY.md 当前项目已经没有 _book 目录,证明发生报错时确实已经删除了 _book 目录,但是某种原因无权再次创建该文件夹而重启失败. 然而,这只是表现现象,老师告诉我们,要透过现象看本质,即使现在没有 _book 文件再次启动服务器还是会启动成功并创建 _book 文件的,所以真想只有一个! 那就是,gitbook 控制台在说谎! 虽然排除了 gitbook 无权创建 _book 目录的嫌疑,那又怎么解释重启服务器却没能创建 _book目录这件事呢? debug: cleanup folder \"G:\\sublime\\gitbook-test\\_book\" events.js:174 throw er; // Unhandled 'error' event ^ Error: EPERM: operation not permitted, lstat 'G:\\sublime\\gitbook-test\\_book' Emitted 'error' event at: at FSWatcher._handleError (C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\index.js:236:10) at ReaddirpReadable.emit (events.js:189:13) at Immediate. (C:\\Users\\snowdreams1006\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\node_modules\\readdirp\\stream-api.js:82:32) at runCallback (timers.js:705:18) at tryOnImmediate (timers.js:676:5) at processImmediate (timers.js:658:5) 先看一下 FSWatcher._handleError 异常信息: sed -n \"223,239p\" ~/.gitbook/versions/3.2.3/node_modules/chokidar/index.js . 分析发现: FSWatcher._handleError 是私有方法,作用是处理异常信息,和这起事故关联不大. Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master) $ sed -n \"223,239p\" ~/.gitbook/versions/3.2.3/node_modules/chokidar/index.js // Private method: Common handler for errors // // * error - object, Error instance // // Returns the error if defined, otherwise the value of the // FSWatcher instance's `closed` flag FSWatcher.prototype._handleError = function(error) { var code = error && error.code; var ipe = this.options.ignorePermissionErrors; if (error && code !== 'ENOENT' && code !== 'ENOTDIR' && (!ipe || (code !== 'EPERM' && code !== 'EACCES')) ) this.emit('error', error); return error || this.closed; }; 我们接着往下找,再看一下 ReaddirpReadable.emit (events.js:189:13) ,这里没有给出文件的具体路径,所以暂时无法定位. 那我们再看下一个 Immediate. : sed -n \"78,96p\" ~/.gitbook/versions/3.2.3/node_modules/chokidar/node_modules/readdirp/stream-api.js Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master) $ sed -n \"78,96p\" ~/.gitbook/versions/3.2.3/node_modules/chokidar/node_modules/readdirp/stream-api.js proto._handleFatalError = function (err) { var self = this; setImmediate(function () { if (self._paused) return self._errors.push(err); if (!self._destroyed) self.emit('error', err); }); } function createStreamAPI () { var stream = new ReaddirpReadable(); return { stream : stream , processEntry : stream._processEntry.bind(stream) , done : stream._done.bind(stream) , handleError : stream._handleError.bind(stream) , handleFatalError : stream._handleFatalError.bind(stream) }; } 遗憾的是,仍然没有找到具体问题,那就继续看一下一条线索. timers.js:705:18 和 events.js:189:13 都没有显示具体的文件位置,如果也在 chokidar 模块的话就好了. Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master) $ tree -P \"events.js\" --prune ~/.gitbook/versions/3.2.3/ /c/Users/Administrator/.gitbook/versions/3.2.3/ └── node_modules ├── cheerio │ └── node_modules │ └── jsdom │ └── lib │ └── jsdom │ └── level2 │ └── events.js └── gitbook-plugin-theme-default └── src └── js └── core └── events.js 11 directories, 2 files Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master) $ tree -P \"timers.js\" --prune ~/.gitbook/versions/3.2.3/ /c/Users/Administrator/.gitbook/versions/3.2.3/ 0 directories, 0 files git-bash 命令行正常没有 tree 命令,如需扩展参考我另外一篇文章. 经过肉眼验证,发现 events.js 根本就没有 174 行文件,所以这两个文件大都不是目标文件. 既然命令行中无法找到目标文件,那就请专业的搜索工具全系统查找这两个文件吧,这里使用的是 Everything 搜索工具. 然并卵,依然没有找到目标文件. 毕竟不是柯南,没有发现真相 求助官方 gitbook 可是开源产品,出现问题的应该不止我一个,所以去 github 看看有没有遇到和我一样的问题. 虽然找到了志同道合的小伙伴,但是并没有提供解决方案,连官方都放弃了,那我还有什么可留恋的? 点击查看 gitbook serve livereload error 自己动手 最害怕的不是 bug,而是发现了 bug 却无法定位,虽然控制台有报错信息但是没有找到真正的文件! 首先确认下当前系统版本,然后采取版本切换方式测试其他版本是否存在该问题. $ gitbook --version CLI version: 2.3.2 GitBook version: 3.2.3 升级到最新版 gitbook ls 是列出当前已安装的版本,而 gitbook ls-remote 则是列出远程服务器版本. # 列出本地已安装版本 $ gitbook ls GitBook Versions Installed: * 3.2.3 Run \"gitbook update\" to update to the latest version. # 列出远程可用版本 $ gitbook ls-remote Available GitBook Versions: 4.0.0-alpha.6, 4.0.0-alpha.5, 4.0.0-alpha.4, 4.0.0-alpha.3, 4.0.0-alpha.2, 4.0.0-alpha.1, 3.2.3, 3.2.2, 3.2.1, 3.2.0, 3.2.0-pre.1, 3.2.0-pre.0, 3.1.1, 3.1.0, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-pre.15, 3.0.0-pre.14, 3.0.0-pre.13, 3.0.0-pre.12, 3.0.0-pre.11, 3.0.0-pre.10, 3.0.0-pre.9, 3.0.0-pre.8, 3.0.0-pre.7, 3.0.0-pre.6, 3.0.0-pre.5, 3.0.0-pre.4, 3.0.0-pre.3, 3.0.0-pre.2, 3.0.0-pre.1, 2.6.9, 2.6.8, 2.6.7, 2.6.6, 2.6.5, 2.6.4, 2.6.3, 2.6.2, 2.6.1, 2.6.0, 2.5.2, 2.5.1, 2.5.0, 2.5.0-beta.7, 2.5.0-beta.6, 2.5.0-beta.5, 2.5.0-beta.4, 2.5.0-beta.3, 2.5.0-beta.2, 2.5.0-beta.1, 2.4.3, 2.4.2, 2.4.1, 2.4.0, 2.3.3, 2.3.2, 2.3.1, 2.3.0, 2.2.0, 2.1.0, 2.0.4, 2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-beta.5, 2.0.0-beta.4, 2.0.0-beta.3, 2.0.0-beta.2, 2.0.0-beta.1, 2.0.0-alpha.9, 2.0.0-alpha.8, 2.0.0-alpha.7, 2.0.0-alpha.6, 2.0.0-alpha.5, 2.0.0-alpha.4, 2.0.0-alpha.3, 2.0.0-alpha.2, 2.0.0-alpha.1 Tags: latest : 2.6.9 pre : 4.0.0-alpha.6 目前最新发布版本是 3.2.3 ,而我们本地已安装的版本正是该版本,所以现在应该测试 4.0.0-alpha.6 版. 看到 4.0.0-alpha.6 心里有些忐忑,根据版本管理约定,版本号一般有三部分组成,第一部分代表不兼容的重大升级,第二部分代表主干兼容的功能升级,第三部分是小版本修复. 由 3.2.3 直接跨度到 4.0.0-alpha.6 意味着 gitbook 发生了重大重构! 算了,先下载试试看! gitbook fetch 下载 和 gitbook update升级,两种方式都可以体验最新版本,这里选择下载方式方便进行不同版本的切换. # 下载 `4.0.0-alpha.6` 版本 $ gitbook fetch 4.0.0-alpha.6 Installing GitBook 4.0.0-alpha.6 gitbook@4.0.0-alpha.6 C:\\Users\\SNOWDR~1\\AppData\\Local\\Temp\\tmp-8912hSrxNvTCrFEH\\node_modules\\gitbook ├── escape-html@1.0.3 ├── escape-string-regexp@1.0.5 ├── destroy@1.0.4 ├── ignore@3.1.2 └── ied@2.3.6 (lodash.memoize@4.1.2, lodash.frompairs@4.0.1, force-symlink@0.0.2, semver@5.7.0, minimist@1.2.0, node-uuid@1.4.8, npm-package-arg@4.2.1, source-map-support@0.4.18, ora@0.2.3, easy-table@1.1.1, rimraf@2.6.3, tar-fs@1.16.3, gunzip-maybe@1.4.1, init-package-json@1.10.3, rxjs@5.0.0-rc.1, needle@1.0.0, node-pre-gyp@0.6.39, node-gyp@3.8.0) GitBook 4.0.0-alpha.6 has been installed 先看一下本地安装 gitbook 版本,确保待会运行时使用最新的 4.0.0-alpha.6 版本. # 列出本地已安装版本 $ gitbook ls GitBook Versions Installed: * 4.0.0-alpha.6 3.2.3 Run \"gitbook update\" to update to the latest version. # 列出当前正在使用版本 $ gitbook current GitBook version is 3.2.3 gitbook serve --gitbook=4.0.0-alpha.6 --log=debug 运行 4.0.0-alpha.6 版本并打印 debug 级别日志. 意外的是,竟然没有连启动都没启动成功,提示无法打开 ~\\.gitbook\\versions\\4.0.0-alpha.6\\node_modules\\gitbook-plugin-livereload\\_assets\\plugin.js 文件. 回想到版本号规范,可能 v3 到 v4 更改比较大,版本不兼容吧,重新初始化项目试试看! # 初始化项目并指定 `gitbook` 运行版本 $ gitbook init --gitbook=4.0.0-alpha.6 Warning: Accessing PropTypes via the main React package is deprecated, and will be removed in React v16.0. Use the latest available v15.* prop-types package from npm instead. For info on usage, compatibility, migration and more, see https://fb.me/prop-types-docs info: create SUMMARY.md info: initialization is finished 然而,仍然还是同样的报错,依旧无法启动. $ gitbook serve --gitbook=4.0.0-alpha.6 --log=debug Warning: Accessing PropTypes via the main React package is deprecated, and will be removed in React v16.0. Use the latest available v15.* prop-types package from npm instead. For info on usage, compatibility, migration and more, see https://fb.me/prop-types-docs Live reload server started on port: 35729 Press CTRL+C to quit ... ... Error: ENOENT: no such file or directory, open 'C:\\Users\\snowdreams1006\\.gitbook\\versions\\4.0.0-alpha.6\\node_modules\\gitbook-plugin-livereload\\_assets\\plugin.js' 此路不通,再换一条,既然向上无法处理,那向下回退会不会有结果呢? 回退版本 当前系统版本是 3.2.3,最新测试版本是 4.0.0-alpha.6 ,然而最近一次提交的版本却是 2.6.9 ? 为什么 gitbook-ci 管理的 gitbook 版本号会突然跳水,会不会有什么猫腻,难不成修复了什么 bug ? $ gitbook ls-remote Available GitBook Versions: 4.0.0-alpha.6, 4.0.0-alpha.5, 4.0.0-alpha.4, 4.0.0-alpha.3, 4.0.0-alpha.2, 4.0.0-alpha.1, 3.2.3, 3.2.2, 3.2.1, 3.2.0, 3.2.0-pre.1, 3.2.0-pre.0, 3.1.1, 3.1.0, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-pre.15, 3.0.0-pre.14, 3.0.0-pre.13, 3.0.0-pre.12, 3.0.0-pre.11, 3.0.0-pre.10, 3.0.0-pre.9, 3.0.0-pre.8, 3.0.0-pre.7, 3.0.0-pre.6, 3.0.0-pre.5, 3.0.0-pre.4, 3.0.0-pre.3, 3.0.0-pre.2, 3.0.0-pre.1, 2.6.9, 2.6.8, 2.6.7, 2.6.6, 2.6.5, 2.6.4, 2.6.3, 2.6.2, 2.6.1, 2.6.0, 2.5.2, 2.5.1, 2.5.0, 2.5.0-beta.7, 2.5.0-beta.6, 2.5.0-beta.5, 2.5.0-beta.4, 2.5.0-beta.3, 2.5.0-beta.2, 2.5.0-beta.1, 2.4.3, 2.4.2, 2.4.1, 2.4.0, 2.3.3, 2.3.2, 2.3.1, 2.3.0, 2.2.0, 2.1.0, 2.0.4, 2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-beta.5, 2.0.0-beta.4, 2.0.0-beta.3, 2.0.0-beta.2, 2.0.0-beta.1, 2.0.0-alpha.9, 2.0.0-alpha.8, 2.0.0-alpha.7, 2.0.0-alpha.6, 2.0.0-alpha.5, 2.0.0-alpha.4, 2.0.0-alpha.3, 2.0.0-alpha.2, 2.0.0-alpha.1 Tags: latest : 2.6.9 pre : 4.0.0-alpha.6 带着这些疑问,不妨下载 2.6.9 版本试试,看一下能否热加载? gitbook serve --log=debug --gitbook=2.6.9 指定 gitbook 版本,依旧失败! $ gitbook serve --log=debug --gitbook=2.6.9 Error loading version latest: Error: Cannot find module 'q' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15) at Function.Module._load (internal/modules/cjs/loader.js:508:25) at Module.require (internal/modules/cjs/loader.js:637:17) at require (internal/modules/cjs/helpers.js:22:18) at Object. (C:\\Users\\myHome\\.gitbook\\versions\\2.6.9\\lib\\index.js:3:9) at Module._compile (internal/modules/cjs/loader.js:701:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10) at Module.load (internal/modules/cjs/loader.js:600:32) at tryModuleLoad (internal/modules/cjs/loader.js:539:12) at Function.Module._load (internal/modules/cjs/loader.js:531:3) TypeError: Cannot read property 'commands' of null 重回现场 现在把目光再次聚焦到最初的案发现场,这一次只能背水一战了,自己动手要么丰衣足食要么饿死冻死! Stopping server debug: readme found at README.md debug: summary file found at SUMMARY.md debug: cleanup folder \"G:\\sublime\\private-cloud-backup\\gitbook-test\\_book\" events.js:174 throw er; // Unhandled 'error' event ^ Error: EPERM: operation not permitted, lstat 'G:\\sublime\\private-cloud-backup\\gitbook-test\\_book' Emitted 'error' event at: at FSWatcher._handleError (C:\\Users\\myHome\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\index.js:236:10) at ReaddirpReadable.emit (events.js:189:13) at Immediate. (C:\\Users\\myHome\\.gitbook\\versions\\3.2.3\\node_modules\\chokidar\\node_modules\\readdirp\\stream-api.js:82:32) at runCallback (timers.js:705:18) at tryOnImmediate (timers.js:676:5) at processImmediate (timers.js:658:5) 关于上述错误描述中,在真相只有一个章节中已经探讨过,当时得出的结论是 gitbook 是删除 _book 文件夹再新建 _book 文件夹时发生了意外. 如果这个行为不是由 gitbook 发生而是由我们手动干预的话,也就是说,当成功启动本地服务器后并在即将发生热加载之前,此时人为删除 _book 文件夹,会发生什么? 我的猜想是: 因为 gitbook 的热加载机制是监听本地文件目录系统发生改变,进而停止服务器再重新启动服务器. 当我们手动删除了 _book 文件夹,对于 gitbook 来说,再触发重启服务器的那一刻来说,突然发现没有 _book 文件夹,此时就不会删除也不会新建时发生异常,相当于直接新建 _book 文件夹,变相把热加载弄成了初始启动模式! 希望苍天不负我,如若不行,只能看源码逻辑找 bug 了! 你猜猜会怎么样? it works ! 在实验中,gitbook serve --log=debug 启动本地服务器后,如果本地文件发生修改会重启失败! 但是,如果在启动本地服务器后立即删除 _book 目录,当本地文件发生修改时重启服务就能成功了. 到此为止,总算找到一个解决方案,那就是启动服务后立即删除 _book 目录. 不算完美的总结 windows 系统上启动 gitbook 服务后,如果本地文件发生更改,热加会失败. 如果启动服务器后立即删除 _book 目录,那么之后再怎么修改本地文件都能顺利重启. 目前还没有找到问题的根源,下一次将深入源码继续探讨到底是哪里出问题导致 Windows 系统无法重启. 虽然及时删除 _book 目录并不算是很好的解决方案,但至少 markdown 灰姑娘又能化妆成 html 小姐姐了呢! C:\\Users\\Administrator.gitbook\\versions\\3.2.3\\lib\\output\\website GitBook运行报错 - no such file or directory var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/issue/rm-output-directory.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-04-08 23:06:43 "},"myGitbook/issue/modify-default-fold.html":{"url":"myGitbook/issue/modify-default-fold.html","title":"初始化默认折叠效果","keywords":"","body":"初始化默认折叠效果 Gitbook 是一款产品文档构建工具,也可以用于构建个人博客,默认情况下电脑端访问时左侧菜单是展开状态,可偏偏有人想要实现默认折叠效果,于是诞生了这篇文章! 善良的我选择帮助别人 可能是网上关于 Gitbook 的教程相对来说有些落后,加上写文章时分享了不少关于 gitbook 系列教程,因此关注我的粉丝好友中有不少是来源于 Gitbook. 所以上个月有个好友问我能不能配置 Gitbook 默认折叠的效果,心里有些犯难,作为 gitbook 的忠实粉丝,我都不知道 gitbook 还有这方面的配置?! 但是,善良的我总是有求必应,不忍心拒绝小白用户,于是我便抱着试一试的心态开始研究一下如何默认折叠? 当然,解决问题前还是要先复现一下问题,然后在命令行中熟练敲入了 gitbook serve 命令来启动本地服务器,为了排除缓存等影响,特意打开了 Chrome 浏览器的无痕模式,果不其然默认左侧菜单是展开的! 「雪之梦技术驿站」: 不能复现的问题都不是我的问题,拒绝解决此类问题,搞不好是你自己环境搭建问题呢! 蓦然回首官方文档已走 问题复现后就要开始寻求解决之道,虽然印象中并没有相关配置,但是难保记忆混乱遗漏了某些配置项,所以还是先看看官方文档怎么说的吧! 但是,当你在浏览器中输入 gitbook 官方文档 时,并找不到想象中的官方文档而是新版官网,不信你自己去搜一下,肯定是新版官网. 当你自以为找到了官网时,点击进去查看文档部分,很遗憾,这是新版文档并不是老版文档,你还会继续百度一下寻求可用链接期待找到官方文档. 为了节省宝贵时间,这里推荐访问个人维护的 gitbook 文档,点击访问: gitbook 原版文档 目前提供了中英文两个语言版本的文档,相信可以满足大多数用户的需求了,选择任意一种语言后点击进入翻阅相关设置. 实际上,官方文档也并没有什么用,因为根本就没有提到过如何更改相关配置使其默认折叠而非展开状态. 「雪之梦技术驿站」: 官方不再维护旧版文档,费尽心机找到旧版文档也无济于事,因为并没有提及到相关配置,所以猜测很可能并未提供有关配置项! 百度一下你就知道了吗 俗话说:\"互联网上绝大多数问题别人都已经遇到过并提供了解决方案,我们唯一要做的就是找到它!\" 这也是面向搜索编程的核心思想,遇到默认折叠问题应该也不会例外,那就搜索一下吧! 虽然百度搜索出现了一些相关文章,但是却不是我们想要的效果,大多数是基于 gitbook 插件实现的目录折叠效果,并不是默认折叠左侧菜单效果. 不管是换关键词重新搜索还是谷歌搜索,均未发现有关默认折叠左侧菜单的解决方案,难不成面向搜索失败了,要做解决问题的第一人吗?! 「雪之梦技术驿站」: 多次重复搜索操作均为找到解决方案,由此可见真的很少有人想要默认折叠左侧菜单,我也是很佩服提出该问题的小伙伴骨骼惊奇啊! 自力更生找寻蛛丝马迹 既然依靠别人无法解决问题,那么只能自力更生独自解决问题,是时候考验真正的技术了! 为了排除无关干扰,不能再用自己的 gitbook 项目了,毕竟文件太多不方便后续调试,那么不妨重新创建一个测试项目. 创建测试项目 $ mkdir test && cd test 初始化测试项目 $ gitbook init 启动测试项目 $ gitbook serve 虽然一片空白,并没有什么实质性内容,但是大道至简,对于我们复现并测试问题来说,足够了! 打开 Chrome 浏览器并按下 F12 开启调试模式,鼠标选中左侧的 Elements 元素选项卡并点亮左侧的小鼠标,然后在页面上找到左侧图标按钮,于是选中元素高亮了. 单独摘录 Html 关键代码如下: 稍微熟悉前端的小伙伴可能很轻松就能明白 a 标签的 class 属性表示的含义,见名知意,可以这么解释: btn 应该是控制外观的样式,表现得像是按钮效果. pull-left 应该是控制元素的位置,拉倒左边. js-toolbar-action 应该是控制元素的行为,js 工具栏行为动作. 由此可见,点击该图标实现左侧菜单折叠/展开效果应该是 .js-toolbar-action 在起作用,也就是说某一段 js 肯定是针对该 class 进行了监听! 此时,点击右侧的 Event Listeners 选项卡查看该元素已监听的 click 事件,定位到是哪一个具体的 js 文件在起作用. 果不其然,元素上存在 click 点击事件监听并且发现执行监听的逻辑代码出现在 theme.js 文件,点击进入文件查看具体内容. 压缩后的 js 代码不具备可读性,点击左下方的 {} 图标可以进行代码格式化,但是可能不是单纯的压缩而是进行了丑化或者混淆代码之类的逻辑,格式化后的代码仍然不可读! 「雪之梦技术驿站」: 终于发现了蛛丝马迹,修改的代码逻辑就隐藏在 theme.js 文件中,只要找到相关源码重新编译输出 theme.js 文件并替换应该就能实现默认折叠效果! 不要担心黎明前的黑暗 根据目前已掌握的线索,可以肯定的是有用线索主要有两个: 监听元素 .js-toolbar-action 输出文件 theme.js 一个是源码文件,另一个是输出文件,想要在庞大的 gitbook 项目中迅速定位到相关代码逻辑,个人能力有限,并不熟悉前端开发调试流程,因此采用最简单粗暴傻瓜式搜索方式进行排查! 「雪之梦技术驿站」: 如果读者对于现代前端开发流程比较属性的话,大概过一遍项目结构应该就可以调试定位问题了,用不着像我这样傻瓜式搜索排查! 查看当前 gitbook 版本 $ gitbook current GitBook version is 3.2.3 找到 gitbook 安装位置 gitbook 一般安装在 ~/.gitbook/versions/3.2.3 目录,其中 ~ 表示用户家目录. $ open ~/.gitbook/versions/3.2.3 选择一款熟悉的编辑器并打开 Gitbook 安装目录,这里以 sublime 编辑器为例,选中项目后右键全局搜索关键字 js-toolbar-action 期望找到相关源码文件. 全局搜索后主要出现两个文件包含 js-toolbar-action 关键字,一个是输出文件 theme.js ,另一个是源码文件 toolbar.js . Searching 19744 files for \"js-toolbar-action\" /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/_assets/website/theme.js: ... /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/theme/toolbar.js: ... 4 matches across 2 files 可想而知,源码文件肯定是经过编译处理后统一打包输出,因此不仅仅要找到源码文件还要掌握如何编译. 「雪之梦技术驿站」: 定位到当前 gitbook 目录后借助全局搜索功能定位到具体的文件路径,起作用的是 gitbook-plugin-theme-default 项目,其实这就是 Gitbook 的默认主题. 源码在哪 /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/theme/toolbar.js : // Update a button function updateButton(opts) { var $result; var $toolbar = $('.book-header'); var $title = $toolbar.find('h1'); // Build class name var positionClass = 'pull-'+opts.position; // Create button var $btn = $('', { 'class': 'btn', 'text': opts.text? ' ' + opts.text : '', 'aria-label': opts.label, 'href': '#' }); // Bind click $btn.click(opts.onClick); // Prepend icon if (opts.icon) { $('', { 'class': opts.icon }).prependTo($btn); } // Prepare dropdown if (opts.dropdown) { var $container = $('', { 'class': 'dropdown '+positionClass+' '+opts.className }); // Add button to container $btn.addClass('toggle-dropdown'); $container.append($btn); // Create inner menu var $menu = createDropdownMenu(opts.dropdown); // Menu position $menu.addClass('dropdown-'+(opts.position == 'right'? 'left' : 'right')); $container.append($menu); $result = $container; } else { $btn.addClass(positionClass); $btn.addClass(opts.className); $result = $btn; } $result.addClass('js-toolbar-action'); if ($.isNumeric(opts.index) && opts.index >= 0) { insertAt($toolbar, '.btn, .dropdown, h1', opts.index, $result); } else { $result.insertBefore($title); } } // Update all buttons function updateAllButtons() { $('.js-toolbar-action').remove(); buttons.forEach(updateButton); } 粗略看一下,上述代码是实现触发左侧图标折叠/展开菜单的逻辑实现,这里只是具体实现还不知道谁是使用者,也就是说这种逻辑是在哪里调用的? 只能继续顺藤摸瓜,往上翻看,根据基本开发常识,在该文件的同级目录中存在如下文件,其中的 index.js 应该就是入口文件: snowdreams1006s-MacBook-Pro:theme snowdreams1006$ tree . . ├── dropdown.js ├── index.js ├── keyboard.js ├── loading.js ├── navigation.js ├── platform.js ├── sidebar.js └── toolbar.js 0 directories, 8 files snowdreams1006s-MacBook-Pro:theme snowdreams1006$ ` 打开 index.js 文件,根据注释我们可以看到 init() 函数是入门函数,其中 sidebar.init() 和 sidebar.toggle() 函数无不说明 sidebar.js 和 toolbar.js 关系密切,完全有理由猜想 sidebar.js 是 toolbar.js 的使用者! function init() { // Init sidebar sidebar.init(); // Init keyboard keyboard.init(); // Bind dropdown dropdown.init(); // Init navigation navigation.init(); // Add action to toggle sidebar toolbar.createButton({ index: 0, icon: 'fa fa-align-justify', onClick: function(e) { e.preventDefault(); sidebar.toggle(); } }); } 打开 sidebar.js 文件并查看 init() 初始化函数和 toggle() 触发函数,可以验证我们的猜想,这里就是控制中心! // Prepare sidebar: state and toggle button function init() { // Init last state if not mobile if (!platform.isMobile()) { toggleSidebar(gitbook.storage.get('sidebar', true), false); } // Close sidebar after clicking a link on mobile $(document).on('click', '.book-summary li.chapter a', function(e) { if (platform.isMobile()) toggleSidebar(false, false); }); } 「雪之梦技术驿站」: 非手机端初始化上次状态,默认展开侧边栏,如果是手机端则折叠侧边栏.其中 toggleSidebar() 接收两个参数,第一次参数表示是展开还是折叠,第二个参数暂不可知. // Toggle sidebar with or withour animation function toggleSidebar(_state, animation) { if (gitbook.state != null && isOpen() == _state) return; if (animation == null) animation = true; gitbook.state.$book.toggleClass('without-animation', !animation); gitbook.state.$book.toggleClass('with-summary', _state); gitbook.storage.set('sidebar', isOpen()); } 「雪之梦技术驿站」: 第一个参数确实表示状态而第二个参数表示是否有动画效果,不用看具体代码逻辑而是看注释就能猜出大概逻辑了. 通过上述分析,我们可以得知 init() 初始化函数决定了默认行为是折叠还是展开,同时 gitbook.storage.set('sidebar', isOpen()) 和 gitbook.storage.get('sidebar', true) 应该是设置和获取是否展开菜单的标志! 由此,如果想要默认折叠左侧菜单,那么只需要设置成 gitbook.storage.set('sidebar', false) 应该就会生效! 如何编译 说干就干,于是乎在 init() 函数插入 gitbook.storage.set('sidebar', false) 默认折叠逻辑,接着看一下是否需要重新编译才能生效? // Prepare sidebar: state and toggle button function init() { // Close sidebar as default state gitbook.storage.set('sidebar', false); // Init last state if not mobile if (!platform.isMobile()) { toggleSidebar(gitbook.storage.get('sidebar', true), false); } // Close sidebar after clicking a link on mobile $(document).on('click', '.book-summary li.chapter a', function(e) { if (platform.isMobile()) toggleSidebar(false, false); }); } 接着切换到测试项目再次运行 gitbook serve 启动本地服务器,发现并没有任何变化,很有可能改变源码文件需要重新编译才会生效或者说更改的源码项目也没有生效? 「雪之梦技术驿站」: 该源码文件所在的项目是 gitbook-plugin-theme-default ,根据 gitbook 插件命名规范我们知道,gitbook-plugin-* 一般是功能性插件,这一类的插件有 gitbook-plugin-readmore 阅读更多插件和 gitbook-plugin-copyright 版权保护插件等等. 但是如果插件名以 gitbook-plugin-theme 开头的话,这一类插件就是主题插件,比如 gitbook-plugin-theme-default 就是默认主题. 除此之外,只要遵守该命名规则的插件引入时无需添加 gitbook-plugin- 前缀,可以直接在 gitbook.json 文件中引入剩余的简称作为插件名. 摘录自 Gitbook 项目的配置文件,可以佐证上述规则的正确性. \"plugins\": [ \"toc\", \"pageview-count\", \"mermaid-gb3\", \"-lunr\", \"-search\", \"search-plus\", \"splitter\", \"-sharing\", \"sharing-plus\", \"expandable-chapters-small\", \"anchor-navigation-ex\", \"edit-link\", \"copy-code-button\", \"chart\", \"favicon-custom\", \"github-buttons\", \"advanced-emoji\", \"rss\", \"readmore\", \"copyright\", \"tbfed-pagefooter\", \"mygitalk\", \"donate\" ] 作为普通的 nodejs 包,开发规范规定了 package.json 提供了插件的配置信息,而 Gitbook 插件除了是标准的 nodejs 包之外还有自己的约束,主要体现在提供了 gitbook 节点属性: \"gitbook\": { \"properties\": { \"styles\": { \"type\": \"object\", \"title\": \"Custom Stylesheets\", \"properties\": { \"website\": { \"title\": \"Stylesheet for website output\", \"default\": \"styles/website.css\" }, \"pdf\": { \"title\": \"Stylesheet for PDF output\", \"default\": \"styles/pdf.css\" }, \"epub\": { \"title\": \"Stylesheet for ePub output\", \"default\": \"styles/epub.css\" }, \"mobi\": { \"title\": \"Stylesheet for Mobi output\", \"default\": \"styles/mobi.css\" }, \"ebook\": { \"title\": \"Stylesheet for ebook outputs (PDF, ePub, Mobi)\", \"default\": \"styles/ebook.css\" }, \"print\": { \"title\": \"Stylesheet to replace default ebook css\", \"default\": \"styles/print.css\" } } }, \"showLevel\": { \"type\": \"boolean\", \"title\": \"Show level indicator in TOC\", \"default\": false } } } 默认主题仅仅提供了两个配置项,分别是 styles 样式文件位置和 showLevel 是否显示层级配置. 再一次验证了猜想的正确性,真的需要修改源码才能实现默认折叠左侧菜单的效果,紧着继续在 package.json 中找到项目源码的托管地址,看一下有没有提供二次开发文档. \"repository\": { \"type\": \"git\", \"url\": \"git+https://github.com/GitbookIO/theme-default.git\" } 令人遗憾的是,项目介绍空空如也,除了一张主题预览图,别的什么都没有?! 既然没有二次开发文档,那就看看项目源码有没有别的蛛丝马迹教我们如何编译? 「雪之梦技术驿站」: 绕了这么多,其实还不是因为比较菜,人家都提供给源码都不会编译,留下来没有技术的眼泪! 视角再一次切换到源码目录,除了 js 和 less 目录外,竟然还有一个 build.sh 构建脚本! snowdreams1006s-MacBook-Pro:src snowdreams1006$ tree . ├── build.sh ├── js │ ├── core │ └── theme │ ├── dropdown.js │ ├── index.js │ ├── keyboard.js │ ├── loading.js │ ├── navigation.js │ ├── platform.js │ ├── sidebar.js │ └── toolbar.js └── less 7 directories, 37 files snowdreams1006s-MacBook-Pro:src snowdreams1006$ 这一刻,仿佛看到了九点钟升起的太阳,未来是你们的也是我们的! snowdreams1006s-MacBook-Pro:gitbook-plugin-theme-default snowdreams1006$ cat src/build.sh #! /bin/bash # Cleanup folder rm -rf _assets # Recreate folder mkdir -p _assets/website/ mkdir -p _assets/ebook/ # Compile JS browserify src/js/core/index.js | uglifyjs -mc > _assets/website/gitbook.js browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js # Compile Website CSS lessc -clean-css src/less/website.less _assets/website/style.css # Compile eBook CSS lessc -clean-css src/less/ebook.less _assets/ebook/ebook.css lessc -clean-css src/less/pdf.less _assets/ebook/pdf.css lessc -clean-css src/less/mobi.less _assets/ebook/mobi.css lessc -clean-css src/less/epub.less _assets/ebook/epub.css # Copy fonts mkdir -p _assets/website/fonts cp -R node_modules/font-awesome/fonts/ _assets/website/fonts/fontawesome/ # Copy icons mkdir -p _assets/website/images cp node_modules/gitbook-logos/output/favicon.ico _assets/website/images/ cp node_modules/gitbook-logos/output/apple-touch-icon-152.png _assets/website/images/apple-touch-icon-precomposed-152.png snowdreams1006s-MacBook-Pro:gitbook-plugin-theme-default snowdreams1006$ 这一段脚本中除了看不懂 browserify,uglifyjs,lessc -clean-css 命令外,剩下部分都很简单,大致是编译源码文件并输出到 _assets 目录. 编译 js 的命令主要有以下两条,而我们关心的 theme.js 仅涉及到一条,除此之外没有任何别的依赖,这一点非常好! # Compile JS browserify src/js/core/index.js | uglifyjs -mc > _assets/website/gitbook.js browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js 接下来的重点就是如何运行 browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js 命令了! 摇身一变重新编译源码 browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js 百度一下 browserify 再一次打开熟悉的浏览器输入关键字 browserify 后出现一系列相关文章,很好奇为啥排名第一个都不会是官网呢?不管怎么样,找到 browserify 的 github 项目地址也是不错的! 这里并不关心 browserify 到底是什么,只在乎如何安装基本环境而已! $ npm install -g browserify 「雪之梦技术驿站」: 如果是 mac 电脑,全局安装需要管理员权限,应该运行 sudo npm install -g browserify ,如果嫌弃安装速度慢也可以运行 cnpm install -g browserify ,前提是已安装 cnpm 命令. 谷歌一下 uglifyjs 不吹不黑,少走一点弯路,直接就找到了 github 项目网址,同样的也不关心项目介绍,直接翻看如何安装部分. $ npm install -g uglify-js 重新编译 others 涉及到 browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js 命令的两个插件均已安装完毕,理所应当开始重新编译源码了,但是竟然报错了? 当出现报错时,开始怀疑人生,难道推论不正确,难道环境没有安装成功吗,为啥提示找不到 mousetrap 模块? $ browserify src/js/theme/index.js | uglifyjs -mc > _assets/website/theme.js Error: Cannot find module 'mousetrap' from '/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/theme' at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:46:17 at process (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:173:43) at ondir (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:188:17) at load (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:69:43) at onex (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:92:31) at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:22:47 at FSReqCallback.oncomplete (fs.js:158:21) 算了吧,与其费尽心思猜测为啥无法加载 mousetrap 模块,不如继续安装剩余依赖,最大可能性排除环境问题. 那就先把 src/build.sh 构建脚本涉及到的其他命令全部安装一遍,然后再试一下吧! 除了编译 Js 的命令外,还有编译 Css 的命令,关于构建脚本 build.sh 的其他内容就是基本的复制粘贴之类的操作了. # Compile Website CSS lessc -clean-css src/less/website.less _assets/website/style.css 这里省略面向搜索编程的中间过程,安装命令如下: $ npm install -g less less-plugin-clean-css 当我再一次运行构建脚本时,满心期待会编译成功,没想到现实再一次打脸,这时候错误更多了呢,真的是没想到! snowdreams1006s-MacBook-Pro:gitbook-plugin-theme-default snowdreams1006$ src/build.sh Error: Cannot find module 'jquery' from '/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/core' at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:46:17 at process (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:173:43) at ondir (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:188:17) at load (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:69:43) at onex (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:92:31) at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:22:47 at FSReqCallback.oncomplete (fs.js:158:21) Error: Cannot find module 'mousetrap' from '/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/theme' at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:46:17 at process (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:173:43) at ondir (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:188:17) at load (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:69:43) at onex (/usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:92:31) at /usr/local/lib/node_modules/browserify/node_modules/_resolve@1.1.7@resolve/lib/async.js:22:47 at FSReqCallback.oncomplete (fs.js:158:21) FileError: '../../node_modules/font-awesome/less/font-awesome.less' wasn't found. Tried - /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/font-awesome/less/font-awesome.less,/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/font-awesome/less/font-awesome.less,../../node_modules/font-awesome/less/font-awesome.less in /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/less/website.less on line 2, column 1: 1 @import \"base/all.less\"; 2 @import \"../../node_modules/font-awesome/less/font-awesome.less\"; 3 @import \"../../node_modules/preboot/less/preboot.less\"; FileError: '../../../node_modules/gitbook-markdown-css/less/mixin.less' wasn't found. Tried - /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/gitbook-markdown-css/less/mixin.less,/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/node_modules/gitbook-markdown-css/less/mixin.less,../../../node_modules/gitbook-markdown-css/less/mixin.less in /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/less/base/mixins.less on line 1, column 1: 1 @import \"../../../node_modules/gitbook-markdown-css/less/mixin.less\"; 2 FileError: '../../../node_modules/gitbook-markdown-css/less/mixin.less' wasn't found. Tried - /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/gitbook-markdown-css/less/mixin.less,/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/node_modules/gitbook-markdown-css/less/mixin.less,../../../node_modules/gitbook-markdown-css/less/mixin.less in /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/less/base/mixins.less on line 1, column 1: 1 @import \"../../../node_modules/gitbook-markdown-css/less/mixin.less\"; 2 FileError: '../../../node_modules/gitbook-markdown-css/less/mixin.less' wasn't found. Tried - /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/gitbook-markdown-css/less/mixin.less,/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/node_modules/gitbook-markdown-css/less/mixin.less,../../../node_modules/gitbook-markdown-css/less/mixin.less in /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/less/base/mixins.less on line 1, column 1: 1 @import \"../../../node_modules/gitbook-markdown-css/less/mixin.less\"; 2 FileError: '../../../node_modules/gitbook-markdown-css/less/mixin.less' wasn't found. Tried - /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/node_modules/gitbook-markdown-css/less/mixin.less,/Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/node_modules/gitbook-markdown-css/less/mixin.less,../../../node_modules/gitbook-markdown-css/less/mixin.less in /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/less/base/mixins.less on line 1, column 1: 1 @import \"../../../node_modules/gitbook-markdown-css/less/mixin.less\"; 2 cp: directory _assets/website/fonts/fontawesome does not exist cp: node_modules/gitbook-logos/output/favicon.ico: No such file or directory cp: node_modules/gitbook-logos/output/apple-touch-icon-152.png: No such file or directory 那就继续扩大安装环境范围,这时候对整个 gitbook-plugin-theme-default 进行 npm install 安装相关依赖,这一次会发生什么情况呢? $ npm install 让我们拭目以待! snowdreams1006s-MacBook-Pro:gitbook-plugin-theme-default snowdreams1006$ src/build.sh snowdreams1006s-MacBook-Pro:gitbook-plugin-theme-default snowdreams1006$ 命令行没有了乱七八糟的输出,世界变得安静了! linux 命令行哲学告诉我们,没有消息就是好消息,全部安装项目环境后再次运行 src/build.sh 脚本命令行瞬间安静了! 怀着忐忑不安的心,切换到测试项目运行 gitbook serve 命令后,那一瞬间,感觉世界都静止了,奇迹就这么发生了? 终于成功了,实现默认折叠效果了吗? 为了验证是否成功实现默认折叠失效,做一次反向测试,既然默认折叠左侧菜单设置的是 false,如果设置成 true 的话,默认应该是展开状态. // Prepare sidebar: state and toggle button function init() { // Close sidebar as default state // gitbook.storage.set('sidebar', false); // Open sidebar as default state gitbook.storage.set('sidebar', true); // Init last state if not mobile if (!platform.isMobile()) { toggleSidebar(gitbook.storage.get('sidebar', true), false); } // Close sidebar after clicking a link on mobile $(document).on('click', '.book-summary li.chapter a', function(e) { if (platform.isMobile()) toggleSidebar(false, false); }); } 重新编译后再次启动本地测试项目,如果是展开状态,那就说明成功不是偶然而是靠技巧和努力! 重新编译源码 $ src/build.sh /Users/snowdreams1006/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default 启动本地项目 $ gitbook serve /Users/snowdreams1006/Documents/workspace/test 「雪之梦技术驿站」: 苦心人天不负,不是昙花一现的巧合而是货真价实的现实,就这么实现了默认折叠左侧菜单功能! 懒人直达以及回顾总结 如果你是 Gitbook 普通用户或者懒得折腾,那么推荐你直接替换掉 theme.js 文件: 查看正在使用的 gitbook 版本信息 $ gitbook current GitBook version is 3.2.3 打开正在使用的 gitbook 安装位置 $ open ~/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/ 新文件替换掉原来的 _assets/website/theme.js 文件 可以关注微信公众号回复 20200409 获取重新编译后的新文件 theme.js. 切换到测试项目验证默认折叠是否已生效 $ gitbook serve 如果你不怕麻烦,喜欢折腾,那么不妨体验一下如何重新编译源码文件. 查看正在使用的 gitbook 版本信息 $ gitbook current GitBook version is 3.2.3 打开正在使用的 gitbook 安装位置 $ open ~/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/ 安装 theme-default 默认主题项目所需依赖 $ npm install 安装 build.sh 构建脚本所需依赖 $ sudo npm install -g browserify uglify-js less less-plugin-clean-css 运行 build.sh 构建脚本重新编译 $ src/build.sh 切换到测试项目验证默认折叠是否已生效 $ gitbook serve 值得注意的是,实现默认折叠左侧菜单功能仅仅需要添加一行代码,但是也很有可能和项目中已引入插件存在冲突,毕竟 sidebar 的状态也可以被未知代码所更改! 文件位置: ~/.gitbook/versions/3.2.3/node_modules/gitbook-plugin-theme-default/src/js/theme/sidebar.js // Prepare sidebar: state and toggle button function init() { // Close sidebar as default state gitbook.storage.set('sidebar', false); // Open sidebar as default state // gitbook.storage.set('sidebar', true); // Init last state if not mobile if (!platform.isMobile()) { toggleSidebar(gitbook.storage.get('sidebar', true), false); } // Close sidebar after clicking a link on mobile $(document).on('click', '.book-summary li.chapter a', function(e) { if (platform.isMobile()) toggleSidebar(false, false); }); } 最后希望本文对你有所帮助,面向搜索编程变得不可用时,自力更生也未尝不可,如果大家在使用 Gitbook 中遇到任何问题,欢迎留言评论告诉我,当然我也不一定保证解决,万一哪天心血来潮翻看一下源码就解决了呢! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/issue/modify-default-fold.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-04-13 00:53:44 "},"myGitbook/reference/":{"url":"myGitbook/reference/","title":"参考更多","keywords":"","body":"更多学习笔记 gitbook 简体中文官方文档 gitbook 繁体中文官方文档 敖小剑的 gitbook 学习笔记 gitbook 插件使用笔记 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/myGitbook/reference/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"zero2devops/":{"url":"zero2devops/","title":"从零开始搭建服务器","keywords":"","body":"从零开始搭建个人服务器 nginx docker run --name nginx -d -p 80:80 -p 443:443 --restart=always \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx bark docker run --name bark -dt -p 8080:8080 --restart=always \\ -v ~/bark/data:/data \\ finab/bark-server webhook docker run --name webhook -d -p 9000:9000 --restart=always \\ -v ~/webhook:/etc/webhook \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v /usr/bin/docker:/usr/bin/docker \\ hongkongkiwi/webhook -verbose -hooks=/etc/webhook/hooks.json -hotreload resume docker run --name resume -d -p 1006:80 --restart=always \\ -v ~/resume:/usr/share/nginx/html \\ nginx blog docker run --name blog -d -p 4000:80 --restart=always \\ -v ~/blog:/usr/share/nginx/html \\ nginx mysql docker run --name mysql -d -p 3306:3306 --restart=always \\ -v ~/mysql/conf:/etc/mysql \\ -v ~/mysql/logs:/var/log/mysql \\ -v ~/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=123456 \\ mysql:5.7 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/login-and-logout.html":{"url":"zero2devops/login-and-logout.html","title":"从登录服务器开始","keywords":"","body":"登录和登出远程服务器 双 11 期间入手了一台云服务器,备案花了一两个星期,这两天终于备案通过了.于是在个人服务器上装了 Docker 容器用于部署项目,准备尽量把所有的服务都打包成容器,方便统一管理运维. 于是利用 docker 搭建了 nginx 作为反向代理服务器,负责请求分发,用 nginx 部署静态博客,用 mysql 暴露给个人项目使用... 但是千里之行始于足下,一切还要从登录登出远程服务器开始,本文基于 Centos7.6 环境,不保证其他环境正常. 环境准备 服务器要求 如果你已经有云服务器或者虚拟机服务器,首先需要验证服务器上是否已经安装 ssh 服务,如果没有安装则需要提前安装. 登录服务器后,在命令行窗口中输入 rpm -qa | grep ssh 查看是否包括 ssh 相关文件. [root@snowdreams1006 ~]# rpm -qa | grep ssh openssh-clients-7.4p1-16.el7.x86_64 libssh2-1.4.3-12.el7_6.2.x86_64 openssh-server-7.4p1-16.el7.x86_64 openssh-7.4p1-16.el7.x86_64 默认情况下运行 netstat -antp | grep sshd 命令,可以看到 sshd 服务监听的端口正是默认的 22 端口. [root@snowdreams1006 ~]# netstat -antp | grep sshd tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1051/sshd tcp 0 0 *.*.*.*:22 *.*.*.*:46797 ESTABLISHED 17334/sshd: root@pt 其实一般云服务器均已预装好 ssh 服务,如果没有上述输出,则说明可能并没有安装 ssh 服务,可以使用 yum install openssh-server 进行安装. 下面总结一些关于 sshd 的常用命令,如下 查看 sshd 运行状态 systemctl status sshd 如果运行结果包括 Active: active (running) 则证明 sshd 服务处于激活状态,如果是 Active: inactive (dead) 则表示服务已关闭. [root@snowdreams1006 ~]# systemctl status sshd ● sshd.service - OpenSSH server daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled) Active: active (running) since 五 2019-11-29 21:05:08 CST; 16h ago Docs: man:sshd(8) man:sshd_config(5) Main PID: 1051 (sshd) Tasks: 1 Memory: 10.9M CGroup: /system.slice/sshd.service └─1051 /usr/sbin/sshd -D 启动|关闭|重启 sshd 服务 systemctl start sshd 针对处于已经关闭 sshd 状态时,可以再次启动,如果已经启动,则不会输出结果,但还是启动中. systemctl stop sshd 针对处于正在运行 sshd 状态时,可以停止服务,如果已经停止,则不会输出结果,但还是停止中. systemctl restart sshd 针对处于正在运行 sshd 状态或者已经停止状态时,可以重启服务,虽然都不会输出结果,但已经启动. 开机自启|禁止自启 sshd 服务 systemctl list-unit-files | grep enabled 查看全部开机自启服务,如果包括 sshd 则表示 sshd 已加入开机自启服务,如果没有则不会开机自启. systemctl list-unit-files | grep enabled | grep sshd 如果全部开机自启服务比较多的话,肉眼不太直接看出 sshd 是否自启,在上一条命令的基础上多加一个 grep sshd 即可过滤是否包含 sshd 服务. systemctl enable sshd 开机自启服务,如果某些服务非常重要需要一直后台运行的话,最好加入开机自启,这样能防止意外关机重启服务器后忘记开启服务,比如 sshd 服务和 docker 服务等等. systemctl disable sshd 针对已经开机自启服务进行禁用,运行 systemctl list-unit-files | grep enabled | grep sshd 可以查看当前服务是否会开机自启. 如果你现在还没有服务器但又想学习体验一下,要么立即花钱去买服务器要么免费安装虚拟机,或者先收藏起来以后再看! 针对立即购买服务器的小伙伴,请私信联系我,用我的推广链接购买,你有优惠,我有分成,何乐而不为呢? 我要购买云服务器,最好还是留言或者私信告诉我! 针对免费安装虚拟机体验的用户,可以根据自己的操作系统,参考以下推文进行安装,有问题也可以联系我! 我想安装虚拟机,最好还是留言或者私信告诉我! 下面是之前推文关于什么是虚拟机以及如何安装 Centos 服务器的相关文章,可以点击直接阅读. 给你的计算机一种全新的体验 主要介绍了什么是虚拟机以及实例演示如何给 Windows 电脑装个 VMware 虚拟机. 给 windows 虚拟机装个 centos 在 Windows 电脑已经装好 VMware虚拟机的基础上,装个 Centos 镜像就拥有了自己的 Centos 服务器. 工具资源系列之给mac装个虚拟机 主要介绍了什么是虚拟机以及实例演示如何给 Mac 电脑装个 VMware 虚拟机. 工具资源系列之给虚拟机装个centos 在 Mac 电脑已经装好 VMware虚拟机的基础上,装个 Centos 镜像就拥有了自己的 Centos 服务器. 客户端要求 因为登录服务器需要使用到 ssh 协议,所以首先需要验证本机客户端命令行是否支持 ssh 协议. 打开你正在使用的命令行,直接输入 ssh 如果有下列提示证明是可以的,如果没有请安装支持 ssh 协议命令行终端. 如果你是 Mac 用户,打开默认的 terminal 终端输入 ssh 返回用法说明,证明是支持 ssh 协议的. snowdreams1006$ ssh usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address] [-c cipher_spec] [-D [bind_address:]port] [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file] [-J [user@]host[:port]] [-L address] [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port] [-Q query_option] [-R address] [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]] destination [command] Mac 自带的 terminal 终端是支持 ssh 协议的,Windows 的 Git Bash 命令行也是支持的! 如果你是 Windows 用户正在使用 cmd 命令行窗口,很遗憾并不支持 ssh 协议,请自行选择类 unix 终端进行操作. Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。 C:\\Users\\Administrator>ssh 'ssh' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 C:\\Users\\Administrator> Windows 用户不妨安装 Git for Windows ,默认自带的 Git Bash 命令行就很好用,比 cmd 终端更加简单优雅,给你一种类 unix 操作体验. 命令行登录 ssh 登录服务器前提是知道账号密码,先用密码登录的方式进行设置,然后才能用 ssh 方式进行免密登录. 一般情况下,密码都是自己设置的,如果不清楚默认密码是多少可以询问云服务器厂商或者选择重设密码,这里不再赘述,假设你已经知道服务器密码并且知道服务器公网 ip. 下面我们将会在本机客户端的命令行中远程登录云服务器,从最简单方便的密码登录到无密码的密钥登录再到最后的别名登录,快点跟着我一起动起来吧! 友情提示: 请确保服务端已开启 sshd 服务和本地客户端命令行中支持 ssh 协议,否则还是先收藏起来以后再看吧! 密码登录 打开熟悉的命令行终端,运行 ssh @ 命令直接登录远程云服务器. $ ssh root@snowdreams1006.cn @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the ECDSA key sent by the remote host is SHA256:/RJ5aI+c41Brr1dcBMhdNHQJa7daP+8fbupqsGmHRHc. Please contact your system administrator. Add correct host key in /c/Users/Administrator/.ssh/known_hosts to get rid of this message. Offending ECDSA key in /c/Users/Administrator/.ssh/known_hosts:5 ECDSA host key for 121.40.223.69 has changed and you have requested strict checking. Host key verification failed. 因为我的域名 snowdreams1006.cn 已经备案成功并且做了域名解析,所以这里我可以直接用域名而不是 ip,说白了还是因为懒,谁让 ip 记不住呢! 如果你没有遇到上述提示,恭喜你,可以接着输入账号密码就可以登录到服务器了! 如果你和我一样遇到这种问题,很可能是之前登陆过服务器,不过后来服务器又重装系统,导致无法登录,可以清除本机 ~/.ssh/known_hosts 重新登录. rm -rf ~/.ssh/known_hosts 强制删除 ~/.ssh/known_hosts 文件后重新 ssh root@snowdreams1006.cn 登录到远程云服务器,按照提示选择继续连接 yes ,然后输入自己的密码 password ,登录成功后默认进入到家目录. $ ssh root@snowdreams1006.cn The authenticity of host 'snowdreams1006.cn (*.*.*.*)' can't be established. ECDSA key fingerprint is SHA256:/**********************************. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added 'snowdreams1006.cn,*.*.*.*' (ECDSA) to the list of known hosts. root@snowdreams1006.cn's password: Last login: *** from *.*.*.* Welcome to Alibaba Cloud Elastic Compute Service ! [root@snowdreams1006 ~]# 来都来了,不留下点什么东西不好不意思跟别人说过自己曾将来过,那就随便意思意思好了. 悟空到此一游 [root@snowdreams1006 ~]# whoami root whoami 我是谁: 查看正在登录用户名称 [root@snowdreams1006 ~]# pwd /root pwd 我在哪: 打印当前目录路径 [root@snowdreams1006 ~]# who -u root tty1 2019-11-29 21:05 旧的 596 root pts/0 2019-11-30 14:15 . 17506 (115.217.243.122) root pts/1 2019-11-30 14:28 00:05 17533 (115.217.243.122) who -u 还有谁: 打印系统登录用户 [root@snowdreams1006 ~]# last -a | head -6 root pts/1 Sat Nov 30 14:28 still logged in 115.217.243.122 root pts/0 Sat Nov 30 14:15 still logged in 115.217.243.122 root pts/0 Sat Nov 30 13:10 - 13:51 (00:41) 115.217.243.122 root pts/0 Sat Nov 30 12:24 - 12:25 (00:00) 115.217.243.122 root pts/1 Fri Nov 29 22:08 - 23:12 (01:03) 112.17.241.55 root pts/0 Fri Nov 29 21:33 - 22:13 (00:39) 112.17.241.55 last -a | head -6 最后是谁: 打印最后登录用户 好像不认识你 如果发现登录用户除了自己还有别人或者不记得自己其他终端登陆过没有退出,这时候应该踢出这些登录终端. 如果要踢出别人,首先要自己登录的终端终端中哪个是自己,不能自己把自己踢了,那就好笑了! whoami 仅仅打印登录名,没有登录终端信息,但是 who am i 展示信息就相对多了,这里要记住当前用户登录终端的名称! [root@snowdreams1006 ~]# who am i root pts/0 *** (*.*.*.*) who am i : 我是谁,比 whoami 显示的信息更多. who -u 可以展示全部登录用户,不过这里推荐使用 w 查看登录用户终端信息,因为敲入的命令更少! [root@snowdreams1006 ~]# w 14:53:04 up 17:48, 3 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root tty1 五21 17:46m 0.00s 0.00s -bash root pts/0 115.217.243.122 14:15 0.00s 0.81s 0.00s w root pts/1 115.217.243.122 14:51 1:37 0.00s 0.00s -bash who -u 和 w 都可以查看登录用户终端信息,pts/0 是当前登录终端,其他终端 pts/1 就可以被踢掉了. # pkill -kill -t pts/1 pkill -kill -t pts/ 踢出登录终端后,被踢出的终端则会自动退出显示连接已关闭. [root@snowdreams1006 ~]# w 14:58:46 up 17:53, 2 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root tty1 五21 17:52m 0.00s 0.00s -bash root pts/0 115.217.243.122 14:15 6.00s 0.83s 0.00s w w : 有谁,踢出其他终端后再次查看登录终端发现确实没有 pts/1 了,证明操作成功! 回家洗洗睡吧 登录到服务器查看了一些信息,并成功踢出了其他终端,天已经黑了,是时候洗洗睡了,这时候需要退出服务器回到本地客户端的命令行. [root@snowdreams1006 ~]# exit 登出 Connection to ssh.snowdreams1006.cn closed. 除了敲入命令 exit 还可以敲入快捷键 Ctrl+D 退出连接. 密钥登录 一般来说,使用密码登录的方式更加适合不常用的场合,偶尔用终端登录一下没什么问题,如果是经常性需要登录到远程服务器的话,每次连接都需要登录就比较费事了. 既然不用密码登录,那怎么证明自己的身份以保证合法性登录请求呢? 这里就要用到密钥代替密码进行登录,首先在本机客户端生成一种认证信息,然后将这种认证信息安装到远程服务器中,只要完成这种操作就意味着下一次来自该客户端的登录请求都是合法的,不然服务端怎么会有你的认证信息? 所以,可以猜想的是,即使是相同的账号,如果使用的是不同的认证信息,那么远程服务器还是会已保存的认证信息为准,比如你的 Mac 已经实现免密登录,但是你的 Windows 由于并没有上传自己的认证信息,所以免密登录只对 Mac 有效! 当然,如果 Mac 和 Windows 电脑的认证信息是一样的,那么服务器保存一份是不是可以免密登录了呢? 答案是: 正确的! 由此可见,密钥登录是针对终端设备而言的,特别适合常用的终端,偶尔性的电脑前往不要设置密钥,不然哪天你忘记了,人家都能直接登录你的服务器,多可怕! 所以,要完成免密登录,需要两个操作,一是本地客户端生成认证文件,二是远程服务器保存该认证文件. 本地客户端生成公钥 $ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0gC0u4gR4oba4oHS59Tcc4eAVkaJHsCmys0v4IupoSLQKkdUJVxSfkmL5JZEkr7JNySD7Y52ukRcxx1ZMW0oK7lq+FvfEwzIfAOqVoM4bCoh2D/iC6Xf43ilxCM6oMhpWyITGtxPVzW/ZmmxRGcQzeVrrvoSLhOt0+L0rvFuiQZmnhkV0zqGTRKTQ5uEKycigfdItEaHFIg9fMxugN/bgeflJoEBZjAJHXkqd0mq/4AqeAbkoruEz6D+OiqBhoN8CsbaPCaccMoKd8Tze5UszC3PsQWo96nQoXMXk7HYoFwvJCAgAfKP0CaTwGEK/D7SFvXm3UMlFwAHxELr2bbTv snowdreams1006@163.com ~/.ssh/id_rsa.pub 文件被称为公钥,即用于发送给服务器的认证文件,可以公开到互联网,只要服务器保存该公钥,那么生成该公钥的本地客户端就能免密连接到服务器. 如果你的本地客户端没有该文件,可以使用 ssh-keygen -t rsa 生成一份,按照提示输入相关的信息即可,然后再次运行 cat ~/.ssh/id_rsa.pub 就能查看公钥文件的具体内容了. 远程服务器接收公钥 本地客户端将自己的公钥文件内容追加到远程服务器的授权文件中就能完成免密登录,那么问题来了,公钥文件保存在哪呢? 答案是: ~/.ssh/authorized_keys [root@snowdreams1006 ~]# cat ~/.ssh/authorized_keys ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0gC0u4gR4oba4oHS59Tcc4eAVkaJHsCmys0v4IupoSLQKkdUJVxSfkmL5JZEkr7JNySD7Y52ukRcxx1ZMW0oK7lq+FvfEwzIfAOqVoM4bCoh2D/iC6Xf43ilxCM6oMhpWyITGtxPVzW/ZmmxRGcQzeVrrvoSLhOt0+L0rvFuiQZmnhkV0zqGTRKTQ5uEKycigfdItEaHFIg9fMxugN/bgeflJoEBZjAJHXkqd0mq/4AqeAbkoruEz6D+OiqBhoN8CsbaPCaccMoKd8Tze5UszC3PsQWo96nQoXMXk7HYoFwvJCAgAfKP0CaTwGEK/D7SFvXm3UMlFwAHxELr2bbTv snowdreams1006@163.com 这里可以看出,远程服务器的 ~/.ssh/authorized_keys 内容包含了本地客户端的 ~/.ssh/id_rsa.pub 公钥内容. 所以接下来的动作就是复制粘贴的操作,复制本地客户端的 ~/.ssh/id_rsa.pub 公钥文件内容,粘贴到远程服务器的 ~/.ssh/authorized_keys 文件中. 只不过这里需要用到 vim 操作,这也是对于新手来说唯一的挑战了,但是我们还有神器来简化这种复制粘贴操作! 那就是: ssh-copy-id root@snowdreams1006.cn 首先确保本地客户端已经生成公钥,这里查看一下公钥文件的内容: cat ~/.ssh/id_rsa.pub $ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1UGGBXbqINEfQNCweCOWDlqvRfw3iIqkX9UnI71GgyJkkPUZbycw3L4dVeBkpo76OJjJhJmsAGbHAuhYLloqoNjD9+c/hk7vgP0uZHqVXehqKuP5VvOOkqeLXZkjdXQ49MhARHBVm1LaD44iOOneYclSPiRjKs+6eCxU9SQp+dVUcZMrbAE1lktGgDQEkjtFl8BE9BQkCU24r8xcOUix4iZgdDIa5gnE9YLg1rNXO6LgQG61JLvErrc2g7KkkR4i2P1R+0uV3KdYyMv8Y2aYwYGqY1PjqXUVfaJjTor4Dr8HHBp4VHE3kNVZitLJ2S7RFYuYGFXTEX0xmi6o1r5xP Administrator@snowdreams1006 这里是 Windows 电脑,演示前并没有配置免密登录,所以执行完 ssh-copy-id root@snowdreams1006.cn 应该也像 Mac 电脑一样支持免密登录. $ ssh-copy-id root@snowdreams1006.cn /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: \"/c/Users/Administrator/.ssh/id_rsa.pub\" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys root@snowdreams1006.cn's password: Number of key(s) added: 1 Now try logging into the machine, with: \"ssh 'root@snowdreams1006.cn'\" and check to make sure that only the key(s) you wanted were added. 这里接着用已经免密登录到远程服务器的 Mac 电脑验证一下,Windows 电脑的公钥是否已经添加到远程服务器的 ~/.ssh/authorized_keys . [root@snowdreams1006 ~]# cat ~/.ssh/authorized_keys ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0gC0u4gR4oba4oHS59Tcc4eAVkaJHsCmys0v4IupoSLQKkdUJVxSfkmL5JZEkr7JNySD7Y52ukRcxx1ZMW0oK7lq+FvfEwzIfAOqVoM4bCoh2D/iC6Xf43ilxCM6oMhpWyITGtxPVzW/ZmmxRGcQzeVrrvoSLhOt0+L0rvFuiQZmnhkV0zqGTRKTQ5uEKycigfdItEaHFIg9fMxugN/bgeflJoEBZjAJHXkqd0mq/4AqeAbkoruEz6D+OiqBhoN8CsbaPCaccMoKd8Tze5UszC3PsQWo96nQoXMXk7HYoFwvJCAgAfKP0CaTwGEK/D7SFvXm3UMlFwAHxELr2bbTv snowdreams1006@163.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1UGGBXbqINEfQNCweCOWDlqvRfw3iIqkX9UnI71GgyJkkPUZbycw3L4dVeBkpo76OJjJhJmsAGbHAuhYLloqoNjD9+c/hk7vgP0uZHqVXehqKuP5VvOOkqeLXZkjdXQ49MhARHBVm1LaD44iOOneYclSPiRjKs+6eCxU9SQp+dVUcZMrbAE1lktGgDQEkjtFl8BE9BQkCU24r8xcOUix4iZgdDIa5gnE9YLg1rNXO6LgQG61JLvErrc2g7KkkR4i2P1R+0uV3KdYyMv8Y2aYwYGqY1PjqXUVfaJjTor4Dr8HHBp4VHE3kNVZitLJ2S7RFYuYGFXTEX0xmi6o1r5xP Administrator@snowdreams1006 可以看到,远程服务器已经保存了 Windows 电脑刚刚上传的公钥文件内容,所以说 ssh-copy-id 简直就是神器,再也不用手动复制粘贴公钥了! 至于登陆后可以做什么,我想你还是可以到此一游,顺便再踢出其他终端之类的,最后别忘了退出登录 exit 命令哟! 别名登录 无论是密码登录还是密钥登录,我们都是采用 ssh 协议进行登录,而密钥登录的配置也是依赖于密码登录,不管怎么说,两者敲入的命令都不少! 怎不能快速登录呢? 答案是: 可以的! 既然想要快速登录,但是登录信息肯定是必不可少的,所以无外乎是将必要的登录信息写到配置文件中,登录时再读取配置文件进行登录. 原理很好理解,关键是实现过程需要我们自己去实现么? 幸运的是: 不需要! 同样地,编辑 ssh 的配置文件设置一些登录信息即可,配置文件的位于 ~/.ssh/config . Host User HostName 按照上述格式,将登录信息配置如下: Host github.com User snowdreams1006 Hostname ssh.github.com Host snowdreams1006.cn User root Hostname ssh.snowdreams1006.cn 其中 Host 是对外暴露的唯一标识,通过 Host 就可以代替账号和 ip 了,两个变成一个是不是简化了呢? 原来的登录命令是 ssh @ 而现在则是 ssh $ ssh snowdreams1006.cn Last login: **** from *.*.*.* Welcome to Alibaba Cloud Elastic Compute Service ! [root@snowdreams1006 ~]# 图形化登录 推荐 SecureCRT 图形化工具远程连接 Linux 实例,图形化工具基本上很容易上手,配置一下就可以登录成功了,这里已 Windows 电脑为例演示一下相关过程. 首次登录后会弹出快速连接配置,基本上是关于服务域名端口之类的,并没有密码的配置项. 点击连接后会弹出是否加入主机指纹之类的,选择接受并保存. 输入密码后选择确定,过一会就连接到服务器了. 来都来了,还是随便敲个命令再走吧! 云服务登录 登录控制台找到 ECS 服务器 ,然后找到自己的云服务器,因此远程连接->连接密码->用户名/密码,然后就可以登录成功了! 回顾总结 密码登录和密钥登录两者均存在适合场景,如果觉得密码登录不安全的话,也可以禁用密码登录只保留密钥登录! 远程登录到服务器,找到 /etc/ssh/ssh_config 文件并编辑如下内容来禁用密码登录. Host * PasswordAuthentication no 密码登录 $ ssh @ ssh root@snowdreams1006.cn 密钥登录 $ ssh @ ssh root@snowdreams1006.cn 简化登录 $ ssh ssh snowdreams1006.cn 阅读更多 CentOS下开启SSH Server服务 linux 远程连接ssh提示IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY解决 Linux 7开机自启项查看并设置 SSH简介及两种远程登录的方法 服务器快速免密ssh登录配置 linux 信息查看及命令 linux下踢出已登录用户 Mac安装SecureCRT SecureCRT 8.1.4 破解教程 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/login-and-logout.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/docker.html":{"url":"zero2devops/docker.html","title":"更加优雅地部署项目","keywords":"","body":"更好的部署方式 如果你需要经常性需要多处部署同样的项目,如果你曾经也遇到过\"明明在我电脑运行得好好的\"问题,如果听说过 Docker 但还没用过,如果你不确定你到底需不需要 Docker ,那么,希望你花时间阅读一下这篇文章! 因为 Docker 将帮助你轻松运行自己不熟悉语言编写的开源项目,帮助你更加优雅地部署自己的项目,省去重复下载并配置环境的繁琐过程... 现在让我们先睹为快,预览一下基于 Docker 部署项目的实际效果,希望能让你对 Docker 有个初步的印象! Docker 部署的 nginx 作为反向代理服务器,支持 https 访问以及泛域名解析. 体验地址: https://snowdreams1006.cn/ Docker 部署的 letsencrypt 免费制作泛域名证书并整合反向代理服务 nginx 实现 https 访问. 体验地址: https://www.snowdreams1006.cn/ Docker 部署的 nginx 作为静态服务器,部署静态网站用于演示静态博客功能. 体验地址: https://resume.snowdreams1006.cn/ Docker 部署的 bark 作为后端服务器,部署开源项目用于充当消息推送服务器. 体验地址: https://bark.snowdreams1006.cn/ping Docker 部署的 webhook 作为后端服务器,部署开源项目用于接收 Webhook 事件回调. 体验地址: https://webhook.snowdreams1006.cn/hooks/github Docker 部署的 blog 作为静态服务器,基于 Github Action 或 Webhook 实现博客内容自动更新并推送消息. Github 仓库内容更新后触发 Github Action 自动构建并部署远程服务器静态博客,同时发送的 Webhook 事件给 webhook 钩子容器,紧接着调用 bark 消息推送容器,实现消息推送到微信消息以及 app 通知. Github 仓库更新后自动运行 Github Action 源码构建静态博客并上传到远程服务器,blog 容器会立即重启完成内容更新. Github 仓库更新后发送 Webhooks 到远程服务器,webhook 容器接收到请求后转发给 bark 容器,进而推送给手机. 无论是熟悉的开源项目还是陌生的开源项目,Docker 让这些不一样变得一样,统一的管理方式使得使用成本大大降低,更加优雅地部署项目,真的不止是说说而已! 前提条件 目前在 Linux 系统上安装 Docker,对系统版本有以下要求: CentOS : 7 Debian : 7.7(Wheezy LTS)、8.0(Jessie LTS)、9(Stretch) Fedora : 24、25 Ubuntu : 16.04(Xenial LTS)、14.04(Trusty LTS)、17.04(Zesty) 一方面上述前提条件基本上新服务器都会满足,另一方面笔者对此并未深入实验,请读者自行验证,下面主要以 Centos7.6 为例讲解如何安装 Docker . 验证环境 对于新手来说,尽管安装 Docker 非常简单,但是总是不可避免地会遇到一些意外情况,或许是安装出错需要重新安装或者是不确定远程服务器是否已经安装,所以开始安装前还是先看一下到底有没有安装过 Docker 吧! 调用 docker 命令 首先连接到远程服务器后运行 docker 命令,如果像下面那样输出一大堆用法介绍,那么证明 Docker 已经成功安装过,并且可能已经配置好相关环境了. 你现在唯一要做的就是学习一下 Docker 的基本用法,因为不用自己安装 Docker 环境,基本上也可以不必往下看了. [root@snowdreams1006 ~]# docker Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Options: --config string Location of client config files (default \"/root/.docker\") -c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with \"docker context use\") -D, --debug Enable debug mode -H, --host list Daemon socket(s) to connect to -l, --log-level string Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\") (default \"info\") --tls Use TLS; implied by --tlsverify --tlscacert string Trust certs signed only by this CA (default \"/root/.docker/ca.pem\") --tlscert string Path to TLS certificate file (default \"/root/.docker/cert.pem\") --tlskey string Path to TLS key file (default \"/root/.docker/key.pem\") --tlsverify Use TLS and verify the remote -v, --version Print version information and quit Management Commands: builder Manage builds config Manage Docker configs container Manage containers context Manage contexts engine Manage the docker engine image Manage images network Manage networks node Manage Swarm nodes plugin Manage plugins secret Manage Docker secrets service Manage services stack Manage Docker stacks swarm Manage Swarm system Manage Docker trust Manage trust on Docker images volume Manage volumes Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes to files or directories on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on Docker objects kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry logout Log out from a Docker registry logs Fetch the logs of a container pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart one or more containers rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information wait Block until one or more containers stop, then print their exit codes Run 'docker COMMAND --help' for more information on a command. 如果你输入 docker 提示 command not found ,说明服务器很可能并没有安装 Docker 环境,下面就教你如何一步一步安装 Docker 环境! 安装 Docker Step 1 : 移除旧版本 sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine 这一步是可选的,是因为最新版 Docker 的名称已经发生了变化,为了保证安装的是最新版的 Docker-CE ,所以首先卸载可能已经安装过的旧版本. Step 2 : 安装必要系统依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 安装一些必要依赖,跟着官方教程说明走就好了,即使系统已存在该环境也可以再次运行,放心复制粘贴吧! Step 3 : 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo Step 4 : 更新 yum 缓存 sudo yum makecache fast Step 5 : 安装 docker-ce sudo yum -y install docker-ce 如果上述安装过程中没有出现任何报错,那么现在已经安装好基本的 Docker 环境! 启动 Docker 查看状态 sudo systemctl status docker 初次安装成功后默认是不会自动启动 Docker 服务的,此时查看运行状态的输出结果不会包括 Active: active (running) 而是 Active: inactive (dead) . 首次启动 sudo systemctl start docker 安装后默认是没有启动 Docker 服务的,因此安装后需要先启动 Docker 服务,再次查看运行状态 sudo systemctl status docker 应该会出现正在运行 Active: active (running) . 重新启动 sudo systemctl restart docker 如果 Docker 服务已停止可以重新启动,如果已经启动也可以重新启动. 停止服务 sudo systemctl stop docker 如果正在运行的 Docker 存在问题需要停止维修,那么可以先停止 Docker 服务,待维修结束后可以运行 sudo systemctl start docker 再次启动服务. 检查自启 systemctl list-unit-files | grep enabled | grep docker 检查 Docker 服务是否会开机自启,如果存在结果则表示会开机自启,如果没有结果则表示不会开机自启. 开机自启 sudo systemctl enable docker Docker 服务是非常重要的进程服务,一般需要开机自启,保证意外关机后能自行恢复服务,推荐开机自启. 禁止自启 sudo systemctl disable docker 如果不小心设置了开机自启而你真的不打算开机自启的话,那么可以禁用开机自启功能,下次电脑重启后不会自动启动Docker 服务. 查看版本 docker version 查看当前安装的 Docker 版本信息,可以看出来主要分为两部分: Client: Docker Engine - Community 和 Server: Docker Engine - Community . [root@snowdreams1006 ~]# docker version Client: Docker Engine - Community Version: 19.03.5 API version: 1.40 Go version: go1.12.12 Git commit: 633a0ea Built: Wed Nov 13 07:25:41 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.5 API version: 1.40 (minimum version 1.12) Go version: go1.12.12 Git commit: 633a0ea Built: Wed Nov 13 07:24:18 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.10 GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339 runc: Version: 1.0.0-rc8+dev GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657 docker-init: Version: 0.18.0 GitCommit: fec3683 现在并不必关心具体的版本信息,只要运行 docker version 命令后能够输出类似信息即可,接下来开始真正的表演! 镜像加速 Docker 服务已经安装并启动,接下来我们可以基于 Docker 部署应用了,当然现在离真正部署自己的应用还有不小距离,但是我们可以运行公开的应用啊! 学习任何新语言的第一件事就是运行 hello world ,学习 Docker 容器化部署也不例外,我们也运行 Docker 版本的 hello world ! 当我们敲入 docker run hello-world 命令后,终端会输出下列内容,只要输出 Hello from Docker! 字样就证明环境已经搭建完毕! [root@snowdreams1006 ~]# docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:4df8ca8a7e309c256d60d7971ea14c27672fc0d10c5f303856d7bc48f8cc17ff Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ [root@snowdreams1006 ~]# 如果你的网速比较慢,上述过程可能有一些耗时,但是如果你的网速一般而上述过程异常慢,很可能是因为你没有配置镜像! 因为 Docker 默认是先从国外下载项目到本地,然后再运行服务的,正如我们平时访问 Github 一样,那网速不是一般的慢! Github 没有镜像加速地址并不能为我们加速访问,但是 Docker 项目仓库是有镜像仓库的,国内提供这种镜像服务的有不少,基本上都需要注册账号获取镜像地址之类的. 这里提供一下网易的镜像仓库地址 http://hub-mirror.c.163.com 以及阿里云的个人镜像仓库地址 https://8upnmlh3.mirror.aliyuncs.com . 只要将镜像地址配置给 Docker ,下一次再下载项目时速度应该就会得到明显提升! 首选打开并编辑 /etc/docker/daemon.json 文件,如果没有的话就新建该文件,内容如下: { \"registry-mirrors\": [\"http://hub-mirror.c.163.com\"] } 保存后重启 Docker 服务,试一试运行 docker pull nginx 会不会很快呢? 卸载 docker 假如发现意外想要重新安装 Docker 服务或者就是想要卸载 Docker ,那么只需要简单运行下列命令就能清除掉 Docker 环境! sudo yum remove docker-ce sudo rm -rf /var/lib/docker 此时再次运行 docker 命令就会提示 command not found ,期待下一次相见会让人焕然一新! 基本命令 正如初次见面的那样,当我们成功安装 Docker 后控制台输出了一大堆关于用法的介绍,只不过当时年少轻狂并不在乎,蓦然回首,竟发现如此有用! [root@snowdreams1006 ~]# docker Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Options: --config string Location of client config files (default \"/root/.docker\") -c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with \"docker context use\") -D, --debug Enable debug mode -H, --host list Daemon socket(s) to connect to -l, --log-level string Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\") (default \"info\") --tls Use TLS; implied by --tlsverify --tlscacert string Trust certs signed only by this CA (default \"/root/.docker/ca.pem\") --tlscert string Path to TLS certificate file (default \"/root/.docker/cert.pem\") --tlskey string Path to TLS key file (default \"/root/.docker/key.pem\") --tlsverify Use TLS and verify the remote -v, --version Print version information and quit Management Commands: builder Manage builds config Manage Docker configs container Manage containers context Manage contexts engine Manage the docker engine image Manage images network Manage networks node Manage Swarm nodes plugin Manage plugins secret Manage Docker secrets service Manage services stack Manage Docker stacks swarm Manage Swarm system Manage Docker trust Manage trust on Docker images volume Manage volumes Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes to files or directories on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on Docker objects kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry logout Log out from a Docker registry logs Fetch the logs of a container pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart one or more containers rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information wait Block until one or more containers stop, then print their exit codes Run 'docker COMMAND --help' for more information on a command. 自我介绍 用法介绍的第一段就是自我介绍,用法是: docker [OPTIONS] COMMAND ,即 docker + 可选选项 + 必选命令. 表示的含义就是 A self-sufficient runtime for containers 为容器提供一个自包含的运行环境! Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Docker 类似于生活中超级货轮,运输着统一规格的集装箱,而集装箱装着各种各样的货物,开往不同的目的地. 容器则是集装箱,货轮为集装箱提供了自包含的环境,集装箱之间是相互独立的,这也是对第一段话的简单解释. 选项配置 下面我们继续看第二段内容,主要解释了有哪些配置项以及这些配置项背后表示的具体含义. Options: --config string Location of client config files (default \"/root/.docker\") -c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with \"docker context use\") -D, --debug Enable debug mode -H, --host list Daemon socket(s) to connect to -l, --log-level string Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\") (default \"info\") --tls Use TLS; implied by --tlsverify --tlscacert string Trust certs signed only by this CA (default \"/root/.docker/ca.pem\") --tlscert string Path to TLS certificate file (default \"/root/.docker/cert.pem\") --tlskey string Path to TLS key file (default \"/root/.docker/key.pem\") --tlsverify Use TLS and verify the remote -v, --version Print version information and quit 只要有一定英语基础的人应该都能看懂其中的意思,如果对个人细节不是很清楚的话,可以复制粘贴到浏览器在线翻译,这里就不全文解释了. 不消息看到了最后一个 -v, --version 选项,表示的意思是打印版本信息并且退出. 看到这里我们就明白了,原来之前运行的 docker version 和这里的 --version 并不是一回事啊! [root@snowdreams1006 ~]# docker -v Docker version 19.03.5, build 633a0ea [root@snowdreams1006 ~]# docker --version Docker version 19.03.5, build 633a0ea 单纯从输出结果来说,docker --version 更加简洁,如果只是验证环境安装是否成功,还是运行docker --version 比较简单明了. 管理命令 第三部分是 Docker 支持的管理命令,现在不去深究细节,只要有印象就行,注意这里有个关于镜像的命令 docker image Management Commands: builder Manage builds config Manage Docker configs container Manage containers context Manage contexts engine Manage the docker engine image Manage images network Manage networks node Manage Swarm nodes plugin Manage plugins secret Manage Docker secrets service Manage services stack Manage Docker stacks swarm Manage Swarm system Manage Docker trust Manage trust on Docker images volume Manage volumes 因为自我介绍中关于用法是 docker [OPTIONS] COMMAND ,而中括号 [] 表示该内容是可选的,所以不加任何选项的基本用法就是 docker COMMAND ,因此其中关于 image 命令的完整用法就是: docker image . [root@snowdreams1006 ~]# docker image Usage: docker image COMMAND Manage images Commands: build Build an image from a Dockerfile history Show the history of an image import Import the contents from a tarball to create a filesystem image inspect Display detailed information on one or more images load Load an image from a tar archive or STDIN ls List images prune Remove unused images pull Pull an image or a repository from a registry push Push an image or a repository to a registry rm Remove one or more images save Save one or more images to a tar archive (streamed to STDOUT by default) tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE Run 'docker image COMMAND --help' for more information on a command. 别有洞天,管理命令中还有子命令,大概用法和之前介绍的内容大致相同,基本用法是: docker image COMMAND . 其中支持的命令中有 ls ,因此调用 ls 命令的最终完整命令就是: docker image ls . # docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kB 服务器已安装的 image 中就包括我们熟悉的 hello-world ,至于什么是 REPOSITORY ,什么是 IMAGE 暂时也不用深究,只需要知道如何无文档使用这些命令即可! 如果用心留意的话,可以看到 Run 'docker image COMMAND --help' for more information on a command. 这么一句话,看来我们有现成的帮助文档供我们学习啊! 还是以 ls 命令为例,演示一下如何使用 docker image COMMAND --help 查看帮助文档. [root@snowdreams1006 ~]# docker image ls --help Usage: docker image ls [OPTIONS] [REPOSITORY[:TAG]] List images Aliases: ls, images, list Options: -a, --all Show all images (default hides intermediate images) --digests Show digests -f, --filter filter Filter output based on conditions provided --format string Pretty-print images using a Go template --no-trunc Don't truncate output -q, --quiet Only show numeric IDs 麻雀虽小五脏俱全,没想到 ls 命令还有更加细粒度的用法说明,支持可选参数和 [REPOSITORY[:TAG]] ,除此之外还有 ls, images, list 别名! 如果 ls 有 images 和 list 别名,那么岂不是意味着 docker image ls 等价于 docker image images 和 docker image list ? [root@snowdreams1006 ~]# docker image list REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kB [root@snowdreams1006 ~]# docker image images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 11 months ago 1.84kB 从上述输出结果来看,三者的运行效果确实是一样的,看来又发现了新大陆! 普通命令 回到 docker 命令的主线,除了管理命令外还是普通命令,这部分命令也是经常性使用到的命令也是重点学习掌握的命令! Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes to files or directories on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on Docker objects kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry logout Log out from a Docker registry logs Fetch the logs of a container pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart one or more containers rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information wait Block until one or more containers stop, then print their exit codes 命令虽好但不可贪多,还是找到最简单刚刚用过的 docker run 和 docker version 命令吧! docker run : Run a command in a new container 表示在新的容器内运行命令,翻译成生活语言就是在集装箱内做着不可告人的神秘操作! docker version : Show the Docker version information 显示 Docker 版本信息,还记得 docker --version 吗? 忘记了的话,往上翻翻看,--version 的描述是 Print version information and quit ,是一种更加简单的版本信息. 无论是管理命令还是普通命令,直接输入命令后都会有相应的用法说明以及帮助信息,同样地追加 --help 即可! [root@snowdreams1006 ~]# docker run \"docker run\" requires at least 1 argument. See 'docker run --help'. Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container 帮助信息 最后的才是亮点,在命令结尾处追加 --help 可以获取更加详细的帮助信息,这一点不仅适合一级命令 docker image --help 还适合二级子命令 docker image ls --help . Run 'docker COMMAND --help' for more information on a command. 所以,遇到不懂的或者陌生的命令请一定要记住 --help 帮助命令,这是 docker 全部命令中最重要的一点! 回忆总结 Docker 是一种规范化的部署运维新方式,相对于传统打包部署的来说,更加统一规范化,货物是各种各样的正如开发语言的多样性一样,但是集装箱的出现却颠覆了物流运输,带来了巨大的进步! 如果你是 Java 后台开发,或多或少肯定有着自己独立部署项目的经历,先登录服务器装个 Java 环境再装个 Tomcat 环境,最后在上传自己的 War 包到 Tomcat 部署目录,如此重复繁琐的劳动还不一定能保证一次性成功! 因为有时你的代码中很有可能有些绝对路径,部署到服务器肯定会报错,如果缺少了个人文件也会报错等等,这时候就出现了经典的对话:明明在我的电脑运行地好好的啊! Docker 的出现在一定程度上解决了这种问题,将应用打包到集装箱,Docker 作为超级货轮承载着集装箱安全快速地运送到目的地,集装箱内的环境是自给自足的封闭环境,所有的相关依赖一次性全部都给你. 无论是本机运输这个封闭的集装箱还是远程服务器运输这个集装箱结果都是一样的,再也不会出现环境不一致而导致的相互埋怨情况的发生了! 那么问题来了,如果给你一个集装箱,你能安全快速运输到目的地吗?如果你手头上已经有一批货需要这种集装箱服务,如何快速封装成集装箱呢? 对于第一个问题,本文已经给出答案,那就是 docker + docker COMMAND --help 查询支持的命令以及查看命令的帮助文档. [root@snowdreams1006 ~]# docker Usage: docker [OPTIONS] COMMAND A self-sufficient runtime for containers Options: --config string Location of client config files (default \"/root/.docker\") -c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with \"docker context use\") -D, --debug Enable debug mode -H, --host list Daemon socket(s) to connect to -l, --log-level string Set the logging level (\"debug\"|\"info\"|\"warn\"|\"error\"|\"fatal\") (default \"info\") --tls Use TLS; implied by --tlsverify --tlscacert string Trust certs signed only by this CA (default \"/root/.docker/ca.pem\") --tlscert string Path to TLS certificate file (default \"/root/.docker/cert.pem\") --tlskey string Path to TLS key file (default \"/root/.docker/key.pem\") --tlsverify Use TLS and verify the remote -v, --version Print version information and quit Management Commands: builder Manage builds config Manage Docker configs container Manage containers context Manage contexts engine Manage the docker engine image Manage images network Manage networks node Manage Swarm nodes plugin Manage plugins secret Manage Docker secrets service Manage services stack Manage Docker stacks swarm Manage Swarm system Manage Docker trust Manage trust on Docker images volume Manage volumes Commands: attach Attach local standard input, output, and error streams to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes to files or directories on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on Docker objects kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry logout Log out from a Docker registry logs Fetch the logs of a container pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart one or more containers rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information wait Block until one or more containers stop, then print their exit codes Run 'docker COMMAND --help' for more information on a command. 对于第二个问题,请先预习 docker 相关命令,下一次将实例分享如何使用 docker 运输集装箱,感谢你的阅读! 如果想要更方便地查看系列文章,欢迎访问我的网站 https://snowdreams1006.tech/ 或者 https://blog.snowdreams1006.cn/ ,喜欢就点个赞再走呗! 参考资料 Get Docker Engine - Community for CentOS Linux(Centos版本)如何快速安装docker 安装Docker 官方镜像加速 镜像仓库概述 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/docker.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/docker-get-started.html":{"url":"zero2devops/docker-get-started.html","title":"跟着官方体验 Docker","keywords":"","body":"跟着官方体验 Docker 传统部署开发项目时往往需要上传项目,安装依赖环境,配置环境变量等等操作才能启动项目,而更加优雅的部署项目应该可以简化上述步骤,这种需求的刺激下 Docker 也就应运而生. Docker 不仅可以打包你的项目还可以打包运行环境,只要本地开发正常,打包后上传到服务器也会正常,再也不会喊出 \"在我的电脑运行好好的啊!\" 这种话了呢! 话不多说,先让我们跟着官方一起学习体验一下 Docker 的神奇魅力之处吧! 搜一下 docker 找官网 打开任意一款浏览器,在搜索框中输入关键字 docker 后百度一下,搜索结果右侧带有 官方 标识或者下方网址包含 docker 的那条记录就是目标网站,比如以下示例的第二条,此方法也适用于探索其他陌生网站. 只要耐心往下翻翻找到搜索结果右侧的 官方 标志,那么这条搜索结果就是官方网站,除了用电脑进行搜索,平时使用最多的就是手机浏览器了. 好奇心的驱使下,分别用 今日头条,百度,QQ浏览器 搜索 docker 关键字,发现搜索结果很有意思. 今日头条 搜索结果中第一条结果就是官网,最令人满意,看样子今日头条布局搜索后确实不错! 百度 搜索结果中第一条是广告,接下来是百度翻译,百度百科,第二页结果才出现了官方,还是那个我们熟悉的百度. QQ浏览器 最牛逼了,不管是第一页还是第二页,根本就没有官网,无招胜有招,这让我无话可说了. 现在打开你的浏览器,搜索 docker 关键字看看官方出现在什么位置,欢迎评论区留言告诉我! 宾至如归地部署项目 官方网站: https://www.docker.com/ 根据上文找到官网后,首先映入眼帘的便是简明扼要的自我介绍,Debug your app, not your environment 翻译成中文是说不在你的环境调试你的应用,这句话可能有些歧义,调试自己的应用时难道不是自己的开发环境? 所以下方还有略小的说明文字辅助解释,Securely build, share and run any application, anywhere 即 安全地构建,分享,运行任何应用到任何地方. 由此可见,Debug your app, not your environment 其实表达的意思是问题场景,而 Securely build, share and run any application, anywhere 说的才是功能介绍. 问题: 如果遇到非本地开发环境情况下,需要调试你的应用怎么办? 回答: docker 可以安全构建,分享并运行任何程序到任意地方.(弦外之音是说无论身在何处,真实运行环境都和你本地开发环境保持一致,还会怕因为环境不同步而导致的意外 bug 吗) 如果让我一句话概况 docker 到底是什么,那么我会说: 是一种宾至如部署项目的工具! 下载目标平台安装包 初次体验 docker $ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ 阅读更多 Install Docker Desktop on Mac Install Docker Desktop on Mac Install Docker Desktop on Mac var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/docker-get-started.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/docker-example.html":{"url":"zero2devops/docker-example.html","title":"快速尝鲜基于 Docker 部署项目","keywords":"","body":"快速尝鲜基于 Docker 部署项目 平时工作学习中不可避免要接触到服务器运维工作,简单的 linux 操作是基础工作,打包上传部署应用更是家常便饭,如果只是为了应付工作需求,相信部署应用也不是特别麻烦伤神的一件事. 但是如果需要重复劳动同时操作多台服务器或者需要快速运行体验一下项目实际效果或者部署根本不熟悉的应用,那么不得不提到 Docker 容器化解决方案了! 编程语言千千万,学会三两种其实也不难,想要精通才是真正的考验,不可避免存在技术盲区,假如刚好遇到不太熟悉开源项目,想要快速体验一下运行效果,最最基本的条件就是先安装好运行环境! 一般来说,项目的运行环境依赖编程语言环境,而编程语言又依赖操作系统,所以想要快速配置好运行环境就要先装语言环境,设置语言环境的环境变量再配置项目,最后才能运行项目. 如果其中任何环节出错,那么这个安装过程就会非常耗时,尤其对于不熟悉的编程环境来说,很可能会配置镜像以及环境变量之类,这些都是相当大的挑战. 当成功安装好运行环境后,终于可以体验实际效果了,只会有两种情况,一是觉得效果不错,可以直接投放生产环境,二是觉得效果欠佳,白白浪费了这么多时间,最后还要卸载掉项目,顺便还要卸载运行环境. 这种反反复复的折腾最终只会让我们变得更加谨慎,不敢再去轻易尝试小众化的项目而是选择跟随主流选择评价比较高的优质项目. Github 作为世界上最大的同性交友网站,从根本上改变了编程开发的方式,现在绝大多数开发需求都应该或多或少找到相关现成的轮子,再也不用重头开始造轮子. Github 作为开源项目的托管平台,有着非常多的优质项目供我们学习参考,其中最重要的指标就是点赞量 Star . 开源项目的 Star 数越多表示项目越受欢迎,越有可能是优质项目,当然也不意味着 Star 数较少的项目质量就不行. 因为编程语言不同而产生的开源项目自然更是千差万别,即便是相同语言也不会有一模一样的开源项目(除非你是克隆 Fork 别人的项目),那么这些项目能否在某种意义上达到一种统一呢? 同一个世界,同样的需求但是却有不同的实现方式,而我们平时很有可能因为太注重于编程语言本身从而一定程度上忽略了实际需求,比如说: 客户只是想要搭建一个网站,不管你是用 Java 还是 Go 或者其他什么别的语言,只要能实现就行! 从编程语言自身的差异性到语言无关的统一性,这种思维方式的转变势必需要新的规范或者工具来实现,一旦完成编程语言的无关性,任何编程语言出身的开发者都能轻松玩转其他语言的项目. 如果你从未听说过 Docker 容器化或者并不知道自己到底是否需要了解 Docker ,那么我相信你大概是需要的,不然也不会看到现在了. Docker 就是将不同编程语言的开发项目统一管理运行的一种解决方案,如果以码头运输货物举例的话,可能更加直观帮你理解什么是 Docker . 不同的编程语言开发的实际项目是迥然不同的,好比是现实中需要运输到目的港的货物也是不一样的,但是这些货物都不是零散分布的而是封箱统一的集装箱,所以开发项目类比于集装箱. 超级货轮上可以运输众多集装箱,货轮可以装箱,封箱,开箱等操作进行统一管理,并不在乎集装箱内部到底是什么货物,因此不同的开发项目都可以被超级货轮 Docker 进项统一管理调度. 超级货轮从始发港到目的港的运输过程就完成了集装箱的转移,正如项目可以从本地环境迁移到远程服务器环境,如果这种目的港始终都是同一个,那么就意味着这个港口存放着众多的集装箱! 假如这个目的港又是对外开放的话,港口的集装箱也是对外开放了,能够同时托管这么多数量巨大的集装箱也真的是超级港口,类比到软件开发上岂不是相当于众多项目被托管到公开仓库? 因为 Docker 负责托运着数量众多的集装箱,所以 Docker 自己建立了免费的 Docker Hub 托管网站. 既然目的港可以对外公开也可以对外隐藏,所以除了有公共仓库还有不少私有仓库,主要职责都是负责管理维护 Docker 运输来的集装箱. web 服务器 Official build of Nginx : nginx 无论是 Github 还是 Dockerhub 网站,只要是浏览器上能够给人在线访问的网站都需要一款 Web 服务器提供对外服务. Web 服务器也不是一家独大,市面上有着众多优秀的 Web 服务器供不同编程语言的开发者去选择,这里以 nginx 为例,讲解如何基于 Docker 快速搭建 Web 服务. 如果你没听说过 Nginx ,那你总听说过 Apache 或者 Tomcat 之类吧? 只是一种 Web 服务器而已! 如果没有注册过 Dockerhub 账号的话,可以现在就去注册一个账号,正如注册 Github 账号那样,方便后续上传自己的项目. 登录 Dockerhub 网站后直接搜索 nginx 找到下载量 download 或点赞量 star 最多的那个点击查看使用方法. $ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx 上述命令的含义是基于 docker 部署自定义路径下的静态网站,要求必须先指定网站路径 这里为了简单起见,还是跟着文章思路来部署 nginx 吧,当然官方介绍不是不可以而是针对有一定基础的 docker 用户更加适合. docker pull nginx 在解释上述命令之前,有必要继续... var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/docker-example.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/aliyun-oss-static.html":{"url":"zero2devops/aliyun-oss-static.html","title":"利用阿里云 OSS部署静态网站","keywords":"","body":"利用阿里云 OSS部署静态网站 $ ping snowdreams1006.tech -t 3 PING snowdreams1006.tech.w.kunlunca.com (36.158.216.227): 56 data bytes 64 bytes from 36.158.216.227: icmp_seq=0 ttl=54 time=25.075 ms 64 bytes from 36.158.216.227: icmp_seq=1 ttl=54 time=22.077 ms 64 bytes from 36.158.216.227: icmp_seq=2 ttl=54 time=21.125 ms --- snowdreams1006.tech.w.kunlunca.com ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 21.125/22.759/25.075/1.683 ms name: deploy to aliyun oss on: [push] jobs: build: runs-on: ubuntu-latest steps: # 切代码到 runner - uses: actions/checkout@v1 with: submodules: true # 下载 git submodule - uses: srt32/git-actions@v0.0.3 with: args: git submodule update --init --recursive # 使用 node:10 - name: use Node.js 10.x uses: actions/setup-node@v1 with: node-version: 10.x # npm install - name: npm install and build run: | npm install npm run build env: CI: true # 设置阿里云OSS的 id/secret,存储到 github 的 secrets 中 - name: setup aliyun oss uses: manyuanrong/setup-ossutil@master with: endpoint: oss-cn-beijing.aliyuncs.com access-key-id: ${{ secrets.OSS_KEY_ID }} access-key-secret: ${{ secrets.OSS_KEY_SECRET }} - name: cp files to aliyun run: ossutil cp -rf .vuepress/dist oss://shanyue-blog/ name: MainWorkflow on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: \"12.x\" - name: Build Blog run: | npm install npm install -g hexo-cli hexo generate - uses: manyuanrong/setup-ossutil@v1.0 with: # endpoint 可以去oss控制台上查看 endpoint: \"oss-cn-hongkong.aliyuncs.com\" # 使用我们之前配置在secrets里面的accesskeys来配置ossutil access-key-id: ${{ secrets.ACCESS_KEY_ID }} access-key-secret: ${{ secrets.ACCESS_KEY_SECRET }} - name: Deply To OSS run: ossutil cp public oss://enok-blog/ -rf name: blog on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: \"12.x\" - name: Build blog run: | npm install -g gitbook-cli gitbook install gitbook build - name: Upload blog uses: appleboy/scp-action@master env: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} KEY: ${{ secrets.KEY }} with: source: _book/* target: ~/blog rm: true strip_components: 1 - name: Deploy blog uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | docker restart blog - uses: manyuanrong/setup-ossutil@v1.0 with: endpoint: oss-cn-hangzhou.aliyuncs.com access-key-id: ${{ secrets.ALI_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.ALI_ACCESS_KEY_SECRET }} - name: Upload files to aliyun oss run: ossutil cp -rf ./_book oss://snowdreams1006/ https://snowdreams1006.tech/ https://blog.snowdreams1006.cn/ https://snowdreams1006.tech/markdown/ https://blog.snowdreams1006.cn/markdown/ https://blog.snowdreams1006.cn/git/ https://blog.snowdreams1006.cn/git/base/about.html https://blog.snowdreams1006.cn/git/base/install.html https://blog.snowdreams1006.cn/git/server/private.html ^/(.*?)/$ /$1/index.html break Github Action 部署博客到阿里云OSS 在阿里云OSS托管你的个人网站 manyuanrong/setup-ossutil var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/aliyun-oss-static.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"zero2devops/nginx-https.html":{"url":"zero2devops/nginx-https.html","title":"免费实现https访问网站","keywords":"","body":"免费实现https访问网站 Let's Encrypt免费证书签发过程包含以下三个阶段: 在本地服务器上安装Certbot,Certbot是签发/更新证书的客户端程序; 运行Certbot获取SSL/TLS证书,证书有效期为3个月; 设置定时脚本每周运行一次Certbot更新证书。如果证书有效期小于30天,Certbot会更新证书; 实际运行 安装 certbot yum install -y epel-release certbot 申请证书 使用方法:certbot certonly --webroot -w [Web站点目录] -d [站点域名] -m [联系人email地址] --agree-tos sudo certbot certonly --webroot -w ~/snowdreams1006.github.io -d snowdreams1006.cn -m snowdreams1006@163.com --agree-tos pip install --upgrade --force-reinstall 'requests==2.6.0' urllib3 证书位置: tree /etc/letsencrypt/live/snowdreams1006.cn /etc/letsencrypt/live/snowdreams1006.cn ├── cert.pem -> ../../archive/snowdreams1006.cn/cert1.pem ├── chain.pem -> ../../archive/snowdreams1006.cn/chain1.pem ├── fullchain.pem -> ../../archive/snowdreams1006.cn/fullchain1.pem ├── privkey.pem -> ../../archive/snowdreams1006.cn/privkey1.pem └── README IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem Your cert will expire on 2020-02-17. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \"certbot renew\" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le 查看命令有效期 openssl x509 -noout -dates -in /etc/letsencrypt/live/snowdreams1006.cn/cert.pem 定时自动更新 yum install -y vixie-cron crontabs service crond status service crond restart # +---------------- minute 分钟(0 - 59) # | +------------- hour 小时(0 - 23) # | | +---------- day 日期(1 - 31) # | | | +------- month 月份(1 - 12) # | | | | +---- week 星期(0 - 7) (星期天=0 or 7) # | | | | | # * * * * * 要运行的命令 配置crontab,每月1号5时更新证书,并重启docker容器 00 05 01 * * sudo /usr/bin/certbot renew --quiet && sudo docker restart nginx crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数 crontab -l //列出某个用户cron服务的详细内容 crontab -r //删除没个用户的cron服务 crontab -e //编辑某个用户的cron服务 生成 PFS 键值 #创建目录 mkdir -p /etc/ssl/private/ #执行命令 cd /etc/ssl/private/ openssl dhparam 2048 -out dhparam.pem docker 启动 nginx docker run --name nginx -d -p 80:80 -p 443:443 \\ -v ~/snowdreams1006.github.io:/usr/share/nginx/html \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn; access_log /var/log/nginx/access-blog.log main; error_log /var/log/nginx/error-blog.log warn; root ~/snowdreams1006.github.io; index index.html index.htm; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets on; ssl_buffer_size 8k; ssl_dhparam /etc/ssl/private/dhparam.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 一般推荐使用的ssl_ciphers值: https://wiki.mozilla.org/Security/Server_Side_TLS ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK'; ssl_prefer_server_ciphers on; location / { #security headers add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\"; add_header X-XSS-Protection \"1; mode=block\" always; add_header X-Content-Type-Options \"nosniff\" always; add_header X-Frame-Options \"DENY\" always; #CSP add_header Content-Security-Policy \"frame-src 'self'; default-src 'self'; script-src 'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com; img-src 'self'; style-src 'self' https://maxcdn.bootstrapcdn.com; font-src 'self' data: https://maxcdn.bootstrapcdn.com; form-action 'self'; upgrade-insecure-requests;\" always; add_header Referrer-Policy \"strict-origin-when-cross-origin\" always; } location ~* ^.+\\.(jpg|jpeg|gif|png|bmp|css|js|swf)$ { access_log off; #break; } } yum install -y bind-utils dig www.baidu.com # 注xxx.com请根据自己的域名自行更改 ./certbot-auto --server https://acme-v02.api.letsencrypt.org/directory -d \"*.xxx.com\" --manual --preferred-challenges dns-01 certonly ./certbot-auto --server https://acme-v02.api.letsencrypt.org/directory -d \"*.xxx.com\" -d \"xxx.com\" --manual --preferred-challenges dns-01 certonly certbot certonly -d snowdreams1006.cn -d *.snowdreams1006.cn --manual \\ --preferred-challenges dns \\ --server https://acme-v02.api.letsencrypt.org/directory IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/snowdreams1006.cn-0001/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/snowdreams1006.cn-0001/privkey.pem Your cert will expire on 2020-02-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \"certbot renew\" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le ls /etc/letsencrypt/live/snowdreams1006.cn/ 最佳实践 添加 epel 源 curl -o /etc/yum.repos.d/epel-7.repo https://mirrors.aliyun.com/repo/epel-7.repo 安装 Certbot 工具 yum install -y certbot 申请证书 certbot certonly -d snowdreams1006.cn -d *.snowdreams1006.cn --manual \\ --preferred-challenges dns \\ --server https://acme-v02.api.letsencrypt.org/directory 配置 dns 解析 _acme-challenge.snowdreams1006.cn with the following value: AioofHwGnTzH7J_sKQczQnYc2QcXde_4lj42VNKk6FA Please deploy a DNS TXT record under the name _acme-challenge.snowdreams1006.cn with the following value: hKdp1-NVK9uSSZje3tePc_tLrh_d-LUhEUBVM6wlJhc Before continuing, verify the record is deployed. (This must be set up in addition to the previous challenges; do not remove, replace, or undo the previous challenge tasks yet. Note that you might be asked to create multiple distinct TXT records with the same name. This is permitted by DNS standards.) 验证 dns 解析 dig -t txt _acme-challenge.snowdreams1006.cn 生成提示 IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem Your cert will expire on 2020-02-18. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \"certbot renew\" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le - We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https://act.eff.org. tree /etc/letsencrypt /etc/letsencrypt ├── accounts │ └── acme-v02.api.letsencrypt.org │ └── directory │ └── 41c8e91d7dcd718b2f48c09ab9a32cc4 │ ├── meta.json │ ├── private_key.json │ └── regr.json ├── archive │ └── snowdreams1006.cn │ ├── cert1.pem │ ├── chain1.pem │ ├── fullchain1.pem │ └── privkey1.pem ├── csr │ └── 0000_csr-certbot.pem ├── keys │ └── 0000_key-certbot.pem ├── live │ ├── README │ └── snowdreams1006.cn │ ├── cert.pem -> ../../archive/snowdreams1006.cn/cert1.pem │ ├── chain.pem -> ../../archive/snowdreams1006.cn/chain1.pem │ ├── fullchain.pem -> ../../archive/snowdreams1006.cn/fullchain1.pem │ ├── privkey.pem -> ../../archive/snowdreams1006.cn/privkey1.pem │ └── README ├── renewal │ └── snowdreams1006.cn.conf └── renewal-hooks ├── deploy ├── post └── pre 15 directories, 16 files 查看命令有效期 openssl x509 -noout -dates -in /etc/letsencrypt/live/snowdreams1006.cn/cert.pem 设置定时任务自动更新 配置 crontab,每月1好5时更新证书,并重启docker容器 00 01 01 * * sudo /usr/bin/certbot renew --quiet && sudo docker restart nginx 生成 PFS 键值 #创建目录 mkdir -p /etc/ssl/private/ #执行命令 cd /etc/ssl/private/ openssl dhparam 2048 -out dhparam-2048.pem 配置 nginx server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; index index.html index.htm; root /usr/share/nginx/html/; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; } server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; return 301 https://$server_name$request_uri; } 启动 nginx docker run -p 80:80 -p 443:443 \\ -v ~/snowdreams1006.github.io:/usr/share/nginx/html \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx docker run --name nginx -d -p 80:80 -p 443:443 \\ -v ~/snowdreams1006.github.io:/usr/share/nginx/html \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx docker run -d -p 8888:80 -v ~/test:/usr/share/nginx/html nginx 端口转发 server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; index index.html index.htm; root /usr/share/nginx/html/; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; } server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name artipub.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; location / { proxy_pass http://172.16.166.99:8000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name artipub.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 80; server_name test.snowdreams1006.cn; location / { proxy_pass http://127.0.0.1:8888; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } 访问效果 https://snowdreams1006.cn/ https://www.snowdreams1006.cn/ https://blog.snowdreams1006.cn/ 宿主机 nginx nginx 默认目录 whereis nginx 添加安装源 sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 安装该rpm后,我们就能在/etc/yum.repos.d/ 目录中看到一个名为nginx.repo 的文件。 安装 nginx sudo yum install -y nginx 启动 nginx sudo systemctl start nginx.service 查看开机自启 sudo systemctl list-unit-files systemctl list-unit-files | grep enable 设置开机自启动 sudo systemctl enable nginx.service 关闭开机自启 sudo systemctl disable nginx.service 查看 nginx 效果 curl http://www.example.com/ 启动 nginx nginx 测试配置文件 nginx -t 优雅重启 nginx -s reload 查看nginx 进程号 ps -ef |grep nginx 停止 nginx nginx -s stop kill -9 pid /etc/nginx/nginx.conf user root; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] \"$request\" ' '$status $body_bytes_sent \"$http_referer\" ' '\"$http_user_agent\" \"$http_x_forwarded_for\"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; } /etc/nginx/conf.d/default.conf server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; index index.html index.htm; root /root/snowdreams1006.github.io; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; } server { listen 80; server_name test.snowdreams1006.cn; location / { proxy_pass http://172.16.166.99:4000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl http2; server_name test.snowdreams1006.cn; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass https://172.16.166.99:4000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } 启动 docker 容器 nginx server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn blog.snowdreams1006.cn; index index.html index.htm; root /usr/share/nginx/html; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; } server { listen 80; server_name resume.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name resume.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_dhparam /etc/ssl/private/dhparam-2048.pem; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://172.16.166.99:1006; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name artipub.snowdreams1006.cn; location / { proxy_pass http://172.16.166.99:8000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name artipubapi.snowdreams1006.cn; location / { proxy_pass http://172.16.166.99:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker run -p 80:80 -p 443:443 \\ -v ~/snowdreams1006.github.io:/usr/share/nginx/html \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx docker run --name nginx -d -p 80:80 -p 443:443 \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ -v /etc/ssl:/etc/ssl \\ nginx 参考文档 User Guide centos7下docker部署nginx使用let's encrypt免费证书 ImportError: No module named 'requests.packages.urllib3 centos crontab详解 在Docker容器环境中用Let's Encrypt部署HTTPS Docker环境下自动更新Let’s Encrypt SSL证书 申请 Let's Encrypt 通配符 HTTPS 证书 Centos通过acme申请Let’s Encrypt通配符HTTPS证书-简单粗暴 centos 安装 dig CentOS 7,使用yum安装Nginx CentOS7中使用yum安装Nginx的方法 CertBot自签Let's Encrypt免费SSL单域名证书和泛域名证书 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/nginx-https.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-03-02 22:22:38 "},"zero2devops/webhook.html":{"url":"zero2devops/webhook.html","title":"webhook","keywords":"","body":"webhook https://snapcraft.io/install/webhook/centos sudo yum install -y epel-release sudo yum install -y snapd sudo systemctl enable --now snapd.socket sudo ln -s /var/lib/snapd/snap /snap sudo snap install webhook [ { \"id\": \"redeploy-webhook\", \"execute-command\": \"/root/snap/webhook/redeploy.sh\", \"command-working-directory\": \"/root/snap/webhook\" } ] #! /bin/sh echo \"Hi,WebHook\" webhook -hooks hooks.json -verbose http://snowdreams1006.cn:9000/hooks/redeploy-webhook https://webhook.snowdreams1006.cn/hooks/redeploy-webhook 卸载 sudo yum remove -y epel-release sudo yum remove -y snapd sudo systemctl disable --now snapd.socket sudo rm -rf /snap sudo snap remove webhook centos install webhook docker pull almir/webhook [ { \"id\": \"test\", \"execute-command\": \"/etc/webhook/redeploy.sh\", \"command-working-directory\": \"/etc/webhook\" } ] #! /bin/sh echo \"Test WebHook\" curl https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=test&desp=Message from [webhook.snowdreams1006.cn/hooks/test](https://webhook.snowdreams1006.cn/hooks/test) docker run -d -p 9000:9000 -v /root/webhook:/etc/webhook --name=webhook \\ almir/webhook -verbose -hooks=/etc/webhook/hooks.json -hotreload #! /bin/sh echo \"Test WebHook\" docker ps chmod 777 docker.sock docker run -d -p 9000:9000 --name=webhook \\ -v /root/webhook:/etc/webhook \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v /usr/bin/docker:/usr/bin/docker \\ almir/webhook -verbose -hooks=/etc/webhook/hooks.json -hotreload curl https://webhook.snowdreams1006.cn/hooks/test docker pull hongkongkiwi/webhook hooks.json [ { \"id\": \"test\", \"execute-command\": \"/etc/webhook/test.sh\", \"command-working-directory\": \"/etc/webhook\" } ] test.sh #! /bin/sh echo \"Test WebHook\" docker stop resume docker run docker run -d -p 9000:9000 --name=webhook \\ -v /root/webhook:/etc/webhook \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v /usr/bin/docker:/usr/bin/docker \\ hongkongkiwi/webhook -verbose -hooks=/etc/webhook/hooks.json -hotreload chmod 777 docker.sock trigger curl https://webhook.snowdreams1006.cn/hooks/test hooks.json [ { \"id\": \"test\", \"execute-command\": \"/etc/webhook/test.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"Test received successfully!\" }, { \"id\": \"github\", \"execute-command\": \"/etc/webhook/github.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"Github received successfully!\", \"pass-arguments-to-command\": [ { \"source\": \"payload\", \"name\": \"head_commit.id\" }, { \"source\": \"payload\", \"name\": \"pusher.name\" }, { \"source\": \"payload\", \"name\": \"pusher.email\" } ], \"trigger-rule\": { \"and\": [ { \"match\": { \"type\": \"payload-hash-sha1\", \"secret\": \"blog.snowdreams1006.cn\", \"parameter\": { \"source\": \"header\", \"name\": \"X-Hub-Signature\" } } }, { \"match\": { \"type\": \"value\", \"value\": \"refs/heads/master\", \"parameter\": { \"source\": \"payload\", \"name\": \"ref\" } } } ] } }, { \"id\": \"query\", \"execute-command\": \"/etc/webhook/query.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"Query received successfully!\" }, { \"id\": \"file\", \"execute-command\": \"/etc/webhook/file.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"File received successfully!\", \"pass-file-to-command\": [ { \"source\": \"payload\", \"name\": \"file\", \"envname\": \"ENV_VARIABLE\", \"base64decode\": false } ], \"include-command-output-in-response\": true } ] test.sh #! /bin/sh echo \"Test received successfully!\" github.sh #! /bin/sh echo \"Github received successfully!\" query.sh #! /bin/sh echo \"Query received successfully!\" file.sh #! /bin/sh echo \"File received successfully!\" trigger curl https://webhook.snowdreams1006.cn/hooks/test curl https://webhook.snowdreams1006.cn/hooks/github curl https://webhook.snowdreams1006.cn/hooks/query { \"file\":\"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2lpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjEzMTA4RDI0QzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjEzMTA4RDIzQzMxQjExRTBCMzYzRjY1QUQ1Njc4QzFBIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzMgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOkFDMUYyRTgzMzI0QURGMTFBQUI4QzUzOTBEODVCNUIzIiBzdFJlZjpkb2N1bWVudElEPSJ1dWlkOkM5RDM0OTY2NEEzQ0REMTFCMDhBQkJCQ0ZGMTcyMTU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+IBFgEwAAAmJJREFUeNqkk89rE1EQx2d/NNq0xcYYayPYJDWC9ODBsKIgAREjBmvEg2cvHnr05KHQ9iB49SL+/BMEfxBQKHgwCEbTNNIYaqgaoanFJi+rcXezye4689jYkIMIDnx47837zrx583YFx3Hgf0xA6/dJyAkkgUy4vgryAnmNWH9L4EVmotFoKplMHgoGg6PkrFarjXQ6/bFcLj/G5W1E+3NaX4KZeDx+dX5+7kg4HBlmrC6JoiDFYrGhROLM/mp1Y6JSqdCd3/SW0GUqEAjkl5ZyHTSHKBQKnO6a9khD2m5cr91IJBJ1VVWdiM/n6LruNJtNDs3JR3ukIW03SHTHi8iVsbG9I51OG1bW16HVasHQZopDc/JZVgdIQ1o3BmTkEnJXURS/KIpgGAYPkCQJPi0u8uzDKQN0XQPbtgE1MmrHs9nsfSqAEjxCNtHxZHLy4G4smUQgyzL4LzOegDGGp1ucVqsNqKVrpJCM7F4hg6iaZvhqtZrg8XjA4xnAU3XeKLqWaRImoIZeQXVjQO5pYp4xNVirsR1erxer2O4yfa227WCwhtWoJmn7m0h270NxmemFW4706zMm8GCgxBGEASCfhnukIW03iFdQnOPz0LNKp3362JqQzSw4u2LXBe+Bs3xD+/oc1NxN55RiC9fOme0LEQiRf2rBzaKEeJJ37ZWTVunBeGN2WmQjg/DeLTVP89nzAive2dMwlo9bpFVC2xWMZr+A720FVn88fAUb3wDMOjyN7YNc6TvUSHQ4AH6TOUdLL7em68UtWPsJqxgTpgeiLu1EBt1R+Me/mF7CQPTfAgwAGxY2vOTrR3oAAAAASUVORK5CYII=\" } curl -H \"Content-Type:application/json\" -X POST -d @file.json \\ https://webhook.snowdreams1006.cn/hooks/file #! /bin/sh echo \"Query received successfully!\" curl https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=服务器又发来新消息啦!&desp=欢迎访问[雪之梦技术驿站](https://blob.snowdreams1006.cn/),请关注微信公众号:「 雪之梦技术驿站 」 ![wechat:snowdreams1006](https://snowdreams1006.github.io/snowdreams1006-wechat-public.jpeg) #! /bin/sh echo \"Query received successfully!\" curl https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=服务器又发来新消息啦!&desp=欢迎访问雪之梦技术驿站 : https://blob.snowdreams1006.cn #! /bin/sh echo \"Query received successfully!\" curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8F%88%E5%8F%91%E6%9D%A5%E6%96%B0%E6%B6%88%E6%81%AF%E5%95%A6!&desp=%E6%AC%A2%E8%BF%8E%E8%AE%BF%E9%97%AE%5B%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99%5D(https%3A%2F%2Fblob.snowdreams1006.cn%3FtokenId%3D$(uuidgen))%2C%E8%AF%B7%E5%85%B3%E6%B3%A8%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%3A%E3%80%8C+%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99+%E3%80%8D+!%5Bwechat%3Asnowdreams1006%5D(https%3A%2F%2Fsnowdreams1006.github.io%2Fsnowdreams1006-wechat-public.jpeg)\" curl https://webhook.snowdreams1006.cn/hooks/query [ { \"id\": \"query\", \"execute-command\": \"/etc/webhook/query.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"webhook.snowdreams1006.cn received successfully!\", \"pass-arguments-to-command\": [ { \"source\": \"url\", \"name\": \"title\" }, { \"source\": \"url\", \"name\": \"body\" } ] }, { \"id\": \"github\", \"execute-command\": \"/etc/webhook/github.sh\", \"command-working-directory\": \"/etc/webhook\", \"pass-arguments-to-command\": [ { \"source\": \"payload\", \"name\": \"head_commit.id\" }, { \"source\": \"payload\", \"name\": \"pusher.name\" }, { \"source\": \"payload\", \"name\": \"pusher.email\" } ], \"trigger-rule\": { \"and\": [ { \"match\": { \"type\": \"payload-hash-sha1\", \"secret\": \"blog.snowdreams1006.cn\", \"parameter\": { \"source\": \"header\", \"name\": \"X-Hub-Signature\" } } }, { \"match\": { \"type\": \"value\", \"value\": \"refs/heads/master\", \"parameter\": { \"source\": \"payload\", \"name\": \"ref\" } } } ] } } ] #! /bin/sh title=$1 body=$2 echo \"title=${title},body=${body}\" curl -i -X GET \\ \"https://bark.snowdreams1006.cn/DtuwQJ6JYWD5CX3hCtwKnd/${title}/${body}?automaticallyCopy=1©=${body}\" curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=${title}---$(uuidgen)&desp=${body}\" #! /bin/sh title=\"雪之梦技术驿站博客更新啦!\" body=\"Github发来了贺电,雪之梦技术驿站博客更新啦!\" echo \"title=${title} body=${body}\" curl -i -X GET \\ \"https://bark.snowdreams1006.cn/DtuwQJ6JYWD5CX3hCtwKnd/${title}/${body}?automaticallyCopy=1©=${body}&url=https://blog.snowdreams1006.cn/\" curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=${title}---$(uuidgen)&desp=${body}\" #! /bin/sh title=\"雪之梦技术驿站博客更新啦!\" body=\"Github发来了贺电,雪之梦技术驿站博客更新啦!\" echo \"title=${title} body=${body}\" curl -i -X GET \\ \"https://bark.snowdreams1006.cn/DtuwQJ6JYWD5CX3hCtwKnd/${title}/${body}?automaticallyCopy=1©=${body}&url=https://blog.snowdreams1006.cn/\" curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=${title}---$(uuidgen)&desp=${body}\" #! /bin/sh leftDays=$((($(date +%s -d '20191215') - $(date +%s ))/86400)) title=\"跳跳真的快要来了!\" body=\"现在距离接驾日期还剩${leftDays}天,您真的准备好了吗?\" echo \"title=${title} body=${body}\" curl -i -X GET \\ \"https://bark.snowdreams1006.cn/DtuwQJ6JYWD5CX3hCtwKnd/${title}/${body}?automaticallyCopy=1©=${body}&url=https://blog.snowdreams1006.cn/\" curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=${title}---$(uuidgen)&desp=${body}\" https://github.com/adnanh/webhook var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/zero2devops/webhook.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-05-18 01:54:34 "},"devops/":{"url":"devops/","title":"运维部署","keywords":"","body":"运维部署 系统版本 查看系统版本 $ uname -a cat /proc/version 查看 centos 版本号 $ cat /etc/centos-release 内存配额 $ free -h 公网 ip ifconfig.me $ curl ifconfig.me icanhazip.com $ curl icanhazip.com 内网 ip $ ifconfig eth0 TODO 清单 使用 ansible 做自动化运维 使用 docker 高效部署前端应用 前端部署演化史 linux 调优各项监控指标小记 sed 命令使用及示例 jq: 命令行下的 json 处理工具 博客域名更换记录以及衍生问题解决方案 使用 Let's Encrypt 为 Traefik 制作证书并自动续期 github 托管私有仓库,并结合 github action 做 CI/CD quay 构建镜像 cloudflare 免费的 CDN sentry 异常上报 aws-lambda 简单的 API 参考文档 SSH简介及两种远程登录的方法 服务器快速免密ssh登录配置 linux 信息查看及命令 github连接报\"ssh: connect to host github.com port 22: Connection timed out\"错误 LICENSE 知识共享许可协议 Figure: 知识共享许可协议 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-env.html":{"url":"devops/docker-env.html","title":"docker 环境","keywords":"","body":"docker 环境 Docker 官方文档, CentOS 官方安装文档. 注意: 以下方式安装的是社区免费版 docker-ce 前提条件 目前在Linux系统上安装Docker,对系统版本有以下要求: CentOS:7 Debian:7.7(Wheezy LTS)、8.0(Jessie LTS)、9(Stretch) Fedora:24、25 Ubuntu:16.04(Xenial LTS)、14.04(Trusty LTS)、17.04(Zesty) 通过 uname -r 命令查看内核版本 uname -r 安装 docker 移除旧版本 sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine 安装必要系统依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 更新 yum 缓存 sudo yum makecache fast 安装 docker-ce sudo yum -y install docker-ce 运行 docker 启动 docker sudo systemctl start docker 开机自启 sudo systemctl enable docker 测试 hello-world docker run hello-world 镜像加速 镜像地址: http://hub-mirror.c.163.com 配置文件: /etc/docker/daemon.json { \"registry-mirrors\": [\"http://hub-mirror.c.163.com\"] } 卸载 docker sudo yum remove docker-ce sudo rm -rf /var/lib/docker 快速安装 安装 # step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装Docker-CE sudo yum makecache fast sudo yum -y install docker-ce # Step 4: 开启Docker服务 sudo service docker start # 注意: # 官方软件源默认启用了最新的软件,您可以通过编辑软件源的方式获取各个版本的软件包。例如官方并没有将测试版本的软件源置为可用,您可以通过以下方式开启。同理可以开启各种测试版本等。 # vim /etc/yum.repos.d/docker-ee.repo # 将[docker-ce-test]下方的enabled=0修改为enabled=1 # # 安装指定版本的Docker-CE: # Step 1: 查找Docker-CE的版本: # yum list docker-ce.x86_64 --showduplicates | sort -r # Loading mirror speeds from cached hostfile # Loaded plugins: branch, fastestmirror, langpacks # docker-ce.x86_64 17.03.1.ce-1.el7.centos docker-ce-stable # docker-ce.x86_64 17.03.1.ce-1.el7.centos @docker-ce-stable # docker-ce.x86_64 17.03.0.ce-1.el7.centos docker-ce-stable # Available Packages # Step2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.0.ce.1-1.el7.centos) # sudo yum -y install docker-ce-[VERSION] 验证 docker version 安装记录 卸载旧版本 [root@iZbp19ryeo103foh7nc3rmZ ~]# sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine 安装必要软件 sudo yum install -y yum-utils \\ device-mapper-persistent-data \\ lvm2 添加软件源 官方源 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 阿里源 sudo yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo 更新缓存 sudo yum makecache fast 安装 docker-ce sudo yum install -y docker-ce docker-ce-cli containerd.io docker 版本 docker version 启动 docker sudo systemctl start docker 查看状态 sudo systemctl status docker 测试 docker sudo docker run hello-world 镜像加速 针对Docker客户端版本大于 1.10.0 的用户 /etc/docker/daemon.json 阿里私人镜像地址: 例如: 公网Mirror:[系统分配前缀].mirror.aliyuncs.com https://8upnmlh3.mirror.aliyuncs.com docker info 重新加载 systemctl daemon-reload 重启 docker sudo systemctl restart docker 参考资料 Get Docker Engine - Community for CentOS Linux(Centos版本)如何快速安装docker 安装Docker 官方镜像加速 镜像仓库概述 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-env.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-ops.html":{"url":"devops/docker-ops.html","title":"docker 操作","keywords":"","body":"docker 操作 docker login sudo docker login --username=雪之梦技术驿站 registry.cn-hangzhou.aliyuncs.com config.json cat ~/.docker/config.json `` ## docker pull ## docker images ## docker push ## 从零开始学习 docker ```shell docker docker 帮助命令 docker command --help 运行容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] docker run -it ubuntu /bin/bash 退出容器 exit 查看容器 docker ps -a 启动容器 docker start b750bbbcfd88 后台运行 docker run -itd --name ubuntu-test ubuntu /bin/bash 停止容器 docker stop 重启容器 docker restart 进入容器 docker attach 和 docker exec docker attach 1e560fca3906 注意: 如果从这个容器退出,会导致容器的停止. docker exec -it 243c32535da7 /bin/bash 导出容器 docker export 1e560fca3906 > ubuntu.tar 导入容器 docker import - test/ubuntu:v1 删除容器 docker rm -f 1e560fca3906 清理掉所有处于终止状态的容器 docker container prune 端口映射 docker port bf08b7f2cd89 查看容器日志 docker logs -f bf08b7f2cd89 查看容器进程 docker top wizardly_chandrasekhar 镜像操作 列出镜像 docker images 下载镜像 docker pull 查找镜像 docker search httpd 删除镜像 docker rmi hello-world 创建镜像 docker commit -m=\"updated\" -a=\"snowdreams1006\" eb3c83541f05 snowdreams1006/ubuntu 构建镜像 FROM centos:6.7 MAINTAINER Fisher \"fisher@sudops.com\" RUN /bin/echo 'root:123456' |chpasswd RUN useradd runoob RUN /bin/echo 'runoob:123456' |chpasswd RUN /bin/echo -e \"LANG=\\\"en_US.UTF-8\\\"\" >/etc/default/local EXPOSE 22 EXPOSE 80 CMD /usr/sbin/sshd -D Dockerfile docker build -t runoob/centos:6.7 . 设置镜像标签 docker tag 860c279d2fec runoob/centos:dev web 应用 随机映射 docker run -d -P training/webapp python app.py 指定端口 docker run -d -p 5000:5000 training/webapp python app.py 指定地址 docker run -d -p 127.0.0.1:5001:5000 training/webapp python app.py 容器互联 命名容器 docker run -d -P --name runoob training/webapp python app.py 新建网络 docker network create -d bridge test-net 连接容器 docker run -itd --name test1 --network test-net ubuntu /bin/bash sudo docker logs -f -t --tail 10 s12 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-ops.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-compose.html":{"url":"devops/docker-compose.html","title":"docker compose","keywords":"","body":"docker compose Run this command to download the current stable release of Docker Compose: sudo curl -L \"https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose Apply executable permissions to the binary: sudo chmod +x /usr/local/bin/docker-compose Test the installation. docker-compose --version compose Docker-Compose安装(centos7环境) var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-compose.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-nginx.html":{"url":"devops/docker-nginx.html","title":"docker + nginx","keywords":"","body":"docker + nginx docker pull nginx docker run --name nginx --rm -p 80:80 -d nginx docker stop nginx docker exec nginx whereis nginx docker exec nginx ls /etc/nginx docker cp nginx:/etc/nginx/nginx.conf ~/nginx yum install -y tree docker exec nginx ls /etc/nginx/conf.d docker cp nginx:/etc/nginx/conf.d ~/nginx/conf.d docker run --name nginx -d -p 80:80 -p 443:443 \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ nginx docker stop nginx docker run --name nginx --rm -d -p 80:80 -p 443:443 \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ nginx curl -o /etc/yum.repos.d/epel-7.repo https://mirrors.aliyun.com/repo/epel-7.repo yum install -y certbot pip install --upgrade --force-reinstall 'requests==2.6.0' urllib3 certbot certonly -d *.snowdreams1006.cn -d snowdreams1006.cn --manual \\ --preferred-challenges dns \\ --server https://acme-v02.api.letsencrypt.org/directory Please deploy a DNS TXT record under the name _acme-challenge.snowdreams1006.cn with the following value: 2_F8ljNNjU_P6_fUVpaaB0A3QprSIiA4ODWvd77HFnQ Before continuing, verify the record is deployed. Please deploy a DNS TXT record under the name _acme-challenge.snowdreams1006.cn with the following value: Lwb2Ef3Fch7YFyG7iWDTRanoP3AyuUiYYgIIckCzGcQ Before continuing, verify the record is deployed. (This must be set up in addition to the previous challenges; do not remove, replace, or undo the previous challenge tasks yet. Note that you might be asked to create multiple distinct TXT records with the same name. This is permitted by DNS standards.) find / -type f -name \".certbot.lock\" -exec rm {} \\; IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem Your cert will expire on 2020-02-27. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \"certbot renew\" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le [root@snowdreams1006 nginx]# tree /etc/letsencrypt /etc/letsencrypt ├── accounts │ └── acme-v02.api.letsencrypt.org │ └── directory │ └── 4a7ed3e412c035edcf077438d642b1e7 │ ├── meta.json │ ├── private_key.json │ └── regr.json ├── archive │ └── snowdreams1006.cn │ ├── cert1.pem │ ├── chain1.pem │ ├── fullchain1.pem │ └── privkey1.pem ├── csr │ ├── 0000_csr-certbot.pem │ └── 0001_csr-certbot.pem ├── keys │ ├── 0000_key-certbot.pem │ └── 0001_key-certbot.pem ├── live │ ├── README │ └── snowdreams1006.cn │ ├── cert.pem -> ../../archive/snowdreams1006.cn/cert1.pem │ ├── chain.pem -> ../../archive/snowdreams1006.cn/chain1.pem │ ├── fullchain.pem -> ../../archive/snowdreams1006.cn/fullchain1.pem │ ├── privkey.pem -> ../../archive/snowdreams1006.cn/privkey1.pem │ └── README ├── renewal │ └── snowdreams1006.cn.conf └── renewal-hooks ├── deploy ├── post └── pre 15 directories, 18 files crontab -e 00 01 01 * * sudo /usr/bin/certbot renew --quiet && sudo docker restart nginx server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn; location / { root /usr/share/nginx/html; index index.html index.htm; } ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; } docker run --name nginx --rm -d -p 80:80 -p 443:443 \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ nginx bark docker run --name bark -dt -p 8888:8080 --restart=always \\ -v ~/bark/data:/data \\ finab/bark-server curl http://0.0.0.0:8888/ping server { listen 80; server_name bark.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name bark.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:8888; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker restart nginx curl https://bark.snowdreams1006.cn/ping webhook docker pull hongkongkiwi/webhook docker run -d -p 9000:9000 --name=webhook --restart=always \\ -v ~/webhook:/etc/webhook \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v /usr/bin/docker:/usr/bin/docker \\ hongkongkiwi/webhook -verbose -hooks=/etc/webhook/hooks.json -hotreload [ { \"id\": \"query\", \"execute-command\": \"/etc/webhook/query.sh\", \"command-working-directory\": \"/etc/webhook\", \"response-message\": \"webhook.snowdreams1006.cn received successfully!\" } ] hooks.json #! /bin/sh curl -i -X GET \\ \"https://sc.ftqq.com/SCU67099T95840f46f3bad01fae1c893c968be0e25dd94acd8217a.send?text=%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8F%88%E5%8F%91%E6%9D%A5%E6%96%B0%E6%B6%88%E6%81%AF%E5%95%A6!&desp=%E6%AC%A2%E8%BF%8E%E8%AE%BF%E9%97%AE%5B%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99%5D(https%3A%2F%2Fblob.snowdreams1006.cn%3FtokenId%3D$(uuidgen))%2C%E8%AF%B7%E5%85%B3%E6%B3%A8%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%3A%E3%80%8C+%E9%9B%AA%E4%B9%8B%E6%A2%A6%E6%8A%80%E6%9C%AF%E9%A9%BF%E7%AB%99+%E3%80%8D+!%5Bwechat%3Asnowdreams1006%5D(https%3A%2F%2Fsnowdreams1006.github.io%2Fsnowdreams1006-wechat-public.jpeg)\" query.sh chmod +x query.sh curl http://0.0.0.0:9000/hooks/query curl https://webhook.snowdreams1006.cn/hooks/query server { listen 80; server_name webhook.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name webhook.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:9000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker restart nginx curl https://webhook.snowdreams1006.cn/hooks/query blog docker exec nginx whereis nginx docker exec nginx ls /usr/share/nginx/html docker cp nginx:/usr/share/nginx/html/index.html ~/blog docker run --name blog -d -p 4000:80 --restart=always -v ~/blog:/usr/share/nginx/html nginx server { listen 80; server_name blog.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name blog.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:4000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker restart nginx curl https://blog.snowdreams1006.cn name: blog on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: \"12.x\" - name: Build blog run: | npm install -g gitbook-cli gitbook install gitbook build - name: Upload blog uses: appleboy/scp-action@master env: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} KEY: ${{ secrets.KEY }} with: source: _book/* target: ~/blog rm: true strip_components: 1 - name: Deploy blog uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | docker restart blog curl https://blog.snowdreams1006.cn server { listen 80; server_name snowdreams1006.cn www.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name snowdreams1006.cn www.snowdreams1006.cn; location / { proxy_pass http://172.16.166.99:4000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; } docker stop nginx docker run --name nginx -d -p 80:80 -p 443:443 --restart=always \\ -v ~/nginx/nginx.conf:/etc/nginx/nginx.conf \\ -v ~/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf \\ -v ~/nginx/logs:/var/log/nginx \\ -v /etc/letsencrypt:/etc/letsencrypt \\ nginx curl https://snowdreams1006.cn curl https://www.snowdreams1006.cn curl https://blog.snowdreams1006.cn resume docker exec nginx whereis nginx docker exec nginx ls /usr/share/nginx/html docker cp nginx:/usr/share/nginx/html/index.html ~/resume docker run --name resume -d -p 1006:80 --restart=always -v ~/resume:/usr/share/nginx/html nginx server { listen 80; server_name resume.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name resume.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:1006; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker restart nginx curl https://resume.snowdreams1006.cn name: resume.snowdreams1006.cn on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Upload resume uses: appleboy/scp-action@master env: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} KEY: ${{ secrets.KEY }} with: source: static,index.html,README.md,LICENSE target: /root/resume rm: true - name: Deploy resume uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | docker restart resume curl https://resume.snowdreams1006.cn Nginx部署 Let’s Encrypt时报错:Another instance of Certbot is already running certbot/certbot quay.io/letsencrypt/letsencrypt:latest User Guide centos7下docker部署nginx使用let's encrypt免费证书 ImportError: No module named 'requests.packages.urllib3 centos crontab详解 在Docker容器环境中用Let's Encrypt部署HTTPS Docker环境下自动更新Let’s Encrypt SSL证书 申请 Let's Encrypt 通配符 HTTPS 证书 Centos通过acme申请Let’s Encrypt通配符HTTPS证书-简单粗暴 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-nginx.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-blog.html":{"url":"devops/docker-blog.html","title":"docker + blog","keywords":"","body":"docker + blog docker run --name blog -d -p 4000:80 --restart=always -v ~/blog:/usr/share/nginx/html nginx var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-blog.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-resume.html":{"url":"devops/docker-resume.html","title":"docker + resume","keywords":"","body":"docker + resume docker exec nginx whereis nginx docker exec nginx ls /usr/share/nginx/html docker cp nginx:/usr/share/nginx/html/index.html ~/resume docker run --name resume -d -p 1006:80 --restart=always -v ~/resume:/usr/share/nginx/html nginx server { listen 80; server_name resume.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name resume.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:1006; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } docker restart nginx curl https://resume.snowdreams1006.cn name: resume.snowdreams1006.cn on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Upload resume uses: appleboy/scp-action@master env: HOST: ${{ secrets.HOST }} USERNAME: ${{ secrets.USERNAME }} KEY: ${{ secrets.KEY }} with: source: static,index.html,README.md,LICENSE target: /root/resume rm: true - name: Deploy resume uses: appleboy/ssh-action@master with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | docker restart resume curl https://resume.snowdreams1006.cn var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-resume.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-gitbook.html":{"url":"devops/docker-gitbook.html","title":"docker + gitbook","keywords":"","body":"docker + gitbook docker 搜索镜像 docker search nginx 列出镜像 docker images 下载镜像 docker pull nginx 运行容器 docker ps 启动容器 docker run --name nginx-test -p 80:80 -d nginx 部署到 nginx 服务器创建 nginx 文件 mkdir -p ~/nginx/www ~/nginx/conf ~/nginx/logs 拷贝到容器内部 docker cp 6dd4380ba708:/etc/nginx/nginx.conf ~/nginx/conf 部署 nginx docker run -d -p 80:80 --name blob.snodreams1006.cn -v ~/nginx/www:/usr/share/nginx/html -v ~/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v ~/nginx/logs:/var/log/nginx nginx 重新载入 nginx docker kill -s HUP container-name 重启 nginx 容器 docker restart container-name 实际情况 安装 tree yum install -y tree 启动容器 docker run --name blob.snodreams1006.cn -p 80:80 -d nginx 拷贝到容器内部 docker cp 6af3f4d1911c:/etc/nginx/nginx.conf ~/nginx/conf 停止容器 docker stop blob.snodreams1006.cn 上传服务器 scp -r /Users/snowdreams1006/Documents/workspace/snowdreams1006.github.io/_book/* root@121.40.223.69:~/nginx/www 部署 nginx docker run -d -p 80:80 --name blob.snowdreams1006.cn -v ~/nginx/www:/usr/share/nginx/html -v ~/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v ~/nginx/logs:/var/log/nginx nginx 参考资料 Docker 安装 Nginx 检查TCP 80端口是否正常工作 安全组应用案例 Docker 安装 Nginx Mac/Linux/Centos终端中上传文件到Linux云服务器 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-gitbook.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-bark.html":{"url":"devops/docker-bark.html","title":"docker + Bark","keywords":"","body":"docker + Bark https://github.com/Finb/bark-server install docker run -dt --name bark -p 8080:8080 -v `pwd`/data:/data finab/bark-server test curl http://0.0.0.0:8080/ping curl https://bark.snowdreams1006.cn/ping var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-bark.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-py12306.html":{"url":"devops/docker-py12306.html","title":"docker + py12306","keywords":"","body":"docker + py12306 https://github.com/pjialin/py12306 将配置文件下载到本地 mkdir py12306 && cd py12306 docker run --rm pjialin/py12306 cat /config/env.py > env.py # tree . └── env.py 修改好配置后运行 docker run --rm --name py12306 -p 8008:8008 -d -v $(pwd):/config -v py12306:/data pjialin/py12306 # tree . ├── 12306.log └── env.py tail -f 12306.log 检测 python main.py -t python main.py -t -n test mkdir py12306 && cd py12306 docker run --rm pjialin/py12306 cat /config/env.py > env.py docker run --rm --name py12306 -p 8008:8008 -d -v $(pwd):/config -v py12306:/data pjialin/py12306 docker run --rm --name py12306 -p 8008:8008 -d -v $(pwd):/config -v $(pwd)/data:/data pjialin/py12306 docker exec py12306 python main.py -c /config/env.py -t var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-py12306.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-mojo-Weixin.html":{"url":"devops/docker-mojo-Weixin.html","title":"docker + mojo-Weixin","keywords":"","body":"docker + mojo-Weixin https://github.com/sjdy521/Mojo-Weixin 拉取镜像 docker pull sjdy521/mojo-weixin 运行镜像 docker run -it --env MOJO_WEIXIN_LOG_ENCODING=utf8 -p 3000:3000 -v /tmp:/tmp sjdy521/mojo-weixin docker run -it --env MOJO_WEIXIN_LOG_ENCODING=utf8 -p 3000:3000 -v /root/mojo/tmp:/tmp sjdy521/mojo-weixin https://github.com/sjdy521/Mojo-Weixin/blob/master/Docker.md var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-mojo-Weixin.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-wechat-work-message-push-go.html":{"url":"devops/docker-wechat-work-message-push-go.html","title":"docker + wechat-work-message-push-go","keywords":"","body":"docker + wechat-work-message-push-go https://github.com/cloverzrg/wechat-work-message-push-go docker-compose docker-compose up -d var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-wechat-work-message-push-go.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-email.html":{"url":"devops/docker-email.html","title":"docker + email","keywords":"","body":"docker + email docker pull lejmr/iredmai:mysql-lastest docker run -d --privileged -p 90:80 -p 8443:443 \\ -e \"DOMAIN=snowdreams1006.cn\" -e \"HOSTNAME=mail\" \\ -e \"MYSQL_ROOT_PASSWORD=123456\" \\ -e \"SOGO_WORKERS=1\" \\ -e \"TIMEZONE=Europe/Prague\" \\ -e \"POSTMASTER_PASSWORD=123456\" \\ -e \"IREDAPD_PLUGINS=['reject_null_sender', 'reject_sender_login_mismatch', 'greylisting', 'throttle', 'amavisd_wblist', 'sql_alias_access_policy']\" \\ -v `pwd`/mysql:/var/lib/mysql \\ -v `pwd`/vmail:/var/vmail \\ -v `pwd`/clamav:/var/lib/clamav \\ --name=mail lejmr/iredmail:mysql-latest scp /Users/snowdreams1006/Downloads/docker-compose.yml root@snowdreams1006.cn:/root/mail docker-compose -f docker-compose.iredmail.yml up -d docker-compose.iredmail.yml mail.snowdreams1006.cn/iredadmin/dashboard/checknew hostnamectl set-hostname snowdreams1006.cn snowdreams1006.cn:90/iredadmin/dashboard/checknew 最佳实践 EwoMail 开源企业邮件系统 的docker镜像 bestwu/ewomail 启动容器 docker run -d -h mail.snowdreams1006.cn --restart=always \\ -p 25:25 \\ -p 587:587 \\ -p 465:465 \\ -p 143:143 \\ -p 993:993 \\ -p 110:110 \\ -p 995:995 \\ -p 109:109 \\ -p 90:80 \\ -p 9090:8080 \\ -v `pwd`/mysql/:/ewomail/mysql/data/ \\ -v `pwd`/vmail/:/ewomail/mail/ \\ -v `pwd`/rainloop:/ewomail/www/rainloop/data \\ -v `pwd`/ssl/certs/:/etc/ssl/certs/ \\ -v `pwd`/ssl/private/:/etc/ssl/private/ \\ -v `pwd`/ssl/dkim/:/ewomail/dkim/ \\ --name mail bestwu/ewomailserver 访问 邮箱管理后台 http://localhost:8080 ewomail.snowdreams1006.cn 默认用户: admin 默认密码: ewomail123 Rainloop 管理端 http://localhost/?admin 默认用户: admin 默认密码: 12345 mail.snowdreams1006.cn Rainloop 用户端 http://localhost 设置 DNS 将 mail.snowdreams1006.cn 改成你的域名 spf记录: v=spf1 include:snowdreams1006.cn -all DKIM 设置 docker exec mail amavisd showkeys ; key#1, domain snowdreams1006.cn, /ewomail/dkim/mail.pem dkim._domainkey.snowdreams1006.cn. 3600 TXT ( \"v=DKIM1; p=\" \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDyk2JYMeTjn55AVU7OlZF++6ed\" \"Eu4tGtB35/6+sbQ3ugm0QflplbIWE2vu/gFsuatSn4xKUYIsrp0njaMMbC00qwkT\" \"dWjfI/lmFP/23i/ejKNFNxA4O/zWrtIfCbQ3dxlgkvtKE0oGcNHX+Q3le3LxCRua\" \"FIq1QRT7GOzHS7R67QIDAQAB\") dkim._domainkey v=DKIM1;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDyk2JYMeTjn55AVU7OlZF++6edEu4tGtB35/6+sbQ3ugm0QflplbIWE2vu/gFsuatSn4xKUYIsrp0njaMMbC00qwkTdWjfI/lmFP/23i/ejKNFNxA4O/zWrtIfCbQ3dxlgkvtKE0oGcNHX+Q3le3LxCRuaFIq1QRT7GOzHS7R67QIDAQAB 测试生效 docker exec mail amavisd testkeys yum install xinetd telnet telnet-server -y telnet smtp.qq.com 25 参考资料 利用Docker自建多功能加密邮件服务器 version: '2.3' services: portainer: image: \"ewomail/ewomail\" container_name: \"mail\" hostname: \"mail.snowdreams1006.cn\" restart: always ports: - \"0.0.0.0:2210:22\" - \"0.0.0.0:25:25\" - \"0.0.0.0:109:109\" - \"0.0.0.0:110:110\" - \"0.0.0.0:143:143\" - \"0.0.0.0:465:465\" - \"0.0.0.0:587:587\" - \"0.0.0.0:993:993\" - \"0.0.0.0:995:995\" - \"172.16.166.99:8010:8000\" - \"172.16.166.99:8011:8010\" - \"172.16.166.99:8012:8020\" volumes: - \"./cgroup:/sys/fs/cgroup:ro\" privileged: true tty: true stdin_open: true mail: image: bestwu/ewomailserver hostname: mail.ewomail.com container_name: ewomail restart: always ports: - \"25:25\" - \"143:143\" - \"587:587\" - \"993:993\" - \"109:109\" - \"110:110\" - \"465:465\" - \"995:995\" - \"80:80\" - \"8080:8080\" volumes: - ./mysql:/ewomail/mysql/data - ./vmail:/ewomail/mail - ./rainloop:/ewomail/www/rainloop/data - ./ssl/certs/:/etc/ssl/certs/ - ./ssl/private/:/etc/ssl/private/ - ./ssl/dkim/:/ewomail/dkim/ 认证官方镜像 docker pull ewomail/ewomail 下载配置文件 curl -o docker-compose.yml https://raw.githubusercontent.com/EwoMail/ewomail-docker/master/docker-compose.yml 修改配置文件 version: '2.3' services: portainer: image: \"ewomail/ewomail\" container_name: \"mail\" hostname: \"mail.snowdreams1006.cn\" restart: always ports: - \"0.0.0.0:25:25\" - \"0.0.0.0:587:587\" - \"0.0.0.0:465:465\" - \"0.0.0.0:143:143\" - \"0.0.0.0:993:993\" - \"0.0.0.0:110:110\" - \"0.0.0.0:995:995\" - \"0.0.0.0:109:109\" - \"0.0.0.0:2210:22\" - \"127.0.0.1:8000:8000\" - \"127.0.0.1:8010:8010\" - \"127.0.0.1:8020:8020\" volumes: - \"/root/mail/cgroup:/sys/fs/cgroup:ro\" privileged: true tty: true stdin_open: true 启动容器 docker-compose up -d 拉取镜像 docker pull bestwu/ewomail 体验测试 邮箱管理后台:http://IP:8010 (默认账号admin,密码ewomail123) ewomail.snowdreams1006.cn web邮件系统:http://IP:8000 mail.snowdreams1006.cn 开放端口 8000,8010,8020,25,143,993,995,587,110,109,22,80,465 启动容器 docker run -d -h mail.snowdreams1006.cn --restart=always \\ -p 25:25 \\ -p 587:587 \\ -p 465:465 \\ -p 143:143 \\ -p 993:993 \\ -p 110:110 \\ -p 995:995 \\ -p 109:109 \\ -p 90:80 \\ -p 9090:8080 \\ -v `pwd`/mysql/:/ewomail/mysql/data/ \\ -v `pwd`/vmail/:/ewomail/mail/ \\ -v `pwd`/ssl/certs/:/etc/ssl/certs/ \\ -v `pwd`/ssl/private/:/etc/ssl/private/ \\ -v `pwd`/rainloop:/ewomail/www/rainloop/data \\ -v `pwd`/ssl/dkim/:/ewomail/dkim/ \\ --name ewomail bestwu/ewomailserver 进入虚拟机 docker exec -it ewomail /bin/bash 在/etc/hosts中已有域名指向 172.17.0.2 mail.snowdreams1006.cn mail sendmail:用于发邮件。资格最老的邮局,所有Linux发行版基本都带。但是配置麻烦。 postfix:Wietse Venema觉得sendmail配置太麻烦了,就开发了一个“简化配置版sendmail”,即postfix。支持smtp协议。 dovecot:用于收邮件,支持imap/pop3。 spamassasin:垃圾邮件过滤器。可以自订规则。 clamav:邮件杀毒工具。 opendkim:生成dkim签名。有什么用?详见下面的“反垃圾邮件技术”。 fail2ban:防止别人暴力破解用户名密码的工具。 配置 运行成功后访问 邮箱管理后台 http://localhost:8080 默认用户: admin 默认密码: ewomail123 ewomail.snowdreams1006.cn Rainloop 管理端 http://localhost/?admin rainloop.snowdreams1006.cn/?admin 默认用户: admin 默认密码: 12345 Rainloop 用户端 http://localhost rainloop.snowdreams1006.cn 设置域名DNS 将mail.ewomail.cn 改成你的域名 v=spf1 include:snowdreams1006.cn -all dkim 设置 docker exec ewomail amavisd showkeys docker exec ewomail amavisd testkeys docker pull tvial/docker-mailserver:latest curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh; chmod a+x ./setup.sh curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist curl -o .env https://raw.githubusercontent.com/tomav/docker-mailserver/master/.env.dist curl -o env-mailserver https://raw.githubusercontent.com/tomav/docker-mailserver/master/env-mailserver.dist docker-compose up -d mail ./setup.sh email add [] ./setup.sh email add test@snowdreams1006.cn 123456 ./setup.sh config dkim cat config/opendkim/keys/domain.tld/mail.txt cat config/opendkim/keys/snowdreams1006.cn/mail.txt mail._domainkey IN TXT ( \"v=DKIM1; h=sha256; k=rsa; \" \"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsmu8hYaaFpFDlSbJPRupiHwhhdsicOYrYDJpJXUMtf8Rh5rXbOpi4vJ59ml9EUyjB62LaHlK65a2rB4GUuwq2YZJvd2gCdqJv8wsidlavU/LLNI9/gIzqG/2JqsENGz6tClMpDVqFFEN7ShOAApMjn3Yq80Qy4F+vNhDTjaoBl1odQYwDW5fy3Oorh8ipf50J1H+7ehiUG30yC\" \"S537m6A35HoFpCx2g/ThuwWHK1P7HiSJ20bvoPZn/FKwAoQt+DM3R4H2Na+NudVWadmXmGezz+KWToe/dDUTfN66qMvMuzPbhbJy3MUfFvqcscZsD7PrJdOTuLeYG5ESgSccse0QIDAQAB\" ) ; ----- DKIM key mail for snowdreams1006.cn mail._domainkey v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsmu8hYaaFpFDlSbJPRupiHwhhdsicOYrYDJpJXUMtf8Rh5rXbOpi4vJ59ml9EUyjB62LaHlK65a2rB4GUuwq2YZJvd2gCdqJv8wsidlavU/LLNI9/gIzqG/2JqsENGz6tClMpDVqFFEN7ShOAApMjn3Yq80Qy4F+vNhDTjaoBl1odQYwDW5fy3Oorh8ipf50J1H+7ehiUG30yCS537m6A35HoFpCx2g/ThuwWHK1P7HiSJ20bvoPZn/FKwAoQt+DM3R4H2Na+NudVWadmXmGezz+KWToe/dDUTfN66qMvMuzPbhbJy3MUfFvqcscZsD7PrJdOTuLeYG5ESgSccse0QIDAQAB docker exec mail openssl s_client -connect 0.0.0.0:587 -starttls smtp -CApath /etc/letsencrypt/ docker exec mail openssl s_client -connect 0.0.0.0:993 -starttls imap -CApath /etc/letsencrypt/ docker-compose up -d mail 教你搭建自己的邮件服务器-Ubuntu 18.04下通过Docker使用EwoMail实现 使用docker搭建一个功能完善但非常简单的邮件服务器 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-email.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-mysql.html":{"url":"devops/docker-mysql.html","title":"docker + mysql","keywords":"","body":"docker + mysql https://hub.docker.com/_/mysql/ 启动 docker run -p 3306:3306 --name mysql \\ -v ~/mysql/conf:/etc/mysql \\ -v ~/mysql/logs:/var/log/mysql \\ -v ~/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=123456 \\ -d mysql:5.7.28 docker exec mysql whereis mysql docker exec mysql ls /etc/mysql docker cp mysql:/etc/mysql/my.cnf ~/mysql/conf/ 链接 mysql docker exec -it mysql bash mysql -uroot -p123456 use mysql select user,authentication_string from user; update user set authentication_string='' where user='root'; alter user 'root'@'%' IDENTIFIED BY 'root@alsk1029'; alter user 'root'@'localhost' IDENTIFIED BY 'root@alsk1029'; flush privileges; host: 127.0.0.1 port: 3306 user: root password: 123456 修改密码 docker exec -it mysql bash mysqladmin -uroot -p123456 password 123 使用Docker搭建MySQL服务 进入Docker容器中修改mysql密码 docker 安装报错 ERROR 1045 (28000): Access denied for user 'mysql'@'localhost' (using password: YES)解决方法 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-mysql.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-springboot.html":{"url":"devops/docker-springboot.html","title":"docker + springboot","keywords":"","body":"docker + springboot https://spring.io/guides/topicals/spring-boot-docker/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-springboot.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-redis.html":{"url":"devops/docker-redis.html","title":"docker + redis","keywords":"","body":"docker + redis https://hub.docker.com/_/redis/ docker run -p 6379:6379 -v ~/redis/data:/data -d redis redis-server --appendonly yes docker exec -ti d0b86 redis-cli docker exec -ti d0b86 redis-cli -h localhost -p 6379 docker exec -ti d0b86 redis-cli -h 127.0.0.1 -p 6379 docker exec -ti d0b86 redis-cli -h 172.17.0.3 -p 6379 // 注意,这个是容器运行的ip,可通过 docker inspect redis_s | grep IPAddress 查看 docker run --name redis -d -p 6379:6379 --restart=always \\ -v ~/redis/data:/data \\ -v ~/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf \\ -v ~/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=123456 \\ redis redis-server /usr/local/etc/redis/redis.conf --appendonly yes --requirepass \"redis123\" Docker安装redis Docker安装运行Redis docker启动redis并设置密码 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-redis.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-gogs.html":{"url":"devops/docker-gogs.html","title":"docker + gogs","keywords":"","body":"docker + gogs docker run --name gogs -d -p 10022:22 -p 10080:3000 --restart=always \\ -v ~/gogs:/data \\ gogs/gogs github git@github.com:snowdreams1006/snowdreams1006.github.io.git https://github.com/snowdreams1006/snowdreams1006.github.io.git gogs git@ssh.snowdreams1006.cn:snowdreams1006/snowdreams1006.github.io.git https://gogs.snowdreams1006.cn/snowdreams1006/snowdreams1006.github.io.git ./gogs cert -ca=true -duration=8760h0m0s -host=gogs.snowdreams1006.cn docker cp gogs:$(docker exec gogs pwd)/cert.pem gogs/https gogs/gogs 配置文件手册 Docker下gogs的使用 docker安装gogs docker gogs安装 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-gogs.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-gitlab.html":{"url":"devops/docker-gitlab.html","title":"docker + gitlab","keywords":"","body":"docker + gitlab https://hub.docker.com/r/gitlab/gitlab-ce docker run --name gitlab -d -p 4433:443 -p 8000:80 -p 2222:22 --rm \\ -v ~/gitlab/config:/etc/gitlab \\ -v ~/gitlab/logs:/var/log/gitlab \\ -v ~/gitlab/data:/var/opt/gitlab \\ gitlab/gitlab-ce server { listen 80; server_name ssh.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name ssh.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:2222; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 80; server_name git.snowdreams1006.cn; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name git.snowdreams1006.cn; ssl on; ssl_certificate /etc/letsencrypt/live/snowdreams1006.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/snowdreams1006.cn/privkey.pem; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; location / { proxy_pass http://172.16.166.99:8000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } https://git.snowdreams1006.cn/ GitLab Docker images docker下gitlab安装配置使用(完整版) var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-gitlab.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-gitea.html":{"url":"devops/docker-gitea.html","title":"docker + gitea","keywords":"","body":"docker + gitea https://hub.docker.com/r/gitea/gitea docker run --rm -d --name=gitea -p 2222:22 -p 8000:3000 -v ~/gitea:/data gitea/gitea:latest https://git.snowdreams1006.cn/install docker run --name mysql -d -p 3306:3306 --restart=always \\ -v ~/mysql/conf:/etc/mysql \\ -v ~/mysql/logs:/var/log/mysql \\ -v ~/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=123456 \\ mysql:5.7 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-gitea.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"devops/docker-crawlab.html":{"url":"devops/docker-crawlab.html","title":"docker + crawlab","keywords":"","body":"docker + crawlab 配置文件 docker-compose.yml version: '3.3' services: master: image: tikazyq/crawlab:latest container_name: master environment: CRAWLAB_API_ADDRESS: \"http://localhost:8000\" CRAWLAB_SERVER_MASTER: \"Y\" CRAWLAB_MONGO_HOST: \"mongo\" CRAWLAB_REDIS_ADDRESS: \"redis\" ports: - \"8080:8080\" # frontend - \"8000:8000\" # backend depends_on: - mongo - redis mongo: image: mongo:latest restart: always ports: - \"27017:27017\" redis: image: redis:latest restart: always ports: - \"6379:6379\" 启动命令 docker-compose up 访问地址 curl http://localhost:8080 文档 crawlab-team/crawlab Crawlab Docker安装部署 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/devops/docker-crawlab.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"tools/":{"url":"tools/","title":"工具资源","keywords":"","body":"工具资源 总结记录下使用过程中值得推荐的软件,正所谓\"授人以鱼不如授人以渔\",有些博主总结的更加全面有用,在此也分享下. 工具资源 快典小报 快典小报,每周不定期精选优质内容! Chrome插件英雄榜 Chrome插件英雄榜, 为优秀的Chrome插件写一本中文说明书, 让Chrome插件英雄们造福人类~ Github星聚弃疗榜(Github星爆沙雕榜) 为Github创意项目写一本推荐书,让Github优秀项目造福人类~ IRM Markdowner | 微信排版编辑器 微信公众号格式化工具 : https://github.com/hadeshe93/irm-markdowner WeChat Format | 微信公众号排版编辑器 微信公众号排版编辑器 : https://github.com/lyricat/wechat-format 微信公众号格式化工具 在线Markdown转换器 : https://github.com/dyc87112/online-markdown/ -图表秀帮助文档 最好用的在线图表制作网站 : https://www.tubiaoxiu.com/ Cocos Creator v2.0 用户手册 gitbook 搭建的 Cocos Creator v2.0 用户手册 https://docs.cocos.com/creator/manual/zh/ OpenFalcon 产品文档 OpenFalcon是一款企业级、高可用、可扩展的开源监控解决方案 : http://book.open-falcon.org/ azk docs/ azk 是本地化开发的开源协调器 : http://docs.azk.io/ Learning Git Branching | 闯关游戏顺便学 git 游戏闯关中学习 git 常用技巧 : https://github.com/pcottle/learnGitBranching carbon | 源码转图片 将源码转成图片分享出去! https://github.com/dawnlabs/carbon GPSspg | 在线地图经度纬度查询 基于Google谷歌地图、百度地图、腾讯地图QQ地图、高德地图、图吧地图在线地图技术,可实现经度纬度查询地名位置、地名查询经度纬度位置、任意地图位置解析经纬度。海拔高度查询。 Regexper | 正则表达式可视化工具 正则表达式可视化工具 : https://gitlab.com/javallone/regexper-static sourceforge | 开源软件托管平台 完全开源和商业软件平台 : https://sourceforge.net/ Pandownload | 网盘下载神器 在线解析百度网盘资源,加速下载不封号 : https://www.baiduwp.com/ convert anything to anything 功能强大且免费的文档转换工具 : https://cloudconvert.com/ 博客主页 八一菜刀 有志者,事竟成!苦心人,天不负!加油吧~!!! Lyric 歌词经理,是一名产品经理. 程序猿DD 《Spring Cloud微服务实战》作者,SpringCloud中文社区创始人(bbs.springcloud.com.cn),Spring4All社区联合发起人(spring4all.com) 大大的微笑 博客最核心的还是内容, 内容为王! 再华丽的简介也只是表象, 有内涵才是关键! 敖小剑的博客 敖小剑,资深码农,十六年软件开发经验,微服务专家,Service Mesh布道师,Servicemesher社区联合创始人。专注于基础架构,Cloud Native 拥护者,敏捷实践者,坚守开发一线打磨匠艺的架构师。曾在亚信、爱立信、唯品会等任职,对基础架构和微服务有过深入研究和实践. 沈煜的博客 我叫沈煜,目前是个码农。上学的时候效仿业界大佬,整了个博客,那时候用的域名还不是现在用的这个,现在看到这个博客内容已经重新开始,不定期会写点什么,内容比较随意。 Zhang Jikai的博客 暂无个人介绍,来源于 gitbook 学习而关注. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/mac-install-vmware.html":{"url":"tools/mac-install-vmware.html","title":"给 mac 装个 vmware 虚拟机","keywords":"","body":"给mac装个vmware虚拟机 mac 系统安装虚拟机目前有两种主流软件,一种是 Parallels Desktop ,另一种是 vmware. 本教程选用的是 vmware ,因为我之前 windows 上安装的虚拟机软件就是vmware,所以当然选择熟悉的方式鼓捣 mac 虚拟机了啊! 如果你没听说过虚拟机,可能你走错门了,不太适合看这篇教程.如果你执意要了解一下新知识,那我只能用自己浅薄的知识简单介绍下虚拟机的基本概念,一家之言,仅供参考哟! 正常来说,我们当前使用的电脑一般只有一个系统,比如你买的是 mac 笔记本,那么电脑系统就是 mac 操作系统,肯定不会是 windows 操作系统,所以你在 mac 电脑上看不到 windows 的办公软件三件套(word,excel,ppt). 当然你的电脑如果是 联想,戴尔,华硕,神舟 等等品牌的话 ,操作系统一般都是 windows,自然也不会是看到 mac 电脑上的 xcode 软件. 这一点很好理解,每种电脑与生俱来自带特定的操作系统,但是,竟然有一种方法能够突破这层限制! 什么是虚拟机 虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能,运行在一个完全隔离环境中的完整计算机系统. 虚拟机,顾名思义是虚拟的机器.虚拟意味着不是真实的,机器意味着功能像是一台机器. 所以,我们能够理解上述百度百科的定义,虚拟机就是通过软件模拟实现真实机器功能. 软件模拟硬件是手段,实现机器功能是目的,既然如此理论上应该能够模拟任何操作系统,从而实现一台真实的物理机可以有多种不同的操作系统! 这样一来是不是很神奇?一台 mac 可以模拟出 Windows 电脑,也可以模拟出 linux 电脑. 换言之,只要有一台真实的物理机,通过软件我们就可以模拟出任意操作系统,这种软件就是我们接下来要介绍的 vmware . 虚拟机的使用场景 作为软件开发者,尽管很多语言支持跨平台运行,但是为了检验真实效果,我们需要运行到不同的环境中,比如windows 和 linux 系统的差异就不是一星半点! 或者为了教程的完整性,需要在各个平台测试运行后才能放心讲解某个知识点,不然别人按照教程发现运行不了,既浪费了别人的时间,又惹得人家不高兴,好心办坏事,大家都不好受. 所以,多个系统是刚需,如果真实环境中能够提供的话,那么自然不需要虚拟机. 只有实际情况下,不能提供真实的多种操作系统的情况下,我们才使用虚拟机技术来模拟不同的操作系统. 为什么是 vmware 通过软件模拟实现虚拟机目标,关键在于软件能力如何,所以选择哪一款软件直接决定了我们的虚拟机性能如何. 市面上,这种软件并不是唯一一家,至少目前我了解的就有 vmware ,Parallels Desktop 和 virtualbox .那么为什么选择 vmware 呢? 没有为什么,因为我之前用过 vmware 而已,对于小白的我,并没研究过三者软件有什么区别,哪一种更好,只要操作足够简单,市面上足够流行就可以了. 快速体验 vmware 本教程使用的是 mac 电脑,利用虚拟机安装了三种不同系统,分别是 win7旗舰版 , centos7.5 和 Ubuntu18. windows 虚拟机 启动 windows7 旗舰版 虚拟机,并且打开 chrome 浏览器测试. centos 虚拟机 启动 centos7.5 虚拟机,并且输入 pwd 命令测试. ubuntu 虚拟机 启动 ubuntu18 虚拟机,并且输入 pwd 命令测试. 如何安装 vmware 需要实现准备好下载工具以及留下足够的内存空间,因为虚拟机和镜像毕竟都挺大,下载挺费时间,安装也比较占内存,毕竟是完整的虚拟机. 下载软件以及序列号生成工具 链接: https://pan.baidu.com/s/1D0LL_muZ_YEbmgS4A6l3pw 提取码: ti8v 复制这段内容后打开百度网盘手机App,操作更方便哦 VMware-Fusion-11.0.3-12992109.dmg [必选] 安装软件 vmware 软件是收费软件,有一段时间的试用期,这里采用序列号激活方式,有条件的话,建议支持下正版. KeyMaker.app [可选] 序列号生成工具 如果是选择官网试用版或者已购买正版,自然不需要序列号生成功能. 安装 VMware-Fusion 软件 双击安装 VMware-Fusion-11.0.3-12992109.dmg 软件,接下来一路允许按照提示操作即可. 双击安装,因为软件源不是从 App Store 下载的,所以苹果默认策略不允许安装第三方来源. 既然询问是否打开软件,当然打开,不然怎么安装呢? 然而,还是太年轻,尽管刚才已经选择打开软件,然而苹果怕是担心我们不小心安装了有害应用吧?还是需要再问我们一遍,你确定要安装吗?我确定!我怎么知道是你本人?你输入管理员密码试试,密码正确我就让你安装. 千呼万呼使出来,你终于相信我是我了,安装进行中... 安装到一定程度时,会让我们输入产品密钥进行激活,否则只能试用30天,到期会再次提醒输入密钥,接下来我们来获取序列号. 打开 KeyMaker 软件 双击运行 KeyMaker.app ,弹出一系列序列号,随意选择某一行的序列号复制到上一步安装VMware-Fusion 的产品密钥并验证. Some good serial numbers.. KGLWE-VA5KZ-D1QHT-2R51Q-ZKQVV VTZMD-ZYTKX-D1ZCR-C6QCZ-QZZEV GQZX9-ZFX3T-Z1Z6Y-AFPCW-ZZ5GZ THQQR-00TZQ-81L0R-10LEG-G2ZTZ P1VXR-GFNGC-R1JJR-JXG3T-PQ7XT ZXYXY-VMTKZ-Y1YCX-7MQ9X-MQQ6V Here another one GK9QC-9KEM4-V1VAQ-P8JEP-MK77V Greets to Corby 随便复制一个序列号,继续正常安装. 输入产品密钥后基本上就是 vmware 用户,除非你不同意它的产品协议,当然同意了! 本以为安装到此结束,没想到还想要获取辅助功能权限,没办法,既然你想要,那我就给你啊,保不齐缺胳膊少腿的. 和安装相同,不是你选择允许苹果就允许,仍然需要你提供管理员密码以此确保主观操作意愿. 授予辅助功能权限,并再次锁定该项操作,可以与想的是,以后有应用想要申请辅助功能,必须经过管理员同意才可以,为了安全需要这么多步骤,好吧. 我只想安安静静使用 vmware 产品,不希望使用数据被上传收集,当然也有点小担心,毕竟也不是正儿八经的用户,所以才不加入体验计划呢! 验证 vmware 软件 在访达或启动台中找到 VMware-Fusion 软件单击启动,测试能否正常运行软件. 小结 本节主要介绍了什么是虚拟机,虚拟机的使用场景以及如何安装虚拟机,下一节我们将介绍如何给虚拟机加点料,让虚拟机派上练武之地! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/mac-install-vmware.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/mac-vmware-install-windows.html":{"url":"tools/mac-vmware-install-windows.html","title":"给 mac 虚拟机装个 windows","keywords":"","body":"给 mac 虚拟机装个 windows 前面我们介绍了如何在 mac 宿主机安装 VMware 虚拟机软件,本节我们将继续介绍如何给虚拟机安装 windows 镜像,切换不同的操作系统. VMware 软件是容器,镜像是内核,这里的镜像指的是操作系统. 下载镜像 windows 操作系统下载: https://msdn.itellyou.cn/ 按照实际需要选择适合自己的操作系统,这里选择的是 win7 旗舰版 ,然后选择详情会弹出下载链接. 一般需要使用迅雷等第三方工具下载种子链接,大小一般在 3g 多,下载时间稍微比较久! ed2k://|file|cn_windows_7_ultimate_x64_dvd_x15-66043.iso|3341268992|7DD7FA757CE6D2DB78B6901F81A6907A|/ 配置镜像 准备好已下载的镜像文件: cn_windows_7_ultimate_x64_dvd_x15-66043.iso 打开 VMware 软件,选择 文件->新建 选项开始安装镜像文件. 弹出安装配置界面,选择 从光盘或镜像中安装 选项,然后将已下载的镜像文件拖动到安装区进行识别. 识别到镜像文件后选中该文件,点击 继续 准备下一步安装. 配置账号信息以及产品密钥等信息,暂时不需要激活的话,也可以不填写产品密钥. 集成方式选择 更加独立 ,然后点击 继续. 提示下载 VMware Tools 工具,如果可以的话,最好还是先下载,也可以安装完毕后再手动下载. 确认配置信息无误后,点击 完成 ,等待镜像安装,,, 安装镜像 人生若只如初见,远远望见熟悉的背影,便确定了你就是我要安装的操作系统. 期待花开,耐心等待你的文件复制进程. 花开花落又是一年,你说重启才能遇到最美的季节,那我便等待你的凤凰涅槃. 见证你的凤凰涅槃,期待你的浴火重生. 浴火重生后的操作系统,还差最后一步就能欣赏你的容颜. 千呼万唤始出来,犹抱琵琶半遮面,正在进行最后的准备桌面. 终于等到你,还好我没放弃! 小结 总体来说,mac 系统安装 windows 镜像配置比较简单,基本上按照默认配置即可. 下载镜像时文件一般比较大,需要利用专门的第三方工具下载,比如本文提供的下载链接是种子文件,选择的第三方工具就是迅雷. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/mac-vmware-install-windows.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/mac-vmware-install-centos.html":{"url":"tools/mac-vmware-install-centos.html","title":"给 mac 虚拟机装个 centos","keywords":"","body":"给 mac 虚拟机装个 centos 前文我们已经讲解了如何在 mac 系统上安装虚拟机软件,这节我们接着讲解如何利用虚拟机安装 centos 镜像. 安装镜像的大致步骤基本相同,只不过是配置项略显不同而已,如果需要安装其他系统镜像,请参考另外两篇教程. 下载镜像 centos 操作系统下载: https://www.centos.org/download/ DVD ISO 和 Minimal ISO 两种类型,普通用户推荐选择前一种标准版,开发用户建议选择后一种最小版. 标准版功能比较齐全,最小版保证最小依赖,后续缺啥填啥,比较灵活节省空间内存. 按照实际需要选择适合自己的操作系统,这里选择的是 centos7.6 ,然后选择合适的下载方式(直接下载或下载种子链接). 建议选择镜像服务器下载,如果直接下载官网的地址,速度感人,时间有点长. 依次选择 list of current mirrors -> http://mirrors.aliyun.com/centos/ -> 7.6.1810/ -> isos/ -> x86_64/ -> CentOS-7-x86_64-Minimal-1810.iso 选择合适的版本点击下载. 配置镜像 准备好已下载的镜像文件: CentOS-7-x86_64-Minimal-1804.iso 打开 VMware 软件,选择 文件->新建 选项开始安装镜像文件. 弹出安装配置界面,选择 从光盘或镜像中安装 选项,然后将已下载的镜像文件拖动到安装区进行识别. 识别到镜像文件后选中该文件,点击 继续 准备下一步安装. 选择固件类型,默认方式 传统 BIOS .然后点击 继续 . 确认配置信息无误后,点击 完成 ,等待镜像安装,,, 安装镜像 只因在人群中看见了 centos ,便确定了你就是我要安装的操作系统. 阅览安装摘要信息,等待继续安装. 设置用户信息,包括设置 root 用户密码和创建初始用户账号信息. 花开花落又是一年,你说重启才能遇到最美的季节,那我便等待你的凤凰涅槃. 终于等到你,还好我没放弃! 按照之前配置的用户信息登录系统,打印出当前路径,证明安装成功. 小结 总体来说,mac 系统安装 centos 镜像配置比较简单,基本上按照默认配置即可. 下载镜像时文件一般比较大,需要利用专门的第三方工具下载,既可以选择下载种子链接也可以直接下载. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/mac-vmware-install-centos.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/mac-vmware-install-ubuntu.html":{"url":"tools/mac-vmware-install-ubuntu.html","title":"给 mac 虚拟机装个 ubuntu","keywords":"","body":"给 mac 虚拟机装个 ubuntu 前文我们已经讲解了如何在 mac 系统上安装虚拟机软件,这节我们接着讲解如何利用虚拟机安装 Ubuntu 镜像. 安装镜像的大致步骤基本相同,只不过是配置项略显不同而已,如果需要安装其他系统镜像,请参考另外两篇教程. 下载镜像 Ubuntu 操作系统下载: https://www.ubuntu.com/download 这里我们选择桌面版(Ubuntu Desktop),接着选择 LTS 长期支持版进行下载安装. 按照实际需要选择适合自己的操作系统,这里选择的是 Ubuntu18 LTS ,然后选择下载. 配置镜像 准备好已下载的镜像文件: ubuntu-18.04.2-desktop-amd64.iso 打开 VMware 软件,选择 文件->新建 选项开始安装镜像文件. 弹出安装配置界面,选择 从光盘或镜像中安装 选项,然后将已下载的镜像文件拖动到安装区进行识别. 识别到镜像文件后选中该文件,点击 继续 准备下一步安装. linux 快捷安装选项中配置用户信息,点击 继续 . 确认配置信息无误后,点击 完成 ,等待镜像安装... 安装镜像 只因在人群中看见了 Ubuntu ,便确定了你就是我要安装的操作系统. 惊鸿一瞥,容颜出现,安装进行时. 熟悉的命令行,成功只差一步. 现在输入之前配置的账号信息,开始登陆系统,见证奇迹的时刻即将来临... 终于等到你,还好我没放弃! 小结 总体来说,mac 系统安装 Ubuntu 镜像配置比较简单,基本上按照默认配置即可. 下载镜像时文件一般比较大,需要利用专门的第三方工具下载,既可以选择下载种子链接也可以直接下载. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/mac-vmware-install-ubuntu.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/windows-install-vmware.html":{"url":"tools/windows-install-vmware.html","title":"给 windows 装个 vmware 虚拟机","keywords":"","body":"给 windows 装个 vmware 虚拟机 如果长时间处于同一种环境,慢慢得我们会觉得有些无聊,所以适当地出去走走看看外面的世界能带给我们不一样的体验. 所以,何不出去走走,看看另一个世界? 然而,平时需要工作很难抽身无所顾忌地潇洒走开,这是不是意味着无法离开,要画地为牢了呢? 既然是工作问题,那么我们就从工作本身开始改变,我们每个人的电脑正常来说都只有一个操作系统,如果有一种方式能够让你切换到另一种操作系统上,岂不是相当于计算机的旅游了吗? 虽然我们本人不能亲身去另外一个地方看看,就让计算机代替我们去体验不同的环境吧! 虚拟机理论上支持任何操作系统,换句话说,Windows 系统可以装 Windows ,也可以装 Mac 和 Linux 等等. 如何换个新环境 Windows 电脑想要体验另一种操作系统,最简单的方式莫过于借助虚拟机方式,何为虚拟机? 虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能,运行在一个完全隔离环境中的完整计算机系统. 虚拟机,顾名思义就是虚拟的计算机,虚拟意味着并不是真正的,计算机意味着拥有普通电脑的基本功能. 所以虚拟机要表达的意思就是说,创建一台并不是真实的计算机,但这种计算机却拥有普通计算机的基本能力. 正是由于虚拟机概念的提出,使得原本单一的操作系统支持多种不同的操作系统.Windows 计算机可以装Windows ,也可以装 Mac ,当然还有开发人员专用的 Linux. 原来的计算机称之为物理机也叫作宿主机,新产生的计算机就是虚拟机. 只要有明确的目标,虚拟机就能带你的计算机去另外一个世界. 平时不敢在物理机进行的秘密实验,你可以搬到虚拟机去实验; 羡慕键盘如飞的电脑黑客,你可以装个 Linux 虚拟机去体验一把命令行操作的灵活自由; 某些操作只能使用 Mac 电脑完成而苦于身边没有 Mac 电脑,也可以装个 Mac 虚拟机感受一下苹果的优雅. ... 不论是哪一种应用场景,虚拟机基本上都能满足,值得注意的是,虚拟机虽好,不要贪多哟! 只有物理机的性能足够强劲,才建议安装虚拟机,否则的话,病怏怏的身体怎么承受得住活泼好动灵魂的折腾. 安装虚拟机软件 市面上的虚拟机软件可选性有不少,而我主要介绍的是 VMware 软件的解决方案. VMware 软件不仅支持 Windows 宿主机,也支持 Mac 宿主机,而且一直在用也挺好的. 既然应认定了 VMware ,那还等什么,赶紧出来让我们看一眼吧! 百度搜索 vmware 或者直接进入 https://www.vmware.com/cn.html 即可访问 vmware 官网. 如果无法访问,可能需要另辟蹊径,具体原因你猜猜看. 打开 下载 > 免费产品试用版和演示 > Workstation Pro 查看下载页面. 跳转到下载页面后,选择 Windows 版本,点击 立即下载,耐心等待文件下载. Windows 下载链接: https://www.vmware.com/go/getworkstation-win 下载完毕后,双击 VMware-workstation-full-15.0.4-12990004.exe 进行默认安装,安装过程比较简单,以下动图仅供参考. 产品密钥可以使用 KeyGen.exe 自动生成,也可以从下列密钥中随意挑选一个. GF1T2-D8G97-M85MY-LDMNC-PZA96 AV34H-DDG8L-48EXQ-CQZET-ZZUR2 YY51H-FJXEQ-H85YQ-U5M5X-Q38D0 VY74R-FXX81-085PQ-DMMQT-X2AF6 VY10K-8WY03-H808Y-35YZE-NKKV2 YY11K-8UY46-M88MP-VMYEE-MYAF6 AA30K-27ZEL-480DQ-3DZ7C-MQKU4 VV7N8-D2E41-M852Q-8EQEX-ZQRU0 VC190-46W06-08E8P-TGQ5T-MLR8D AZ3NH-DQX9N-488RP-15ZXC-Q68VA 现在 vmware 已经安装成功,接下来我们将创建新的虚拟机,开始真正的计算机换装旅行吧! 在菜单栏依次点击 帮助 > 关于 确认一下是否注册成功. 虽然提供激活码注册方式,但是还是想说有条件的小伙伴请支持正版! 回顾总结 本文主要介绍了什么是虚拟机和虚拟机的应用场景以及如何安装 Vmware 软件从而安装虚拟机. 简单来说,虚拟机就是运行在本机上的一个虚拟独立的计算机,虽然不是真实的物理机,但是却拥有计算机的基本属性,不论是想在新电脑上瞎折腾还是想体验不同的操作系统,虚拟机都可以满足你的需求. 值得注意的是,虚拟机虽好,不要贪多哟,毕竟虚拟机很占资源,如果宿主机本身不给力的话,虚拟机也很难流畅地运行. 下节预告: 给 windows 虚拟机装个 windows 给 windows 虚拟机装个 centos 给 windows 虚拟机装个 ubantu var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/windows-install-vmware.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/windows-vmware-install-mac.html":{"url":"tools/windows-vmware-install-mac.html","title":"给 windows 虚拟机装个 mac","keywords":"","body":"给 windows 虚拟机装个 mac 众所周知,Mac 很好但也很贵,对一般大众而言,漂亮简洁高颜值,对软件开发者而言,方便省心有点贵. 好到什么程度? 内置大量常用的开发工具,省去了初学者安装配置环境的麻烦,版本控制工具 svn 默认已安装,web 服务器 apache 默认已安装,编程开发环境 php 默认已安装等等,例子很多就不一一列举了. 除此之外,Mac 系统不同于 Windows 系统,Mac 系统是一种类 Unix 操作系统,命令行工具很好用,相当于提前熟悉 Linux 语法了,这一点是 Windows 望尘莫及的. 贵到什么程度? 随随便便的普通版七八千,如果再稍微挑挑拣拣,起码一两万! 如果预算不够但又想体验一下 Mac 电脑,该怎么办? 答案很简单,直线去去线下苹果体验店啊! 哈哈,我是开玩笑的,言归正传,买不起真实的苹果笔记本也没关系,我们可以像安装普通软件那样,安装一个苹果笔记本! 下面我们将介绍一种方法,让你能够在 Windows 电脑上安装一个苹果笔记本,用起来和真的一样,只不过你我都心知肚明,那并不是真实的机器! 知识扫盲 虚拟机是相对于真实的物理机而言的概念,是在我们当前正在使用的计算机基础上,通过软件或硬件的方式创造的新的计算机. VMware 是常用的虚拟机软件之一,物理机安装好 VMware 软件就可以利用该软件虚拟出任意计算机即虚拟机. VMware 支持 Windows ,Mac 和 Linux 等常见操作系统,是名副其实的跨平台软件. 镜像 是一种特殊格式的文件,文件后缀名一般是 .iso ,但也要例外,比如本文安装的 macOS Mojave 10.14 18A391 Lazy Installer(MD5-CDD5EDA714D8BCC8E799F8272556CF3B).cdr 的后缀名却是 .cdr ,镜像文件就是创建虚拟机的必要程序,有了它 VMware 软件才能创建出虚拟机. 总的来说,VMware 软件加载 xxos.iso 镜像文件创建出 xxos 虚拟机. 所以,阅读教程前请务必准备好 VMware 虚拟机以及相应的镜像文件. 安装准备 虚拟机技术能够虚拟出任何操作系统,并不局限于 Windows 安装 Mac ,也可以安装 Linux 或者 Windows . 同理,虚拟机技术也不局限于 VMware ,也可以是 VirtualBox 或者 Parallels Desktop 等等. 为了避免选择困难症,下面以 VMware 虚拟机安装 Mac 为例,简单演示一下安装流程. 安装 Mac 操作系统需要两个前提条件: Windows 电脑已安装好 VMware 虚拟机. Windows 电脑已下载好 Mac 操作系统镜像. 链接: https://pan.baidu.com/s/1zL7-nB7ukif6nWBQ8KyOMA 提取码: hrgr 给 windows 装个 vmware 虚拟机 给 mac 装个 vmware 虚拟机 如果尚未安装 VMware 虚拟机,请参考上述链接进行安装,如果链接已失效,请私信我补发. macOS Unlocker for VMware v3.0.2.zip 解锁文件,是安装镜像文件的前提. macOS Mojave 10.14 18A391 Lazy Installer(MD5-CDD5EDA714D8BCC8E799F8272556CF3B).cdr 镜像文件,是安装 Mac 操作系统的灵魂. 友情提示,百度云下载大文件限速太厉害,一定要准备好下载方案或者开通超级VIP进行下载. 安装镜像 解锁镜像 打开 VMware 软件,选择 文件-> 创建新的虚拟机 或者在主页中选择 创建新的虚拟机 . 选择已下载好的镜像文件,选择文件时默认后缀名是 .iso 而我们安装的镜像文件是 .cdr ,因此一定要选择全部文件,这样就能选中 macOS Mojave 10.14 18A391 Lazy Installer(MD5-CDD5EDA714D8BCC8E799F8272556CF3B).cdr 镜像文件了! 虽然已经加载镜像文件,但是存在警告信息: 无法检测此光盘镜像中的操作系统. 无关紧要,下一步手动指定安装的镜像文件是 Mac 10.14 操作系统即可! 当我们理所当然点击下一步时,顿时傻眼了,竟然没有 Mac os 操作系统,凭什么?! 客户机操作系统选项没有 Mac 操作系统是不是因为上一步的警告信息呢?还真不是,因为并没有解锁! 解压 macOS Unlocker for VMware v3.0.2.zip 并找到 win-install.cmd 文件,选中该文件右键以管理员身份运行! 测试时运行效果是一闪而过,应该也无碍,只要再次安装镜像时出现 Mac Os 操作系统就是解锁成功. 如果没有解锁成功,打开 windows 任务管理器 并杀死 vmware 相关的全部进程,再次运行win-install.cmd 命令. 继续安装 解锁成功后,再次打开 VMware 软件继续安装镜像文件,此时已经出现 Mac os 操作系统选项了,如果没有出现该选项,请返回上一步. 接下来正常安装,傻瓜式操作均采用默认配置,直接点击下一步,直到安装完成. 安装虚拟机后,有啥秘密试验都可以在虚拟机上进行操作啦,再也不担心会不小心损坏物理机了呢! 开机体验 虚拟机安装完毕后,选择启动该虚拟机,正如物理机按开机按键一样,静静等待传说中的黑苹果! 安装一会接着提示\"无法在更新服务器上找到组件\",这是因为网络不通或者破解软件的原因,可以暂时忽略该错误. 设置语言,使劲往下滑直到最后,然后选择简体中文,紧接着下一步. 开始安装 macOS 操作系统,选择\"继续\". 同意协议并继续下一步. 不好意思,我又指了一条错误的安装道路! 下一步无路可走,只好返回,现在又回到准备安装的界面,看来我们必须准备好安装磁盘才能继续! 选择 实用工具->磁盘工具 开始准备安装磁盘. 打开的新页面左侧有两块磁盘,选择下面以 VMWare 开头的磁盘,编辑好磁盘名称(如 snowdreams1006),然后点击抹掉. 完成后关闭当前页面并返回到开始安装页面,紧接着继续下一步直到上次停留的页面. 现在已经出现了安装磁盘,选择刚刚命名的 snowdreams1006 安装磁盘,继续愉快的下一步! 虽然说还剩 16min ,实测感觉要长多了,幸运的是,接下来的安装步骤没有特别需要注意点,因此整理成动图略过. 终于安装完毕,退出安装光盘,大功告成! 并不是所有的安装版本都是最新版,当初的最新版也不一定是现在的最初版,如果追求最新操作系统,那你可以手动升级啊! 总结 VMware 软件是一款跨平台的虚拟机操作软件,加载到有效的镜像文件就能创造出虚拟机,值得注意的是,由于创造的虚拟机是一个完整的操作系统,占用了物理机一定的资源,因此物理机性能不够强劲的话,不要创建过多的虚拟机. 一两个虚拟机足矣,不使用虚拟机时一定要及时关机,否则电脑卡到怀疑人生! 安装虚拟机的必要文件是各种各种的镜像文件,有个镜像文件就有了相应的虚拟机,一般来说,镜像文件都是网络上别人制作好的或者官方提供的,当然,如果你愿意的话,你也可以制作自己的镜像供别人下载使用. 镜像文件一般都是比较大的,小则 3g ,大则 7g ,因此下载镜像文件比较费时,不同镜像文件安装时间也不尽相同,快则 1h,慢则 3h. 至于虚拟机配置方面,一般来说采用系统默认值即可,除非你有特殊需求或者明白你正在设置的选项含义,否则不要随意更改推荐设置. 安装虚拟机中途可能会多次重启虚拟机,请耐心等待,不要中断安装操作,该完成时自会完成! 最后,感谢你的阅读,希望能够对你有所帮助! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/windows-vmware-install-mac.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/windows-vmware-install-windows.html":{"url":"tools/windows-vmware-install-windows.html","title":"给 windows 虚拟机装个 windows","keywords":"","body":"给 windows 虚拟机装个 windows 前面我们已经介绍了如何在 Windows 宿主机安装 VMware 虚拟机,这节我们将利用安装好的 VMware 软件安装 Windows 系统. 前情回顾 虚拟机是相对于真实的物理机而言的概念,是在我们当前正在使用的计算机基础上,通过软件或硬件的方式创造的新的计算机. 本文主要介绍的是 VMware 虚拟机,下载并安装 VMware 软件再安装操作系统即可模拟出另一台计算机的效果,这种模拟出来的计算机就是虚拟机. VMware 不仅支持 Windows 也支持 Linux ,对于 Mac 系统也是支持的,如需了解 Mac 宿主机如何安装使用虚拟机,可以参考工具资源系列之给mac装个虚拟机. Windows 物理机如何安装虚拟机请参考 给 windows 装个 vmware 虚拟机 下载镜像 VMware 为我们安装虚拟机提供了环境,真正的虚拟机到底是 Windows 系统还是 Linux 系统取决于我们要安装什么操作系统. 安装操作系统最简单便捷的方式就是下载操作系统的镜像文件,VMware 识别到镜像文件后就会一步一步安装操作系统. 所以我们第一步要做的就是寻找镜像文件,正所谓\"知自知彼方能百战不殆\",意味着首先要确定下我们到底要安装哪一个版本的操作系统? Windows 操作系统有很多种,有 Win7 ,Win8 和 Win10 ,还有旗舰版和家庭版. 如果我们购买真实的计算机,那肯定要好好考虑一下,毕竟金钱要花的有价值,然而我们要安装的虚拟机,并不收费,这么多版本我们可以任意挑选安装! 如果有明确的目标,可以按照实际需求自行下载相应的操作系统; 如果没有明确的目标,不妨和我一样安装一个和本机相似的虚拟机,一来操作比较熟悉,而来可以在虚拟机进行任意实验. 所以,首先我要看一下本机的系统版本,因此我决定安装同款 Win7 旗舰版! 选择 我的电脑 > 右键属性 > 查看计算机的相关属性 ,我的电脑是 Windows 7 旗舰版 现在已经明确了虚拟机的操作系统,那我们去哪里下载目标虚拟机的镜像文件呢? 这里推荐一下 https://msdn.itellyou.cn/ 网站,方便使用,好评! Windows 7 Enterprise (x64) - DVD (Chinese-Simplified) : 64 位企业版 Windows 7 Enterprise (x86) - DVD (Chinese-Simplified) : 32 位企业版 Windows 7 Enterprise with Service Pack 1 (x64) - DVD (Chinese-Simplified) : 64 位企业版且带有service package 1 Windows 7 Enterprise with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位企业版且带有service package 1 Windows 7 Home Basic (x86) - DVD (Chinese-Simplified) : 32 位家庭普通版 Windows 7 Home Basic with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位家庭普通版且带有service package 1 Windows 7 Home Premium (x64) - DVD (Chinese-Simplified) : 64 位家庭高级版 Windows 7 Home Premium (x86) - DVD (Chinese-Simplified) : 32 位家庭高级版 Windows 7 Home Premium with Service Pack 1 (x64) - DVD (Chinese-Simplified) : 64 位家庭高级版且带有service package 1 Windows 7 Home Premium with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位家庭高级版且带有service package 1 Windows 7 Professional (x64) - DVD (Chinese-Simplified) : 64 位专业版 Windows 7 Professional (x86) - DVD (Chinese-Simplified) : 32 位专业版 Windows 7 Professional with Service Pack 1 (x64) - DVD (Chinese-Simplified) : 64 位专业版且带有service package 1 Windows 7 Professional with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位专业版且带有service package 1 Windows 7 Professional with Service Pack 1, VL Build (x64) - DVD (Chinese-Simplified) : 64 位专业版且带有service package 1,并基于 VL 进行构建. Windows 7 Professional with Service Pack 1, VL Build (x86) - DVD (Chinese-Simplified) : 32 位专业版且带有service package 1,并基于 VL 进行构建. Windows 7 Professional, VL Build (x64) - DVD (Chinese-Simplified) : 64 位专业版并基于 VL 进行构建. Windows 7 Professional, VL Build (x86) - DVD (Chinese-Simplified) : 32 位专业版并基于 VL 进行构建. Windows 7 Starter (x86) - DVD (Chinese-Simplified) : 32 位初级版 Windows 7 Starter with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位初级版且带有service package 1 Windows 7 Ultimate (x64) - DVD (Chinese-Simplified) : 64 位旗舰版 Windows 7 Ultimate (x86) - DVD (Chinese-Simplified) : 32 位旗舰版 Windows 7 Ultimate with Service Pack 1 (x64) - DVD (Chinese-Simplified) : 64 位旗舰版且带有service package 1 Windows 7 Ultimate with Service Pack 1 (x86) - DVD (Chinese-Simplified) : 32 位旗舰版且带有service package 1 Windows Automated Installation Kit for Windows 7 and Windows Server 2008 R2 (x86, x64, ia64) - DVD (Chinese-Simplified) : Windows 7 and Windows Server 2008 R2 (x86, x64, ia64) 自动安装包套件 Windows Automated Installation Kit for Windows 7 and Windows Server 2008 R2 Service Pack 1 (x86, x64, ia64) - DVD (Chinese-Simplified) : Windows 7 and Windows Server 2008 R2 Service Pack 1 (x86, x64, ia64) 自动安装包套件 上述这么多的版本是不是让人有些眼花缭乱,具体版本之间有何差异以及自己适合哪一种请百度一下再理性分析! 以下以 64 位旗舰版且带有服务包操作系统为例进行演示,主要是创建一个和宿主机一样的操作环境,方便后续进行秘密实验! cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso 镜像文件,其中 cn 表示中文简体语言, Windows_7 表示 Win7 操作系统, ultimate 表示旗舰版,sp1 表示service package 1 ,x64 表示 64 位操作系统,dvd 表示 DVD 安装方式,677408 应该是版本号,.iso 是镜像文件的后缀. ed2k://|file|cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso|3420557312|B58548681854236C7939003B583A8078|/ 由于镜像文件本身比较大,因此推荐使用专业的下载工具进行,这里使用的是迅雷下载磁力链接. 镜像文件: cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso ,其中后缀是 .iso ,千万不要解压! 不要解压! 不要解压! 安装镜像 准备好已下载的镜像文件: cn_windows_7_ultimate_x64_dvd_x15-66043.iso 打开 VMware 软件,选择 文件-> 创建新的虚拟机 或者在主页中选择 创建新的虚拟机 . 总体来说,安装过程比较简单,前面相关配置按照默认值即可,后面真正安装过程可能耗费时间比较长,耐心等待安装完成. VMware 仅能识别出镜像文件基本信息,具体版本信息还是需要手动校准,下载的镜像文件是 Win7 旗舰版 ,因此安装版本也是 Win7 旗舰版 . 一系列安装配置完毕后,还有最后一步配置确认操作,确认无误后点击 完成 就可以真正进行安装虚拟机了! 安装过程中可能要求输入产品密钥以及设置用户,这些操作和新买计算机刚开机时操作一模一样,百度找一下相应版本的产品密钥即可,可以设置登录用户也可以不设置用户. 至此,安装成功! 安装虚拟机后,有啥秘密试验都可以在虚拟机上进行操作啦,再也不担心会不小心损坏物理机了呢! 回忆总结 VMware 软件提供了虚拟机环境,差一个操作系统就能创建出虚拟机,而这种操作系统大部分是 .iso镜像文件. 镜像文件基本上至少 3g ,下载镜像文件也比较耗时,可以利用专业第三方下载工具进行下载. 虚拟机配置比较简单,采用推荐的默认值进行设置即可,除非你有特殊需求或者明白你正在设置的选项含义,否则不要随意更改推荐设置. 安装虚拟机中途可能会多次重启虚拟机,请耐心等待,不要中断安装操作,完成后自会完成! 工具资源系列之给windows装个虚拟机 工具资源系列之给mac装个虚拟机 工具资源系列之给mac虚拟机装个windows 如果觉得本文写的不错,欢迎点赞留言和转发哟! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/windows-vmware-install-windows.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/windows-vmware-install-centos.html":{"url":"tools/windows-vmware-install-centos.html","title":"给 windows 虚拟机装个 centos","keywords":"","body":"给 windows 虚拟机装个 centos 前面我们已经介绍了如何在 Windows 宿主机安装 VMware 虚拟机,这节我们将利用安装好的 VMware 软件安装 centos 系统. 前情回顾 由于大多数人使用的 Windows 电脑而工作中可能需要一台 centos 电脑,如果条件允许的话,一般公司会有相应的测试服务器. 但是,如果是个人使用的话,公司的测试服务器就不能轻易做各种实验了,毕竟测试服务器是大家公用的,万一不小心搞坏了影响了他人的使用就不好交代了. 因此,最好能够有一台私有的 centos 计算机,可以随意鼓捣还不用担心影响到其他人,这种情况下虚拟机提供了很好的解决思路. 虚拟机是相对宿主机而言较为独立的计算机,即使不小心把虚拟机搞崩了也不要紧,重新装下虚拟机就好了,也不会损坏真实的宿主机. 而我们介绍的虚拟机软件是 VMware ,跨平台支持三大主流操作系统,因此无论是 Windows 还是 Mac 或者 Linux 系统都可以借壳生蛋,继续创造出不同的操作系统. 给 windows 装个 vmware 虚拟机 给 mac 装个 vmware 虚拟机 下载镜像 centos 操作系统下载: https://www.centos.org/download/ 安装 centos 操作系统需要镜像文件,寻找镜像文件最简单的方法是 centos 自己的官网,因此我们直接去官方看一下吧! 这里提供了 DVD ISO 和 Minimal ISO 两种类型,普通用户推荐选择前一种标准版,开发用户建议选择后一种最小版. 标准版功能比较齐全,最小版保证最小依赖,后续缺啥填啥,比较灵活节省空间内存. 按照实际需要选择适合自己的操作系统,这里选择的是 centos7.6 ,然后选择合适的下载方式(直接下载或下载种子链接). 建议选择镜像服务器下载,如果直接下载官网的地址,速度感人,时间有点长. 依次选择 list of current mirrors -> http://mirrors.aliyun.com/centos/ -> 7.6.1810/ -> isos/ -> x86_64/ -> CentOS-7-x86_64-Minimal-1810.iso 选择合适的版本点击下载. 镜像文件: CentOS-7-x86_64-Minimal-1810.iso ,其中后缀是 .iso ,千万不要解压! 不要解压! 不要解压! 安装镜像 准备好已下载的镜像文件: CentOS-7-x86_64-Minimal-1810.iso 打开 VMware 软件,选择 文件-> 创建新的虚拟机 或者在主页中选择 创建新的虚拟机 . 总体来说,安装过程比较简单,前面相关配置按照默认值即可,后面真正安装过程可能耗费时间比较长,耐心等待安装完成. 一系列安装配置完毕后,还有最后一步配置确认操作,确认无误后点击 完成 就可以真正进行安装虚拟机了! 安装过程中配置语言,默认是英语,中文简体在最下面,一直往下翻选择简体中文即可. 设置超级管理员密码以及添加用户,用于安装完毕后登陆系统. 至此,安装成功! 安装虚拟机后,有啥秘密试验都可以在虚拟机上进行操作啦,再也不担心会不小心损坏物理机了呢! 回忆总结 VMware 软件提供了虚拟机环境,只要一个操作系统的镜像文件就能轻易创建出虚拟机,认准镜像文件的后缀名是 .iso . VMware 虚拟机配置比较简单,基本上采用默认值进行设置即可,除非你有特殊需求或者明白你正在设置的选项含义,否则不要随意更改推荐设置. 安装虚拟机中途可能会多次重启虚拟机,请耐心等待,不要中断安装操作,完成后自会完成! 工具资源系列之给windows装个虚拟机 工具资源系列之给mac装个虚拟机 工具资源系列之给虚拟机装个centos 如果觉得本文写的不错,欢迎点赞留言和转发哟! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/windows-vmware-install-centos.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/windows-vmware-install-ubuntu.html":{"url":"tools/windows-vmware-install-ubuntu.html","title":"给 windows 虚拟机装个 ubuntu","keywords":"","body":"给 windows 虚拟机装个 ubuntu 前面我们已经介绍了如何在 Windows 宿主机安装 VMware 虚拟机,这节我们将利用安装好的 VMware 软件安装 Ubuntu 系统. 前情回顾 虚拟机为我们在 Windows 宿主机体验别的系统提供了可能,虚拟机的强大之处在于我们可以自由安装任意操作系统,不管是同款 Windows 还是 Linux 都可以! 关于如何安装 VMware 软件可以参考上一篇文章,这里给出 Windows 和 Mac 的安装教程. 给 windows 装个 vmware 虚拟机 给 mac 装个 vmware 虚拟机 下载镜像 Ubuntu 操作系统下载: https://www.ubuntu.com/download 这里我们选择桌面版(Ubuntu Desktop),接着选择 LTS 长期支持版进行下载安装. 按照实际需要选择适合自己的操作系统,这里选择的是 Ubuntu18 LTS ,然后选择下载. 镜像文件: ubuntu-18.04.2-desktop-amd64.iso ,其中后缀是 .iso ,千万不要解压! 不要解压! 不要解压! 安装镜像 准备好已下载的镜像文件: ubuntu-18.04.2-desktop-amd64.iso 打开 VMware 软件,选择 文件-> 创建新的虚拟机 或者在主页中选择 创建新的虚拟机 . 总体来说,安装过程比较简单,前面相关配置按照默认值即可,后面真正安装过程可能耗费时间比较长,耐心等待安装完成. 一系列安装配置完毕后,还有最后一步配置确认操作,确认无误后点击 完成 就可以真正进行安装虚拟机了! 安装过程中需要添加用户,用于安装完毕后登陆系统. 至此,安装成功! 安装虚拟机后,有啥秘密试验都可以在虚拟机上进行操作啦,再也不担心会不小心损坏物理机了呢! 回忆总结 VMware 虚拟机配置比较简单,安装 Ubuntu 镜像文件时耗费时间相当长,慢慢等待一切会自动安装好的. 安装虚拟机过程可能会重启虚拟机,请耐心等待,不要中断安装操作,完成后自会完成! 工具资源系列之给windows装个虚拟机 工具资源系列之给mac装个虚拟机 工具资源系列之给虚拟机装个ubuntu 如果觉得本文写的不错,欢迎点赞留言和转发哟! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/windows-vmware-install-ubuntu.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/chrome-extend-learn.html":{"url":"tools/chrome-extend-learn.html","title":"学会开发专属 chrome 插件","keywords":"","body":"学习开发自己的 chrome 插件 Extend the Browser 【干货】Chrome插件(扩展)开发全攻略 Chrome插件(chrome Extensions)开发攻略 一篇文章教你顺利入门和开发chrome扩展程序(插件) 【干货】Chrome插件(扩展)开发全攻略 手把手教你开发chrome扩展一:开发Chrome Extenstion其实很简单 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/chrome-extend-learn.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"tools/12306-algorithm-web-js.html":{"url":"tools/12306-algorithm-web-js.html","title":"攻克 12306 前端加密算法","keywords":"","body":"还原 12306 前端加密算法 郑重声明: 本文仅供学习使用,禁止用于非法用途,否则后果自负,如有侵权,烦请告知删除,谢谢合作! 开篇明义 本文针对自主开发的抢票脚本在抢票过程中常常遇到的请求无效等问题,简单分析了 12306 网站的前端加密算法,更准确的说,是探究 RAIL_DEVICEID 的生成过程. 因为该 cookie 值是抢票请求的核心基础,没有它将无法正确发送请求,或者一段时间后就会到期失效需要重新获取,或者明明更改了浏览器用户代理(navigator.userAgent)标识却还是被限制访问... 因为它并不是真正的客户端标识,只是迷惑性战术,浏览器唯一标识其实是 RAIL_OkLJUJ 而它却被 12306 网站设计者故意没有添加到 cookie ,因此造成了很强的欺骗性,编程真的是一门艺术! 你以为你的爬虫已经可以正常模仿浏览器,殊不知,只要没搞懂谁才是真正的浏览器标识,那么再怎么换马甲也难逃造假事实. 上图展示了 RAIL_OkLJUJ 的存在位置,可能是为了兼容市面上绝大数浏览器,也可能是为了联合各种前端缓存技术作为特征码,总是除了 cookie 之外,RAIL_OkLJUJ 存在于 Local Storage , Session Storage , IndexedDB 和Web SQL 等. 值得注意的是,cookie 中故意没有设置 RAIL_OkLJUJ ,如果清空全部缓存后再次刷新网页,你就会发现 RAIL_DEVICEID 已经发生变化了而 RAIL_OkLJUJ 依旧没变! 下面简单验证一下说明谁才是真正的浏览器唯一标识: step 1 : 复制当前获取到的 RAIL_DEVICEID 和 RAIL_OkLJUJ 的值 打开控制台(Console),通过 js 代码方式取出本地存储(localStorage) 的值: localStorage.getItem(\"RAIL_DEVICEID\"); localStorage.getItem(\"RAIL_OkLJUJ\"); 控制台会立即返回该值,接下来需要手动复制到其他地方等待和第二次结果作比较. 但是程序员总是喜欢能偷懒就偷懒,手动复制也懒得复制怎么办? 当然,继续使用js 代码复制了啊! copy('雪之梦技术驿站欢迎您的访问,https://snowdreams1006.cn'); 比如这句代码就会把文本'雪之梦技术驿站欢迎您的访问,https://snowdreams1006.cn'复制到剪贴板,接下来选择文本编辑器右键粘贴就能看到效果啦! 所以改造一下代码就能复制第一次访问 12306 网站获取到的 RAIL_DEVICEID 和 RAIL_OkLJUJ 的值. copy(\"RAIL_DEVICEID:::\"+localStorage.getItem(\"RAIL_DEVICEID\")); // RAIL_DEVICEID:::E5BDkKrPkZ6nuZruqUj9-3lUG1LBM7t9aTDbZwFSdrboaFG6odrWZ9yuphnas4Jwq5E_FXIwwqlRoSXFbJULUiBNwNGt61Ow6Zv0GFXRABipaeDJJ0Ub7G2g_B_aGwMF5DNZ5KJR4eWVl-P3zSHGKbczLB3WN0z- copy(\"RAIL_OkLJUJ:::\"+localStorage.getItem(\"RAIL_OkLJUJ\")); // RAIL_OkLJUJ:::FGFOJ75VdD8dQc2yh3yTJf2RBWES6uGI step 2 : 等待 5 min 后再次获取 RAIL_DEVICEID 和 RAIL_OkLJUJ 的值 copy(\"RAIL_DEVICEID:::\"+localStorage.getItem(\"RAIL_DEVICEID\")); // RAIL_DEVICEID:::VUye37EEUdGHgrpJGo9J95hWMNSIUFPeYBjabDgCiYJbQIr53iVzIPQJwcLhbijL4OyPVGmzolsVEK8Pw7_DG_oPrUDpfbnRe7HvMWMJvU2MAbk-7EwNEePAlpnVb9QVZz4dtOUSCRVbS2zlwgS0xe2BOThpR9oy copy(\"RAIL_OkLJUJ:::\"+localStorage.getItem(\"RAIL_OkLJUJ\")); // RAIL_OkLJUJ:::FGFOJ75VdD8dQc2yh3yTJf2RBWES6uGI 或者清空网站 cookie 后再次刷新当前网页,总之就是想办法触发浏览器再次运行相关逻辑重新生成 RAIL_DEVICEID 和 RAIL_OkLJUJ . step 3 : 对比第一次和第二次获取到的 RAIL_DEVICEID 和 RAIL_OkLJUJ 的值 RAIL_DEVICEID:::E5BDkKrPkZ6nuZruqUj9-3lUG1LBM7t9aTDbZwFSdrboaFG6odrWZ9yuphnas4Jwq5E_FXIwwqlRoSXFbJULUiBNwNGt61Ow6Zv0GFXRABipaeDJJ0Ub7G2g_B_aGwMF5DNZ5KJR4eWVl-P3zSHGKbczLB3WN0z- RAIL_OkLJUJ:::FGFOJ75VdD8dQc2yh3yTJf2RBWES6uGI RAIL_DEVICEID:::VUye37EEUdGHgrpJGo9J95hWMNSIUFPeYBjabDgCiYJbQIr53iVzIPQJwcLhbijL4OyPVGmzolsVEK8Pw7_DG_oPrUDpfbnRe7HvMWMJvU2MAbk-7EwNEePAlpnVb9QVZz4dtOUSCRVbS2zlwgS0xe2BOThpR9oy RAIL_OkLJUJ:::FGFOJ75VdD8dQc2yh3yTJf2RBWES6uGI 显而易见,肉眼直接就能看出两次请求时 RAIL_OkLJUJ 的值并没有变化而 RAIL_DEVICEID 的值很大可能会发生改变. 因此,RAIL_DEVICEID 应该并不是浏览器唯一标识,而 RAIL_OkLJUJ 才是真正的唯一标识! 本文并不适合全部读者,如果你属于以下情况之一,那么本文对你绝对帮助甚多,否则对你来说只能算是浪费生命. 适合对自主抢票或者脚本抢票有需求的天涯游子 适合拥有一定 web 前端开发相关知识的开发者 适合耐得住寂寞能够独自研究加密算法的孤独人 最后的核心前提是有网,当然WiFi更佳,否则流量真的吃不消啊! 故事背景 独在异乡为异客 每逢佳节要抢票 手动自动一起上 时常掉线心好伤 动手实践出真理 原来身份是唯一 想要封你没商量 只能动手来伪装 加密请求在前端 后端返还控制权 还原算法改身份 稳定抢票不担心 多种途径齐上阵 车票速速快现身 不知道你是否遭遇过一票难求的困境,尽管网络上关于第三方工具的加速包是否加速有过辟谣,但是每逢节假日总是会遇到抢不到车票的问题,大部分人还是会选择买个心理安慰吧! 目前为止,12306 官方线上售票渠道仅仅包括 12306 网站以及手机 app 客户端,因此市面上流行的第三方抢票软件均为非正常途径,而这些第三方渠道中最简单的实现方式应该就算是爬虫技术了. 不论是网页端还是手机端,统统称为客户端,客户端的作用仅仅是传声筒,真正负责执行命令的人就是服务端. 当你提交购票需求时,客户端会把这些车票信息一起打包发送给服务端,如果服务端有票的话,那么有可能就会返回给客户端成功信息,恭喜你订票成功. 但是尽管有票也不一定会给你,唯一确定的是无票一定会失败,总之不管结果如何服务端和客户端总是按照既定的约定协议在默默交流着..... 尽管官方渠道最可靠也最准确,可官方也还是没能给你买到车票啊! 所以想要抢票还是得亲自动手,不能完全依靠官方,这里就诞生了爬虫技术来冒充客户端,想要成功骗过服务端就要先了解真正的客户端到底有哪些特征? 由于本文篇幅有限,暂时不做关于抢票方面的相关论述,直奔重点,讲解 RAIL_DEVICEID 的请求过程,带你一步一步还原 12306 网站的前端加密算法的实现逻辑! 效果预览 在浏览器控制台运行 chromeHelper.prototype.encryptedFingerPrintInfo() 方法时会计算真实浏览器信息,如果发现计算结果中的 value 值和真正请求 https://kyfw.12306.cn/otn/HttpZF/logdevice%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/80.0.3987.87%20Safari/537.36&E3gR=9f7fa43e794048f6193187756181b3b9) 的 hashcode 值相同,那么恭喜您,说明 12306 相关算法还没更新,如果不相同估计算法又稍微调整了! 事实证明,12306 算法虽然在变但都是小打小闹,根本没有伤筋动骨,所以自己动手改改又能满血复活了哟! { \"key\": \"&FMQw=0&q4f3=zh-CN&VPIf=1&custID=133&VEek=unknown&dzuS=0&yD16=0&EOQP=c227b88b01f5c513710d4b9f16a5ce52&jp76=52d67b2a5aa5e031084733d5006cc664&hAqN=MacIntel&platform=WEB&ks0Q=d22ca0b81584fbea62237b14bd04c866&TeRS=777x1280&tOHY=24xx800x1280&Fvje=i1l1o1s1&q5aJ=-8&wNLf=99115dfb07133750ba677d055874de87&0aew=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36&E3gR=9f7fa43e794048f6193187756181b3b9\", \"value\": \"owRJc8M4EkFMvcTkzibRFJoDSkUKCx6N9ictZIJLIeY\" } step 1 : 使用 Chrome 浏览器打开 12306 网站并清空该站点全部缓存数据. 请确保当前正在使用的是谷歌 Chrome 浏览器,IE和 firefox 等浏览器暂未测试. step 2 : 手动清空 window.name 属性,保证浏览器处于首次打开 12306 网站状态. 因为非首次加载会携带上一次的请求信息,不方便学习验证,经过分析试验发现历史状态还保存在 window 对象的 name 属性,因此仅仅清空缓存还不够,还需要手动清空 name 属性的值. step 3 : 强制刷新当前页面并保持记录请求信息,过滤请求类型 js ,找到 /otn/HttpZF/logdevice 请求. 在找到该请求保存查询参数名为 hashCode: owRJc8M4EkFMvcTkzibRFJoDSkUKCx6N9ictZIJLIeY ,方便和之后的计算方式生成的结果做对比. 除了查询请求信息外,更为重要的是查看响应信息,当初次请求 /otn/HttpZF/logdevice 时除了返回过期时间 exp 和 dfp 设备信息之外,还会返回 cookieCode 设备唯一标识. 如果等到过期时间或手动清空站点缓存后,/otn/HttpZF/GetJS 脚本中的相关逻辑会再次发起 /otn/HttpZF/logdevice 请求,那时候的响应内容再也没有 cookieCode 参数了. 让我们再好好看一看初次请求的响应信息吧! callbackFunction('{\"exp\":\"1581948102442\",\"cookieCode\":\"FGHcXsVmjf3oV0zm5qTDPFt-VcNhuDA-\",\"dfp\":\"QNCYH1J5E9M7rl97uo_PUR1OSwRTcCe1xdnbX7h2V6Ewcq6kML0qzXD5y11rLv3FPX1ndOnhL_bjVkwwgtWTsHMFums60_4H9Lr-vJzJGq4tkaUEGfRNXN9IJlvptReSBa5PP7N5gxpSOBo-YlF5Ac98f-YlNlxi\"}') 如果将 callbackFunction() 回调函数去掉,不难发现其实返回数据是 json 格式,格式化后发现响应内容如下: { \"exp\": \"1581948102442\", \"cookieCode\": \"FGHcXsVmjf3oV0zm5qTDPFt-VcNhuDA-\", \"dfp\": \"QNCYH1J5E9M7rl97uo_PUR1OSwRTcCe1xdnbX7h2V6Ewcq6kML0qzXD5y11rLv3FPX1ndOnhL_bjVkwwgtWTsHMFums60_4H9Lr-vJzJGq4tkaUEGfRNXN9IJlvptReSBa5PP7N5gxpSOBo-YlF5Ac98f-YlNlxi\" } 这里不得不佩服 12306 的设计思路了,故布疑阵,当你误以为自己已经更新了 RAIL_DEVICEID 的值,实际上 cookieCode 的值才是唯一标识而它恰恰没有设置到 cookie 中去,仅仅作为本地缓存保持了,用于再次请求 RAIL_DEVICEID. step 4 : 复制源码实现到控制台,输入 chromeHelper.prototype.encryptedFingerPrintInfo() 获取请求 /otn/HttpZF/logdevice 的查询参数,提取出其中的 value 值和真正的请求参数作对比. 假设真正请求参数 hashcode 的值已设置成变量,chromeHelper.prototype.encryptedFingerPrintInfo().value === hashcode 返回结果 true 说明复现算法实现还在正常运行,否则很可能是相关算法又更新了! 直奔重点 如果你正在学习自动抢票或者打算研究如何自动抢票,那么我可以负责任得告诉你,RAIL_DEVICEID 的值绝对是绕不过去的坎,堪称 12306 反爬虫技术的最精华手段! 现在目标已经锁定,赶紧动手和我一起去探究 12306 到底是如何处理 RAIL_DEVICEID 的值吧! 无痕模式下访问网站 众所周知,谷歌 Chrome 浏览器是程序员专属的浏览器,是因为提供了强大的开发调试能力,简单网站请求甚至根本不需要借助第三方专业的抓包工具就能独立完成分析整过程. 如果你还没有听说过 Chrome 浏览器或者正在使用其他浏览器,那么建议你先自行下载最新版 Chrome 浏览器,和文章使用一样的工具有助于顺利复现相关步骤,否则遇到莫名奇怪的问题只能自己研究了. 首先打开 Chrome 浏览器的无痕模式,处于无痕模式最大的特点就是不会保存 cookie,在一定程度上对目标网站而言是新用户(主要指的是新的客户端终端). 输入 12306 官网后,打开开发者控制台(F12或右键检查),选择网络(network)选项卡,确保一直处于监听网咯请求并实时记录状态. 具体而言,最左边的监听状态圆心是红色,保持日志(Preserve log)的复选框已勾选,禁用缓存(Disable cache) 的复选框已勾选,这三者是分析所有网络请求的基础. 准备工作就绪后开始完整走一遍购票流程,即从首页进入登录页,登录并买票等过程,请求步骤越完整可提供分析的资料越多,也就基本上不会遗漏重要步骤,离真相越逼近. 凡是涉及到登录操作,建议先故意输出错误的账号密码等信息,这样有利于登录成功后重定向次数过多而导致无法找到之前的登录请求,如果网络(network)选项卡中的保持日志(Preserve log)的功能没有开启的话,这一现象将会更加严重! 再次输入正确的登录信息成功登录后进行买票行为等操作,但是无需付款,只要正常操作到下单完成即可视为整个购票流程. 下单成功后整个购票流程已经基本完成,接下来开始全局搜索关键字 RAIL_DEVICEID 查看在哪里生成又在何处使用? 全局模糊查找关键字 现在整个购票流程基本上已经完成,接下来开始全局搜索全部请求中是否包含关键字 RAIL_DEVICEID 吧! 首先打开网络(network)选项卡,从左往右数第四个放大镜图标就是搜索功能,输入搜素关键字 RAIL_DEVICEID 会过滤符合条件的网络请求. 不搜不要紧,一搜一大把,只能看出来大部分网络请求都会自动携带该 cookie,反而淹没了到底是哪个网络请求生成的 cookie? 所以必须想办法精确搜索,过滤出生成该 cookie 的网络请求,所以接下来的问题就变成了如果 RAIL_DEVICEID 属于后端直接设置的行为,那么这样的网络请求应该长啥样的? 最好的学习就是模仿,假设并不知道真实的设置过程如何,但是我们可以查看其它 cookie 的设置过程啊! 同样地,在网络(network)选项卡选择第三个过滤器漏斗图标,展开网络请求类型,大致分为 All|XHR|JS|CSS|Img|Media|Font|Doc|WS|Manifest|Other等类型. 简单说一下网络请求类型的相关含义,整理出表格直观感受一下: 类型 名称 描述 代码 XHR XHR adn Fetch ajax异步请求 X-Requested-With: XMLHttpRequest JS Scripts js脚本 Sec-Fetch-Dest: script CSS Stylesheets css样式 Sec-Fetch-Dest: style Img Images 图片 Sec-Fetch-Dest: image Media Media 音视频媒体 Sec-Fetch-Dest: audio Font Fonts 字体 Sec-Fetch-Dest: font Doc Documents html文档 Sec-Fetch-Dest: document WS WebSockets 长连接通信 暂无 Manifest Manifest 版本文件 暂无 由于 12306 暂未包括后两种请求类型,所以无法判断该请求有什么特点,除了 ajax 异步请求外,其余类型的网络请求都是通过请求头 Sec-Fetch-Dest 属性标识的,当然设置浏览器的 cookie 也不例外,只不过大多数是通过服务端进行设置的,也就是网络请求的响应头标志了如何设置 cookie 的行为. 在前端 web 开发的过程中并不是一上来就前后端分离的,很长一段时间内前端页面也是由后端人员完成的,因此好多网站至今为止还保留着新旧交替的痕迹. 在上述网络请求类型中,最能体现这种变化特点就是 XHR 和 Doc 请求,XHR的常见封装实现 之一就是风靡全球的 ajax 异步请求,用于实现无刷新局部更新网页内容,而 Doc 是文档类型,无论是直接输出原生 html 还是使用模板技术动态渲染页面,最终输出展现结果一律是 html 文档,这一类的网络请求最容易设置 cookie 之类的请求,体现了上一代技术的一贯风格,恨不得一个人一次性把全部的活都干完! 但是随着技术的发展进步旧技术暴露的问题越来越多,引起了包括开发者在内的业内重视,各大企业已经逐步开始转变,也就是各司其职,物尽其用. 总结来说,XHR 和 Doc 有如下特点: XHR 请求的数据绝大多数工作是在前端方面完成的,后端把相关数据返回给前端接口调用者,前端取到数据后进行业务组装展现. XHR 请求绝大多数是异步ajax 请求,优点是当前页面不需要刷新就能看到最新内容,缺点是一旦涉及到相互依赖的业务就会出现请求等待噩梦. XHR 请求是又前端发起也就是浏览器主动发出给后端,等后端服务器返回数据后继续由前端完成相关业务,这部分数据传输量极少但非常重要. Doc 请求大多数是后端在控制,方便设置各种页面元素的表现形式,但是也不排除前端使用相关的模板引擎结合 XHR 数据在控制生成文档. Doc 请求设置包括 cookie 在内的一系列网络行为,转发和重定向更是权限控制的常用做法. 下面我们已包括 RAIL_DEVICEID 关键字的网络请求,简单感受一下两者的差异性. XHR 请求重点在于如何请求数据和接收数据,主要体现在 Request Data 和 Response Data 两方面,至于请求头一般都是默认设置. Doc 请求的重点就不一样了,绝大多数请求就是输入网址后自动跳转页面,因而关注的重点应该放在请求头和响应头信息上,因为 cookie 的值就是通过请求头进行发送到后端服务器,后端如需新增或修改 cookie 值就是通过响应头进行设置的. 柿子还要先捏软柿子,现在无法判定 RAIL_DEVICEID 到底是服务端直接设置还是客户端自行设置的,而客户端的行为不太直观,所以相对而言还是先捏服务端软柿子吧! 回顾刚才讲解的 Doc 网络请求,不难发现设置 cookie 的行为代码类似如下: Set-Cookie: JSESSIONID=D4CE095F5A21B38DF3389070F1E01FE6; Path=/otn 现在找到了学习对象,开始模仿查找类似请求的关键字应该是: Set-Cookie: RAIL_DEVICEID= 查无结果! 一般情况下出现无结果很可能是以下原因之一: 恭喜您,真的查无结果,可以换条思路继续探索了. 很遗憾,当前网络请求数据不足刚好缺失符合条件的请求. 日了狗,操作不当误输多余符号或者本是关键字搜索实际上却开启了正则匹配等 所以逐一排查以上原因,首先考虑换一个关键字 Set-Cookie: JSESSIONID 能否查找出相应的结果. 事实胜于雄辩,查询过程并没有任何问题而是查询结果真的不存在,如此一来一次性排查两个原因,那么很有可能生成逻辑在于前端而非后端. 接下来只能在众多请求中碰碰运气寻找前端到底是在何处生成的 RAIL_DEVICEID ,然而请求众多还是要稍微讲究一下策略方向的. 既然是前端在控制 cookie 的生成逻辑,那么很有可能是某个 js 文件在起作用,当然也不排除其他类型的文件有操作浏览器行为的能力. 因此,通过分析进一步缩小范围:在请求类型为 JS 的网络请求中查找包括关键字 RAIL_DEVICEID 的全部请求. 理想很丰满,现实太骨感,本以为选中 js 再搜索关键字能够一起生效,结果并没有,请求类型依旧没有过滤出去,还是一大片的请求! 同时随便选中任意请求可以看到此时网络选项卡搜索匹配大结果其实是请求头这些基本信息,应该并没有包括 js 或者 doc 源码,方向错了,走再多路也是浪费时间. 虽然我不知道你从哪里来,中间经历了什么,但是我知道你的最终归宿一定会落到网络文件系统. Chrome 浏览器除了可以看出网络请求也能看到最终呈现给用户的文件系统,既然中间过程找不到你,那么我直接到目的地去搜索吧! 打开源码(Source)选项卡,整个面板大致分为三部分,左侧文件数,中间文件区,右侧调试区. 其中左侧的文件结构可以清楚看到当前所处的层级结构,有利于快速掌握项目轮廓.右侧的调试区针对心中有想法但还不确定是否正确提供了非常好的验证工具,调试别人的代码就如本地开发那样边预言边验证. 中间的文件区域面积最大,功能自然也不能太弱,选中左侧具体文件后可以显示源码,方便查看,进而去调试验证心有所想. 所以问题来了,这一切的一切都要源于心有所想才能有行动,那么应该去哪里搜索包括关键字 RAIL_DEVICEID 的文件呢? 一般而言,良好的用户体验是不需要告诉你用户手册的,给你一大堆详尽的说明文档也未必会耐得住去看一篇,先用用再说! 人生苦短,不要浪费太多时间放在无聊的事情上面,简短的三行提醒富含哲理性,第一行告诉你怎么打开文件,第二行告诉你如何运行命令,第三行告诉你如何操作实现什么效果. 如果三行代码还不足以解决你的问题,阅读更多自己慢慢琢磨说明文档吧! 当然,这里和在文件系统中查找包含关键字 RAIL_DEVICEID 的需求最为接近的应该就是第二行,运行命令了,那就试试看吧! 输入搜索 search 后果然弹出了相关搜索命令,于是点击开发工具(DevTools)后出现了搜索框,顺理成章输入关键字 RAIL_DEVICEID 进行搜索了啊! 终于等到你,还好我没放弃,你就是我的唯一,看样子和 RAIL_DEVICEID 有关的处理逻辑全部都在这么一个 js 文件里,看你还往哪里跑! 直捣黄龙还往哪里跑 找到该文件后点击查看,红蓝黑密密麻麻一大片js 代码,绝对不是给人看的而是给机器看的,想要给人阅读还需要美化一下,将源码丑化混淆成难以阅读的代码也是防止他人偷窥复制拷贝自己的劳动成果,同时也能减少文件大小,加速网络传输数据,让你的网站速度更快一些. 点击中间区域的左下角格式化图标进行美化代码,然后在文件中搜素关键字 RAIL_DEVICEID 定位到具体代码. 非常人性化的是,搜索功能是通用的快捷键 Ctrl + F,现在定位到具体代码,截图留念下,接下来才是真正考验技术的时刻! $a.getJSON(\"https://kyfw.12306.cn/otn/HttpZF/logdevice\" + (\"?algID\\x3drblubbXDx3\\x26hashCode\\x3d\" + e + a), null, function(a) { var b = JSON.parse(a); void 0 != lb && lb.postMessage(a, r.parent); for (var d in b) \"dfp\" == d ? F(\"RAIL_DEVICEID\") != b[d] && (W(\"RAIL_DEVICEID\", b[d], 1E3), c.deviceEc.set(\"RAIL_DEVICEID\", b[d])) : \"exp\" == d ? W(\"RAIL_EXPIRATION\", b[d], 1E3) : \"cookieCode\" == d && (c.ec.set(\"RAIL_OkLJUJ\", b[d]), W(\"RAIL_OkLJUJ\", \"\", 0)) }) 本地备份js方便复现 既然已经找到关键文件,自然需要留存快照进行存档操作,否则哪一天文件更新了都不知道哪里发生变化了,难不成还要从头再分析一遍,我选择差量更新而不是全量覆盖! 选中源文件右键弹出菜单,选择任意一款喜欢的方式复制源文件到本地留作学习备份,准备工作就绪后准备大干一场. 高楼大厦寻关键线索 Js文件中关于网络请求最典型的就是异步回调,将原本简单的操作复杂化,非要你等我,我等他,他还等着他的她. 最终直接结果就是整个请求流程反过来了,假设正常流程:是 A->B->C-D-E-F,那么异步请求很可能陷入这样的陷阱: F 所以一层又一层的回调函数真的是难以维护,这种技术也在慢慢淘汰更新成更容易维护的方式,还是不再展开了,回到正题上来,还是先找到程序到底什么时候开始调用的吧! ja.prototype = { initEc: function(a) { var b = \"\" , c = this , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : \"\"; c.checkWapOrWeb(); this.ec.get(\"RAIL_OkLJUJ\", function(a) { b = a; c.getDfpMoreInfo(function() { if (!(9E5 核心代码最外层函数是 initEc 函数,而该函数的写法明显是传统 js 的属性方法,因此判断挂载于该对象的属性方法应该都是完成某些相同的功能. 暂时先不着急继续寻找谁在调用 initEc 函数,先搞懂整个函数结构是什么轮廓. function ja() { this.ec = new evercookie; this.deviceEc = new evercookie; this.cfp = new aa; this.packageString = \"\"; this.moreInfoArray = [] } ja.prototype = { getScrWidth: function() { return new l(\"scrWidth\",r.screen.width.toString()) }, ... , checkWapOrWeb: function() { return \"WindowsPhone\" == Ha() || \"iOS\" == Ha() || \"Android\" == Ha() ? !0 : !1 } } 如果熟悉 web 开发,那么你一定不难发现这是标准的面向对象的写法,ja 函数作为构造函数内置了一大堆成员变量,并且在原型链上继承了一大堆方法. 更何况,对象属性中还有三个带有 new 关键字的构造函数,估计也是类似于 ja 这种设计思路,高楼大厦平地起,还原相关算法之路预期并不简单! 但是想一想车票真难抢还动不动访问错误,是可忍孰不可忍,还是要研究算法一劳永逸搞定 RAIL_DEVICEID 的生成逻辑,自己用算法计算实现完美伪装浏览器! 现在以 initEc 函数名继续搜素,寻找到底是谁在调用,轻而易举又找到了新的函数名: getFingerPrint ja.prototype = { getFingerPrint: function() { var a = this; r.RTCPeerConnection || r.webkitRTCPeerConnection || r.mozRTCPeerConnection ? nb(function(b) { a.initEc(b) }) : a.initEc() } } 同样地,不再过多停留,继续以 getFingerPrint 为关键字搜索,找到了 Pa 函数,终于不再是 ja 的方法了. function Pa() { if (-1 == F(\"RAIL_EXPIRATION\")) for (var a = 0; 10 > a; a++) G(function() { (new ja).getFingerPrint() }, 20 + 2E3 * Math.pow(a, 2)); else (new ja).getFingerPrint(); G(function() { r.setInterval(function() { (new ja).getFingerPrint() }, 3E5) }, 3E5) } 与此同时,Pa 函数也是 js 文件的第一行代码,来都来了,那就顺便看一眼 js 的整体结构代码吧! (function() { })(); 自执行的匿名函数实现的闭包,这样的好处在于函数内的变量不会污染其他文件,更何况混淆之后的变量名称充斥着大量的变量 a,b,c,d,e,f之类的,不用闭包也不行啊! 现在继续以 Pa 为线索搜索,最终发现了函数入口,除此之外再无其他. var mb = !1; u.addEventListener ? u.addEventListener(\"DOMContentLoaded\", function b() { u.removeEventListener(\"DOMContentLoaded\", b, !1); Pa() }, !1) : u.attachEvent && u.attachEvent(\"onreadystatechange\", function c() { mb || \"interactive\" != u.readyState && \"complete\" != u.readyState || (u.detachEvent(\"onreadystatechange\", c), Pa(), mb = !0) }) js 是典型的事件驱动型编程语言,当发生什么什么事件后我要干这个,页面加载时我要开始工作了,按钮被点击了我要登录了,页面关闭时我要下班了等等诸如此类的逻辑. 上述代码实现的就是页面元素加载成功后开始执行 Pa() 函数,而 Pa 函数又会执行 (new ja).getFingerPrint() ,紧接着又会执行 initEc 函数. 现在基本流程已经大致清楚了,总结一下基本代码逻辑如下: (function() { var mb = !1; u.addEventListener ? u.addEventListener(\"DOMContentLoaded\", function b() { u.removeEventListener(\"DOMContentLoaded\", b, !1); Pa() }, !1) : u.attachEvent && u.attachEvent(\"onreadystatechange\", function c() { mb || \"interactive\" != u.readyState && \"complete\" != u.readyState || (u.detachEvent(\"onreadystatechange\", c), Pa(), mb = !0) }) function Pa() { if (-1 == F(\"RAIL_EXPIRATION\")) for (var a = 0; 10 > a; a++) G(function() { (new ja).getFingerPrint() }, 20 + 2E3 * Math.pow(a, 2)); else (new ja).getFingerPrint(); G(function() { r.setInterval(function() { (new ja).getFingerPrint() }, 3E5) }, 3E5) } function ja() { this.ec = new evercookie; this.deviceEc = new evercookie; this.cfp = new aa; this.packageString = \"\"; this.moreInfoArray = [] } ja.prototype = { getFingerPrint: function() { var a = this; r.RTCPeerConnection || r.webkitRTCPeerConnection || r.mozRTCPeerConnection ? nb(function(b) { a.initEc(b) }) : a.initEc() }, initEc: function(a) { var b = \"\" , c = this , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : \"\"; c.checkWapOrWeb(); this.ec.get(\"RAIL_OkLJUJ\", function(a) { b = a; c.getDfpMoreInfo(function() { if (!(9E5 从以上代码分析中,相信你会发现相关逻辑应该兼容 IE 浏览器,同时设置了定时程序反复更新 cookie 值,并且还有远程 RTC 保持通信,不得不说做得还真不错,不愧是国民出行的代步工具啊! 精力有限,这里选择最简单的一种情况进行算法还原过程的研究,浏览器选择谷歌 Chrome 浏览器,这样就可以屏蔽关于 IE 的兼容性补丁处理,同时也不考虑 RTCPeerConnection 的情况,于是乎,代码逻辑简化成这样: (function() { document.addEventListener(\"DOMContentLoaded\", Pa,false) function Pa() { (new ja).getFingerPrint(); } function ja() { this.ec = new evercookie; this.deviceEc = new evercookie; this.cfp = new aa; this.packageString = \"\"; this.moreInfoArray = [] } ja.prototype = { getFingerPrint: function() { this.initEc() }, initEc: function(a) { var b = \"\" , c = this , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : \"\"; c.checkWapOrWeb(); this.ec.get(\"RAIL_OkLJUJ\", function(a) { b = a; c.getDfpMoreInfo(function() { if (!(9E5 所以现在问题的核心在于搞清楚 initEc 函数的数据流向,还原算法实现过程不是梦! 断点调试追踪调用栈 静态分析程序结构后开始断电调试观察一下数据流向,做到心中有数,同时为了该过程具有可重复性需要保持每一次操作环境一致. 具体而言,首先 Chrome 浏览器处于无痕模式,接着是每次试验时清空站点缓存,最后就可以愉快刷新当前网页等待进入下一轮断电调试了. 提前在关键点打入断点(鼠标左键点击行号),然后等待程序进入调试模式,稍等一会后进入断点可以一步一步看到程序运行的值,在调试区还可以监控变量的值. 当然也可以有函数调用栈的关系,这一切只是辅助手段,最关键还是要靠自己分析弄清楚函数的调用顺序流程,原则上先大后小,先整体再细节. 函数最后会发送 ajax 请求获取 cookie 并写入本地以及 cookie 中,亲测数据如下: {\"exp\":\"1582097104310\",\"cookieCode\":\"FGH8SO9zGaWtwuld2jrurRzwmZKeXABx\",\"dfp\":\"EKLLyLS1K7tqtcuZ6LEPYoUKsxmVNyrAlWNLDi3P-gA-tJMLkTxMuhsRNHEhbk7ntCFCsIpymD57I4AyfPUoWB4D_a_Fe5usS8sfJxP_OJjoun5QjAfgDBBmDLh_m2OeRVN2NnRK0-paM6dCSVKdjFGILKUOJYWT\"} 一次请求完成后顺利生成了 cookie 也写入了本地缓存中,如果不清空那么进入下一次断点的流程就和这一次不一样了,所以为了可重复操作,再次断点调试时需要还原操作环境. 首次加载时变量 a 并没有值,一不小心进入下一个过程时,这一次 a 已经有值了,多次试验后搞清楚了数据流向也明白了如何还原操作环境,保持实验结果的一致性. 经过多次重复试验,先将基本数据流向还原如下: (function() { ja.prototype = { // C:initEc initEc: function(a) { this.ec.get(\"RAIL_OkLJUJ\", function(a) { c.getDfpMoreInfo(function() { }) }, 1) }, // c.getDfpMoreInfo:A getDfpMoreInfo: function(a) { } } // this.ec.get(\"RAIL_OkLJUJ\":B window.evercookie = window.Evercookie = function(a) { this.get = function(a, b, c) { } } })(); 异步请求 C this.ec.get(\"RAIL_OkLJUJ\" 函数,等到 window.evercookie.get 运行完成后会调用 c.getDfpMoreInfo 函数,等到 getDfpMoreInfo 函数运行结束后会调用函数核心关键代码. 除了总的来看是各种异步请求相互回调之外,不少细节中也充斥着大量的回调函数,以 getDfpMoreInfo 函数为例,居然要收集这么多信息才开始做自己的事情! ja.prototype = { getDfpMoreInfo: function(a) { var b = this; this.moreInfoArray = []; b.cfp.get(function(c, d) { b.moreInfoArray.push(b.getCanvansCode(c + \"\")); for (var e in d) { c = d[e].key; var f = d[e].value + \"\"; switch (c) { case \"session_storage\": b.moreInfoArray.push(b.getSessionStorage(f)); break; case \"local_storage\": b.moreInfoArray.push(b.getLocalStorage(f)); break; case \"indexed_db\": b.moreInfoArray.push(b.getIndexedDb(f)); break; case \"open_database\": b.moreInfoArray.push(b.getOpenDatabase(f)); break; case \"do_not_track\": b.moreInfoArray.push(b.getDoNotTrack(f)); break; case \"ie_plugins\": b.moreInfoArray.push(b.getPlugins(f)); break; case \"regular_plugins\": b.moreInfoArray.push(b.getPlugins()); break; case \"adblock\": b.moreInfoArray.push(b.getAdblock(f)); break; case \"has_lied_languages\": b.moreInfoArray.push(b.getHasLiedLanguages(f)); break; case \"has_lied_resolution\": b.moreInfoArray.push(b.getHasLiedResolution(f)); break; case \"has_lied_os\": b.moreInfoArray.push(b.getHasLiedOs(f)); break; case \"has_lied_browser\": b.moreInfoArray.push(b.getHasLiedBrowser(f)); break; case \"touch_support\": b.moreInfoArray.push(b.getTouchSupport(f)); break; case \"js_fonts\": b.moreInfoArray.push(b.getJsFonts(f)) } } \"function\" == typeof a && a() }) } } function aa(a) { if (!(this instanceof aa)) return new aa(a); this.options = this.extend(a, { detectScreenOrientation: !0, swfPath: \"flash/compiled/FontList.swf\", sortPluginsFor: [/palemoon/i], swfContainerId: \"fingerprintjs2\", userDefinedFonts: [] }); this.nativeForEach = Array.prototype.forEach; this.nativeMap = Array.prototype.map } aa.prototype = { get: function(a) { var b = [] , b = this.userAgentKey(b) , b = this.languageKey(b) , b = this.colorDepthKey(b) , b = this.pixelRatioKey(b) , b = this.screenResolutionKey(b) , b = this.availableScreenResolutionKey(b) , b = this.timezoneOffsetKey(b) , b = this.sessionStorageKey(b) , b = this.localStorageKey(b) , b = this.indexedDbKey(b) , b = this.addBehaviorKey(b) , b = this.openDatabaseKey(b) , b = this.cpuClassKey(b) , b = this.platformKey(b) , b = this.doNotTrackKey(b) , b = this.pluginsKey(b) , b = this.canvasKey(b) , b = this.webglKey(b) , b = this.adBlockKey(b) , b = this.hasLiedLanguagesKey(b) , b = this.hasLiedResolutionKey(b) , b = this.hasLiedOsKey(b) , b = this.hasLiedBrowserKey(b) , b = this.touchSupportKey(b) , c = this; this.fontsKey(b, function(b) { var d = []; c.each(b, function(a) { var b = a.value; \"undefined\" !== typeof a.value.join && (b = a.value.join(\";\")); d.push(b) }); var f = c.x64hash128(d.join(\"~~~\"), 31); return a(f, b) }) } } getDfpMoreInfo 函数的执行过程中首先会运行 b.cfp.get 函数,通过搜索追根溯源发现是 aa.prototype.get 函数,这个函数除了获取浏览器简单信息外还涉及到字体的加密处理: var f = c.x64hash128(d.join(\"~~~\"), 31); 然而想要继续分析 getDfpMoreInfo 函数需要先弄清楚 b.cfp.get(function(c, d) { 和 getDfpMoreInfo: function(a) { 中的回调函数参数到底是什么,因此必须从头到尾逐一分析,这也是异步请求的陷阱. 看似请求逻辑一气呵成,真的要维护时却困难重重,想要分析 C 必须先分析 B,没想到 B 又要依赖于 A. 所以下一步的操作就是先从突破口 initEc 函数顺藤摸瓜,找到最初的函数 A 再根据断点调试看看是如何回调一步步回到 initEc 函数的,也就是将异步请求改造成同步请求的过程. 一步一步慢慢转同步 (function() { ja.prototype = { // C:initEc initEc: function(a) { this.ec.get(\"RAIL_OkLJUJ\", function(a) { c.getDfpMoreInfo(function() { }) }, 1) }, // c.getDfpMoreInfo:A getDfpMoreInfo: function(a) { } } // this.ec.get(\"RAIL_OkLJUJ\":B window.evercookie = window.Evercookie = function(a) { this.get = function(a, b, c) { } } })(); 顺着这条路继续分析 getDfpMoreInfo 的调用过程,可以添加更多的调用细节,因此现在完善成如下代码: 其中约定标记为字母表 A,B,C的同步调用顺序,对应异步请求 C 为了表现出层次性,第二层的 C,B,A 可以表示为 AC,AB,AA,以此类推. 这样最终技能通过层级调用关系形成调用树状图,最终效果大致如下: . ├── C │ ├── CC │ ├── CB │ └── CA ├── B │ ├── BC │ ├── BB │ └── BA └── A ├── AC ├── AB └── AA 调用栈树状图浏览异步函数调用顺序一目了然,仿照数据结构的栈结构进行设计,后进先出是最外层的 C,然后发现 C 还要依赖B,B 要依赖 A,执行完返回上一层继续执行,这个设计感觉很棒啊,为自己点赞! (function() { ja.prototype = { // C initEc: function(a) { this.ec.get(\"RAIL_OkLJUJ\", function(a) { c.getDfpMoreInfo(function() { }) }, 1) }, // A getDfpMoreInfo: function(a) { } } // AC aa.prototype = { get: function(a) { } } // B window.evercookie = window.Evercookie = function(a) { this.get = function(a, b, c) { } } })(); 然而实际分析过程中发现同级请求不总是三级 ABC的形式,这里可以根据实际情况按照这个思路自行分析研究再结合断点调试验证猜想. step 1 : 获取基本信息并在获取加密字体后回调 aa.prototype = { get: function(a) { var b = [] , b = this.userAgentKey(b) , b = this.languageKey(b) , b = this.colorDepthKey(b) , b = this.pixelRatioKey(b) , b = this.screenResolutionKey(b) , b = this.availableScreenResolutionKey(b) , b = this.timezoneOffsetKey(b) , b = this.sessionStorageKey(b) , b = this.localStorageKey(b) , b = this.indexedDbKey(b) , b = this.addBehaviorKey(b) , b = this.openDatabaseKey(b) , b = this.cpuClassKey(b) , b = this.platformKey(b) , b = this.doNotTrackKey(b) , b = this.pluginsKey(b) , b = this.canvasKey(b) , b = this.webglKey(b) , b = this.adBlockKey(b) , b = this.hasLiedLanguagesKey(b) , b = this.hasLiedResolutionKey(b) , b = this.hasLiedOsKey(b) , b = this.hasLiedBrowserKey(b) , b = this.touchSupportKey(b) , c = this; this.fontsKey(b, function(b) { var d = []; c.each(b, function(a) { var b = a.value; \"undefined\" !== typeof a.value.join && (b = a.value.join(\";\")); d.push(b) }); var f = c.x64hash128(d.join(\"~~~\"), 31); return a(f, b) }) } } var f = c.x64hash128(d.join(\"~~~\"), 31); 涉及到一系列的加密操作,暂时不用管,最重要的下面这句 return a(f, b) 会执行回调函数继续下一个逻辑. step 2 : 获取浏览器更多信息并在最后回调 ja.prototype = { getDfpMoreInfo: function(a) { var b = this; this.moreInfoArray = []; b.cfp.get(function(c, d) { b.moreInfoArray.push(b.getCanvansCode(c + \"\")); for (var e in d) { c = d[e].key; var f = d[e].value + \"\"; switch (c) { case \"session_storage\": b.moreInfoArray.push(b.getSessionStorage(f)); break; case \"local_storage\": b.moreInfoArray.push(b.getLocalStorage(f)); break; case \"indexed_db\": b.moreInfoArray.push(b.getIndexedDb(f)); break; case \"open_database\": b.moreInfoArray.push(b.getOpenDatabase(f)); break; case \"do_not_track\": b.moreInfoArray.push(b.getDoNotTrack(f)); break; case \"ie_plugins\": b.moreInfoArray.push(b.getPlugins(f)); break; case \"regular_plugins\": b.moreInfoArray.push(b.getPlugins()); break; case \"adblock\": b.moreInfoArray.push(b.getAdblock(f)); break; case \"has_lied_languages\": b.moreInfoArray.push(b.getHasLiedLanguages(f)); break; case \"has_lied_resolution\": b.moreInfoArray.push(b.getHasLiedResolution(f)); break; case \"has_lied_os\": b.moreInfoArray.push(b.getHasLiedOs(f)); break; case \"has_lied_browser\": b.moreInfoArray.push(b.getHasLiedBrowser(f)); break; case \"touch_support\": b.moreInfoArray.push(b.getTouchSupport(f)); break; case \"js_fonts\": b.moreInfoArray.push(b.getJsFonts(f)) } } \"function\" == typeof a && a() }) } } b.cfp.get(function(c, d) {}) 函数就是上一步的 aa.prototype.get() 函数. step 3 : 打包参数前先获取浏览器机器码 ja.prototype = { getMachineCode: function() { return [this.getUUID(), this.getCookieCode(), this.getUserAgent(), this.getScrHeight(), this.getScrWidth(), this.getScrAvailHeight(), this.getScrAvailWidth(), this.md5ScrColorDepth(), this.getScrDeviceXDPI(), this.getAppCodeName(), this.getAppName(), this.getJavaEnabled(), this.getMimeTypes(), this.getPlatform(), this.getAppMinorVersion(), this.getBrowserLanguage(), this.getCookieEnabled(), this.getCpuClass(), this.getOnLine(), this.getSystemLanguage(), this.getUserLanguage(), this.getTimeZone(), this.getFlashVersion(), this.getHistoryList(), this.getCustId(), this.getSendPlatform()] } } 该函数来自于 initEc 函数中 c.getDfpMoreInfo( 回调函数里的 g = c.getpackStr(b),this.getMachineCode() 心机很深,暗藏玄机. step 4 : 打包参数前再组合更多信息并重新排序 ja.prototype = { getpackStr: function(a) { var b = [] , b = [] , b = this.getMachineCode() , b = b.concat(this.moreInfoArray); null != a && void 0 != a && \"\" != a && 32 == a.length && b.push(new l(\"cookieCode\",a)); b.sort(function(a, b) { var c, d; if (\"object\" === typeof a && \"object\" === typeof b && a && b) return c = a.key, d = b.key, c === d ? 0 : typeof c === typeof d ? c 该函数同样来自于 initEc 函数中 c.getDfpMoreInfo( 回调函数里的 g = c.getpackStr(b),值得学习研究! step 5 : 重新分类浏览器信息并加密生成请求参数 ja.prototype = { initEc: function(a) { var b = \"\" , c = this , d = void 0 != a && void 0 != a.localAddr ? a.localAddr : \"\"; c.checkWapOrWeb(); this.ec.get(\"RAIL_OkLJUJ\", function(a) { b = a; c.getDfpMoreInfo(function() { if (!(9E5 e = c.hashAlg(m, a, e); 加密过程比基本信息的加密更加复杂,但是总体来说难度也不大,主要涉及到字符串反转,分段组装,哈希算法以及 base64 编码等等. 遇到瓶颈则略过细节 通读全文并结合断点调试反复验证猜想后,我们发现为了最终请求https://kyfw.12306.cn/otn/HttpZF/logdevice 时的参数可真的为呕心沥血,费心尽力啊! 总体来说,获取浏览器各种信息并且还涉及兼容 IE 浏览器而处理的各种补丁,对于原始信息较长时采用独特的加密算法进行加密处理,仅此还不够,还存在判断是否伪造浏览器参数的相关逻辑判断,真的是大写的服! 关于获取浏览器的相关信息反而很简单,只要结合如何识别是否说谎的代码一起设置就能绕过这部分逻辑,比如说常见的设置浏览器用户代码如下: aa.prototype = { userAgentKey: function(a) { this.options.excludeUserAgent || a.push({ key: \"user_agent\", value: this.getUserAgent() }); return a }, getUserAgent: function() { var a = g.userAgent; return a = a.replace(/\\&|\\+|\\?|\\%|\\#|\\/|\\=/g, \"\") } } ja.prototype = { getUserAgent: function() { var a = g.userAgent , a = a.replace(/\\&|\\+/g, \"\"); return new l(\"userAgent\",a.toString()) } } 不同对象对同一个用户代理的处理逻辑不同,类似上述例子还有很多,简单到处都是陷阱,不看源码根本就不知道,看完以后你还会吐槽 12306 技术垃圾吗? 接下来的是三个加密算法,一个是最初基本信息加密,一个字体信息加密,还有一个是最终分类信息加密. function ba(a) { for (var b = [], c = (1 > 5] |= (a.charCodeAt(d / ca) & c) > 5] |= 128 >> 9 > 2] >> d % 4 * 8 + 4 & 15) + a.charAt(b[d >> 2] >> d % 4 * 8 & 15); return c } aa.prototype = { x64hash128: function(a, b) { a = a || \"\"; b = b || 0; for (var c = a.length % 16, d = a.length - c, e = [0, b], f = [0, b], h, p, g = [2277735313, 289559509], m = [1291169091, 658871167], l = 0; l >> 0).toString(16)).slice(-8) + (\"00000000\" + (e[1] >>> 0).toString(16)).slice(-8) + (\"00000000\" + (f[0] >>> 0).toString(16)).slice(-8) + (\"00000000\" + (f[1] >>> 0).toString(16)).slice(-8) } } ja.prototype = { hashAlg: function(a, b, c) { a.sort(function(a, b) { var c, d; if (\"object\" === typeof a && \"object\" === typeof b && a && b) return c = a.key, d = b.key, c === d ? 0 : typeof c === typeof d ? c c || (e = a.substring(0, 1 * d), f = a.substring(1 * d, 2 * d), a = a.substring(2 * d, c) + e + f); a = Qa(a); a = Qa(a); c = R.SHA256(a).toString(R.enc.Base64); c = R.SHA256(c).toString(R.enc.Base64); return new l(b,c) } } 这三个算法看起来比较吓人,实际上只要耐心调试是可以慢慢还原的,不过是字母的各种排列组合顺序而已,谁让他没有加密能让我们看到源码呢,仅仅的变量名称替换是难不倒聪明才智的开发者的,更何况这部分和业务逻辑关心不大,暂且略过吧. 算法复现 算法整体采用闭包设计面向对象的编程风格,基于原型链特性实现原始对象的加密逻辑,添加特有方法用于临时修改浏览器相关信息,最后将自定义对象 chromeHelper 直接挂载于 window 属性,方便外部调用. /** * chrome 浏览器简化版,主要还原初次加载 RAIL_OkLJUJ 和 RAIL_DEVICEID 的基本流程,如许更新 RAIL_DEVICEID 需要结合 RAIL_OkLJUJ 一起加密,仅仅多增加一个 cookieCode 参数而已,除此之外并无特殊之处,不再赘述. * @author: snowdreams1006 * @wechat: snowdreams1006(雪之梦技术驿站) */ (function(window) { /** * 默认空构造函数 */ function chromeHelper() { } /** * 设置用户代理,检测方式: navigator.userAgent */ chromeHelper.setUserAgent = function(userAgent) { if (!userAgent) { userAgent = \"Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36\"; } Object.defineProperty(navigator, \"userAgent\", { value: userAgent, writable: false }); } /** * 基于原型链实现面向对象编程的继承特性 */ chromeHelper.prototype = { /** * 获取初始化浏览器设备信息,来源于initEc中的e = c.hashAlg(k, a, e); */ encryptedFingerPrintInfo: function() { // 获取分类后的浏览器指纹信息 classifiedBrowserFingerPrintInfoArr = this.getClassifiedBrowserFingerPrintInfo(); encryptedFingerPrintInfoMap = this.hashAlg(classifiedBrowserFingerPrintInfoArr, \"\", \"\"); return encryptedFingerPrintInfoMap; } } /** * 直接挂载在全局变量 window 对象方便直接调用. */ window.chromeHelper = chromeHelper; })(window); step 1 : 获取基本信息 chromeHelper.prototype = { /** * 获取浏览器基本信息,来源于getDfpMoreInfo中的b.cfp.get和aa的get */ getBasicInfoArr: function() { // 基本信息,若数据无效则返回 void 0,即 undefined var basicInfoArr = []; // 用户代理 basicInfoArr.push(this.getUserAgentKeyAndValue(1)); // 语言 basicInfoArr.push(this.getLanguageKeyAndValue(1)); // 颜色深度 basicInfoArr.push(this.getColorDepthKeyAndValue(1)); // 像素比例 basicInfoArr.push(this.getPixelRatioKeyAndValue(1)); // 屏幕分辨率 basicInfoArr.push(this.getScreenResolutionKeyAndValue(1)); // 可用屏幕分辨率 basicInfoArr.push(this.getAvailableScreenResolutionKeyAndValue(1)); // 时区偏移量 basicInfoArr.push(this.getTimezoneOffsetKeyAndValue(1)); // Session存储 basicInfoArr.push(this.getSessionStorageKeyAndValue(1)); // Local存储 basicInfoArr.push(this.getLocalStorageKeyAndValue(1)); // IndexedDb存储 basicInfoArr.push(this.getIndexedDbKeyAndValue(1)); // websql存储 basicInfoArr.push(this.getOpenDatabaseKeyAndValue(1)); // cpu类型 basicInfoArr.push(this.getCpuClassKeyAndValue(1)); // 平台类型 basicInfoArr.push(this.getPlatformKeyAndValue(1)); // 反追踪 basicInfoArr.push(this.getDoNotTrackKeyAndValue(1)); // 插件 basicInfoArr.push(this.getPluginsKeyAndValue(1)); // TODO 画布 basicInfoArr.push(this.getCanvasKeyAndValue(0)); // webgl画布 basicInfoArr.push(this.getWebglKeyAndValue(1)); // adBlock广告拦截 basicInfoArr.push(this.getAdBlockKeyAndValue(1)); // 说谎语言 basicInfoArr.push(this.getHasLiedLanguagesKeyAndValue(1)); // 说谎分辨率 basicInfoArr.push(this.getHasLiedResolutionKeyAndValue(1)); // 说谎操作系统 basicInfoArr.push(this.getHasLiedOsKeyAndValue(1)); // 说谎浏览器 basicInfoArr.push(this.getHasLiedBrowserKeyAndValue(1)); // 触摸支持 basicInfoArr.push(this.getTouchSupportKeyAndValue(1)); // 字体 basicInfoArr.push(this.getFontsKeyAndValue(1)); return basicInfoArr; } } 用户代理 chromeHelper.prototype = { /** * 获取用户代理键值对 */ getUserAgentKeyAndValue: function(trueOrFake) { return { \"key\": \"user_agent\", \"value\": this.getUserAgent(trueOrFake) } }, /** * 获取用户代理,注意并不是原始userAgent而是去掉了&+?%#/=等特殊字符,例如: \"Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36\" */ getUserAgent: function(trueOrFake) { if (trueOrFake) { return navigator.userAgent.replace(/\\&|\\+|\\?|\\%|\\#|\\/|\\=/g, \"\") } return \"Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36\" } } 语言 chromeHelper.prototype = { /** * 获取语言类型键值对 */ getLanguageKeyAndValue: function(trueOrFake) { return { \"key\": \"language\", \"value\": this.getLanguage(trueOrFake) } }, /** * 获取语言类型,例如: \"zh-CN\" */ getLanguage: function(trueOrFake) { if (trueOrFake) { return navigator.language } return \"zh-CN\" } } 颜色深度 chromeHelper.prototype = { /** * 获取颜色深度键值对 */ getColorDepthKeyAndValue: function(trueOrFake) { return { \"key\": \"color_depth\", \"value\": this.getColorDepth(trueOrFake) } }, /** * 获取颜色深度,例如: 24 */ getColorDepth: function(trueOrFake) { if (trueOrFake) { return screen.colorDepth } return 24 } } 像素比例 chromeHelper.prototype = { /** * 获取像素比例键值对 */ getPixelRatioKeyAndValue: function(trueOrFake) { return { \"key\": \"pixel_ratio\", \"value\": this.getPixelRatio(trueOrFake) } }, /** * 获取像素比例,例如: 2 */ getPixelRatio: function(trueOrFake) { if (trueOrFake) { return window.devicePixelRatio } return 2 } } 屏幕分辨率 chromeHelper.prototype = { /** * 获取屏幕分辨率键值对 */ getScreenResolutionKeyAndValue: function(trueOrFake) { return { \"key\": \"resolution\", \"value\": this.getScreenResolution(trueOrFake) } }, /** * 获取屏幕分辨率,例如: [1280, 800] */ getScreenResolution: function(trueOrFake) { if (trueOrFake) { return [screen.width, screen.height] } return [1280, 800] } } 可用屏幕分辨率 chromeHelper.prototype = { /** * 获取可用屏幕分辨率键值对 */ getAvailableScreenResolutionKeyAndValue: function(trueOrFake) { return { \"key\": \"available_resolution\", \"value\": this.getAvailableScreenResolution(trueOrFake) } }, /** * 获取可用屏幕分辨率,例如: [1280, 777] */ getAvailableScreenResolution: function(trueOrFake) { if (trueOrFake) { return [screen.availWidth, screen.availHeight] } return [1280, 777] } } 时区偏移量 chromeHelper.prototype = { /** * 获取时区偏移量键值对 */ getTimezoneOffsetKeyAndValue: function(trueOrFake) { return { \"key\": \"timezone_offset\", \"value\": this.getTimezoneOffset(trueOrFake) } }, /** * 获取时区偏移量,例如: -480 */ getTimezoneOffset: function(trueOrFake) { if (trueOrFake) { return (new Date).getTimezoneOffset() } return -480 } } Session存储 chromeHelper.prototype = { /** * 获取Session存储键值对 */ getSessionStorageKeyAndValue: function(trueOrFake) { return { \"key\": \"session_storage\", \"value\": this.getSessionStorage(trueOrFake) } }, /** * 获取Session存储,例如: 1 */ getSessionStorage: function(trueOrFake) { if (trueOrFake) { return !!window.sessionStorage ? 1 : 0; } return void 0; } } Local存储 chromeHelper.prototype = { /** * 获取Local存储键值对 */ getLocalStorageKeyAndValue: function(trueOrFake) { return { \"key\": \"local_storage\", \"value\": this.getLocalStorage(trueOrFake) } }, /** * 获取Local存储,例如: 1 */ getLocalStorage: function(trueOrFake) { if (trueOrFake) { return !!window.localStorage ? 1 : 0; } return void 0; } } IndexedDb存储 chromeHelper.prototype = { /** * 获取indexedDb存储键值对 */ getIndexedDbKeyAndValue: function(trueOrFake) { return { \"key\": \"indexed_db\", \"value\": this.getIndexedDb(trueOrFake) } }, /** * 获取indexedDb存储,例如: 1 */ getIndexedDb: function(trueOrFake) { if (trueOrFake) { return !!window.indexedDB ? 1 : 0; } return void 0; } } AddBehavior存储 chromeHelper.prototype = { /** * 获取addBehavior存储键值对 */ getAddBehaviorKeyAndValue: function(trueOrFake) { return { \"key\": \"add_behavior\", \"value\": this.getAddBehavior(trueOrFake) } }, /** * 获取addBehavior存储(Chrome 不支持!!!),例如: 0 */ getAddBehavior: function(trueOrFake) { if (trueOrFake) { return !!document.body.addBehavior ? 1 : 0; } return void 0 } } websql存储 chromeHelper.prototype = { /** * 获取Websql存储键值对 */ getOpenDatabaseKeyAndValue: function(trueOrFake) { return { \"key\": \"open_database\", \"value\": this.getOpenDatabase(trueOrFake) } }, /** * 获取Websql存储,例如: 1 */ getOpenDatabase: function(trueOrFake) { if (trueOrFake) { return !!window.openDatabase ? 1 : 0; } return void 0 } } cpu类型 chromeHelper.prototype = { /** * 获取cpu类型键值对 */ getCpuClassKeyAndValue: function(trueOrFake) { return { \"key\": \"cpu_class\", \"value\": this.getCpuClass(trueOrFake) } }, /** * 获取cpu类型(Chrome 不支持!!!),例如: \"unknown\" */ getCpuClass: function(trueOrFake) { if (trueOrFake) { return navigator.cpuClass || \"unknown\" } return \"unknown\" } } 平台类型 chromeHelper.prototype = { /** * 获取平台类型键值对 */ getPlatformKeyAndValue: function(trueOrFake) { return { \"key\": \"navigator_platform\", \"value\": this.getPlatform(trueOrFake) } }, /** * 获取平台类型,例如: \"MacIntel\" */ getPlatform: function(trueOrFake) { if (trueOrFake) { return navigator.platform || \"unknown\" } return \"MacIntel\" } } 反追踪 chromeHelper.prototype = { /** * 获取反追踪键值对 */ getDoNotTrackKeyAndValue: function(trueOrFake) { return { \"key\": \"do_not_track\", \"value\": this.getDoNotTrack(trueOrFake) } }, /** * 获取反追踪,例如: \"unknown\" */ getDoNotTrack: function(trueOrFake) { if (trueOrFake) { return navigator.doNotTrack ? navigator.doNotTrack : navigator.msDoNotTrack ? navigator.msDoNotTrack : window.doNotTrack ? window.doNotTrack : \"unknown\" } return \"unknown\" } } 插件 chromeHelper.prototype = { /** * 获取插件键值对 */ getPluginsKeyAndValue: function(trueOrFake) { return { \"key\": \"regular_plugins\", \"value\": this.getPlugins(trueOrFake) } }, /** * getRegularPlugins 1381 TODO 获取插件,例如: [\"Chrome PDF Plugin::Portable Document Format::application/x-google-chrome-pdf~pdf\",\"Chrome PDF Viewer::::application/pdf~pdf\",\"Native Client::::application/x-nacl~,application/x-pnacl~\"] */ getPlugins: function(trueOrFake) { return [ \"Chrome PDF Plugin::Portable Document Format::application/x-google-chrome-pdf~pdf\", \"Chrome PDF Viewer::::application/pdf~pdf\", \"Native Client::::application/x-nacl~,application/x-pnacl~\" ] } } TODO 画布 chromeHelper.prototype = { /** * 获取画布键值对 */ getCanvasKeyAndValue: function(trueOrFake) { return { \"key\": \"canvas\", \"value\": this.getCanvas(trueOrFake) } }, /** * 画布,例如: \"canvas winding:yes~canvas fp:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAADICAYAAACwGnoBAAAgAElEQVR4XuzdeXxU9b3/8deZzGQDEpawhF1ARJAdxLWVarXWtddCN2vdCFq197q0tfW2TX+3t6211bZWK8GtWm2r3ta2aqtd6L2uKDuCoCA7YQlLICQkM5nze3zOzAknYZJMQhKIvr+P2yvMnPM93/M8Cf+8z+fzdTjGh4s7ABgLnAiMAAYDhUABMKSR5W8AyoBSwP68FngHWO7gbPHPcXG7AKOBMYH/9gBygOzkf+3P/v/s1KoG/zuY/PseYAWw0v+vg3Ogbn1u294HzqH7OMYfoZYnAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABCQgAQlIoFMIOMfaKl1cC8rPBs4ETgUGtfEa9wMVQBbQs43nrpvO0vrn89j94iiqF55C1z1n0c27I4v922ZsAl4HXgb+gePYJdMabhFuWgd+wA5ySjhmft6Liwl9aQiZte5JvWNubl+jDjuV23fsfbvs1H1UO8XEg/wuOA4fzuf2Afsx1O1IQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABCQgAQkcwwLHRKDo4p4CfAq4KFlpfgyTNb60N4A/AH9OlrunPHICcDpwWvIVgbZ7PcACdLv0H3AcW0qjQwH60f3xeu+mnnk5Uwsuye7d4+IufQdOCOcVFOCGiO3fWXZg+5ZlB3bs+dP7i7f9cfpP9+49uivV1SUgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABCTw4RI4agG6i2u12FcC1wCjOiu79Yl/FHgIWNWam7BXBy4ALgbGtWaClOfYUmxJj+I4tsR6QwF6mzm3eKIFxUNHDRkVvq3HxGlXZ4y80IGTgO7JeSwvX0Xtu8+ze/Hrj2x+v+rHk7650bYE0JCABCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABDpA4PAAvbg4xJb+5wEfBc7AcfcB84iFH+Dha6z9+RENF9f2M78BmJ3uRA+xiidZQzVxZjKMPVTzHSane3qzxz3LeuaxlZ9xGrup5j94jdsYz7gmOrwvB+4D5jQ7e6oD3gUsLD25/pfnJmvwrQ6/sd3dG063vwqeeQPOGw/9U3aktyXeh+PYkr3RWID+BsN4gI9wP0+SS02r7qyjTqohzGr6Mpa6Le2bvXSLWrjPKrkSxz2BktnfaHbiNA/4538ed8K4sc4vep1x2jn0v4LaWHcyMtxEY3bXhRDU1kJGeB9s+y3lr73yz7Urq2+c/K0NabfnT3MpOkwCEpCABCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEkgh0CBAdx2KSiwXvh54H3gR1xmH456O4/6Twm0fp7i43t7M6aq6uFZf/VXg8nTPseNWsZcTeYrT6cd0+rOTKl5nO0v5dEumafLY77GIH7GUfVzFZg4wiCf4E+dxUYoUexlwF/DrI7r6q8BWYEbqWbIDQfrnbHPsJi5Wth/u+A185XwY02Q/eFvyXTjOssYC9KeZzEyK2MmtFHjbxB+74yPcxmm8zw/5fdqLbFGAXjTnbuBsSmaPT/sCTRz4wk0j8k46qfqngz564lWMvJQDu6tY8fJCho0fTcHgId4O57s2bGDNkrcZ85GJdO2VB2uep3Tessfnr+zxlU/9dInaubfFg9AcEpCABCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEmhCoH6APvuB7+I638Z1iphbNLfuvNkPXI7rPA5cR8nsFhVdu7i9ge8kq85b/DD+zhY+zvNeYN5URXiLJw6ckE6AvhP4brLq/EiulTi3mQA9eAHr8P2lZLN7a3rfcKQfoPtn3rdjtnODPZSGozMF6MP5b2awsP0C9CN/yHUzuMXFoaU5v/rCmPGZj4THnZFBjzzeXbCaP931N06beQqnfOYMcGD+U6/y6m9e4+Jbz2HktFGw9wCxZa/G31lafc2qtyY9PvPpp2vbcFmaSgISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABCQgAQlIQAISaCBQP0AvmrMd13mduUWX1j/OdZg19wc47vs47m6vQr0y92Iev+IARXNycdw/Ay8x57o7vfOK5lhp9VW1JUV/+WL4nz/5eGxg5GVKeYnNnEgPfsDJbOGAV/Vt7divZCS3MI4MSxEDw1qr38FbrGQPp9CHGQzzvn2PffySM7w/L2M3xSzgVbbTjxz+jeP4OhPIJoPtVPFZ/sFnGMZdLOME8vkNZ3tV7XezjH9RykcpJI9MnmJtvQr0bzCB19nhXXsghbzHKeyna3J11cCSZBW5FQbb56OBMcnv5wH9ge3AJiAn+b2/1XvDAN3m2pxs6d4nxQ/pDujxNpywBc7tDWcPhrfeh1suhF2BCvSKg/DKarj+45CblZinOgr3vwhnj4Md5bCviqwX76QbkzmRPVzBG1zLK96hfoA+h1/zCKexmR6cxbvcw1N1FelPMI2f8TE20Ivj2cE1vMJVvHbYmudyJv/DRP7I/WQR874vJZ/Pcy138ALn8A7/YiTf55MsZaA31038k8+wABeHq7mCXXTlNzxIF6rZSneu5ErOYjXr6YXN34f9nMcKHuORw64fI+TNbWuwcT3/y/XO55/CdR6jZPbzzH7g67jOQEpm31R3cqLqvIyS2d9nVsm/E4pPYM51VzGr5CfApMMuEor/njnX3dvcvyqv3XxqzsAhm58YdHKPT9FnKPRwWPH6ZuY9tIRx54/g9C9N9AL01x5bwpLn3+VjV45nzOmDYa8LOzex8c1df162ovdnLypZWNnctfS9BCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABCTQeoFDiXXRnMHAhmarzK+dO45QfCmOez5zrvsrsx84G9f5u9fyvWT2cFtK90sf+b+Jr/Y64Z87L+oznN/yPvv4FEP5CIX8hGWUU0MOYb7MaMo4yC9Ywatcwmn0rXcn71HO/azkpyznx5zitXH/Fe/yGtu8ivQ17ON4fstAuvDvjOUd9vAwq72g/SnOYSMVDOFJb87PM4IYce5kGuN4hkJyuYmTvOD8ZbbRjUi9AN3O+QTDeJ/evIs1brfh91O327Vg3PZht37ra5IBuG1eXgj8FrCt4y1Et9DfuuFby3Z7L8EC8mCAvghYAHwE8AP2IINtO/8MkJsI4bM2wYjNcIJ9XFQ/QO/ZDYqfgi99FE6zA4D5a+Dhf8IPPw9/XQL/WgnsAV6gkBGUMo3HeZjLmV8XoNtp1hr9AFn8FxdwCUt5lvu9oHsC3+IaXuXjrOSvjOFRTuNFfsa52LyHxgKGMJVvegH6xSz1vriX6XyFz3ot4pczgI9xixfQf443eZYJ/IWT6tbyGsM5na9xM3/nxzzDJ7kJ+2wV3/HW8RlmcQZrKOJlLvVeZqg/buPT/ISPcyt/88L/73M++71nxWxKZpcwq+RBHHccJbMPbUQ/q2QejruNktmfo2iOBeMfo2T2GIrmXIDjJvrju47tWH4NMBXX+Rxzi+xhNzle/s6wwSN77/9Xn7E9jyM/H3qFeW/ZHl58+F2mXTyEyZfZrx4s+sNGXn92A+dedTwnjO8Ju2NQvp+dS3duWrur4COnFq9a39y19L0EJCABCUhAAhKQgAQkIAEJSEACEpCABCQgAQlIQAISkIAEJNB6gUMB+qyS83HcF4DzKJn9UuNTevukbwR+R8ns2yia8wPg9uTxQzb86gufGlLzxE8fc6fzRY7HAnSrBl/KZYQJ8WOW8VXe8CrIr/OqtiGPR7iZsXyXKYdd9q9s4nz+wjo+x1C6cT2v1AXot/A697C87js7+dss4L9Y5AXs3cn0AvQbGcO9nO7N/S0W8DOWs5XL6UrE++wM/sQydtUL0CczjKWck6ydtupwozkLGAFexXY/YGRyvQeAJ4AzgROTAbptXH4ZXmkxVjhsW5D73/sBus31VnJef66GBG8mq92vSIb19v0zEN4N35kFsyrg24E90O98FsJhuPXCxEQ/fQFCwFc+Cb95JRmgfy/5AoCt7jZ60YOd3FEXoD9Fidce3cYVXMWLjGE7t9V9/0/uZjqrsSrvn/Mx788TvRcK6o8xFDOGrdh8NsbzLU5gu/d328N8Nf3YzNeJkOhMfjE3sJhBbEr+OBVzEd/lQr7E6/yKU3mBezmft71jm2rhXkkmXbiXG5nHvd7LDPAbpnrV760K0IO3ZWE6PIfrfI+5Rd9q/Pfk0Df/un3M1NEDNv6t9wk988nvBvkh9u6rZdGrezhxancKR1iHAihdU8XKN/cy6fTu9OgehvI4lFewc1VZxfvb+n38lP9e80Y619MxEpCABCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpBA6wQOBejX/fIk4qHlOO7VzLnu8J7YwfkTra4voGT2CRTNsfJiK5H+f9/7/ZR548p6Tb+YF9nBFfQm2wvQz6Qfj3rhM/yedVzG33iPzzKCPO+zE/gdn2AQP+O0w+6iqQD9ZP7AAWKswDrGJ8Yb7OBUnvVatVtFuwXoj5EI821cyF+9/z7HJ+rO+SFL+D6LG1Sg23r9UNtakD/sRcAwzcqQkxXntjN6ebJVu1WcW0hvbdwttB2QDMz9y1iIfCowNlmBviL5hYWnnwEyG3mCz4EX4we76lvFulWuz4IpFdDzN3D3+TBmELy6Ch77P/j+5yHkwO1PwLVnw9ThiQB94TrYb5uq2z3YON+b+0r+nU8yhpkUsY5vMpRd3rc/42z+g5m4zGYn3ZjEHV5r95Fs51Ms4d9YxMmkLoz2z93NzWyhO2P5jheCf4IVhHjAa8H+Ud6tu2+rLH+XvuznK3SlmigZnMLtLGKwV4l+N0/XHdtUgP4WQzmZb/A/PMC/sdg7x9rH9+dH9seWV6D7V7127mRC8QU47lMUb\" */ getCanvas: function(trueOrFake) { if (trueOrFake) { var a = [], b = document.createElement(\"canvas\"); b.width = 2E3; b.height = 200; b.style.display = \"inline\"; var c = b.getContext(\"2d\"); c.rect(0, 0, 10, 10); c.rect(2, 2, 6, 6); a.push(\"canvas winding:\" + (!1 === c.isPointInPath(5, 5, \"evenodd\") ? \"yes\" : \"no\")); c.textBaseline = \"alphabetic\"; c.fillStyle = \"#f60\"; c.fillRect(125, 1, 62, 20); c.fillStyle = \"#069\"; c.font = \"11pt Arial\"; c.fillText(\"Cwm fjordbank glyphs vext quiz, \\ud83d\\ude03\", 2, 15); c.fillStyle = \"rgba(102, 204, 0, 0.2)\"; c.font = \"18pt Arial\"; c.fillText(\"Cwm fjordbank glyphs vext quiz, \\ud83d\\ude03\", 4, 45); c.globalCompositeOperation = \"multiply\"; c.fillStyle = \"rgb(255,0,255)\"; c.beginPath(); c.arc(50, 50, 50, 0, 2 * Math.PI, !0); c.closePath(); c.fill(); c.fillStyle = \"rgb(0,255,255)\"; c.beginPath(); c.arc(100, 50, 50, 0, 2 * Math.PI, !0); c.closePath(); c.fill(); c.fillStyle = \"rgb(255,255,0)\"; c.beginPath(); c.arc(75, 100, 50, 0, 2 * Math.PI, !0); c.closePath(); c.fill(); c.fillStyle = \"rgb(255,0,255)\"; c.arc(75, 75, 75, 0, 2 * Math.PI, !0); c.arc(75, 75, 25, 0, 2 * Math.PI, !0); c.fill(\"evenodd\"); a.push(\"canvas fp:\" + b.toDataURL()); return a.join(\"~\") } fakeCanvas = \"canvas winding:yes~canvas fp:data:image/png;base64,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fJK39A1x5rTb8+67t+17von9C3x06u05adkg9l8oKX/dfwNmEiBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAYGIL9Bqgz+y8yn37vra1z1/ckJ98ade+5po0mMC+R9+Qq/+tX/tbO69uXzhYE2YTIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBg4gr0FKDX26hSnZDkk31taaMrfpPf7r9VNuhrtkn9CjyWZJO5v8kjL9yqzxInlpSz+pxrGgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBFoh0HOAXu+qSvXlJG/oa4ff2HxJjljuS+h94fU56YIpS/Lq+/s1/0pJeWOfK5tGgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB1gj0G6DXr3C/LMnuPe/0pcf+IN8558Ce55nQv8DLjvlBvnt2P+bXJTmopNSvcPcQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQIEBgvRboK0CvRapUhyS5pGedjW6/Jsu32zMb9zzThH4EHk4y5bZr8si2e/Yx/dCScmkf80whQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBA6wT6DtDrnVapZieZ0/Ou37r/vHzmilk9zzOhd4G3vXBePju3H+tTS8rpvS9oBgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBNopMFCAXm+5SvUfSV7d0/Y3mn9nbt57m/T7Ve6eFpvEg5ckecb/3plH9tqmR4VvlJQ/73GO4QQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGi1wDAC9JlJvpNkl54kjjpgQb42d7ee5hjcm8Br91+Q8y7v1fjGJC8rKQt7W8xoAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItFtg4AC93n6V6kVJvpVki645yjWP5arnbpB9up5hYC8CVyd53s8fS7XnBj1Muy/JK0vKD3uYYygBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTWC4GhBOi1RJXqyCTn9aRywBE35off7O3mek8LTOLBL3rVjbn8gl5tjyop509iNVsnQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQGASCwwtQK8Nq1RvTnJO9553JRdu/2AOe3TT7ucYOarARRs+mMNv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kkunLMk/H/nLfO/0aXlk27rfefWfJvlWSblmvFqzLgECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBCa7wHoVoK8Wptcvcd8/yfOTvCAbzZ2R53/21rz+u1Nz6G+2z/ZjdPS31tH4Vrfmay+9J1e+dbs8sn/90vYfJbkyydySUv+/hwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgTGWWC9DdBXd61STesE6i/M5j89IFvWQfqFD+SN1z4lL3joqUM9hx89+Y58eY+7csnhm+Xel96d+//48iRXdALzZUNdSzECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECBAgQGIrApAnQ16RVpdoiyX7Zdv5BKQtelLuX7p5Nb/xdpt/4UHZeWmWXezbMs3+zZWYm2alTYVGShUmu3+re3Lj1o7lph5JbdtkkD+6yQabucF2y2w9z2171t8x/XFLuG8opKUKAAAECBAgQIECAAAECBAgQIECAAAECBAgQIECAAAECjQtM6gC9cV0LECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBrBATorTkqjRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAkwIC9CZ11SZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB1ggI0FtzVBolQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgSYFBOhN6qpNgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAq0REKC35qg0SoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCgjQm9RVmwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaIyBAb81RaZQAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEmhQQoDepqzYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEZAgN6ao9IoAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDQpIEBvUldtAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGiNgAC9NUelUQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoUkCA3qSu2gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQGgEBemuOSqMECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0KSAAL1JXbUJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoDUCAvTWHJVGCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBJAQF6k7pqEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBrBATorTkqjRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAkwIC9CZ11SZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB1ggI0FtzVBolQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgSYFBOhN6qpNgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAq0REKC35qg0SoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCgjQm9RVmwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaIyBAb81RaZQAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEmhQQoDepqzYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEZAgN6ao9IoAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDQpIEBvUldtAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGiNgAC9NUelUQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoUkCA3qSu2gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQGgEBemuOSqMECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0KSAAL1JXbUJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoDUCAvTWHJVGCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBJAQF6k7pqEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBrBATorTkqjRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAkwIC9CZ11SZAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgACB1ggI0FtzVBolQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAgSYFBOhN6qpNgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAq0REKC35qg0SoAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQJNCgjQm9RVmwABAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgRaIyBAb81RaZQAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEmhQQoDepqzYBAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQItEZAgN6ao9IoAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECDQpIEBvUldtAgQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIEGiNgAC9NUelUQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoUkCA3qSu2gQIECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECDQGgEBemuOSqMECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAg0KSAAL1JXbUJECBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAoDUCAvTWHJVGCRAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQKBJAQF6k7pqEyBAgAABAgQIECBAgAABAgQIECBAgAABAgQIECBAgEBrBATorTkqjRIgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIBAkwL/Dyu9q22HnlUDAAAAAElFTkSuQmCC\"; return fakeCanvas } } webgl画布 chromeHelper.prototype = { /** * 获取webgl键值对 */ getWebglKeyAndValue: function(trueOrFake) { return { \"key\": \"webgl\", \"value\": this.getWebgl(trueOrFake) } }, /** * webgl,例如: \"data:image/png;base64,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~extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_filter_anisotropic;WEBKIT_EXT_texture_filter_anisotropic;EXT_sRGB;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBKIT_WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBKIT_WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBKIT_WEBGL_lose_context~webgl aliased line width range:[1, 1]~webgl aliased point size range:[1, 255.875]~webgl alpha bits:8~webgl antialiasing:yes~webgl blue bits:8~webgl depth bits:24~webgl green bits:8~webgl max anisotropy:16~webgl max combined texture image units:80~webgl max cube map texture size:16384~webgl max fragment uniform vectors:1024~webgl max render buffer size:16384~webgl max texture image units:16~webgl max texture size:16384~webgl max varying vectors:15~webgl max vertex attribs:16~webgl max vertex texture image units:16~webgl max vertex uniform vectors:1024~webgl max viewport dims:[16384, 16384]~webgl red bits:8~webgl renderer:WebKit WebGL~webgl shading language version:WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)~webgl stencil bits:0~webgl vendor:WebKit~webgl version:WebGL 1.0 (OpenGL ES 2.0 Chromium)~webgl vertex shader high float precision:23~webgl vertex shader high float precision rangeMin:127~webgl vertex shader high float precision rangeMax:127~webgl vertex shader medium float precision:23~webgl vertex shader medium float precision rangeMin:127~webgl vertex shader medium float precision rangeMax:127~webgl vertex shader low float precision:23~webgl vertex shader low float precision rangeMin:127~webgl vertex shader low float precision rangeMax:127~webgl fragment shader high float precision:23~webgl fragment shader high float precision rangeMin:127~webgl fragment shader high float precision rangeMax:127~webgl fragment shader medium float precision:23~webgl fragment shader medium float precision rangeMin:127~webgl fragment shader medium float precision rangeMax:127~webgl fragment shader low float precision:23~webgl fragment shader low float precision rangeMin:127~webgl fragment shader low float precision rangeMax:127~webgl vertex shader high int precision:0~webgl vertex shader high int precision rangeMin:31~webgl vertex shader high int precision rangeMax:30~webgl vertex shader medium int precision:0~webgl vertex shader medium int precision rangeMin:31~webgl vertex shader medium int precision rangeMax:30~webgl vertex shader low int precision:0~webgl vertex shader low int precision rangeMin:31~webgl vertex shader low int precision rangeMax:30~webgl fragment shader high int precision:0~webgl fragment shader high int precision rangeMin:31~webgl fragment shader high int precision rangeMax:30~webgl fragment shader medium int precision:0~webgl fragment shader medium int precision rangeMin:31~webgl fragment shader medium int precision rangeMax:30~webgl fragment shader low int precision:0~webgl fragment shader low int precision rangeMin:31~webgl fragment shader low int precision rangeMax:30\" */ getWebgl: function(trueOrFake) { if (trueOrFake) { function a(a) { b.clearColor(0, 0, 0, 1); b.enable(b.DEPTH_TEST); b.depthFunc(b.LEQUAL); b.clear(b.COLOR_BUFFER_BIT | b.DEPTH_BUFFER_BIT); return \"[\" + a[0] + \", \" + a[1] + \"]\" } var b; b = this.getWebglCanvas(); if (!b) return null; var c = [], d = b.createBuffer(); b.bindBuffer(b.ARRAY_BUFFER, d); var e = new Float32Array([-.2, -.9, 0, .4, -.26, 0, 0, .732134444, 0]); b.bufferData(b.ARRAY_BUFFER, e, b.STATIC_DRAW); d.itemSize = 3; d.numItems = 3; var e = b.createProgram(), f = b.createShader(b.VERTEX_SHADER); b.shaderSource(f, \"attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate\\x3dattrVertex+uniformOffset;gl_Position\\x3dvec4(attrVertex,0,1);}\"); b.compileShader(f); var g = b.createShader(b.FRAGMENT_SHADER); b.shaderSource(g, \"precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor\\x3dvec4(varyinTexCoordinate,0,1);}\"); b.compileShader(g); b.attachShader(e, f); b.attachShader(e, g); b.linkProgram(e); b.useProgram(e); e.vertexPosAttrib = b.getAttribLocation(e, \"attrVertex\"); e.offsetUniform = b.getUniformLocation(e, \"uniformOffset\"); b.enableVertexAttribArray(e.vertexPosArray); b.vertexAttribPointer(e.vertexPosAttrib, d.itemSize, b.FLOAT, !1, 0, 0); b.uniform2f(e.offsetUniform, 1, 1); b.drawArrays(b.TRIANGLE_STRIP, 0, d.numItems); null != b.canvas && c.push(b.canvas.toDataURL()); c.push(\"extensions:\" + b.getSupportedExtensions().join(\";\")); c.push(\"webgl aliased line width range:\" + a(b.getParameter(b.ALIASED_LINE_WIDTH_RANGE))); c.push(\"webgl aliased point size range:\" + a(b.getParameter(b.ALIASED_POINT_SIZE_RANGE))); c.push(\"webgl alpha bits:\" + b.getParameter(b.ALPHA_BITS)); c.push(\"webgl antialiasing:\" + (b.getContextAttributes().antialias ? \"yes\" : \"no\")); c.push(\"webgl blue bits:\" + b.getParameter(b.BLUE_BITS)); c.push(\"webgl depth bits:\" + b.getParameter(b.DEPTH_BITS)); c.push(\"webgl green bits:\" + b.getParameter(b.GREEN_BITS)); c.push(\"webgl max anisotropy:\" + function(a) { var b, c = a.getExtension(\"EXT_texture_filter_anisotropic\") || a.getExtension(\"WEBKIT_EXT_texture_filter_anisotropic\") || a.getExtension(\"MOZ_EXT_texture_filter_anisotropic\"); return c ? (b = a.getParameter(c.MAX_TEXTURE_MAX_ANISOTROPY_EXT), 0 === b && (b = 2), b) : null }(b)); c.push(\"webgl max combined texture image units:\" + b.getParameter(b.MAX_COMBINED_TEXTURE_IMAGE_UNITS)); c.push(\"webgl max cube map texture size:\" + b.getParameter(b.MAX_CUBE_MAP_TEXTURE_SIZE)); c.push(\"webgl max fragment uniform vectors:\" + b.getParameter(b.MAX_FRAGMENT_UNIFORM_VECTORS)); c.push(\"webgl max render buffer size:\" + b.getParameter(b.MAX_RENDERBUFFER_SIZE)); c.push(\"webgl max texture image units:\" + b.getParameter(b.MAX_TEXTURE_IMAGE_UNITS)); c.push(\"webgl max texture size:\" + b.getParameter(b.MAX_TEXTURE_SIZE)); c.push(\"webgl max varying vectors:\" + b.getParameter(b.MAX_VARYING_VECTORS)); c.push(\"webgl max vertex attribs:\" + b.getParameter(b.MAX_VERTEX_ATTRIBS)); c.push(\"webgl max vertex texture image units:\" + b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)); c.push(\"webgl max vertex uniform vectors:\" + b.getParameter(b.MAX_VERTEX_UNIFORM_VECTORS)); c.push(\"webgl max viewport dims:\" + a(b.getParameter(b.MAX_VIEWPORT_DIMS))); c.push(\"webgl red bits:\" + b.getParameter(b.RED_BITS)); c.push(\"webgl renderer:\" + b.getParameter(b.RENDERER)); c.push(\"webgl shading language version:\" + b.getParameter(b.SHADING_LANGUAGE_VERSION)); c.push(\"webgl stencil bits:\" + b.getParameter(b.STENCIL_BITS)); c.push(\"webgl vendor:\" + b.getParameter(b.VENDOR)); c.push(\"webgl version:\" + b.getParameter(b.VERSION)); if (!b.getShaderPrecisionFormat) return c.join(\"~\"); c.push(\"webgl vertex shader high float precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_FLOAT).precision); c.push(\"webgl vertex shader high float precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_FLOAT).rangeMin); c.push(\"webgl vertex shader high float precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_FLOAT).rangeMax); c.push(\"webgl vertex shader medium float precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_FLOAT).precision); c.push(\"webgl vertex shader medium float precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_FLOAT).rangeMin); c.push(\"webgl vertex shader medium float precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_FLOAT).rangeMax); c.push(\"webgl vertex shader low float precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_FLOAT).precision); c.push(\"webgl vertex shader low float precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_FLOAT).rangeMin); c.push(\"webgl vertex shader low float precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_FLOAT).rangeMax); c.push(\"webgl fragment shader high float precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_FLOAT).precision); c.push(\"webgl fragment shader high float precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_FLOAT).rangeMin); c.push(\"webgl fragment shader high float precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_FLOAT).rangeMax); c.push(\"webgl fragment shader medium float precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_FLOAT).precision); c.push(\"webgl fragment shader medium float precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_FLOAT).rangeMin); c.push(\"webgl fragment shader medium float precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_FLOAT).rangeMax); c.push(\"webgl fragment shader low float precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_FLOAT).precision); c.push(\"webgl fragment shader low float precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_FLOAT).rangeMin); c.push(\"webgl fragment shader low float precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_FLOAT).rangeMax); c.push(\"webgl vertex shader high int precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_INT).precision); c.push(\"webgl vertex shader high int precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_INT).rangeMin); c.push(\"webgl vertex shader high int precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.HIGH_INT).rangeMax); c.push(\"webgl vertex shader medium int precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_INT).precision); c.push(\"webgl vertex shader medium int precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_INT).rangeMin); c.push(\"webgl vertex shader medium int precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.MEDIUM_INT).rangeMax); c.push(\"webgl vertex shader low int precision:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_INT).precision); c.push(\"webgl vertex shader low int precision rangeMin:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_INT).rangeMin); c.push(\"webgl vertex shader low int precision rangeMax:\" + b.getShaderPrecisionFormat(b.VERTEX_SHADER, b.LOW_INT).rangeMax); c.push(\"webgl fragment shader high int precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_INT).precision); c.push(\"webgl fragment shader high int precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_INT).rangeMin); c.push(\"webgl fragment shader high int precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.HIGH_INT).rangeMax); c.push(\"webgl fragment shader medium int precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_INT).precision); c.push(\"webgl fragment shader medium int precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_INT).rangeMin); c.push(\"webgl fragment shader medium int precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.MEDIUM_INT).rangeMax); c.push(\"webgl fragment shader low int precision:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_INT).precision); c.push(\"webgl fragment shader low int precision rangeMin:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_INT).rangeMin); c.push(\"webgl fragment shader low int precision rangeMax:\" + b.getShaderPrecisionFormat(b.FRAGMENT_SHADER, b.LOW_INT).rangeMax); return c.join(\"~\") } return \"data:image/png;base64,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~extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_filter_anisotropic;WEBKIT_EXT_texture_filter_anisotropic;EXT_sRGB;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBKIT_WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBKIT_WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBKIT_WEBGL_lose_context~webgl aliased line width range:[1, 1]~webgl aliased point size range:[1, 255.875]~webgl alpha bits:8~webgl antialiasing:yes~webgl blue bits:8~webgl depth bits:24~webgl green bits:8~webgl max anisotropy:16~webgl max combined texture image units:80~webgl max cube map texture size:16384~webgl max fragment uniform vectors:1024~webgl max render buffer size:16384~webgl max texture image units:16~webgl max texture size:16384~webgl max varying vectors:15~webgl max vertex attribs:16~webgl max vertex texture image units:16~webgl max vertex uniform vectors:1024~webgl max viewport dims:[16384, 16384]~webgl red bits:8~webgl renderer:WebKit WebGL~webgl shading language version:WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)~webgl stencil bits:0~webgl vendor:WebKit~webgl version:WebGL 1.0 (OpenGL ES 2.0 Chromium)~webgl vertex shader high float precision:23~webgl vertex shader high float precision rangeMin:127~webgl vertex shader high float precision rangeMax:127~webgl vertex shader medium float precision:23~webgl vertex shader medium float precision rangeMin:127~webgl vertex shader medium float precision rangeMax:127~webgl vertex shader low float precision:23~webgl vertex shader low float precision rangeMin:127~webgl vertex shader low float precision rangeMax:127~webgl fragment shader high float precision:23~webgl fragment shader high float precision rangeMin:127~webgl fragment shader high float precision rangeMax:127~webgl fragment shader medium float precision:23~webgl fragment shader medium float precision rangeMin:127~webgl fragment shader medium float precision rangeMax:127~webgl fragment shader low float precision:23~webgl fragment shader low float precision rangeMin:127~webgl fragment shader low float precision rangeMax:127~webgl vertex shader high int precision:0~webgl vertex shader high int precision rangeMin:31~webgl vertex shader high int precision rangeMax:30~webgl vertex shader medium int precision:0~webgl vertex shader medium int precision rangeMin:31~webgl vertex shader medium int precision rangeMax:30~webgl vertex shader low int precision:0~webgl vertex shader low int precision rangeMin:31~webgl vertex shader low int precision rangeMax:30~webgl fragment shader high int precision:0~webgl fragment shader high int precision rangeMin:31~webgl fragment shader high int precision rangeMax:30~webgl fragment shader medium int precision:0~webgl fragment shader medium int precision rangeMin:31~webgl fragment shader medium int precision rangeMax:30~webgl fragment shader low int precision:0~webgl fragment shader low int precision rangeMin:31~webgl fragment shader low int precision rangeMax:30\" } } adBlock广告拦截 chromeHelper.prototype = { /** * webgl画布,例如: WebGLRenderingContext */ getWebglCanvas: function() { var a = document.createElement(\"canvas\"), b = null; try { b = a.getContext(\"webgl\") || a.getContext(\"experimental-webgl\") } catch (c) {} b || (b = null); return b }, /** * 获取adBlock广告拦截键值对 */ getAdBlockKeyAndValue: function(trueOrFake) { return { \"key\": \"adblock\", \"value\": this.getAdBlock(trueOrFake) } } } 说谎语言 chromeHelper.prototype = { /** * 获取adBlock广告拦截,例如: \"0\" */ getAdBlock: function(trueOrFake) { if (trueOrFake) { var a = document.createElement(\"div\"); a.innerHTML = \"\\x26nbsp;\"; a.className = \"adsbox\"; var b = \"0\"; try { document.body.appendChild(a), 0 === document.getElementsByClassName(\"adsbox\")[0].offsetHeight && (b = \"1\"), document.body.removeChild(a) } catch (c) { b = \"0\" } return b } return \"0\" }, /** * 获取说谎语言键值对 */ getHasLiedLanguagesKeyAndValue: function(trueOrFake) { return { \"key\": \"has_lied_languages\", \"value\": this.getHasLiedLanguages(trueOrFake) } } } 说谎分辨率 chromeHelper.prototype = { /** * 获取说谎语言键值对 */ getHasLiedLanguagesKeyAndValue: function(trueOrFake) { return { \"key\": \"has_lied_languages\", \"value\": this.getHasLiedLanguages(trueOrFake) } }, /** * 获取说谎语言,例如: false */ getHasLiedLanguages: function(trueOrFake) { if (trueOrFake) { if (\"undefined\" !== typeof navigator.languages) try { if (navigator.languages[0].substr(0, 2) !== navigator.language.substr(0, 2)) return !0 } catch (a) { return !0 } return !1 } return !1 } } 说谎操作系统 chromeHelper.prototype = { /** * 获取说谎分辨率键值对 */ getHasLiedResolutionKeyAndValue: function(trueOrFake) { return { \"key\": \"has_lied_resolution\", \"value\": this.getHasLiedResolution(trueOrFake) } }, /** * 获取说谎分辨率,例如: false */ getHasLiedResolution: function(trueOrFake) { if (trueOrFake) { return screen.width 说谎浏览器 chromeHelper.prototype = { /** * 获取说谎操作系统键值对 */ getHasLiedOsKeyAndValue: function(trueOrFake) { return { \"key\": \"has_lied_os\", \"value\": this.getHasLiedOs(trueOrFake) } }, /** * 获取说谎操作系统,例如: false */ getHasLiedOs: function(trueOrFake) { if (trueOrFake) { var a = navigator.userAgent.toLowerCase(), b = navigator.oscpu, c = navigator.platform.toLowerCase(), a = 0 触摸支持 chromeHelper.prototype = { /** * 获取触摸支持键值对 */ getTouchSupportKeyAndValue: function(trueOrFake) { return { \"key\": \"touch_support\", \"value\": this.getTouchSupport(trueOrFake) } }, /** * 获取触摸支持,例如: [0,false,false] */ getTouchSupport: function(trueOrFake) { if (trueOrFake) { var a = 0, b = !1; \"undefined\" !== typeof navigator.maxTouchPoints ? a = navigator.maxTouchPoints : \"undefined\" !== typeof navigator.msMaxTouchPoints && (a = navigator.msMaxTouchPoints); try { document.createEvent(\"TouchEvent\"), b = !0 } catch (c) {} return [a, b, \"ontouchstart\" in window] } return [0, !1, !1] } } 字体 chromeHelper.prototype = { /** * 获取字体键值对 */ getFontsKeyAndValue: function(trueOrFake) { return { \"key\": \"js_fonts\", \"value\": this.getFonts(trueOrFake) } }, /** * 获取字体,例如: [0,false,false] */ getFonts: function(trueOrFake) { if (trueOrFake) { function d() { var a = document.createElement(\"span\"); a.style.position = \"absolute\"; a.style.left = \"-9999px\"; a.style.fontSize = \"72px\"; a.style.lineHeight = \"normal\"; a.innerHTML = \"mmmmmmmmmmlli\"; return a } var e = [\"monospace\", \"sans-serif\", \"serif\"], f = \"Andale Mono;Arial;Arial Black;Arial Hebrew;Arial MT;Arial Narrow;Arial Rounded MT Bold;Arial Unicode MS;Bitstream Vera Sans Mono;Book Antiqua;Bookman Old Style;Calibri;Cambria;Cambria Math;Century;Century Gothic;Century Schoolbook;Comic Sans;Comic Sans MS;Consolas;Courier;Courier New;Garamond;Geneva;Georgia;Helvetica;Helvetica Neue;Impact;Lucida Bright;Lucida Calligraphy;Lucida Console;Lucida Fax;LUCIDA GRANDE;Lucida Handwriting;Lucida Sans;Lucida Sans Typewriter;Lucida Sans Unicode;Microsoft Sans Serif;Monaco;Monotype Corsiva;MS Gothic;MS Outlook;MS PGothic;MS Reference Sans Serif;MS Sans Serif;MS Serif;MYRIAD;MYRIAD PRO;Palatino;Palatino Linotype;Segoe Print;Segoe Script;Segoe UI;Segoe UI Light;Segoe UI Semibold;Segoe UI Symbol;Tahoma;Times;Times New Roman;Trebuchet MS;Verdana;Wingdings;Wingdings 2;Wingdings 3\".split(\";\"), g = \"Abadi MT Condensed Light;Academy Engraved LET;ADOBE CASLON PRO;Adobe Garamond;ADOBE GARAMOND PRO;Agency FB;Aharoni;Albertus Extra Bold;Albertus Medium;Algerian;Amazone BT;American Typewriter;American Typewriter Condensed;AmerType Md BT;Andalus;Angsana New;AngsanaUPC;Antique Olive;Aparajita;Apple Chancery;Apple Color Emoji;Apple SD Gothic Neo;Arabic Typesetting;ARCHER;ARNO PRO;Arrus BT;Aurora Cn BT;AvantGarde Bk BT;AvantGarde Md BT;AVENIR;Ayuthaya;Bandy;Bangla Sangam MN;Bank Gothic;BankGothic Md BT;Baskerville;Baskerville Old Face;Batang;BatangChe;Bauer Bodoni;Bauhaus 93;Bazooka;Bell MT;Bembo;Benguiat Bk BT;Berlin Sans FB;Berlin Sans FB Demi;Bernard MT Condensed;BernhardFashion BT;BernhardMod BT;Big Caslon;BinnerD;Blackadder ITC;BlairMdITC TT;Bodoni 72;Bodoni 72 Oldstyle;Bodoni 72 Smallcaps;Bodoni MT;Bodoni MT Black;Bodoni MT Condensed;Bodoni MT Poster Compressed;Bookshelf Symbol 7;Boulder;Bradley Hand;Bradley Hand ITC;Bremen Bd BT;Britannic Bold;Broadway;Browallia New;BrowalliaUPC;Brush Script MT;Californian FB;Calisto MT;Calligrapher;Candara;CaslonOpnface BT;Castellar;Centaur;Cezanne;CG Omega;CG Times;Chalkboard;Chalkboard SE;Chalkduster;Charlesworth;Charter Bd BT;Charter BT;Chaucer;ChelthmITC Bk BT;Chiller;Clarendon;Clarendon Condensed;CloisterBlack BT;Cochin;Colonna MT;Constantia;Cooper Black;Copperplate;Copperplate Gothic;Copperplate Gothic Bold;Copperplate Gothic Light;CopperplGoth Bd BT;Corbel;Cordia New;CordiaUPC;Cornerstone;Coronet;Cuckoo;Curlz MT;DaunPenh;Dauphin;David;DB LCD Temp;DELICIOUS;Denmark;DFKai-SB;Didot;DilleniaUPC;DIN;DokChampa;Dotum;DotumChe;Ebrima;Edwardian Script ITC;Elephant;English 111 Vivace BT;Engravers MT;EngraversGothic BT;Eras Bold ITC;Eras Demi ITC;Eras Light ITC;Eras Medium ITC;EucrosiaUPC;Euphemia;Euphemia UCAS;EUROSTILE;Exotc350 Bd BT;FangSong;Felix Titling;Fixedsys;FONTIN;Footlight MT Light;Forte;FrankRuehl;Fransiscan;Freefrm721 Blk BT;FreesiaUPC;Freestyle Script;French Script MT;FrnkGothITC Bk BT;Fruitger;FRUTIGER;Futura;Futura Bk BT;Futura Lt BT;Futura Md BT;Futura ZBlk BT;FuturaBlack BT;Gabriola;Galliard BT;Gautami;Geeza Pro;Geometr231 BT;Geometr231 Hv BT;Geometr231 Lt BT;GeoSlab 703 Lt BT;GeoSlab 703 XBd BT;Gigi;Gill Sans;Gill Sans MT;Gill Sans MT Condensed;Gill Sans MT Ext Condensed Bold;Gill Sans Ultra Bold;Gill Sans Ultra Bold Condensed;Gisha;Gloucester MT Extra Condensed;GOTHAM;GOTHAM BOLD;Goudy Old Style;Goudy Stout;GoudyHandtooled BT;GoudyOLSt BT;Gujarati Sangam MN;Gulim;GulimChe;Gungsuh;GungsuhChe;Gurmukhi MN;Haettenschweiler;Harlow Solid Italic;Harrington;Heather;Heiti SC;Heiti TC;HELV;Herald;High Tower Text;Hiragino Kaku Gothic ProN;Hiragino Mincho ProN;Hoefler Text;Humanst 521 Cn BT;Humanst521 BT;Humanst521 Lt BT;Imprint MT Shadow;Incised901 Bd BT;Incised901 BT;Incised901 Lt BT;INCONSOLATA;Informal Roman;Informal011 BT;INTERSTATE;IrisUPC;Iskoola Pota;JasmineUPC;Jazz LET;Jenson;Jester;Jokerman;Juice ITC;Kabel Bk BT;Kabel Ult BT;Kailasa;KaiTi;Kalinga;Kannada Sangam MN;Kartika;Kaufmann Bd BT;Kaufmann BT;Khmer UI;KodchiangUPC;Kokila;Korinna BT;Kristen ITC;Krungthep;Kunstler Script;Lao UI;Latha;Leelawadee;Letter Gothic;Levenim MT;LilyUPC;Lithograph;Lithograph Light;Long Island;Lydian BT;Magneto;Maiandra GD;Malayalam Sangam MN;Malgun Gothic;Mangal;Marigold;Marion;Marker Felt;Market;Marlett;Matisse ITC;Matura MT Script Capitals;Meiryo;Meiryo UI;Microsoft Himalaya;Microsoft JhengHei;Microsoft New Tai Lue;Microsoft PhagsPa;Microsoft Tai Le;Microsoft Uighur;Microsoft YaHei;Microsoft Yi Baiti;MingLiU;MingLiU_HKSCS;MingLiU_HKSCS-ExtB;MingLiU-ExtB;Minion;Minion Pro;Miriam;Miriam Fixed;Mistral;Modern;Modern No. 20;Mona Lisa Solid ITC TT;Mongolian Baiti;MONO;MoolBoran;Mrs Eaves;MS LineDraw;MS Mincho;MS PMincho;MS Reference Specialty;MS UI Gothic;MT Extra;MUSEO;MV Boli;Nadeem;Narkisim;NEVIS;News Gothic;News GothicMT;NewsGoth BT;Niagara Engraved;Niagara Solid;Noteworthy;NSimSun;Nyala;OCR A Extended;Old Century;Old English Text MT;Onyx;Onyx BT;OPTIMA;Oriya Sangam MN;OSAKA;OzHandicraft BT;Palace Script MT;Papyrus;Parchment;Party LET;Pegasus;Perpetua;Perpetua Titling MT;PetitaBold;Pickwick;Plantagenet Cherokee;Playbill;PMingLiU;PMingLiU-ExtB;Poor Richard;Poster;PosterBodoni BT;PRINCETOWN LET;Pristina;PTBarnum BT;Pythagoras;Raavi;Rage Italic;Ravie;Ribbon131 Bd BT;Rockwell;Rockwell Condensed;Rockwell Extra Bold;Rod;Roman;Sakkal Majalla;Santa Fe LET;Savoye LET;Sceptre;Script;Script MT Bold;SCRIPTINA;Serifa;Serifa BT;Serifa Th BT;ShelleyVolante BT;Sherwood;Shonar Bangla;Showcard Gothic;Shruti;Signboard;SILKSCREEN;SimHei;Simplified Arabic;Simplified Arabic Fixed;SimSun;SimSun-ExtB;Sinhala Sangam MN;Sketch Rockwell;Skia;Small Fonts;Snap ITC;Snell Roundhand;Socket;Souvenir Lt BT;Staccato222 BT;Steamer;Stencil;Storybook;Styllo;Subway;Swis721 BlkEx BT;Swiss911 XCm BT;Sylfaen;Synchro LET;System;Tamil Sangam MN;Technical;Teletype;Telugu Sangam MN;Tempus Sans ITC;Terminal;Thonburi;Traditional Arabic;Trajan;TRAJAN PRO;Tristan;Tubular;Tunga;Tw Cen MT;Tw Cen MT Condensed;Tw Cen MT Condensed Extra Bold;TypoUpright BT;Unicorn;Univers;Univers CE 55 Medium;Univers Condensed;Utsaah;Vagabond;Vani;Vijaya;Viner Hand ITC;VisualUI;Vivaldi;Vladimir Script;Vrinda;Westminster;WHITNEY;Wide Latin;ZapfEllipt BT;ZapfHumnst BT;ZapfHumnst Dm BT;Zapfino;Zurich BlkEx BT;Zurich Ex BT;ZWAdobeF\".split(\";\"); for (var f = f.concat([]), g = document.getElementsByTagName(\"body\")[0], p = document.createElement(\"div\"), h = document.createElement(\"div\"), m = {}, l = {}, k = [], n = 0, q = e.length; n step 2 : 加密基本信息 chromeHelper.prototype = { /** * 加密浏览器基本信息,来源于aa的get中var f = c.x64hash128(d.join(\"~~~\"), 31); */ encryptedBasicInfoArr: function(basicInfoArr) { // 剔除无效 undefined 数据并处理数组对象 concatArr = []; for (i = 0; i x64hash128 chromeHelper.prototype = { x64hash128: function(a, b) { a = a || \"\"; b = b || 0; for (var c = a.length % 16, d = a.length - c, e = [0, b], f = [0, b], g, p, h = [2277735313, 289559509], m = [1291169091, 658871167], l = 0; l >> 0).toString(16)).slice(-8) + (\"00000000\" + (e[1] >>> 0).toString(16)).slice(-8) + (\"00000000\" + (f[0] >>> 0).toString(16)).slice(-8) + (\"00000000\" + (f[1] >>> 0).toString(16)).slice(-8) } } x64Multiply chromeHelper.prototype = { x64Multiply: function(a, b) { a = [a[0] >>> 16, a[0] & 65535, a[1] >>> 16, a[1] & 65535]; b = [b[0] >>> 16, b[0] & 65535, b[1] >>> 16, b[1] & 65535]; var c = [0, 0, 0, 0]; c[3] += a[3] * b[3]; c[2] += c[3] >>> 16; c[3] &= 65535; c[2] += a[2] * b[3]; c[1] += c[2] >>> 16; c[2] &= 65535; c[2] += a[3] * b[2]; c[1] += c[2] >>> 16; c[2] &= 65535; c[1] += a[1] * b[3]; c[0] += c[1] >>> 16; c[1] &= 65535; c[1] += a[2] * b[2]; c[0] += c[1] >>> 16; c[1] &= 65535; c[1] += a[3] * b[1]; c[0] += c[1] >>> 16; c[1] &= 65535; c[0] += a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0]; c[0] &= 65535; return [c[0] x64Rotl chromeHelper.prototype = { x64Rotl: function(a, b) { b %= 64; if (32 === b) return [a[1], a[0]]; if (32 > b) return [a[0] >> 32 - b, a[1] >> 32 - b]; b -= 32; return [a[1] >> 32 - b, a[0] >> 32 - b] } } x64Add chromeHelper.prototype = { x64Add: function(a, b) { a = [a[0] >>> 16, a[0] & 65535, a[1] >>> 16, a[1] & 65535]; b = [b[0] >>> 16, b[0] & 65535, b[1] >>> 16, b[1] & 65535]; var c = [0, 0, 0, 0]; c[3] += a[3] + b[3]; c[2] += c[3] >>> 16; c[3] &= 65535; c[2] += a[2] + b[2]; c[1] += c[2] >>> 16; c[2] &= 65535; c[1] += a[1] + b[1]; c[0] += c[1] >>> 16; c[1] &= 65535; c[0] += a[0] + b[0]; c[0] &= 65535; return [c[0] x64Xor chromeHelper.prototype = { x64Xor: function(a, b) { return [a[0] ^ b[0], a[1] ^ b[1]] } } x64Fmix chromeHelper.prototype = { x64Fmix: function(a) { a = this.x64Xor(a, [0, a[0] >>> 1]); a = this.x64Multiply(a, [4283543511, 3981806797]); a = this.x64Xor(a, [0, a[0] >>> 1]); a = this.x64Multiply(a, [3301882366, 444984403]); return a = this.x64Xor(a, [0, a[0] >>> 1]) } } x64LeftShift chromeHelper.prototype = { x64LeftShift: function(a, b) { b %= 64; return 0 === b ? a : 32 > b ? [a[0] >> 32 - b, a[1] step 3 : 获取更多信息 chromeHelper.prototype = { /** * 获取浏览器更多信息,来源于getpackStr中的b = b.concat(this.moreInfoArray); */ getDfpMoreInfo: function(basicInfoArr, encryptedStr) { // 更多信息 var moreInfoArr = []; // 添加画布信息 moreInfoArr.push(this.getCanvansCode(encryptedStr + \"\")); // 添加浏览器本地存储累以及语言插件类信息 for (var index in basicInfoArr) { var name = basicInfoArr[index].key; var value = basicInfoArr[index].value + \"\"; switch (name) { case \"session_storage\": moreInfoArr.push(this.getSessionStorageCode(value)); break; case \"local_storage\": moreInfoArr.push(this.getLocalStorageCode(value)); break; case \"indexed_db\": moreInfoArr.push(this.getIndexedDbCode(value)); break; case \"open_database\": moreInfoArr.push(this.getOpenDatabaseCode(value)); break; case \"do_not_track\": moreInfoArr.push(this.getDoNotTrackCode(value)); break; case \"regular_plugins\": moreInfoArr.push(this.getPluginsCode()); break; case \"adblock\": moreInfoArr.push(this.getAdblockCode(value)); break; case \"has_lied_languages\": moreInfoArr.push(this.getHasLiedLanguagesCode(value)); break; case \"has_lied_resolution\": moreInfoArr.push(this.getHasLiedResolutionCode(value)); break; case \"has_lied_os\": moreInfoArr.push(this.getHasLiedOsCode(value)); break; case \"has_lied_browser\": moreInfoArr.push(this.getHasLiedBrowserCode(value)); break; case \"touch_support\": moreInfoArr.push(this.getTouchSupportCode(value)); break; case \"js_fonts\": moreInfoArr.push(this.getJsFontsCode(value)); break; } } return moreInfoArr; } } 获取画布代码 chromeHelper.prototype = { /** * 获取画布代码,例如: \"9f7fa43e794048f6193187756181b3b9\" */ getCanvansCode: function(code) { return { \"key\": \"webSmartID\", \"value\": code } } } 获取Session存储代码 chromeHelper.prototype = { /** * 获取Session存储代码,例如: \"1\" */ getSessionStorageCode: function(code) { return { \"key\": \"sessionStorage\", \"value\": code } } } 获取Local存储代码 chromeHelper.prototype = { /** * 获取Local存储代码,例如: \"1\" */ getLocalStorageCode: function(code) { return { \"key\": \"localStorage\", \"value\": code } } } 获取IndexedDb存储代码 chromeHelper.prototype = { /** * 获取IndexedDb存储代码,例如: \"1\" */ getIndexedDbCode: function(code) { return { \"key\": \"indexedDb\", \"value\": code } } } 获取Websql存储代码 chromeHelper.prototype = { /** * 获取Websql存储代码,例如: \"1\" */ getOpenDatabaseCode: function(code) { return { \"key\": \"openDatabase\", \"value\": code } } } 获取反追踪代码 chromeHelper.prototype = { /** * 获取反追踪代码,例如: \"unknown\" */ getDoNotTrackCode: function(code) { return { \"key\": \"doNotTrack\", \"value\": code } } } 获取插件代码 chromeHelper.prototype = { /** * 获取插件代码,例如: \"d22ca0b81584fbea62237b14bd04c866\" */ getPluginsCode: function(code) { if (!code) { a = navigator.plugins; var b = \"\"; for (i = 0; i 获取广告拦截代码 chromeHelper.prototype = { /** * 获取广告拦截代码,例如: \"0\" */ getAdblockCode: function(code) { return { \"key\": \"adblock\", \"value\": code } } } 获取说谎语言代码代码 chromeHelper.prototype = { /** * 获取说谎语言代码代码,例如: \"false\" */ getHasLiedLanguagesCode: function(code) { return { \"key\": \"hasLiedLanguages\", \"value\": code } } } 获取说谎分辨率代码代码 chromeHelper.prototype = { /** * 获取说谎分辨率代码代码,例如: \"false\" */ getHasLiedResolutionCode: function(code) { return { \"key\": \"hasLiedResolution\", \"value\": code } } } 获取说谎操作系统代码代码 chromeHelper.prototype = { /** * 获取说谎操作系统代码代码,例如: \"false\" */ getHasLiedOsCode: function(code) { return { \"key\": \"hasLiedOs\", \"value\": code } } } 获取说谎浏览器代码代码 chromeHelper.prototype = { /** * 获取说谎浏览器代码代码,例如: \"false\" */ getHasLiedBrowserCode: function(code) { return { \"key\": \"hasLiedBrowser\", \"value\": code } } } 获取说谎浏览器代码代码 chromeHelper.prototype = { /** * 获取说谎浏览器代码代码,例如: \"99115dfb07133750ba677d055874de87\" */ getTouchSupportCode: function(code) { code = ba(code.replace(RegExp(\",\", \"gm\"), \"#\")); return { \"key\": \"touchSupport\", \"value\": code } } } 获取说谎字体代码 chromeHelper.prototype = { /** * 获取说谎字体代码,例如: \"99115dfb07133750ba677d055874de87\" */ getJsFontsCode: function(code) { code = ba(code.replace(RegExp(\",\", \"gm\"), \"#\")); return { \"key\": \"jsFonts\", \"value\": code } } } step 4 : 获取机器信息 chromeHelper.prototype = { /** * 机器码信息,来源于getpackStr中的this.getMachineCode() */ getMachineCode: function() { // 机器码信息,若数据无效则返回 new var machineCodeArr = []; // uuid代码 machineCodeArr.push(this.getUUIDCode()); // cookie代码 machineCodeArr.push(this.getCookieCode()); // 用户代理代码 machineCodeArr.push(this.getUserAgentCode(1)); // 源高度代码 machineCodeArr.push(this.getScrHeightCode(1)); // 源宽度代码 machineCodeArr.push(this.getScrWidthCode(1)); // 可用高度代码 machineCodeArr.push(this.getScrAvailHeightCode(1)); // 可用宽度代码 machineCodeArr.push(this.getScrAvailWidthCode(1)); // 颜色深度代码 machineCodeArr.push(this.getMd5ScrColorDepthCode(1)); // 源设备XDPI代码 machineCodeArr.push(this.getScrDeviceXDPICode()); // app代码名称代码 machineCodeArr.push(this.getAppCodeNameCode(1)); // app名称代码 machineCodeArr.push(this.getAppNameCode(1)); // Java是否启用代码 machineCodeArr.push(this.getJavaEnabledCode(1)); // 媒体类型代码 machineCodeArr.push(this.getMimeTypesCode(1)); // 平台代码 machineCodeArr.push(this.getPlatformCode(1)); // app次版本代码 machineCodeArr.push(this.getAppMinorVersionCode()); // 浏览器语言代码 machineCodeArr.push(this.getBrowserLanguageCode(1)); // Cookie是否启用代码 machineCodeArr.push(this.getCookieEnabledCode(1)); // Cpu类型代码 machineCodeArr.push(this.getCpuClassCode()); // 是否在线代码 machineCodeArr.push(this.getOnLineCode(1)); // 系统语言代码 machineCodeArr.push(this.getSystemLanguageCode()); // 用户语言代码 machineCodeArr.push(this.getUserLanguageCode()); // 时区偏移代码 machineCodeArr.push(this.getTimeZoneCode(1)); // flash版本代码 machineCodeArr.push(this.getFlashVersionCode(1)); // 历史记录条数代码 machineCodeArr.push(this.getHistoryListCode(1)); // 自定义ID代码 machineCodeArr.push(this.getCustIdCode()); // 发送平台代码 machineCodeArr.push(this.getSendPlatformCode()); return machineCodeArr; } } 浏览器uuid代码 chromeHelper.prototype = { /** * 浏览器uuid代码,例如: \"new\" */ getUUIDCode: function() { return { \"key\": \"cookieCode\", \"value\": \"new\" } } } cookie 代码 chromeHelper.prototype = { /** * cookie 代码,例如: \"new\" | \"FGH3kVgP6_yvbDscWdJ4k6j4SoYuWlLf\" */ getCookieCode: function(trueOrFake) { // 初次加载时可忽略该选项,再次加载时会读取本地缓存查找RAIL_DEVICEID的值,该值正是初次请求/otn/HttpZF/logdevice返回的cookieCode return { \"key\": \"cookieCode\", \"value\": \"new\" } } } 用户代理代码 chromeHelper.prototype = { /** * 用户代理代码,例如: \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36\" */ getUserAgentCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"userAgent\", \"value\": navigator.userAgent.replace(/\\&|\\+/g, \"\").toString() } } return { \"key\": \"userAgent\", \"value\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36\" } } } 源高度代码 chromeHelper.prototype = { /** * 源高度代码,例如: \"800\" */ getScrHeightCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"scrHeight\", \"value\": window.screen.height.toString() } } return { \"key\": \"scrHeight\", \"value\": \"800\" } } } 源宽度代码 chromeHelper.prototype = { /** * 源宽度代码,例如: \"1280\" */ getScrWidthCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"scrWidth\", \"value\": window.screen.width.toString() } } return { \"key\": \"scrWidth\", \"value\": \"1280\" } } } 可用宽度代码 chromeHelper.prototype = { /** * 可用宽度代码,例如: \"777\" */ getScrAvailHeightCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"scrAvailHeight\", \"value\": window.screen.availHeight.toString() } } return { \"key\": \"scrAvailHeight\", \"value\": \"777\" } } } 可用宽度代码 chromeHelper.prototype = { /** * 可用宽度代码,例如: \"1280\" */ getScrAvailWidthCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"scrAvailWidth\", \"value\": window.screen.availWidth.toString() } } return { \"key\": \"scrAvailWidth\", \"value\": \"1280\" } } } 颜色深度代码 chromeHelper.prototype = { /** * 颜色深度代码,例如: \"24\" */ getMd5ScrColorDepthCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"scrColorDepth\", \"value\": window.screen.colorDepth.toString() } } return { \"key\": \"scrColorDepth\", \"value\": \"24\" } } } 源设备XDPI代码 chromeHelper.prototype = { /** * 源设备XDPI代码(Chrome 不支持!!!),例如: \"\" */ getScrDeviceXDPICode: function() { return { \"key\": \"scrDeviceXDPI\", \"value\": \"\" } } } app代码名称代码 chromeHelper.prototype = { /** * app代码名称代码,例如: \"Mozilla\" */ getAppCodeNameCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"appCodeName\", \"value\": navigator.appCodeName.toString() } } return { \"key\": \"appCodeName\", \"value\": \"Mozilla\" } } } app名称代码 chromeHelper.prototype = { /** * app名称代码,例如: \"Netscape\" */ getAppNameCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"appName\", \"value\": navigator.appName.toString() } } return { \"key\": \"appName\", \"value\": \"Netscape\" } } } Java是否启用代码 chromeHelper.prototype = { /** * Java是否启用代码,例如: \"0\" */ getJavaEnabledCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"javaEnabled\", \"value\": navigator.javaEnabled() ? \"1\" : \"0\" } } return { \"key\": \"javaEnabled\", \"value\": \"0\" } } } 媒体类型代码 chromeHelper.prototype = { /** * 媒体类型代码,例如: \"52d67b2a5aa5e031084733d5006cc664\" */ getMimeTypesCode: function(trueOrFake) { if (trueOrFake) { for (var a = navigator.mimeTypes, b = \"\", c = 0; c 平台代码 chromeHelper.prototype = { /** * 平台代码,例如: \"MacIntel\" */ getPlatformCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"os\", \"value\": navigator.platform.toString() } } return { \"key\": \"os\", \"value\": \"MacIntel\" } } } app次版本信息 chromeHelper.prototype = { /** * app次版本信息(Chrome 不支持!!!),例如: \"\" */ getAppMinorVersionCode: function() { return { \"key\": \"appMinorVersion\", \"value\": \"\" } } } 浏览器语言代码 chromeHelper.prototype = { /** * 浏览器语言代码,例如: \"zh-CN\" */ getBrowserLanguageCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"browserLanguage\", \"value\": navigator.language.toString() } } return { \"key\": \"browserLanguage\", \"value\": \"zh-CN\" } } } cookie是否启用代码 chromeHelper.prototype = { /** * cookie是否启用代码,例如: \"1\" */ getCookieEnabledCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"cookieEnabled\", \"value\": navigator.cookieEnabled ? \"1\" : \"0\" } } return { \"key\": \"cookieEnabled\", \"value\": \"1\" } } } cpu类型代码 chromeHelper.prototype = { /** * cpu类型代码(Chrome 不支持!!!),例如: \"\" */ getCpuClassCode: function() { return { \"key\": \"cpuClass\", \"value\": \"\" } } } 是否在线代码 chromeHelper.prototype = { /** * 是否在线代码,例如: \"true\" */ getOnLineCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"onLine\", \"value\": navigator.onLine.toString() } } return { \"key\": \"onLine\", \"value\": \"true\" } } } 系统语言代码 chromeHelper.prototype = { /** * 系统语言代码(Chrome 不支持!!!),例如: \"\" */ getSystemLanguageCode: function() { return { \"key\": \"systemLanguage\", \"value\": \"\" } } } 用户语言代码 chromeHelper.prototype = { /** * 用户语言代码(Chrome 不支持!!!),例如: \"\" */ getUserLanguageCode: function() { return { \"key\": \"userLanguage\", \"value\": \"\" } } } 偏移时区代码 chromeHelper.prototype = { /** * 偏移时区代码,例如: -8 */ getTimeZoneCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"timeZone\", \"value\": (new Date).getTimezoneOffset() / 60 } } return { \"key\": \"timeZone\", \"value\": -8 } } } flash 版本代码 chromeHelper.prototype = { /** * flash 版本代码,例如: 0 */ getFlashVersionCode: function(trueOrFake) { if (trueOrFake) { var code = 0; navigator.plugins && 0 历史记录条数代码 chromeHelper.prototype = { /** * 历史记录条数代码,例如: 2 */ getHistoryListCode: function(trueOrFake) { if (trueOrFake) { return { \"key\": \"historyList\", \"value\": window.history.length } } return { \"key\": \"historyList\", \"value\": 2 } } } 自定义ID代码 chromeHelper.prototype = { /** * 自定义ID代码,例如: \"133\" */ getCustIdCode: function() { return { \"key\": \"custID\", \"value\": \"133\" } } } 发送平台代码 chromeHelper.prototype = { /** * 发送平台代码,例如: \"WEB\" */ getSendPlatformCode: function() { return { \"key\": \"platform\", \"value\": \"WEB\" } } } step 5 : 合成指纹信息 chromeHelper.prototype = { /** * 获取浏览器原始指纹信息,来源于initEc中的l = c.getpackStr(b) */ getOriginBrowserFingerPrintInfo: function() { // 浏览器指纹信息 var originBrowserFingerPrintArr = []; // 基本信息,用于生成更多信息 var basicInfoArr = this.getBasicInfoArr(); // 基本信息加密摘要 var encryptedStr = this.encryptedBasicInfoArr(basicInfoArr); // 更多信息,用于组合机器码信息 var moreInfoArr = this.getDfpMoreInfo(basicInfoArr, encryptedStr); // 机器码信息 var machineCodeArr = this.getMachineCode(moreInfoArr); // 组合信息并重新排序 originBrowserFingerPrintArr = this.concatMachineCodeAndDfpMoreInfo(machineCodeArr, moreInfoArr); return originBrowserFingerPrintArr; } } chromeHelper.prototype = { /** * 组合机器码和浏览器更多信息构成原始指纹,来源于getpackStr中的getpackStr */ concatMachineCodeAndDfpMoreInfo: function(machineCodeArr, moreInfoArr) { // 机器码合并更多信息 var tempArr = machineCodeArr.concat(moreInfoArr); // 重新排序 tempArr.sort(function(a, b) { var c, d; if (\"object\" === typeof a && \"object\" === typeof b && a && b) return c = a.key, d = b.key, c === d ? 0 : typeof c === typeof d ? c step 6 : 重新分类指纹 chromeHelper.prototype = { /** * 获取浏览器指纹信息,来源于initEc中的k.push(new p(\"scrAvailSize\",h)); */ getClassifiedBrowserFingerPrintInfo: function() { // 浏览器指纹信息 var originBrowserFingerPrintArr = this.getOriginBrowserFingerPrintInfo(); // 分类键名 var Gb = \"appCodeName appMinorVersion appName cpuClass onLine systemLanguage userLanguage historyList hasLiedLanguages hasLiedResolution hasLiedOs hasLiedBrowser\".split(\" \"), Hb = [\"scrAvailWidth\", \"scrAvailHeight\"], Ib = [\"scrDeviceXDPI\", \"scrColorDepth\", \"scrWidth\", \"scrHeight\"], Jb = [\"sessionStorage\", \"localStorage\", \"indexedDb\", \"openDatabase\"]; // 本地存储类,键名对应 Jb var storeDbArr = []; // 屏幕实际尺寸类,键名对应 Ib var srcScreenSizeArr = []; // 屏幕可用尺寸类,键名对应 Hb var scrAvailSizeArr = []; // 其他类也是分类后的浏览器指纹信息 var classifiedBrowserFingerPrintArr = [] // 提取出本地存储类,屏幕实际尺寸类,屏幕可用尺寸类以及其他类 for (var i = 0; i step 7 : 加密分类指纹 chromeHelper.prototype = { /** * 获取初始化浏览器设备信息,来源于initEc中的e = c.hashAlg(k, a, e); */ encryptedFingerPrintInfo: function() { // 获取分类后的浏览器指纹信息 classifiedBrowserFingerPrintInfoArr = this.getClassifiedBrowserFingerPrintInfo(); encryptedFingerPrintInfoMap = this.hashAlg(classifiedBrowserFingerPrintInfoArr, \"\", \"\"); return encryptedFingerPrintInfoMap; } } hashAlg chromeHelper.prototype = { /** * 加密算法每天都可能更新,主要是调整加密次序,核心逻辑并没有实质性改变! */ hashAlg: function(data, param, hashcode) { // 将对象数组按照字母表排序键名 data = this.sortArray(data); // 处理对象数组中的值并加密参数键名 param_hashcode_map = this.encryptedKeyInArray(data, param, hashcode); param = param_hashcode_map['param']; hashcode = param_hashcode_map['hashcode']; // 反转字符串 reverse_hashcode = this.reverse(hashcode); // 按照次序分为两部分 reverse_two_part_hashcode = this.split2partInOrder(reverse_hashcode); // 按照次序分成三部分 reverse_two_part_three_part_hashcode = this.split3partInOrder(reverse_two_part_hashcode); // 按照次序分为两部分 reverse_two_part_three_part_two_part_hashcode = this.split2partInOrder(reverse_two_part_three_part_hashcode); // 转换成字母码 reverse_two_part_three_part_two_part_charCode_hashcode = this.covert2charCode(reverse_two_part_three_part_two_part_hashcode) // 对请求参数进行第三次加密 encrypted_hashcode = ya(reverse_two_part_three_part_two_part_charCode_hashcode); return { \"key\": param, \"value\": encrypted_hashcode } } } sortArray chromeHelper.prototype = { sortArray: function(data) { // 数据列表进行排序,按照字母表升序排序 data.sort(function(self, other) { // 正在参与比较的键名和另外的键名 var selfKey, otherKey; // 参与比较的对象是键值对map类型 if (\"object\" === typeof self && \"object\" === typeof other && self && other) { selfKey = self.key; otherKey = other.key; if (selfKey === otherKey) { return 0; } else { if (typeof selfKey === typeof otherKey) { if (selfKey encryptedKeyInArray chromeHelper.prototype = { encryptedKeyInArray: function(data, param, hashcode) { // 原始参数和加密参数对应关系 var param_relationship = { adblock: \"FMQw\", scrAvailSize: \"TeRS\", appMinorVersion: \"qBVW\", scrColorDepth: \"qmyu\", userLanguage: \"hLzX\", hasLiedLanguages: \"j5po\", systemLanguage: \"e6OK\", scrHeight: \"5Jwy\", plugins: \"ks0Q\", historyList: \"kU5z\", storeDb: \"Fvje\", timeZone: \"q5aJ\", appcodeName: \"qT7b\", hasLiedResolution: \"3neK\", hasLiedBrowser: \"2xC5\", doNotTrack: \"VEek\", indexedDb: \"3sw-\", mimeTypes: \"jp76\", cookieEnabled: \"VPIf\", online: \"9vyE\", browserName: \"-UVA\", scrAvailHeight: \"88tV\", scrAvailWidth: \"E-lJ\", cookieCode: \"VySQ\", hasLiedOs: \"ci5c\", userAgent: \"0aew\", scrDeviceXDPI: \"3jCe\", webSmartID: \"E3gR\", cpuClass: \"Md7A\", localStorage: \"XM7l\", scrWidth: \"ssI5\", jsFonts: \"EOQP\", browserVersion: \"d435\", localCode: \"lEnu\", os: \"hAqN\", openDatabase: \"V8vl\", browserLanguage: \"q4f3\", flashVersion: \"dzuS\", srcScreenSize: \"tOHY\", javaEnabled: \"yD16\", touchSupport: \"wNLf\", sessionStorage: \"HVia\" }; // 翻译数据字段实现加密请求参数 for (var i = 0; i reverse chromeHelper.prototype = { reverse: function(str) { temp = '' for (i = str.length - 1; 0 split2partInOrder chromeHelper.prototype = { split2partInOrder: function(str) { temp = ''; len = str.length; if (0 == len % 2) { first_str = str.substring(len / 2, len); second_str = str.substring(0, len / 2); temp = first_str + second_str; } else { first_str = str.substring(len / 2 + 1, len); second_str = str.charAt(len / 2); third_str = str.substring(0, len / 2); temp = first_str + second_str + third_str; } return temp } } split3partInOrder chromeHelper.prototype = { split3partInOrder: function(str) { temp = \"\"; temp_len = 0; len = str.length; if (0 == len % 3) { temp_len = parseInt(len / 3); } else { temp_len = parseInt(len / 3) + 1; } if (3 > len) { temp = str; } else { first_str = str.substring(0, 1 * temp_len); second_str = str.substring(1 * temp_len, 2 * temp_len); third_str = str.substring(2 * temp_len, len); temp = second_str + third_str + first_str; } return temp }, } covert2charCode chromeHelper.prototype = { covert2charCode: function(str) { temp = \"\"; len = str.length; for (i = 0; i 加密算法核心代码 var Ja; if (!(Ja = Y)) { var da = Math, pa = {}, qa = pa.lib = {}, ab = function() {}, ea = qa.Base = { create: function() { var a = this.extend(); a.init.apply(a, arguments); return a }, init: function() {}, clone: function() { return this.init.prototype.extend(this) }, mixIn: function(a) { for (var b in a) a.hasOwnProperty(b) && (this[b] = a[b]); a.hasOwnProperty(\"toString\") && (this.toString = a.toString) }, extend: function(a) { ab.prototype = this; var b = new ab; a && b.mixIn(a); b.hasOwnProperty(\"init\") || (b.init = function() { b.$super.init.apply(this, arguments) }); b.init.prototype = b; b.$super = this; return b } }, fa = qa.WordArray = ea.extend({ clone: function() { var a = ea.clone.call(this); a.words = this.words.slice(0); return a }, init: function(a, b) { a = this.words = a || []; this.sigBytes = void 0 != b ? b : 4 * a.length }, toString: function(a) { return (a || vb).stringify(this) }, random: function(a) { for (var b = [], c = 0; c >> 2] |= (c[e >>> 2] >>> 24 - e % 4 * 8 & 255) >> 2] = c[e >>> 2]; else b.push.apply(b, c); this.sigBytes += a; return this }, clamp: function() { var a = this.words, b = this.sigBytes; a[b >>> 2] &= 4294967295 >> 3] |= parseInt(a.substr(d, 2), 16) >> 2] >>> 24 - d % 4 * 8 & 255; c.push((e >>> 4).toString(16)); c.push((e & 15).toString(16)) } return c.join(\"\") } }, bb = Ka.Latin1 = { stringify: function(a) { var b = a.words; a = a.sigBytes; for (var c = [], d = 0; d >> 2] >>> 24 - d % 4 * 8 & 255)); return c.join(\"\") }, parse: function(a) { for (var b = a.length, c = [], d = 0; d >> 2] |= (a.charCodeAt(d) & 255) ga;) { var W; a: { W = ua; for (var zb = ra.sqrt(W), La = 2; La ga && (db[ga] = Ya(ra.pow(ua, .5))), eb[ga] = Ya(ra.pow(ua, 1 / 3)), ga++); ua++ } var S = [], R = R.SHA256 = ta.extend({ _doFinalize: function() { var a = this._data, b = a.words, c = 8 * this._nDataBytes, d = 8 * a.sigBytes; b[d >>> 5] |= 128 >> 9 >> 9 l; l++) { if (16 > l) S[l] = a[b + l] | 0; else { var p = S[l - 15], n = S[l - 2]; S[l] = ((p >> 7) ^ (p >> 18) ^ p >>> 3) + S[l - 7] + ((n >> 17) ^ (n >> 19) ^ n >>> 10) + S[l - 16] } p = N + ((m >> 6) ^ (m >> 11) ^ (m >> 25)) + (m & k ^ ~m & g) + eb[l] + S[l]; n = ((d >> 2) ^ (d >> 13) ^ (d >> 22)) + (d & e ^ d & f ^ e & f); N = g; g = k; k = m; m = h + p | 0; h = f; f = e; e = d; d = p + n | 0 } c[0] = c[0] + d | 0; c[1] = c[1] + e | 0; c[2] = c[2] + f | 0; c[3] = c[3] + h | 0; c[4] = c[4] + m | 0; c[5] = c[5] + k | 0; c[6] = c[6] + g | 0; c[7] = c[7] + N | 0 }, clone: function() { var a = ta.clone.call(this); a._hash = this._hash.clone(); return a }, _doReset: function() { this._hash = new yb.init(db.slice(0)) } }); sa.SHA256 = ta._createHelper(R); sa.HmacSHA256 = ta._createHmacHelper(R); var fb = Y, Ab = fb.lib.WordArray; fb.enc.Base64 = { parse: function(a) { var b = a.length, c = this._map, d = c.charAt(64); d && (d = a.indexOf(d), -1 != d && (b = d)); for (var d = [], e = 0, f = 0; f >> 6 - f % 4 * 2; d[e >>> 2] |= (h | m) >> 2] >>> 24 - e % 4 * 8 & 255) >> 2] >>> 24 - (e + 1) % 4 * 8 & 255) >> 2] >>> 24 - (e + 2) % 4 * 8 & 255, h = 0; 4 > h && e + .75 * h >> 6 * (3 - h) & 63)); if (b = d.charAt(64)) for (; a.length % 4;) a.push(b); return a.join(\"\") }, _map: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\" }; function Ya(a) { return 4294967296 * (a - (a | 0)) | 0 } /** * 标准加密算法实现 */ function ya(a) { return Y.SHA256(a).toString(Y.enc.Base64) } /** * 自定义加密算法实现 */ function rb(a) { var b = a.split(\".\"); if (4 !== b.length) throw Error(\"Invalid format -- expecting a.b.c.d\"); for (var c = a = 0; c d || 255 >>= 0 } return a } function Ya(a) { return 4294967296 * (a - (a | 0)) | 0 } /** * 自定义加密算法实现 */ function ba(a) { var ca = 8; for (var b = [], c = (1 > 5] |= (a.charCodeAt(d / ca) & c) > 5] |= 128 >> 9 > 2] >> d % 4 * 8 + 4 & 15) + a.charAt(b[d >> 2] >> d % 4 * 8 & 15); return c.toLowerCase() } /** * ba->E->A->N */ function E(a, b, c, d, e, f, h) { return A(b & c | ~b & d, a, b, e, f, h) } /** * ba->C->A->N */ function C(a, b, c, d, e, f, h) { return A(b & d | c & ~d, a, b, e, f, h) } /** * ba->A->N */ function A(a, b, c, d, e, f) { a = N(N(b, a), N(d, f)); return N(a >> 32 - e, c) } /** * ba->D->A->N */ function D(a, b, c, d, e, f, h) { return A(c ^ (b | ~d), a, b, e, f, h) } /** * ba->N */ function N(a, b) { var c = (a & 65535) + (b & 65535); return (a >> 16) + (b >> 16) + (c >> 16) 模拟伪装 现在已经还原了算法的实现逻辑,下一步就是如何更好地伪造自己,本文提供临时设置的实现方式,方便在不修改之前复现代码的基础上实现扩展,当然也可以直接在还原算法源码中写入伪造代码. 值得注意的是,这种 Object.defineProperty 方式只会临时生效而且仅仅针对使用 js 代码获取对象属性的值,并不会真正修改对象属性! 设置用户代理 /** * 设置用户代理,检测方式: navigator.userAgent */ chromeHelper.setUserAgent = function(userAgent) { if (!userAgent) { userAgent = \"Mozilla5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit537.36 (KHTML, like Gecko) Chrome80.0.3987.87 Safari537.36\"; } Object.defineProperty(navigator, \"userAgent\", { value: userAgent, writable: false }); } 设置浏览器语言 /** * 设置浏览器语言,检测方式: navigator.language */ chromeHelper.setLanguage = function(language) { if (!language) { language = \"zh-CN\"; } Object.defineProperty(navigator, \"language\", { value: language, writable: false }); } 设置浏览器语言 /** * 设置浏览器语言,检测方式: navigator.languages */ chromeHelper.setLanguages = function(languages) { if (!languages) { languages = [\"zh-CN\", \"zh\", \"en\"]; } Object.defineProperty(navigator, \"languages\", { value: languages, writable: false }); } 设置屏幕颜色深度 /** * 设置屏幕颜色深度,检测方式: screen.colorDepth */ chromeHelper.setColorDepth = function(colorDepth) { if (!colorDepth) { colorDepth = 24; } Object.defineProperty(screen, \"colorDepth\", { value: colorDepth, writable: false }); } 设置设备像素比率 /** * 设置设备像素比率,检测方式: window.devicePixelRatio */ chromeHelper.setDevicePixelRatio = function(devicePixelRatio) { if (!devicePixelRatio) { devicePixelRatio = 24; } Object.defineProperty(window, \"devicePixelRatio\", { value: devicePixelRatio, writable: false }); } 设置屏幕宽度 /** * 设置屏幕宽度,检测方式: screen.width */ chromeHelper.setWidth = function(width) { if (!width) { width = 1280; } Object.defineProperty(screen, \"width\", { value: width, writable: false }); } 设置屏幕高度 /** * 设置屏幕高度,检测方式: screen.height */ chromeHelper.setHeight = function(height) { if (!height) { height = 800; } Object.defineProperty(screen, \"height\", { value: height, writable: false }); } 设置屏幕可用宽度 /** * 设置屏幕可用宽度,检测方式: screen.availWidth */ chromeHelper.setAvailWidth = function(availWidth) { if (!availWidth) { availWidth = 1280; } Object.defineProperty(screen, \"availWidth\", { value: availWidth, writable: false }); } 设置屏幕可用高度 /** * 设置屏幕可用高度,检测方式: screen.availHeight */ chromeHelper.setAvailHeight = function(availHeight) { if (!availHeight) { availHeight = 777; } Object.defineProperty(screen, \"availHeight\", { value: availHeight, writable: false }); } 设置Session存储 /** * 设置Session存储,检测方式: !!window.sessionStorage */ chromeHelper.setSessionStorage = function(sessionStorage) { if (!sessionStorage) { sessionStorage = 1; } if (sessionStorage) { window.sessionStorage = 1 } else { delete window.sessionStorage } } 设置Local存储 /** * 设置Local存储,检测方式: !!window.localStorage */ chromeHelper.setLocalStorage = function(localStorage) { if (!localStorage) { localStorage = 1; } if (localStorage) { window.localStorage = 1 } else { delete window.localStorage } } 设置indexedDB存储 /** * 设置indexedDB存储,检测方式: !!window.indexedDB */ chromeHelper.setIndexedDB = function(indexedDB) { if (!indexedDB) { indexedDB = 1; } if (indexedDB) { window.indexedDB = 1 } else { delete window.indexedDB } } 设置addBehavior存储 /** * 设置addBehavior存储,检测方式: !!document.body.addBehavior */ chromeHelper.setAddBehavior = function(addBehavior) { if (!addBehavior) { addBehavior = 1; } if (addBehavior) { document.body.addBehavior = 1 } else { delete document.body.addBehavior } } 设置Cpu类型 /** * 设置Cpu类型,检测方式: navigator.cpuClass */ chromeHelper.setCpuClass = function(cpuClass) { if (!cpuClass) { cpuClass = \"unknown\"; } Object.defineProperty(navigator, \"cpuClass\", { value: cpuClass, writable: false }); } 设置平台类型 /** * 设置平台类型,检测方式: navigator.platform */ chromeHelper.setPlatform = function(platform) { if (!platform) { platform = \"MacIntel\"; } Object.defineProperty(navigator, \"platform\", { value: platform, writable: false }); } 设置反追踪 /** * 设置反追踪,检测方式: navigator.doNotTrack */ chromeHelper.setDoNotTrack = function(doNotTrack) { if (!doNotTrack) { doNotTrack = \"unknown\"; } Object.defineProperty(navigator, \"doNotTrack\", { value: doNotTrack, writable: false }); } 设置插件 /** * 设置插件,检测方式: navigator.plugins */ chromeHelper.setPlugins = function(plugins) { } 设置Canvas /** * 设置Canvas,检测方式: TODO */ chromeHelper.setCanvas = function(canvas) { } 设置Webgl /** * 设置Webgl,检测方式: TODO */ chromeHelper.setWebgl = function(webgl) { } 设置AdBlock /** * 设置AdBlock,检测方式: TODO */ chromeHelper.setAdBlock = function(AdBlock) { } 设置AdBlock /** * 设置AdBlock,检测方式: TODO */ chromeHelper.setAdBlock = function(AdBlock) { } 设置字体 /** * 设置字体,检测方式: TODO */ chromeHelper.setFonts = function(fonts) { } 设置最多触控点 /** * 设置最多触控点,检测方式: navigator.maxTouchPoints */ chromeHelper.setMaxTouchPoints = function(maxTouchPoints) { if (!maxTouchPoints) { maxTouchPoints = 0; } Object.defineProperty(navigator, \"maxTouchPoints\", { value: maxTouchPoints, writable: false }); } 设置ontouchstart事件 /** * 设置ontouchstart事件,检测方式: \"ontouchstart\" in window */ chromeHelper.setTouchEvent = function(ontouchstart) { if (!ontouchstart) { ontouchstart = false; } if (ontouchstart) { window.ontouchstart = true } else { delete window.ontouchstart } } 设置app代码名称代码 /** * 设置app代码名称代码,检测方式: navigator.appCodeName.toString() */ chromeHelper.setAppCodeName = function(appCodeName) { if (!appCodeName) { appCodeName = \"Mozilla\"; } Object.defineProperty(navigator, \"appCodeName\", { value: appCodeName, writable: false }); } 设置app代码名称代码 /** * 设置app代码名称代码,检测方式: navigator.appName.toString() */ chromeHelper.setAppName = function(appName) { if (!appName) { appName = \"Netscape\"; } Object.defineProperty(navigator, \"appName\", { value: appName, writable: false }); } 设置Java是否启用 /** * 设置Java是否启用,检测方式: navigator.javaEnabled() */ chromeHelper.setJavaEnabled = function(javaEnabled) { } 设置媒体类型 /** * 设置媒体类型,检测方式: navigator.mimeTypes */ chromeHelper.setMimeTypes = function(mimeTypes) { } 设置cookie是否启用 /** * 设置cookie是否启用,检测方式: navigator.cookieEnabled */ chromeHelper.setCookieEnabled = function(cookieEnabled) { if (!cookieEnabled) { cookieEnabled = true; } Object.defineProperty(navigator, \"cookieEnabled\", { value: cookieEnabled, writable: false }); } 设置是否在线 /** * 设置是否在线,检测方式: navigator.onLine.toString() */ chromeHelper.setOnLine = function(onLine) { if (!onLine) { onLine = true; } Object.defineProperty(navigator, \"onLine\", { value: onLine, writable: false }); } 添加历史记录 /** * 添加历史记录,检测方式: window.history */ chromeHelper.pushHistory = function(newUrls) { for (url in newUrls) { history.pushState(null, '', url); } } 使用示例 亲测构造请求 /otn/HttpZF/logdevice时,关于参数 algID 经常性发生变化,因此无法提供静态的请求方法,建议根据实际情况实时改变. 通过翻阅源码实现,最终发现关于发送请求的代码是这样的: e = c.hashAlg(m, a, e); a = e.key; e = e.value; a += \"\\x26timestamp\\x3d\" + (new Date).getTime(); $a.getJSON(\"https://kyfw.12306.cn/otn/HttpZF/logdevice\" + (\"?algID\\x3dmBxuYhGXYR\\x26hashCode\\x3d\" + e + a), null, function(a) { var b = JSON.parse(a); void 0 != mb && mb.postMessage(a, r.parent); for (var d in b) \"dfp\" == d ? G(\"RAIL_DEVICEID\") != b[d] && (V(\"RAIL_DEVICEID\", b[d], 1E3), c.deviceEc.set(\"RAIL_DEVICEID\", b[d])) : \"exp\" == d ? V(\"RAIL_EXPIRATION\", b[d], 1E3) : \"cookieCode\" == d && (c.ec.set(\"RAIL_OkLJUJ\", b[d]), V(\"RAIL_OkLJUJ\", \"\", 0)) }) 其中,参数 a 表示的是加密后的浏览器指纹信息,(new Date).getTime() 是当前时间戳,而 algID\\x3dmBxuYhGXYR\\x26hashCode\\x3d 这部分的 algID 算法参数是暂时性静态的,比如今天一段时间都是 mBxuYhGXYR 而第二天这个值就变成其他值了. hashCode 参数的值就是程序运行结果的 value 值,最后面的变量 a 代表的是剩下的浏览器指纹信息,即运行结果的 key. 假设此时此刻为例,演示如何使用该 js 文件: function ajax(req){ var xhr=new XMLHttpRequest(); xhr.onreadystatechange=function(){ if(xhr.readyState===4){ req.success&&req.success(xhr.responseText,xhr.status); } } req.method=req.method?req.method.toUpperCase():'GET'; var data=null; var url=req.url; if(req.data){ var arg=''; for(var n in req.data){ arg+=n+'='+encodeURIComponent(req.data[n])+'&' } arg=arg.slice(0,-1); if(req.method==='GET'){ url=url+'?'+arg; }else{ data=arg; } } if(req.headers){ for(var h in req.headers){ var v=req.headers[h]; xhr.setRequestHeader(h,v); } } xhr.open(req.method,url); xhr.send(data); } e = chromeHelper.prototype.encryptedFingerPrintInfo(); a = e.key; e = e.value; a += \"\\x26timestamp\\x3d\" + (new Date).getTime(); ajax({ url:\"https://kyfw.12306.cn/otn/HttpZF/logdevice\" + (\"?algID\\x3dmBxuYhGXYR\\x26hashCode\\x3d\" + e + a), success:function(data){ console.log(\"data\",data); startIndex = \"callbackFunction('\".length; endIndex = data.lastIndexOf(\"')\"); jsonStrData = data.substring(startIndex,endIndex); console.log(\"jsonStrData\",jsonStrData); jsonData = JSON.parse(jsonStrData); console.log(\"jsonData\",jsonData); exp = jsonData.exp; cookieCode = jsonData.cookieCode; dfp = jsonData.dfp; console.log(\"RAIL_DEVICEID::: \"+dfp+\" RAIL_EXPIRATION::: \" + exp +\" RAIL_OkLJUJ::: \" + cookieCode); } }); 回顾展望 在还原算法实现过程中,充分复习了 web 前端开发的调试技巧,针对通篇无意义的变量命名方式,有效的应对方式就是采用正则表达式精确匹配进行查找. 同时,为了验证自己的猜想是否正确,还需要结合断点调试,如果存在反调试手段,那么只能靠自己硬啃压缩混淆代码了,我只能说:考验真正技术的时候,到了! 本文给我们留下了不少启发供后续工作学习使用,从开发者的角度上来讲: 不完全依靠现成加密技术,哪怕真正加密时没自己实现也要在加密前后实现自己的混淆逻辑. 例如重新打乱字符串,将字符串分隔成三份,按照首尾中或者尾中首等反人类次序重新排序等. 重复使用同一数据时也不一定要抽象成同一个方法,不同对象调用不同的处理逻辑,更是让人防不胜防,陷入思维惯性误区. 例如获取用户代理采用不同的正则表达式进行替换,获取浏览器语言时采用另外的途径验证上一步获取结果是否造假等. 无序更胜似有序,看似规整优美的代码是给开发人员看的而不该给机器看,一定不能使用源码上线而是要加密处理或者其他处理. 只有要基本的开发经验很容易一叶知秋,进而判断相关技术栈,因为技术都是通用的方案,非常容易复制,打造独特的技术流会增大破解成本,进而吓退一部分菜鸟小白. 前端和后端需要密切配合协同协作,缺少统一指挥难以避免一方或者多方偷懒进而暴露我方阵地. 重要的业务逻辑肯定是放在后端进行处理,哪怕前端已经处理过相同逻辑也不能偷懒,更要保证前后端处理逻辑的一致性. 攻防是矛与盾,作为攻克方要做的一点就是打铁还需自身硬,多了解不同的技术栈才能做到有的放矢而不至于临阵脱逃,望而却步! 最后祝大家抢票愉快,需要买票时人人都有票,再也不需要抢票回家,人生苦短何必浪费生命去抢票? 再次声明,本文仅供学习研究,一切用作它途的行为均与本人无关,如有侵权,烦请告知,谢谢合作. 参考资料 Chrome - JavaScript调试技巧总结(浏览器调试JS) 如何使用Chrome DevTools花式打断点 【译】Chrome浏览器开发者工具的13个有趣技巧——希望你已经掌握 HTML5前端数据库——Web SQL Database localStorage兼容ie6/7 用addBehavior 实现 前端存储之indexedDB localstorage || globalStorage || userData navigator,JS检测浏览器插件 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/tools/12306-algorithm-web-js.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"write/":{"url":"write/","title":"如何写作","keywords":"","body":"如何写作 越来越多的人想写个人博客或者打算做自媒体,笔者也是一样. 最近在学习如何写博客,分享下创作经历,避免走弯路. 首先在于定位,不同的定位决定了不同的平台.由于笔者分享的大多是技术类博客,所以主战场是各大主流的技术类博客论坛,当然还搭建了自己的官网. 考虑到前期仅仅是分享技术博客,无需购买专门的服务器,因此寻求免费的解决方案. 笔者采用的是 github 搭建个人项目官网,优点是不花一分钱,就能免费开启 https 服务,缺点是国内访问速度慢. 下面分为两部分介绍博客的创作平台,一部分是个人官网,另一部分是第三方平台. 个人官网 首先注册 github 账号(例如:用户名 snowdreams1006),然后新建特定规则的项目(例如:项目名 snowdreams1006.github.io),最后在项目下创建首页 index.html .现在访问 https://用户名.github.io/ (https://snowdreams1006.github.io/)即可. 个人官网的基本流程和特点如下: 利用 gitbook 技术将 markdown 源码文件输出为 html 静态网页. 将项目按照特定规则上传到 github 网站公开托管,生成免费网站. 源代码更新后再生成输出文件,然后一起上传到 github,个人官网自动更新. 项目源码 snowdreams1006.github.io,项目官网 https://snowdreams1006.github.io/ 点击图片会自动跳转到 https://snowdreams1006.github.io/ 第三方平台 这里列举了常逛的第三方平台,将从新手视觉看待各家平台特点,试图分析各家平台特点从而决定是否适合自身. 一家之言,仅供各位参考. 排名不分顺序,只是笔者文章依次同步的顺序而已. 1. CSDN https://www.csdn.net/ SEO 优化不错,阅读量稳定,网页端阅读体验一般,手机端干净清爽,阅读量相对稳定. 不限制作者本人点赞,重复统计本人浏览记录,突出阅读数,其次是评论数和点赞数. 2. 博客园 https://www.cnblogs.com/ SEO 优化不错,博客开通需审核,支持发布首页,但也可能因质量不达标而被移除.页面风格满满的时代感,目前暂无手机端. 限制作者本人推荐,不统计本人浏览记录,突出推荐数,其次是阅读量和评论数. 3. 开源中国 https://www.oschina.net/ 国内版 github ,高质量文章可能会被推荐至首页,否则阅读量几乎为零. 限制作者本人点赞,不统计本人浏览记录,突出访问量,其次评论数和点赞数. 4. 简书 https://www.jianshu.com/ 文艺范的自媒体平台,简洁优美文艺性十足,SEO 优化不错,日更活动鼓励持续更新,简书钻和简书贝等虚拟货币增添写作乐趣! 不限制作者本人喜欢,不统计本人浏览记录,突出简书钻,其次阅读量,评论数和点赞数. 5. 思否 https://segmentfault.com/ 国内版Stack OverFlow,专注于技术问答,界面风格绿色清新,SEO 优化不错,但忽略阅读量. 限制作者本人点赞,不统计本人浏览记录,首次发布专栏需要审核,突出投票数,其次是收藏数,最后是阅读数. 6. 掘金 https://juejin.im/timeline 异军突起,风头正盛,时间流布局,掘金小册子是一大亮点,但SEO 很差! 不限制作者本人点赞,不重复统计本人浏览记录,突出点赞数,其次是评论数,最后是阅读量. 7. 慕课网手记 https://www.imooc.com/article 丰富的免费教学视频,正所谓\"成也萧何败萧何\",手记模块相比其他专业平台还有着不少的差距,SEO 一般. 不限制作者本人点赞,重复统计本人浏览记录,文章需要审核,手记功能更像是配套教学视频而诞生的笔记,不太像专门博客. 8. 微信公众号 https://mp.weixin.qq.com/ 目前仅支持富文本编辑器,依靠粉丝流量,碎片化阅读体验,SEO 几乎没有. 需要花费精力运营公众号,限定当天阅读量和\"在看\"数,如果没有粉丝,那肯定没有阅读量. 9. B站专栏 https://www.bilibili.com/ 弹幕视频网站,开通专栏投稿,目前仅支持富文本编辑器,不适合博客,SEO 可忽略. 适合视频教程,暂不适合博客且投稿专栏限制较多,毕竟不是专业做博客的平台,谁让我误入了呢! 总结 大多数平台都有阅读量,评论,点赞等维度数据统计,但不同平台有着不同的推荐策略,优缺点如下: csdn : SEO 不错,阅读量稳定,适合新手积累信心. 博客园 : SEO 不错,阅读量有保障,适合新手提高自信. 开源中国 : SEO 一般,阅读量有挑战,适合优质文章博取官方推荐. 简书 : SEO 不错,阅读量很少,日更活动和简书钻奖励等形式鼓励持续创作,适合自我督促. 思否 : SEO 一般,阅读量一般,适合技术问答. 掘金 : SEO 很差,阅读量一般,适合优质文章. 慕课网手记 : SEO 一般,适合教学视频的配套笔记. 微信公众号 : SEO 很差,适合粉丝用户. B站 : SEO 很差,适合教学视频. 从以上分析中可以看出,只要是优质文章无论到哪都受欢迎,然而\"罗马并非一日建成\",优质文章的诞生不在一朝一夕,所以新手期应该选择适合自己的平台发展,积累到一定程度后方能\"春风得意马蹄疾,一日看遍长安花\". 个人建议: 选择简书平台,保持日更,同步到CSDN积累自信,推送到博客园,提高自信. 官网保持更新,运营公众号慢慢积累粉丝,最后再考虑开源中国,掘金和思否. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"write/markdownOrRichText.html":{"url":"write/markdownOrRichText.html","title":"markdown 和富文本","keywords":"","body":"markdown和富文本 不知道你是否留意过平时写作时的编辑器,有的是 markdown 编辑器,有的是各种富文本编辑器,到底选择哪一个相信你有自己的判断. 如果只是在某一家平台上写作,哪一种编辑器都无所谓,只要你喜欢就好. 可是如果你需要同时发布到各个平台呢?此时,真的需要停下来思考一下,我该使用哪一种编辑器了? 各家的编辑器的界面设计风格迥然不同,不仅按钮排序顺序不一样,而且最终输出效果也不尽相同. 这就给我们带来了一个问题,明明已经排好版的文章,复制到另外一家平台样式不一样了,或者格式被清除了?! 心中一万只羊驼呼啸而过,尽管如此,还是在心里告诉自己要冷静,要冷静! 既然我们追求的一处编写,到处复制,那么我就有必要郑重向你推荐 markdown 编辑器. 简单地说,markdown 编辑器是一种标记语言,写的是源码,输出的是 html. 所以很多情况下, markdown 更适合技术人员写文章,不用关心排版布局,回归写作本质,而富文本格式适合文学工作者,强调布局美观,重视审美体验. 两者看似相互独立,实际上最终展示效果几乎太大差别, markdown 格式和富文本格式最终都输出 html 格式,毕竟绝大多数阅读媒介还是各种浏览器. markdown 语法支持嵌套 html 语法,从而可以实现较为复杂的排版布局. markdown 格式 如果使用的是 markdown 格式编写文章,首先需要记忆常用的基本语法,半个小时足够入门写博客了,比txt 高级,比 html 简洁,取代 word 地位! 正是因为 markdown 语法规范,所以可以说是跨平台的写作语言,基本上各大主流的博客平台均支持 markdown 格式,保证了\"一处编写,到处复制\"的优良特性. 值得注意的是,不同平台对 markdown 格式的渲染结果稍有差异,甚至语法支持度不同,这要求我们尽量写通用语法或者因地制宜有针对性编写文章. ## markdown 二号标题 - markdown 无序列表1 - markdown 无序列表2 - markdown 无序列表3 **markdown 加粗文字效果** [markdown 超链接文字](https://snowdreams1006.github.io/markdown/) ![markdown 图片文字](./images/markdown-preview.png) markdown 快速入门 富文本格式 平常熟悉的 word 编辑器可以理解为一种富文本格式,布局,标题,超链接,图片等均以控件的形式展示,需要填写标题了点一下按钮,需要加粗效果再点一下按钮,效果直观,不需要二次渲染,但不同的平台自然是不同的布局. 一家平台的布局还不一定能够完美复制到另一家平台,虽然适合大多数人,但可移植性差! 如果需要同时发布到多家平台,简直不敢相信,复制不了样式,需要重新排版等问题绝对是一种折磨. 小结 markdown : 拥有编程经验,不关心排版布局,专注写作多家平台发表首选 markdown 编辑器,\"一处编写,到处复制\",可移植性好,最值渲染效果也不错! 富文本格式: 可视化书写文章,无需编程经验的话,首选富文本编辑器,调整鼠标就能搞定页面布局还是很轻松的,同步更新到多家平台时,页面布局格式差强人意,后期维护难度大! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/markdownOrRichText.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"write/markdown2richText.html":{"url":"write/markdown2richText.html","title":"markdown 转富文本","keywords":"","body":"markdown转富文本 正常情况下不太希望你能看到这篇文章,可天不遂人愿, 总有些平台至今不支持 markdown 语言,没办法只能迁就它! 现在遇到的问题是,部分平台仅支持富文本格式,不支持 markdown 格式.既然想要在这些平台上发表文章,不得不按照他们的规定做事. 下面总结下笔者在用的一些方法: 1. markdown转富文本 适合已有 markdown 格式的文章,想要优雅转换成富文本格式,这种情况下我们只要能够获取到渲染后的富文本内容,然后复制到平台的编辑器即可. 不少 markdown 编辑器支持实时预览,一边是 markdown 源码,另一边是 富文本 预览,选中富文本然后复制,相当简单. 或者,将 markdown 源码上传到第三方平台在线转换成富文本格式,推荐 Markdown Here 插件或 在线工具. Markdown Here chrome插件的使用方法: 下载安装 Markdown Here 插件 各大浏览器基本上都有相应插件,如需翻墙,请自行解决. 配置插件并重启浏览器 支持自定义css样式,内嵌多套主题可供选择. 使用快捷键或命令面板转换 默认快捷键CTRL + ALT + M,或者复制到插件的预览窗口,点击Markdown 转换 目标富文本内容拷贝回富文本编辑器 选中渲染后的富文本内容拷贝到目标编辑器 online-markdown 在线工具的使用方法: 复制源 markdown 内容转换成目标富文本格式 将源 markdown 内容复制到左侧代码区,右侧可实时预览富文本效果 目标富文本内容拷贝回富文本编辑器 点击复制后到目标编辑器选择粘贴 2. 截图分享 适合懒癌晚期的作者,第一种方法转换后的富文本可能无法完美移植到某些平台时,而笔者又懒得重新编辑富文本,因此选择截图分享方式发布在该平台,只不过这种体验上稍差些,毕竟图片加载速度比文字相对来说还是慢很多! 简书和开源中国的 app 均支持截长图保存分享,网页端的话滚动截屏插件很多,目前在用fireshot还不错. 3. 重新编辑 适合认真负责的完美主义者,既然不支持 markdown 语言,那就用富文本编辑器重新编辑一份,素材和效果都有参考,再写一遍应该也不至于特别耗费时间,如果需要重写多份的话,那就另当别论了! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/markdown2richText.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"write/static-semi-manual-with-csv.html":{"url":"write/static-semi-manual-with-csv.html","title":"csv 半手动数据统计","keywords":"","body":"csv半手动数据统计 背景 作为正在探索如何写作并发表到各大博客平台的新人,目前虽然已基本弄清写作和发表的基本流程,但是离打造个人知名度还差很大很大一段距离. 尤其处于新手阶段,需要的更是自信与外界的积极反馈,看着各平台日益增长的阅读量和粉丝量,心中自然不甚欣喜. 但是,持续的技术输出能否坚持下去很大程度上靠的是外界的积极反馈,如果写的文章基本没人看,或者反映并不理想,估计也很难再坚持创作了. 所以笔者每天晚上都会统计一下各个平台的数据,看一下有哪些收获,只有看得见的数据才能给我安全感和自信心. 下面简单展示一下每日数据统计效果: 每日数据统计 这里列出的平台默认是没有提供数据分析功能,而有些平台已经提供数据分析,说不定还要丰富图表分析功能,自然不用像下面这般复杂操作. 所以,针对没有提供数据分析的平台,只好采用人工方式进行每日数据统计,一开始文章比较少,用肉眼加计算器就能很轻松得到阅读量和粉丝数等数据. 但是,随着文章的每日更新,不断累加的文章越来越多,人工方式简直让我崩溃,比如昨晚在统计慕课网手记相关数据时就意外被一旁的小侄子打断三次! 简直不可忍受,穷则思变,懒则想法偷懒,所以是时候探索新的方式解决纯手动的弊端了! 全网汇总数据 慕课手记 简书 博客园 腾讯云社区 图表渲染效果来自 gitbook 的 chart 插件,详情请参考 官网文档 懒则想法偷懒 回顾操作流程 要想解放重复劳动量,必须先知道问题瓶颈,现在先回顾一下手动操作流程. 登录各大博客平台后台,找到文章列表. 打开计算器按照阅读量等指标累加每篇文章的相关数据. 更新统计页面数据,利用 chart 插件渲染图标. 修改 chart 渲染数据语法,截图渲染效果. 确认渲染效果并推送到 github 网站 本来不必利用截图表示图表的,只是无奈 github 不支持 chart 插件语法,只好用截图代替了. 思考问题瓶颈 分析上述流程后不难发现,最复杂也是最费时费力的便是第二步的数据统计,由于要肉眼统计文章并用计算器累加,简直是手脑并用,只有高度专注才能保证统计数据的准确性和可靠性. 这也就解释了被打断三次后的崩溃了,找到问题的根源了,想办法如何解决吧! 最容易想到的解决办法是手动复制文章列表数据,然后程序分析提取关键数据,最后再统计数据. 又是三步操作,再分解一下步骤,看看目前能够解决哪部分. 手动复制文章列表数据 程序分析提取关键数据 统计指标数据 在这三步中,只有第二步最为关键,也是目前我能做到的事情,因为第一步可能需要爬虫技术或模拟接口调用,总体来说,总体来说还是比较麻烦的,以后再继续优化吧. 梳理操作流程 因此,现在先着手如何将复制后的文章列表转化成程序能够处理的文件格式,进而调用程序统计. 下面以慕课网手记文章为例,简单介绍下处理流程. 手动复制文章 现在文章已复制到文件,应该保存成什么格式呢?这又是一个思考点. 由于文件内容最终需要被程序处理,而程序处理要求数据需要具备一定的格式,因此自然不能是 txt 或 word 这类文档,平常接触比较多的文档数据处理一般就是 excel 或者 json 类型的文档. 这里需要 excel 这种格式文档,但是 excel 比较笨重,还需要相关软件才能打开 excel 文件,好像并不是很适合,怎么办呢? 但是我真的需要这种一行一行的数据格式啊,有没有折中的处理方案? 当然有!轻量级的 csv 格式不是巧合适合简单文档处理吗? csv 和 excel 具有类似的特征,大体上都是一行一行一列一列地存储数据,最适合统计数据了. 看着乱七八糟的文章列表,csv 也无法处理这种复杂数据啊,接下来还是要手动格式化数据,整理一下数据. 程序分析提取 至此,我们已经完成数据分析的第一步了,接下来是如何读取 csv 文件,由于本人是 java 程序员,所以我要看一下 java 如何处理 csv 文件. 需求很简单,编写一个 csv 工具类并实现基本的写入和读取操作即可. 说到工具类当然首选现成的开源工具了,毕竟小小的需求不值得造轮子. 寻求解决方案 说到开源工具,脑海中第一个闪现的是 Apache Commons 工具类,所以先去 maven 上搜一下有没有 csv 相关的工具类. 在线搜索 commons-csv 天不负我!果然有 csv 相关工具类,下面就开始研究如何调用吧! 集成 commons-csv 工具类 org.apache.commons commons-csv 1.6 编写工具类 /** * 写入csv文件 * * @param data 数据内容 * @param filePath 文件路径 * @throws IOException **/ public static void writeCsv(List data, String filePath) throws IOException { FileWriter fw = new FileWriter(new File(filePath)); final CSVPrinter printer = CSVFormat.EXCEL.print(fw); printer.printRecords(data); printer.flush(); printer.close(); } /** * 读取csv文件 * * @param filePath 文件路径 * @return CSVRecord 迭代对象 * @throws IOException **/ public static Iterable readCSV(String filePath) throws IOException { InputStream inputStream = new FileInputStream(filePath); InputStreamReader isr = new InputStreamReader(inputStream); Iterable records = CSVFormat.EXCEL.parse(isr); return records; } /** * 测试写入并读取csv 文件 */ private static void testWriteAndRead() throws IOException { //写入数据 List data = new ArrayList(); data.add(new String[]{\"张三\", \"18\", \"3000\"}); data.add(new String[]{\"李四\", \"20\", \"4000\"}); data.add(new String[]{\"王二\", \"25\", \"5000\"}); //写入文件路径 String path = \"/Users/sunpo/Downloads/testWriteAndRead.csv\"; //写入 csv 文件 writeCsv(data, path); //读取文件 Iterable records = readCSV(path); for (CSVRecord record : records) { for (String string : record) { System.out.print(string); System.out.print(\" \"); } System.out.println(); } } 测试写入并读取功能 测试结果真实可用,工具类基本功能编写完成. 制定解决方案 已经有了 csv 工具类,那么现在就要想办法解决实际问题,再看一下当前慕课网手记的内容格式吧! 148浏览 2推荐 0评论 204浏览 2推荐 0评论 181浏览 2推荐 0评论 分析上述内容格式有以下特点: 内容数据一行一条数据,可能需要换行符问题 每一行数据以空格分割,可分割成数组或列表再处理 已分割后的列表项包括了有效数据和文字说明,可能需要过滤出有效数据 按照上述分析结果,开始 coding 逐个解决,下面展示下关键代码. 按照空格将每一行数据分割成列表 List row = StringTools.splitToListString(string, \" \"); StringTools.splitToListString 方式是笔者封装的分割字符串方法,目的将字符串按照指定分隔符分割成字符串列表 处理分割后字符串列表并过来出有效数据 String readCountWithDescString = row.get(0); String readCountString = StringUtils.substringBefore(readCountWithDescString, \"浏览\"); String recommendCountWithDescString = row.get(1); String recommendCountString = StringUtils.substringBefore(recommendCountWithDescString, \"推荐\"); String commentCountWithDescString = row.get(2); String commentCountString = StringUtils.substringBefore(commentCountWithDescString, \"评论\"); StringUtils.substringBefore 方法也是Apache Commons 工具类,具体来源于 org.apache.commons.lang3 ,下述涉及到的 StringUtils 静态方法 也是,不再单独说明. 最后一步即统计分析 //浏览数 int readCount = 0; //推荐数 int recommendCount = 0; //评论数 int commentCount = 0; readCount += Integer.parseInt(readCountString); recommendCount += Integer.parseInt(recommendCountString); commentCount += Integer.parseInt(commentCountString); 实施解决方案 如此一来,三步均已解决,现在运行以下统计方法,看一下真实效果如何. /** * 统计慕课手记 * * @throws IOException */ private static void countImooc() throws IOException { //昨日统计数据 String yesterday = DateFormatUtils.format(DateUtils.addDays(new Date(), -1), \"yyyyMMdd\"); String path = String.format(\"/Users/sunpo/Documents/workspace/count/imooc-%s.csv\", yesterday); //总行数 int allRows = 0; //有效行数 int allValidRows = 0; //当前行是否有效 boolean isValidRow = true; //浏览数 int readCount = 0; //推荐数 int recommendCount = 0; //评论数 int commentCount = 0; Iterable records = readCSV(path); for (CSVRecord record : records) { allRows++; for (String string : record) { System.out.println(string); if (StringUtils.isBlank(string)) { isValidRow = false; break; } List row = StringTools.splitToListString(string, \" \"); String readCountWithDescString = row.get(0); String readCountString = StringUtils.substringBefore(readCountWithDescString, \"浏览\"); String recommendCountWithDescString = row.get(1); String recommendCountString = StringUtils.substringBefore(recommendCountWithDescString, \"推荐\"); String commentCountWithDescString = row.get(2); String commentCountString = StringUtils.substringBefore(commentCountWithDescString, \"评论\"); readCount += Integer.parseInt(readCountString); recommendCount += Integer.parseInt(recommendCountString); commentCount += Integer.parseInt(commentCountString); } if (isValidRow) { allValidRows++; } isValidRow = true; } System.out.println(); System.out.println(String.format(\"[慕课手记] 一共读取%d行,有效行: allValidRows = %d ,其中浏览数: readCount = %d ,推荐数: recommendCount = %d ,评论数: commentCount = %d\", allRows, allValidRows, readCount, recommendCount, commentCount)); System.out.println(); } 很完美,终于不必再肉眼统计数据了,虽然很长程度上仍然依赖人工整理好 csv 文件,但是目前已经解决了纯手动的弊端. 因此,上述解决方案是半手动的方式,仍然还有很多可以优化的地方,等下次忍受不了这种方案时再解决! 小结 本文主要介绍了纯手工统计报表遇到的诸多问题,寻求一种相对简单的解决方案. 基本流程大致可以分为下述流程: 手动复制文章列表(包括阅读量,评论量和点赞数),并整理成标准的 csv 格式文件. 编写各个平台的 csv 工具处理类,解析并统计 csv 文件内容. 运行工具类得到最终统计数据,大功告成! 本文主要介绍的是解决问题的思路,对于其中涉及到的相关技术点并未深入展开,关键源码已经贴上,如果还想要更详细的完整源码,可以留言回复. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/static-semi-manual-with-csv.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"write/static-semi-manual-with-js.html":{"url":"write/static-semi-manual-with-js.html","title":"js 半手动数据统计","keywords":"","body":"js半手动数据统计 在日常文章数据统计的过程中,纯手动方式已经难以应付,于是乎,逐步开始了程序介入方式进行统计. 在上一节中,探索利用 csv 文件格式进行文章数据统计,本来以为能够应付一阵子,没想到仅仅一天我就放弃了. 原因还不是因为我懒,需要复制文章内容,然后整理成特定的 csv 格式,最后利用已编写的 java 工具类进行统计. 在这三步操作中,第一步复制文章内容最简单,第二步整理文章格式最麻烦,第三步编写 csv 工具类最技术. 因此,能不能再简单点?懒癌晚期,必须继续寻求新的解决方案. 关于如何利用 csv 文件处理统计数据,可以参考 https://snowdreams1006.github.io/static-semi-manual-with-csv.html 实现效果 慕课手记 慕课手记 : https://www.imooc.com/u/5224488/articles c3.generate({\"bindto\":\"#plugin-chart-1\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-04-01\", \"2019-04-02\", \"2019-04-03\", \"2019-04-04\", \"2019-04-05\", \"2019-04-06\", \"2019-04-07\", \"2019-04-08\", \"2019-04-09\", \"2019-04-10\", \"2019-04-11\", \"2019-04-12\" ], [ \"粉丝\", 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9 ], [ \"阅读量\", 3508, 3645, 3650, 4356, 4528, 4864, 5276, 5593, 5872, 5912, 6271, 6400 ], [ \"手记\", 32, 33, 34, 36, 38, 39, 40, 41, 42, 42, 44, 44 ], [ \"推荐\", 36, 36, 37, 39, 41, 42, 48, 49, 50, 50, 52, 52 ], [ \"积分\", 107, 118, 118, 130, 130, 141, 152, 173, 173, 173, 194, 195 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 简书 简书 : https://www.jianshu.com/u/577b0d76ab87 c3.generate({\"bindto\":\"#plugin-chart-2\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-04-01\", \"2019-04-02\", \"2019-04-03\", \"2019-04-04\", \"2019-04-05\", \"2019-04-06\", \"2019-04-07\", \"2019-04-08\", \"2019-04-09\", \"2019-04-10\", \"2019-04-11\", \"2019-04-12\" ], [ \"粉丝\", 7, 7, 6, 7, 6, 5, 5, 5, 5, 5, 5, 5 ], [ \"阅读量\", 343, 335, 342, 358, 374, 443, 468, 512, 548, 552, 611, 624 ], [ \"文章\", 33, 34, 35, 37, 39, 40, 41, 42, 43, 43, 46, 46 ], [ \"喜欢\", 57, 58, 59, 60, 62, 64, 65, 67, 68, 68, 68, 71, 71 ], [ \"简书钻\", 27, 28, 28, 9,9 ,9, 10, 10, 10, 10, 11, 11 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 博客园 博客园 : https://www.cnblogs.com/snowdreams1006/ c3.generate({\"bindto\":\"#plugin-chart-3\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-04-01\", \"2019-04-02\", \"2019-04-03\", \"2019-04-04\", \"2019-04-05\", \"2019-04-06\", \"2019-04-07\", \"2019-04-08\", \"2019-04-09\", \"2019-04-10\", \"2019-04-11\", \"2019-04-12\" ], [ \"粉丝\", 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18 ], [ \"阅读数\", 3889, 4096, 4207, 4388, 4411, 4435, 4471, 4728, 4866, 4867, 5189, 5274 ], [ \"随笔\", 31, 32, 33, 34, 36, 38, 39, 41, 41, 41, 43, 43 ], [ \"评论数\", 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 腾讯云社区 腾讯云社区 : https://cloud.tencent.com/developer/user/2952369/activities c3.generate({\"bindto\":\"#plugin-chart-4\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-04-04\", \"2019-04-05\", \"2019-04-06\", \"2019-04-07\", \"2019-04-08\", \"2019-04-09\", \"2019-04-10\", \"2019-04-11\", \"2019-04-12\" ], [ \"粉丝\", 13, 13, 13, 13, 13, 13, 13, 13, 13 ], [ \"阅读量\", 1192, 1561, 2131, 2144, 2149, 2158, 2159, 2163, 2165 ], [ \"文章\", 34, 34, 34, 34, 34, 34, 34, 34, 34 ], [ \"点赞\", 107, 108, 110, 107, 107, 107, 107, 107, 107 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); js 抓取分析数据 下面以 chrome 浏览器为例,说明如何利用默认控制台抓取关键数据,本文需要一定的 jQuery 基础. 慕课手记 在目标页面右键选择检查选项,打开默认开发者控制台,点击最左侧的小鼠标箭头,然后选中关键数据,比如浏览量. 此时,开发者控制台自动滚动到元素(Elements)选项卡,在目标数据上右键点击复制(Copy),接着点击复制选择器(Copy selector),现在已经定位到阅读量的节点. 点击控制台(Console)选项卡,并且将选择器更改成 jQuery 选择器,即$(\"复制的选择器\").text(),现在在控制台直接输出内容,看一下能否抓取到浏览量吧! 现在已经成功定位到指定元素,而我们要统计的是全部文章的阅读量,因此需要定位到全部元素. $(\"#articlesList > div:nth-child(1) > div.item-btm.clearfix > div > div:nth-child(1) > em\").text(); 简单分析下文章结构结合选择器分析,可以得知, 浏览,推荐和评论三者文档基本一致,唯一不同之处就是排列顺序而已,因此想要准确定位到浏览数,需要定位到第一个元素,推荐量则是第二个元素,因此类推. 83浏览 1推荐 0评论 弄清楚基本文档结构后,开始着手改造选择器使其定位到全部文章的浏览量,我们做如下改造. $(\"#articlesList div:nth-child(1) > em\").text(); 仅仅保留头部和尾部,再去掉中间部分 > div:nth-child(1) > div.item-btm.clearfix > div > ,这样就轻松定位到全部元素的浏览量了,是不是很简单? 看到控制台输出结果,心里瞬间踏实了,这不刚好是第一页全部文章的浏览量吗?观察输出内容格式可知,我们需要将整个字符串按照空格分割成字符串数组. 需要注意的是,行首还有一个空格哟,因此在分割成字符串数组前,我们先将行首的空格去除掉. // 去除空格前:\" 83浏览 91浏览 114浏览 150浏览 129浏览 175浏览 222浏览 173浏览 225浏览 200浏览 201浏览 217浏览 291浏览 202浏览 229浏览 184浏览 226浏览 155浏览 153浏览 211浏览\" $(\"#articlesList div:nth-child(1) > em\").text().trim(); // 去除空格后: \"83浏览 91浏览 114浏览 150浏览 129浏览 175浏览 222浏览 173浏览 225浏览 200浏览 201浏览 217浏览 291浏览 202浏览 229浏览 184浏览 226浏览 155浏览 153浏览 211浏览\" 现在我们再将这整个字符串按照空格分割成字符串数组. // 分割字符串前: \"83浏览 91浏览 114浏览 150浏览 129浏览 175浏览 222浏览 173浏览 225浏览 200浏览 201浏览 217浏览 291浏览 202浏览 229浏览 184浏览 226浏览 155浏览 153浏览 211浏览\" $(\"#articlesList div:nth-child(1) > em\").text().trim().split(\" \"); // 分割字符串后: [\"83浏览\", \"91浏览\", \"114浏览\", \"150浏览\", \"129浏览\", \"175浏览\", \"222浏览\", \"173浏览\", \"225浏览\", \"200浏览\", \"201浏览\", \"217浏览\", \"291浏览\", \"202浏览\", \"229浏览\", \"184浏览\", \"226浏览\", \"155浏览\", \"153浏览\", \"211浏览\"] 现在我们已经够将整个字符串分割成一个个小的字符串,下面需要再将83浏览中的浏览去掉,仅仅保留数字83. $.each($(\"#articlesList div:nth-child(1) > em\").text().trim().split(\" \"),function(idx,ele){ console.log(ele.substr(0,ele.lastIndexOf(\"浏览\"))); }); 现在我们已经抓取到真正的浏览量,接下来就比较简单了,直接将这些浏览量进行累加即可,需要注意的是,这里的浏览数还是字符串类型,需要转换成数字类型才能进行累加运算哟! //阅读量 var readCount = 0; $.each($(\"#articlesList div:nth-child(1) > em\").text().trim().split(\" \"),function(idx,ele){ readCount += parseInt(ele.substr(0,ele.lastIndexOf(\"浏览\"))); }); console.log(\"阅读量: \" + readCount); 小结 我们以 chrome 浏览器为例,讲解了如何利用自带的控制台工具抓取关键数据,从页面结构分析入口,一步一个脚印提取有效数据,最终从一条数据变成多条数据,进而实现数据的累加统计. 总体来说,还是比较简单的,并不需要太多的基础知识,但还是稍微总结其中涉及到的 jQuery 知识点吧! 定位到具体元素: $(\"这里是复制的选择器\") 定位到具体元素内容: $(\"这里是复制的选择器\").text() 去除字符串首尾空格: $(\"这里是复制的选择器\").text().trim() 将字符串按照空格分割成字符串数组: $(\"这里是复制的选择器\").text().trim().split(\" \") 截取字符串指定部分: ele.substr(0,ele.lastIndexOf(\"浏览\") 将字符串转化成数字类型: parseInt(ele.substr(0,ele.lastIndexOf(\"浏览\"))); 变量累加求和: readCount += parseInt(ele.substr(0,ele.lastIndexOf(\"浏览\"))); 完整示例: //阅读量 var readCount = 0; $.each($(\"#articlesList div:nth-child(1) > em\").text().trim().split(\" \"),function(idx,ele){ readCount += parseInt(ele.substr(0,ele.lastIndexOf(\"浏览\"))); }); console.log(\"阅读量: \" + readCount); //推荐量 var recommendCount = 0; $.each($(\"#articlesList div:nth-child(2) > em\").text().trim().split(\" \"),function(idx,ele){ recommendCount += parseInt(ele.substr(0,ele.lastIndexOf(\"推荐\"))); }); console.log(\"推荐量: \" + recommendCount); //评论量 var commendCount = 0; $.each($(\"#articlesList div:nth-child(3) > em\").text().trim().split(\" \"),function(idx,ele){ commendCount += parseInt(ele.substr(0,ele.lastIndexOf(\"评论\"))); }); console.log(\"评论量: \" + commendCount); 简书 简书的文章数据不一定很规整,比如有的发布文章还没有简书钻,所以阅读量的排列顺序就是不确定的,这一点不像前面介绍的慕课手记,但是简书的关键数据前面是有小图标的,因此我们可以利用图标定位到旁边的数据. 按照前面介绍的步骤,我们仍然定位到阅读量,然而 #note-44847909 > div > div > a:nth-child(2) > i 却不能直接使用,因为我们刚刚分析了,简书不能利用顺序定位只能用图标辅助定位. 所以,还是先看看文档结构,尝试着直接定位到全部的阅读量小图标. 经过分析文章结构,我们可以很轻松定位到全部阅读小图标,当然这是一个元素数组,并不是字符串数组哟! $(\"#list-container .ic-list-read\") 接下来我们看一下能否正确定位到每一个小图标,进而定位到小图标左侧的阅读量. 现在我们已经能够定位到全部的阅读量小图标,现在思考如何定位到旁边的真正阅读量呢? 0.2 2 0 1 昨天 10:39 分析文章结构,我们发现阅读量是小图标的父节点的内容,这一下就简单了,我们顺藤摸瓜定位到父节点自然就能定位到阅读量了! $(\"#list-container .ic-list-read\").each(function(idx,ele){ console.log($(ele).parent().text().trim()); }); 现在既然已经能够定位到阅读量,那么首先累加求和就很简单了. //阅读量 var readCount = 0; $(\"#list-container .ic-list-read\").each(function(idx,ele){ readCount += parseInt($(ele).parent().text().trim()); }); console.log(\"阅读量: \" + readCount); 小结 首先分析文章基本结构发现,简书的阅读量需要定位到阅读量小图标,进而定位到父节点,然后父节点的内容才是真正的阅读量. 定位到真正的阅读量后,一切问题迎刃而解,总结一下新增 jQuery 知识点. 定位到当前节点的父节点: $(ele).parent() 完整示例: //阅读量 var readCount = 0; $(\"#list-container .ic-list-read\").each(function(idx,ele){ readCount += parseInt($(ele).parent().text().trim()); }); console.log(\"阅读量: \" + readCount); //评论量 var commendCount = 0; $(\"#list-container .ic-list-comments\").each(function(idx,ele){ commendCount += parseInt($(ele).parent().text().trim()); }); console.log(\"评论量: \" + commendCount); //喜欢量 var recommendCount = 0; $(\"#list-container .ic-list-like\").each(function(idx,ele){ recommendCount += parseInt($(ele).parent().text().trim()); }); console.log(\"喜欢量: \" + recommendCount); 博客园 博客园的文章列表比较复古,传统的 table 布局,是这几个平台中最简单的,基本上不同怎么介绍. 复制到阅读量选择器: #post-row-10694598 > td:nth-child(4) 此时再结合文章结构,因此我们可以得到全部文章的阅读量选择器. $(\"#post_list td:nth-child(4)\") 接下来需要遍历数组,看看能否抓取到当前页面全部文章的阅读量. $(\"#post_list td:nth-child(4)\").each(function(idx,ele){ console.log($(ele).text().trim()); }); 成功抓取到阅读量,现在开始累加当前页面全部文章的阅读量. //阅读数 var readCount = 0; $(\"#post_list td:nth-child(4)\").each(function(idx,ele){ readCount += parseInt($(ele).text().trim()); }); console.log(\"阅读数: \" + readCount); 小结 中规中矩的传统 table 布局,只需要顺序定位到具体的元素即可,需要注意的是,博客园文章页面采用了分页,如果需要统计全部文章的阅读量,需要将每页的阅读量手动累加计算. 完整示例: //评论数 var commendCount = 0; $(\"#post_list td:nth-child(3)\").each(function(idx,ele){ commendCount += parseInt($(ele).text().trim()); }); console.log(\"评论数: \" + commendCount); //阅读数 var readCount = 0; $(\"#post_list td:nth-child(4)\").each(function(idx,ele){ readCount += parseInt($(ele).text().trim()); }); console.log(\"阅读数: \" + readCount); 腾讯云社区 大致分析腾讯云社区的文章结构,基本上和简书结构差不多,既可以像简书那种采用图标定位方式,也可以像慕课网和博客园那种直接顺序定位. 为了较为精准的定位,现在采用图标定位方式来获取阅读量. #react-root > div:nth-child(1) > div.J-body.com-body.with-bg > section > div > section > div > div.com-log-list > section:nth-child(1) > section > div > div > span > span 既然要根据图标定位,我们需要分析图标和阅读量的关系. 76 3 因此,我们需要做如下改造才能定位到与阅读量. $(\"#react-root .com-i-view\").each(function(idx,ele){ console.log($(ele).next().text().trim()); }); 定位到阅读量,接下来就是简单的数据累加求和了. //阅读量 var readCount = 0; $(\"#react-root .com-i-view\").each(function(idx,ele){ readCount += parseInt($(ele).next().text().trim()); }); console.log(\"阅读量: \" + readCount); 小结 腾讯云社区和简书一样,采用的分页叠加模式,因此需要统计全部文章的话,只需要一直滚动直到加载出全部文章即可. 总结一下涉及到的新增 jQuery 知识点: 获取当前节点的下一个节点: $(ele).next() 完整示例: //阅读量 var readCount = 0; $(\"#react-root .com-i-view\").each(function(idx,ele){ readCount += parseInt($(ele).next().text().trim()); }); console.log(\"阅读量: \" + readCount); //点赞量 var recommendCount = 0; $(\"#react-root .com-i-like\").each(function(idx,ele){ recommendCount += parseInt($(ele).next().text().trim()); }); console.log(\"点赞量: \" + recommendCount); 小结 本文通过 jQuery 方式直接抓取文章数据,简单方便,学习成本低,能够快速上手. 慕课网和博客园的文章列表存在分页,如果需要统计全部文章浏览量,需要将每一页的文章累加,直到最后一页. 简书和腾讯云社区的文章列表虽然也有分支,但会自动累加,所以统计全部文章时只需要先等全部文章加载完毕,再利用 js 脚本一次性统计即可. 好了,本次分享到此结束,如果你觉得本文对你有所帮助,欢迎分享让更多人看到哦,对了,上一篇文章也是解决统计问题的,不过使用的是 java 读取 csv 文件方式,如果有兴趣,也可以看一看. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/static-semi-manual-with-js.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"write/jianshu-optimize-static.html":{"url":"write/jianshu-optimize-static.html","title":"简述优选文章统计","keywords":"","body":"简述优选文章统计 c3.generate({\"bindto\":\"#plugin-chart-5\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\" ], [ \"阅读量\", 14000, \t\t\t\t10000, \t\t\t\t12000, \t\t\t\t9073, \t\t\t\t15000, \t\t\t\t7988, \t\t\t\t18000, \t\t\t\t17036, \t\t\t\t13146, \t\t\t\t16087, \t\t\t\t18788, \t\t\t\t14861, \t\t\t\t19771, \t\t\t\t27232, \t\t\t\t11637, \t\t\t\t12835, \t\t\t\t14173, \t\t\t\t9256, \t\t\t\t31607, \t\t\t\t12552, \t\t\t\t21840, \t\t\t\t10745, \t\t\t\t12573, \t\t\t\t8760, \t\t\t\t10981, \t\t\t\t22641, \t\t\t\t33164, \t\t\t\t28793, \t\t\t\t22393, \t\t\t\t17709, \t\t\t\t16965, \t\t\t\t29429, \t\t\t\t12264, \t\t\t\t25831, \t\t\t\t14984, \t\t\t\t15204 ], [ \"点赞量\", 174, \t\t\t\t70, \t\t\t\t69, \t\t\t\t30, \t\t\t\t191, \t\t\t\t57, \t\t\t\t117, \t\t\t\t124, \t\t\t\t54, \t\t\t\t101, \t\t\t\t101, \t\t\t\t41, \t\t\t\t32, \t\t\t\t83, \t\t\t\t76, \t\t\t\t70, \t\t\t\t61, \t\t\t\t48, \t\t\t\t72, \t\t\t\t74, \t\t\t\t88, \t\t\t\t55, \t\t\t\t28, \t\t\t\t39, \t\t\t\t13, \t\t\t\t36, \t\t\t\t61, \t\t\t\t111, \t\t\t\t28, \t\t\t\t102, \t\t\t\t23, \t\t\t\t55, \t\t\t\t41, \t\t\t\t28, \t\t\t\t31, \t\t\t\t60 ], [ \"评论量\", 57, \t\t\t\t37, \t\t\t\t22, \t\t\t\t43, \t\t\t\t32, \t\t\t\t16, \t\t\t\t57, \t\t\t\t100, \t\t\t\t39, \t\t\t\t67, \t\t\t\t71, \t\t\t\t25, \t\t\t\t59, \t\t\t\t90, \t\t\t\t15, \t\t\t\t96, \t\t\t\t77, \t\t\t\t17, \t\t\t\t37, \t\t\t\t27, \t\t\t\t62, \t\t\t\t17, \t\t\t\t29, \t\t\t\t39, \t\t\t\t11, \t\t\t\t65, \t\t\t\t64, \t\t\t\t41, \t\t\t\t33, \t\t\t\t26, \t\t\t\t27, \t\t\t\t25, \t\t\t\t38, \t\t\t\t36, \t\t\t\t46, \t\t\t\t36 ] ], \"axes\": { \"阅读量\": \"y2\" }, \"types\": { \"阅读量\": \"bar\" } }, \"axis\": { \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"阅读量\", \"position\": \"outer-middle\" } } } }); 阅读量优先 第一名: 27 号 “女生第1次和第5次的区别”,有些东西,只有经历了才懂得 目前为止阅读量最高,33164 次阅读,其中 61 个点赞,评论数也不错,64 个评论,入选精选留言 44. 第二名: 19 号 “叔叔阿姨,那个...能小点声吗?” 阅读量 31607 ,也是不错的成绩,和第一名相比也就差了一两千,但是评论数只有第一名的一半 37,其中入选精选留言数是 34,看样子只要比较正常的留言都入选了,点赞数有 72 比第一名稍微高点. 第三名: 32 号 蔡少芬直言当过小三:老天给了她一手烂牌,她却打出了王炸 阅读量和第二名相差不多,差一点就 3w ,29429 和 31607 相比也是差了一两千,这一点比较有意思了,前三名的阅读量几乎都是差一两千? 评论数有 25 ,入选留言 20 ,点赞数 55 ,和前两名相比中规中矩,没有特别突出之处. 倒数第一名: 6 号 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 7988 次阅读量,排名垫底,评论数是 16 ,入选留言数 12 ,但是点赞数 57 ,数据并不是很差. 倒数第一的阅读量却拥有第三阅读量的点赞数,这一点足以证明该篇文章的优秀,看得人比较少可能是不愿正视自己吧? 倒数第二名: 24 号 “好不容易上个大学,谈什么恋爱啊” 倒数阅读量的标准本身有些歧视,8760 和 倒数第一名的 7988 相差不到一千,39 次评论,入选 33 条留言,并且点赞数 39 ,各项成绩比较平均. 倒数第三名: 4 号 毕业了,谁又不曾有过爱情的遗憾 和倒数第二名的阅读量相差只有一两百,这个差距应该不是很大,至少不是一两千的差距,遗憾的是仍然是倒数的排名. 9073 次阅读,43 条评论,34 条入选留言,30 个点赞. 站在阅读量优先的立场分析前三名与倒数后三名,不难发现这么一个规律: 简述公众号的受众应该偏向中年,娱乐八卦的话题永远不缺观众,校园生活或者毕业季话题虽然有一定的受众,但是远远不是大众化标准. 暗示色情的标题很大程度上吸引人阅读,但是老司机都明白的是,公众号一般不会真正的色情,所以文章一般都是柳暗花明又一村,如果你想歪了,那就证明你不纯洁! 所以这类文章阅读量比较高,有一种上当受骗的感觉不说点什么觉得对不起自己,所以留言也不错,如果能将这类文章转发出去恶搞一下朋友,这是极好的呢! 点赞量优先 第一名: 5 号 越优秀的人,越不安分 目前为止点赞量第一名,191 次的点赞数,阅读量只有 1.5w ,要知道阅读量前三名都是 3w 啊,仅仅用了一半的阅读量就收获了高于近乎 3 倍的点赞数. 可见,文章的阅读量和点赞量没有必然联系,优秀的文章能够令人深思,愿意主动分享出去,同时这篇文章的评论是是 32,入选留言 22 ,数据比较普通,突出之处就是点赞数. 第二名: 1 号 不合群的你,其实也很酷 点赞数 174 ,很第一名相差一二十而已,评论数是 57,入选留言 45 ,阅读量 1.4w ,从三项指标来看,和第一名不差上下,阅读量虽然少一点,但是评论数却多一点. 有意思的是,点赞数前两名都是和个人修养有关的文章,阅读量 1.5w 是最高阅读量 3w 的一半,也就是说虽然没有大众化口味,但是文章本身的内容非常好,愿意积极进取的中青年看过都说好! 第三名: 8 号 你这么内向,那应该找不到对象吧 124 次点赞稳居排行榜第三名,100 条留言,入选 81 条足以证明该话题引发的讨论是多么激烈,可见话题本身具有一定的爆炸性. 1.7w 的阅读量比前两名的阅读量都要高出两三千,但是点赞数却少了五六十,难不成多出的人是纯粹看热闹的吗? 倒数第一名: 25 号 2060年,那个失踪的宇航员回来了...... 13 次点赞创造历史,10981 次阅读仅仅换来 11 条留言,其中 9 条入选精选留言,除了阅读量高,另外两方面都比较地,我猜是不明所以的人点进来看科幻小说,结果失望而归,所以才没太大人气. 倒数第二名: 31 号 微信朋友圈查访客记录:我和我的“朋友圈”爱人 23 次点赞是本次排名的倒数第二,16965 次阅读本可以像 5 号那样收获最高的点赞数,无奈只有 23 个,27 条留言中入选 24 条,说明文章具备一定的话题性,但是质量还不足以让人主动点赞分享. 倒数第三名: 23 号,29 号,34 号 23 富家女离奇死亡,摩天轮内发现惊人一幕 12573 次阅读量还不错,29 条留言中入选 23 条,点赞数是 28 ,平均下来每一条留言就有一人点赞. 典型的编故事,最终效果竟然还不错,有点不理解. 29 我那个从事裸体艺术的前女友去世了... 22393 次阅读,33 条留言中 26 条入选,最终收获的点赞数也是 28 ,比 23 号多了一半的人都是打酱油的. 34 成为谁,也不要成为小s 大致情况和 29 号差不多,25831 次阅读量本身足够优秀,最高的阅读量也就 3w 啊,36 次留言入选了 23 条,最终点赞数只有 28 . 看样子明星的生活有人愿意看,但是不敢苟同你的观点啊. 分析了点赞排名,发现一个有意思现象,点赞量靠前的文章阅读量不一定很高,可能只有最高阅读量的一半左右,但是优秀的文章天生具备传播性,容易让人点赞分享. 前三名文章竟然无一例外都是和个人修养方面有关的,或者反省自身或者看热闹不留名,总而言之,积极地主旋律应该差不了. 然而倒数后三名的情况有些不一样了,标题带有较为明显的虚假故事情节,离奇猎奇的标题一定程度上吸引了更多人阅读,也引发了一定程度上的讨论,但是最终愿意分享点赞的人却寥寥无几,逞一时之快罢了! 评论量优先 第一名: 8 号 你这么内向,那应该找不到对象吧 又见熟悉的 8 号,内向的我找不到女朋友,记得这篇文章的点赞量 124应该是前三名的,没想到评论数 100 竟然是第一名? 100 次留言,入选 81 条,剩下的 19 条难不成是幸灾乐祸的,所以没入选精选留言? 17036 次阅读不仅是评论量第一名还是点赞量的前三名,即使放到阅读量排名中这个成绩也是不错的. 难不成公众号的粉丝都比较内向,不是说好的奋斗的中青年人设吗? 第二名: 16 号 十年工厂生涯,我活成了没有梦想的中年人 96 次评论入选 74 条留言说明关注这类话题的人确实不少,12835 次阅读中有 70 次点赞,各方面的成绩都挺不错的. 中年人压力很大,简述这一类的用户应该也不少,通过阅读量和点赞量分析来看,不难理解这篇文章为什么如何受欢迎了. 第三名: 14 号 谢谢你,给我18厘米的爱情 首先承认我自己不是纯洁的人,谁让标题起得这么露骨,可是点进去发现我错了,90 次的评论,虽然只有 55 条入选,但是 27232 次阅读量不是吹的,而且 83 次点赞也是很不错的成绩. 倒数第一名: 25 号 2060年,那个失踪的宇航员回来了...... 未来的宇航员又见面了,上一次见面好像是点赞量倒数后三名的时候,没想到评论量后三名又一次出现了. 11 次评论,有 9 条留言入选,明明有 10981 次阅读量,至少不是倒数的阅读量可是评论量和点赞量都不是太理想,点赞数只有区区的 13 个. 倒数第二名: 15 号 你现在这么努力,是为了有朝一日“有得选” 15 次评论有 11 个入选精选留言,11637 次阅读如果不能证明文章很好,那 76 个点赞数应该可以自证清白了吧? 或许是这么一个心理: 你说的都对,我不想评论,还是静静的点赞吧! 倒数第三名: 6 号 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 又是熟悉的面孔,虽然从标题上看,有些刺眼,但是自古忠言逆耳,更何况标题给人一种事后诸葛亮的感觉,所以阅读量才 7988 垫底,流失了大批读者后并没有影响到点赞量,可见文章还是有一定的质量的. 最终可惜的是,评论量和阅读量双惨淡,未免让人唏嘘! 如果仅仅考虑评论数的多寡,那么只要是话题性文章或者争议性文章,个个都能收获不少的评论数,如果标题带有一定的性暗示,那么话题自然就来了,阅读量不错,评论量也不错,如果文章也不错的话,那么点赞量也不会差. 如果是自身经历的故事情节,也能唤起很多人的共鸣,可惜的是劝学类文章空有内涵,不受待见,阅读量低,评论量自然也不会太高. 优质文章总结 前面从阅读量,点赞量和评论量三个不同纬度排名了 36 篇简书优质文章,从最终的结果上看,大致发现如下规律: 阅读量高的总体质量不会太差,一般都有着不错的评论量和点赞量. 评论量高的总体质量一般不错,阅读量和点赞量也会相当靠前. 点赞量高的总体质量可能不错,阅读量和评论量都无法保证. 情感类的文章阅读量都不错,因为这方面的文章受众比较广,无论是在校学生还是出入职场菜鸟亦或是油腻的中年,对于这类文章都毫无招架之力,如果文章话题性不错的话,随随便便就几十条留言,质量可以的话,点赞分享更是几十,这类文章堪称王者. 励志类的文章一般能够收获不错的点赞量,人人都需要心灵鸡汤的自我催眠,积极进取是奋斗的主旋律,虽然这类文章可能阅读量没有情感类的那么巨大,评论量也可能不是太多,但这类文章确实占据一席之地. 社会类的文章容易引发共鸣,吸引大量读者留言评论,这类文章阅读量和评论量都很不错,但是点赞量就不那么确定了,考虑到因人而异的社会经历,有一定的探险猎奇心理,也是优质文章的一部分. 优质文章有标准,不那么成功的文章也有案列,以下文章可能就不太适合简书公众号. 25 号 : 2060年,那个失踪的宇航员回来了...... 点赞量和评论量均倒数,阅读量中等偏下,因此可能不太适合简书公众号. 科幻小说,字数有些长,不适合公众号阅读,故事本身结局有些突兀,还没看够呢,感觉要走点击购买电子书的套路? 公众号文章一般都是碎片化阅读,此类小说适合专栏故事形式进行连载,或者购买电子书进行引流. 6 号 : 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 阅读量和评论量均倒数,点赞量中等,因此可能也不太适合. 苦口婆心劝学类文章本不应该太差,可惜标题给人一种事后诸葛亮的说教,但是内容却还不错,文章重点还有加粗标注,点赞量也还可以,所以这篇文章败在标题上,有些遗憾. 数据分析及处理 js 版本数据统计分析示例 var articleStatic = ` 36 https://mp.weixin.qq.com/s/JA_H5y0SHOcS0V-pn8V7Jw 为什么在一线城市待久的人,就很难再回去? 36 23 15204 60 35 https://mp.weixin.qq.com/s/yqcT5VlIcLh9d7Eyz4uyFw 我嫂子——那个“恶女人”的前半生:你哥他...不行 46 30 14984 31 34 https://mp.weixin.qq.com/s/xJRtW340VwRNDU1hxQc0wA 成为谁,也不要成为小s 36 23 25831 28 33 https://mp.weixin.qq.com/s/TTqzmPeof8FN3D1kErSnJQ 你真傻,念念不忘,是没有回响的 38 31 12264 41 32 https://mp.weixin.qq.com/s/TTqzmPeof8FN3D1kErSnJQ 蔡少芬直言当过小三:老天给了她一手烂牌,她却打出了王炸 25 20 29429 55 31 https://mp.weixin.qq.com/s/7aTGbUziPgWijye1xKF7LA 微信朋友圈查访客记录:我和我的“朋友圈”爱人 27 24 16965 23 30 https://mp.weixin.qq.com/s/sNiSuR7acH43kwnP5yox9g 我的好胜心害死了好朋友······ 26 19 17709 102 29 https://mp.weixin.qq.com/s/i7xqmHHbYrmbBHy__aaGfA 我那个从事裸体艺术的前女友去世了... 33 26 22393 28 28 https://mp.weixin.qq.com/s/85tALM-OAaCet78rfQ59Hw 贾玲,你也是活该! 41 28 28793 111 27 https://mp.weixin.qq.com/s/bhKFbcoeXDrVmfDhpyDiUA “女生第1次和第5次的区别”,有些东西,只有经历了才懂得 64 44 33164 61 26 https://mp.weixin.qq.com/s/lG3mGfKAvTrI7UL383D6Kw 我的3次相亲经历,次次都教我做人 65 31 22641 36 25 https://mp.weixin.qq.com/s/bT33bHJcsf_Jpwx9mjswNg 2060年,那个失踪的宇航员回来了...... 11 9 10981 13 24 https://mp.weixin.qq.com/s/FBzJZzmzNrpeNtcQBSY4fQ “好不容易上个大学,谈什么恋爱啊” 39 33 8760 39 23 https://mp.weixin.qq.com/s/1JBaS5FTcrzRnylIBaoAKg 富家女离奇死亡,摩天轮内发现惊人一幕 29 23 12573 28 22 https://mp.weixin.qq.com/s/pfqKkHNI9bQDbhdv4gPAnw 有好教养的人,总是闪闪发光的啊 17 12 10745 55 21 https://mp.weixin.qq.com/s/o6aGhHp5uI3Nquy0usaaww 优质男:姑娘,我凭什么娶你? 62 50 21840 88 20 https://mp.weixin.qq.com/s/PoiHroXm3V1kKjj3u1jnAw 那个改变了我一生的老男人······ 27 20 12552 74 19 https://mp.weixin.qq.com/s/IpXCQ6YPFrq39T1hFu4P-g “叔叔阿姨,那个...能小点声吗?” 37 34 31607 72 18 https://mp.weixin.qq.com/s/8WOH-pBrSCyEyByf8AOi3A “我,23岁,拖延癌晚期,还有救吗?” 17 13 9256 48 17 https://mp.weixin.qq.com/s/74gJMnvGILwajfFMwWFc_g “十二年了,我才没那么想他” 77 71 14173 61 16 https://mp.weixin.qq.com/s/Fb6Ygp07QkaQYFh-O2OBOw 十年工厂生涯,我活成了没有梦想的中年人 96 74 12835 70 15 https://mp.weixin.qq.com/s/n7mTI2dOAPQjG312gXP_8g 你现在这么努力,是为了有朝一日“有得选” 15 11 11637 76 14 https://mp.weixin.qq.com/s/vJpJkwMlqZLLvmf6W342gg 谢谢你,给我18厘米的爱情 90 55 27232 83 13 https://mp.weixin.qq.com/s/TJrq7zNELA48CG_WUJRtnA 我爱上了隔壁那个被家暴的女人 59 40 19771 32 12 https://mp.weixin.qq.com/s/zOQrwG5q7lwvypabYDs27w 你以为是丑拒,其实是...... 25 11 14861 41 11 https://mp.weixin.qq.com/s/jUHhqDS2fjLjTXY551Bn5w 追我那么久,请你放过我 71 64 18788 101 10 https://mp.weixin.qq.com/s/8y2jAt5be1ndl1JNV4H-1A 我犯了一种罪,叫长的不好看 67 34 16087 101 9 https://mp.weixin.qq.com/s/k8uidACpKxz6a8YCg-kwzQ 你都用不起SK-II,还敢熬这么深的夜? 39 34 13146 54 8 https://mp.weixin.qq.com/s/Vt_nd4xx5gEhNXgyzojAcQ 你这么内向,那应该找不到对象吧 100 81 17036 124 7 https://mp.weixin.qq.com/s/SU3CHUNT7l5Hy0P8WWYPAw 想用自律打败低配人生,你配吗? 57 46 1.8w 117 6 https://mp.weixin.qq.com/s/FoKMrZ-gvzn1RWUtsFQzJQ 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 16 12 7988 57 5 https://mp.weixin.qq.com/s/ugG71ETZOAygI-DhMruCEA 越优秀的人,越不安分 32 22 1.5w 191 4 https://mp.weixin.qq.com/s/e1mif6nhCJ-XeRoilhW83A 毕业了,谁又不曾有过爱情的遗憾 43 34 9073 30 3 https://mp.weixin.qq.com/s/nHhoFhuNojiAqqHKkfBYLQ 经营一段亲密关系,难吗? 22 18 1.2w 69 2 https://mp.weixin.qq.com/s/RqrH9kdgUHIiAfS_vxg3QA 婆媳之间,何止是缘分? 37 27 1.0w 70 1 https://mp.weixin.qq.com/s/QMEy489ohNGHiETixElgMQ 不合群的你,其实也很酷 57 45 1.4w 174 `; var untreatedArticles = articleStatic.split('\\n'); var treatedArticles = untreatedArticles.slice(1,untreatedArticles.length-1); var nums = []; var comments = []; var reads = []; var likes = []; for (i = 0; i 简书公众号 36 篇优质文章数据 编号 标题 阅读量 点赞量 评论量 36 为什么在一线城市待久的人,就很难再回去? 15204 60 36 35 我嫂子——那个“恶女人”的前半生:你哥他...不行 14984 31 46 34 成为谁,也不要成为小s 25831 28 36 33 你真傻,念念不忘,是没有回响的 12264 41 38 32 蔡少芬直言当过小三:老天给了她一手烂牌,她却打出了王炸 29429 55 25 31 微信朋友圈查访客记录:我和我的“朋友圈”爱人 16965 23 27 30 我的好胜心害死了好朋友······ 17709 102 26 29 我那个从事裸体艺术的前女友去世了... 22393 28 33 28 贾玲,你也是活该! 28793 111 41 27 “女生第1次和第5次的区别”,有些东西,只有经历了才懂得 33164 61 64 26 我的3次相亲经历,次次都教我做人 22641 36 65 25 2060年,那个失踪的宇航员回来了...... 10981 13 11 24 “好不容易上个大学,谈什么恋爱啊” 8760 39 39 23 富家女离奇死亡,摩天轮内发现惊人一幕 12573 28 29 22 有好教养的人,总是闪闪发光的啊 10745 55 17 21 优质男:姑娘,我凭什么娶你? 21840 88 62 20 那个改变了我一生的老男人······ 12552 74 27 19 “叔叔阿姨,那个...能小点声吗?” 31607 72 37 18 “我,23岁,拖延癌晚期,还有救吗?” 9256 48 17 17 “十二年了,我才没那么想他” 14173 61 77 16 十年工厂生涯,我活成了没有梦想的中年人 12835 70 96 15 你现在这么努力,是为了有朝一日“有得选” 11637 76 15 14 谢谢你,给我18厘米的爱情 27232 83 90 13 我爱上了隔壁那个被家暴的女人 19771 32 59 12 你以为是丑拒,其实是...... 14861 41 25 11 追我那么久,请你放过我 18788 101 71 10 我犯了一种罪,叫长的不好看 16087 101 67 9 你都用不起SK-II,还敢熬这么深的夜? 13146 54 39 8 你这么内向,那应该找不到对象吧 17036 124 100 7 想用自律打败低配人生,你配吗? 1.8w 117 57 6 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 7988 57 16 5 越优秀的人,越不安分 1.5w 191 32 4 毕业了,谁又不曾有过爱情的遗憾 9073 30 43 3 经营一段亲密关系,难吗? 1.2w 69 22 2 婆媳之间,何止是缘分? 1.0w 70 37 1 不合群的你,其实也很酷 1.4w 174 57 36 https://mp.weixin.qq.com/s/JA_H5y0SHOcS0V-pn8V7Jw 为什么在一线城市待久的人,就很难再回去? 36 23 15204 60 35 https://mp.weixin.qq.com/s/yqcT5VlIcLh9d7Eyz4uyFw 我嫂子——那个“恶女人”的前半生:你哥他...不行 46 30 14984 31 34 https://mp.weixin.qq.com/s/xJRtW340VwRNDU1hxQc0wA 成为谁,也不要成为小s 36 23 25831 28 33 https://mp.weixin.qq.com/s/TTqzmPeof8FN3D1kErSnJQ 你真傻,念念不忘,是没有回响的 38 31 12264 41 32 https://mp.weixin.qq.com/s/TTqzmPeof8FN3D1kErSnJQ 蔡少芬直言当过小三:老天给了她一手烂牌,她却打出了王炸 25 20 29429 55 31 https://mp.weixin.qq.com/s/7aTGbUziPgWijye1xKF7LA 微信朋友圈查访客记录:我和我的“朋友圈”爱人 27 24 16965 23 30 https://mp.weixin.qq.com/s/sNiSuR7acH43kwnP5yox9g 我的好胜心害死了好朋友······ 26 19 17709 102 29 https://mp.weixin.qq.com/s/i7xqmHHbYrmbBHy__aaGfA 我那个从事裸体艺术的前女友去世了... 33 26 22393 28 28 https://mp.weixin.qq.com/s/85tALM-OAaCet78rfQ59Hw 贾玲,你也是活该! 41 28 28793 111 27 https://mp.weixin.qq.com/s/bhKFbcoeXDrVmfDhpyDiUA “女生第1次和第5次的区别”,有些东西,只有经历了才懂得 64 44 33164 61 26 https://mp.weixin.qq.com/s/lG3mGfKAvTrI7UL383D6Kw 我的3次相亲经历,次次都教我做人 65 31 22641 36 25 https://mp.weixin.qq.com/s/bT33bHJcsf_Jpwx9mjswNg 2060年,那个失踪的宇航员回来了...... 11 9 10981 13 24 https://mp.weixin.qq.com/s/FBzJZzmzNrpeNtcQBSY4fQ “好不容易上个大学,谈什么恋爱啊” 39 33 8760 39 23 https://mp.weixin.qq.com/s/1JBaS5FTcrzRnylIBaoAKg 富家女离奇死亡,摩天轮内发现惊人一幕 29 23 12573 28 22 https://mp.weixin.qq.com/s/pfqKkHNI9bQDbhdv4gPAnw 有好教养的人,总是闪闪发光的啊 17 12 10745 55 21 https://mp.weixin.qq.com/s/o6aGhHp5uI3Nquy0usaaww 优质男:姑娘,我凭什么娶你? 62 50 21840 88 20 https://mp.weixin.qq.com/s/PoiHroXm3V1kKjj3u1jnAw 那个改变了我一生的老男人······ 27 20 12552 74 19 https://mp.weixin.qq.com/s/IpXCQ6YPFrq39T1hFu4P-g “叔叔阿姨,那个...能小点声吗?” 37 34 31607 72 18 https://mp.weixin.qq.com/s/8WOH-pBrSCyEyByf8AOi3A “我,23岁,拖延癌晚期,还有救吗?” 17 13 9256 48 17 https://mp.weixin.qq.com/s/74gJMnvGILwajfFMwWFc_g “十二年了,我才没那么想他” 77 71 14173 61 16 https://mp.weixin.qq.com/s/Fb6Ygp07QkaQYFh-O2OBOw 十年工厂生涯,我活成了没有梦想的中年人 96 74 12835 70 15 https://mp.weixin.qq.com/s/n7mTI2dOAPQjG312gXP_8g 你现在这么努力,是为了有朝一日“有得选” 15 11 11637 76 14 https://mp.weixin.qq.com/s/vJpJkwMlqZLLvmf6W342gg 谢谢你,给我18厘米的爱情 90 55 27232 83 13 https://mp.weixin.qq.com/s/TJrq7zNELA48CG_WUJRtnA 我爱上了隔壁那个被家暴的女人 59 40 19771 32 12 https://mp.weixin.qq.com/s/zOQrwG5q7lwvypabYDs27w 你以为是丑拒,其实是...... 25 11 14861 41 11 https://mp.weixin.qq.com/s/l5GKBMa0RMYa5Zs_Z-J1bQ 越是难熬的日子,越要有事可做 22 18 13294 101 11 https://mp.weixin.qq.com/s/jUHhqDS2fjLjTXY551Bn5w 追我那么久,请你放过我 71 64 18788 101 10 https://mp.weixin.qq.com/s/8y2jAt5be1ndl1JNV4H-1A 我犯了一种罪,叫长的不好看 67 34 16087 101 9 https://mp.weixin.qq.com/s/k8uidACpKxz6a8YCg-kwzQ 你都用不起SK-II ,还敢熬这么深的夜? 39 34 13146 54 8 https://mp.weixin.qq.com/s/Vt_nd4xx5gEhNXgyzojAcQ 你这么内向,那应该找不到对象吧 100 81 17036 124 7 https://mp.weixin.qq.com/s/SU3CHUNT7l5Hy0P8WWYPAw 想用自律打败低配人生,你配吗? 57 46 1.8w 117 6 https://mp.weixin.qq.com/s/FoKMrZ-gvzn1RWUtsFQzJQ 为什么大多数人宁愿吃生活的苦,也不愿吃学习的苦? 16 12 7988 57 5 https://mp.weixin.qq.com/s/ugG71ETZOAygI-DhMruCEA 越优秀的人,越不安分 32 22 1.5w 191 4 https://mp.weixin.qq.com/s/e1mif6nhCJ-XeRoilhW83A 毕业了,谁又不曾有过爱情的遗憾 43 34 9073 30 3 https://mp.weixin.qq.com/s/nHhoFhuNojiAqqHKkfBYLQ 经营一段亲密关系,难吗? 22 18 1.2w 69 2 https://mp.weixin.qq.com/s/RqrH9kdgUHIiAfS_vxg3QA 婆媳之间,何止是缘分? 37 27 1.0w 70 1 https://mp.weixin.qq.com/s/QMEy489ohNGHiETixElgMQ 不合群的你,其实也很酷 57 45 1.4w 174 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/jianshu-optimize-static.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:53 "},"write/mermaid-flow-chart.html":{"url":"write/mermaid-flow-chart.html","title":"快速上手Mermaid流程图","keywords":"","body":"快速上手Mermaid流程图 本文主要介绍了如何快速上手 Mermaid 流程图,不用贴图上传也不用拖拉点拽绘制,基于源码实时渲染流程图,操作简单易上手,广泛被集成于主流编辑器,包括 markdown 写作环境. 通过本节内容你将学习到以下主要内容: 了解什么是流程图以及Mermaid流程图; 掌握并能记住如何绘制Mermaid流程图; 了解 Gitbook 写作环境的相关集成插件. 什么是Mermaid流程图 关键词 - 项目地址 - 在线编辑 - 官方文档 千言万语不如一张图,使用图形展示事物处理流程的图形称之为流程图. Mermaid是一个基于 Javascript 的图解和制图工具.它基于 markdown 语法来简化和加速生成流程图的过程,也不止于生成流程图. 源码 graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} C -->|One| D[Laptop] C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car] 效果 项目地址: https://github.com/mermaid-js/mermaid 在线编辑: https://mermaidjs.github.io/mermaid-live-editor/ 官方文档: https://mermaid-js.github.io/mermaid/#/flowchart Mermaid流程图快速入门 布局方向 关键词 + TB + BT + LR + RL 流程图布局方向,由四种基本方向组成,分别是英文单词: top(上), bottom(下),left(左)和 right(右).其中可选值: TB (从上到下),BT (从下到上),LR (从左往右)和 RL (从右往左)四种. 核心: 仅支持上下左右四个垂直方向,是英文单词首字母大写缩写. TB 从上到下: from Top to Bottom 源码 graph TB Start --> Stop 效果 BT 从下到上: from Bottom to Top 源码 graph BT Start --> Stop 效果 LR 从左往右: from Left to Right 源码 graph LR Start --> Stop 效果 RL 从右往左: from Right to Left 源码 graph RL Start --> Stop 效果 形状 关键词 - 节点形状 + [矩形] - [[暂不支持]] - [(圆柱)] - [{暂不支持}] - [/平行四边形/] - [\\平行四边形\\] - [/梯形\\] - [\\梯形/] + (圆角矩形) - ((圆形)) - ([体育场]) - ({暂不支持}) + {菱形} - {{六边形}} - {[暂不支持]} - {(暂不支持)} + >不对称矩形] 流程图节点形状,默认支持矩形和圆两种基本形状,包括基本形状的简单变体,支持嵌套组合形式,其中 [] 表示矩形,() 表示圆弧,{} 表示尖角(窃以为 <> 更适合)等等. 核心: 最外层代表主形状,内层辅助修饰. 一次性节点 一次性节点,默认表现为矩形节点,其文本内容直接显示 id 的值,适合后续不会出现多次引用的情况. id 建议直接写成有意义的文本描述而不是当成唯一标识. 源码 graph TD id 效果 可重复节点 可重复节点,指定节点形状,其文本内容不再是 id 的值而是 的值,适合后续出现多次引用相同节点的情况. id 代表节点的唯一标识,当前节点的文本描述由 的值指定,建议 id 写成有意义的唯一标识. 矩形 一般格式: [node description] ,[] 中括号表示节点是矩形形状,node description 是节点的描述文本. 源码 graph LR id1[This is the text in the box] 效果 圆角矩形 一般格式: (node description) ,() 小括号表示节点是圆角矩形形状,node description 是节点的描述文本. 源码 graph LR id1(This is the text in the box) 效果 体育场 一般格式: ([node description]) ,() 小括号嵌套 [] 中括号表示节点是大弧度的圆角矩形形状,也就是体育场形状,node description 是节点的描述文本. 源码 graph LR id1([This is the text in the box]) 效果 圆柱 一般格式: [(node description)] ,[] 中括号嵌套 () 小括号表示节点是圆柱形状,node description 是节点的描述文本. 源码 graph LR id1[(Database)] 效果 圆形 一般格式: ((node description)) ,() 小括号嵌套 () 小括号表示节点是圆形形状,node description 是节点的描述文本. 源码 graph LR id1((This is the text in the circle)) 效果 不对称矩形 一般格式: >node description] ,左边是右尖括号 > ,右边是右中括号 ] 表示不对称矩形形状,node description 是节点的描述文本. 源码 graph LR id1>This is the text in the box] 效果 菱形 一般格式: {node description} ,{} 大括号表示菱形形状,node description 是节点的描述文本. 源码 graph LR id1{This is the text in the box} 效果 六角形 一般格式: {{node description}} ,{} 大括号嵌套 {} 大括号表示六角形形状,node description 是节点的描述文本. 源码 graph LR id1\\{\\{This is the text in the box\\}\\} Gitbook 语法中双大括号 {} 表示特殊意义,上述源码只能转义处理,实际上并不需要 \\ 进行转义. 效果 平行四边形 一般格式: [/node description/] ,[] 中括号嵌套 // 左斜杠表示左斜平行四边形形状,node description 是节点的描述文本. 源码 graph TD id1[/This is the text in the box/] 效果 平行四边形 一般格式: [\\node description\\] ,[] 中括号嵌套 \\\\ 右斜杠表示右斜平行四边形形状,node description 是节点的描述文本. 源码 graph TD id1[\\This is the text in the box\\] 效果 梯形 一般格式: [/node description\\] ,[] 中括号嵌套 /\\ 左右斜杠表示上短下长梯形形状,node description 是节点的描述文本. 源码 graph TD A[/Christmas\\] 效果 另一种梯形 一般格式: [\\node description/] ,[] 中括号嵌套 \\/ 右左斜杠表示上长下短梯形形状,node description 是节点的描述文本. 源码 graph TD B[\\Go shopping/] 效果 连接线 关键词 + 实线/虚线 - -- - -. + 有箭头/无箭头 - > - - + 有描述/无描述 - 实线 + --描述文字 + |描述文字| - 虚线 + -.描述文字 + |描述文字| + 加粗 - == + 组合形式 - --> - --- - -.-> - -.- - 有描述实线有箭头 + --描述文字--> + -->|描述文字| - 有描述实线无箭头 + --描述文字--- + ---|描述文字| - 有描述虚线有箭头 + -.描述文字-.-> + -.->|描述文字| - 有描述虚线无箭头 + -.描述文字-.- + -.-|描述文字| - ==> - === - 有描述加粗实线有箭头(2) + ==描述文字==> + ==>|描述文字| - 有描述加粗实线无箭头(2) + ==描述文字=== + ===|描述文字| 流程图连接线样式,支持实线和虚线以及有箭头样式和无箭头样式,除此之外还支持添加连接线描述文字,其中 -- 代表实线,实线中间多一点 -.- 代表虚线,添加箭头用右尖括号 > ,没有箭头继续用短横线 -. 核心: 先实线再虚线,先有箭头再去箭头,左边位置添加描述文字需要区分实现还是虚线,右边位置添加描述文字格式一致. 有箭头无描述实线 一般格式: --> ,其中 -- 表示实线,> 表示有箭头. 源码 graph LR A-->B 效果 无箭头实线 一般格式: --- ,其中 -- 表示实线,- 表示无箭头. 源码 graph LR A --- B 效果 带描述的有箭头实线 一般格式: --connection line description--> ,其中左边的 -- 添加到实线左边位置,右边的 --> 表示带箭头的实线. 源码 graph LR A-- text -->B 效果 一般格式: |connection line description| ,其中 || 添加到连接线右边位置. 源码 graph LR A-->|text|B 效果 带描述的无箭头实线 一般格式: --connection line description ,其中左边的 -- 添加到实线左边位置,右边的 --- 表示不带箭头的实线. 源码 graph LR A-- This is the text ---B 效果 一般格式: |connection line description| ,其中 || 添加到连接线右边位置. 源码 graph LR A---|This is the text|B 效果 有箭头虚线 一般格式: -.connection line description.-> ,其中左边的 -. 添加到虚线左边位置,右边的 .-> 表示带箭头的虚线. 源码 graph LR A-. text .-> B 效果 有箭头加粗实线 一般格式: ==> ,表示加粗实线. 源码 graph LR A ==> B 效果 带描述的有箭头加粗实线 一般格式: ==connection line description ,左边的 == 添加到加粗实现左边,右边的 ==> 代表加粗实线. 源码 graph LR A == text ==> B 效果 带描述的有箭头加粗实线 一般格式: |connection line description| ,其中 || 添加到连接线右边位置. 源码 graph LR A ==>|text| B 效果 高级用法 关键词 + -->--> + & + \"\" + %% + subgraph 多节点链式连接 源码 支持链式连接方式,A-->B-->C 等价于 A-->B 和 B-->C 形式. graph LR A -- text --> B -- text2 --> C 效果 多节点共同连接 支持共同连接方式,A-->B & C 等价于 A-->B 和 A-->C 形式. 源码 graph LR a --> b & c--> d 效果 多节点相互连接 多节点共同连接的变体形式,A & B --> C & D 等价于 A-->C ,A-->D,B-->C 和 B-->D 四种组合形式. 源码 graph TB A & B--> C & D 效果 双引号包裹特殊字符 连接线描述文字存在特殊字符使用双引号 \"\" 包裹处理,如遇到 [] 和 () 以及 {} 等特殊字符情况. 源码 graph LR id1[\"This is the (text) in the box\"] 效果 双引号包裹转义字符 支持 Html 转移字符 源码 graph LR A[\"A double quote:#quot;\"] -->B[\"A dec char:#9829;\"] 效果 嵌套子流程图 定义 subgraph title graph definition end 示例 graph TB c1-->a2 subgraph one a1-->a2 end subgraph two b1-->b2 end subgraph three c1-->c2 end 注释语法 注释以 %% 开头并且独占一行. graph LR %% this is a comment A -- text --> B{node} A -- text --> B -- text2 --> C 快速入门流程图回顾总结 关键词 - 英文单词缩写 - 几何化形状 - 有限语法 Mermaid 是一款开源的制图工具,可使用 Markdown 语法绘制流程图,支持更改流程图节点形状,添加描述文字以及更改连接线样式等等. 英文单词缩写 四种布局方向的值是英文单词首字母大写缩写形式,默认仅支持垂直方向. 中文 英文 示例 图解 graph graph 流程图类型标识 子图 subgraph subgraph 嵌套子流程图标识 上 top TB 或 BT ,从上到下或从下到上的布局方向 下 bottom BT 或 TB,从下到上或从上到下的布局方向 左 left LR 或 RL,从左往右或从右往左的布局方向 右 right RL 或 LR,从右往左或从左往右的布局方向 几何化形状 键盘符号形象化几何形状,组合形式表示形状的叠加,其中最外层符号是主形状,嵌套符号是辅助形状. 基本单元 表示法 含义 类型 备注 [] 矩形 节点形状 支持 () 圆角矩形 节点形状 支持 {} 菱形 节点形状 支持 <> 菱形 节点形状 不支持 -- 实线 连接线样式 支持 -. 虚线 连接线样式 支持 == 加粗实线 连接线样式 支持 =: 加粗虚线 连接线样式 不支持 > 有箭头 连接线样式 支持 - 无箭头 连接线样式 支持 双竖线 右边连接线描述文字 连接线描述文字 支持 -- 左边实线连接线描述文字 连接线描述文字 支持 -. 左边虚线连接线描述文字 连接线描述文字 支持 == 左边加粗实线连接线描述文字 连接线描述文字 支持 =: 左边加粗虚线连接线描述文字 连接线描述文字 不支持 组合单元 表示法 含义 类型 备注 [[]] 正方形 节点形状 不支持 [()] 圆柱体 节点形状 支持 [{}] 棱柱体 节点形状 不支持 (()) 圆形 节点形状 支持 ([]) 体育场 节点形状 支持 ({}) 圆弧 节点形状 不支持 双大括号 六边形 节点形状 支持 {[]} 正多边形 节点形状 不支持 {()} 圆弧 节点形状 不支持 --> 实线带箭头 连接线样式 支持 --- 实线无箭头 连接线样式 支持 -.> 虚线带箭头 连接线样式 不支持 -.-> 虚线带箭头 连接线样式 支持 .-> 虚线带箭头 连接线样式 支持 -.- 虚线无箭头 连接线样式 支持 .- 虚线无箭头 连接线样式 支持 ==> 加粗实线带箭头 连接线样式 支持 === 加粗实线无箭头 连接线样式 支持 =:> 加粗虚线带箭头 连接线样式 不支持 =:=> 加粗虚线带箭头 连接线样式 不支持 =:= 加粗虚线无箭头 连接线样式 不支持 := 加粗虚线无箭头 连接线样式 不支持 双竖线 右边连接线描述文字 连接线描述文字 支持 --connection line description--> 左边实线带箭头连接线描述文字 连接线描述文字 支持 -.connection line description-.-> 左边虚线带箭头连接线描述文字 连接线描述文字 支持 --connection line description--- 左边实线无箭头连接线描述文字 连接线描述文字 支持 -.connection line description-.- 左边虚线无箭头连接线描述文字 连接线描述文字 支持 ==connection line description==> 左边加粗实线带箭头连接线描述文字 连接线描述文字 支持 =:connection line description=:=> 左边加粗虚线带箭头连接线描述文字 连接线描述文字 不支持 ==connection line description=== 左边加粗实线无箭头连接线描述文字 连接线描述文字 支持 =:connection line description=:= 左边加粗虚线无箭头连接线描述文字 连接线描述文字 不支持 有限语法 不论是节点形状还是连接线样式,语法支持是有限的,并不是随意组合的叠加状态,也可能随着后续更新会支持更多,一切以官方文档为主. 除了提供最基础的操作节点的能力之外,还可以根据 JS 和 CSS 相关知识高度自定义流程图行为表现,具体可参考官方文档. 官方文档: https://mermaid-js.github.io/mermaid/#/flowchart?id=styling-and-classes 交互能力 Interaction : https://mermaid-js.github.io/mermaid/#/flowchart?id=interaction 外观样式 Styling and classes : https://mermaid-js.github.io/mermaid/#/flowchart?id=interaction 字体支持 Basic support for fontawesome: https://mermaid-js.github.io/mermaid/#/flowchart?id=basic-support-for-fontawesome 空格分隔 https://mermaid-js.github.io/mermaid/#/flowchart?id=graph-declarations-with-spaces-between-vertices-and-link-and-without-semicolon var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/mermaid-flow-chart.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-05-05 09:31:30 "},"write/faq.html":{"url":"write/faq.html","title":"常见问题","keywords":"","body":"常见问题 The page build failed for the master branch with the following error 问题描述 看到这封邮件,一脸懵逼,本地运行 gitbook 服务是正常渲染的,控制台并没有任何报错,谁知道推送到 github 时就报错了! 登录到 github 网站查看网站源码已经同步过来了,但是静态网站无法同步,本地实在找不到任何报错信息,这让我如何是好? 再看 github 反馈用的邮件中说道,如有问题可以回复邮件(If you have any questions you can contact us by replying to this email.). 然后死马当活马医,尝试阐释了我的问题,请求帮助定位错误日志,没想到当天下午就收到 github 的回复邮件,提供了解决办法! 问题是由于 Liquid Warning: Liquid syntax error (line 334) 错误,然而我确定这部分代码是没有任何问题的,因为这是我改造 gitbook-plugin-tbfed-pagefooter 插件时的一段代码,反复确认后发现并没有复制粘贴出错啊! var moment = require('moment'); module.exports = { book: { assets: './assets', css: [ 'footer.css' ], }, hooks: { 'page:before': function(page) { var _label = '最后更新时间: ', _format = 'YYYY-MM-DD', _copy = 'powered by snowdreams1006' if(this.options.pluginsConfig['tbfed-pagefooter']) { _label = this.options.pluginsConfig['tbfed-pagefooter']['modify_label'] || _label; _format = this.options.pluginsConfig['tbfed-pagefooter']['modify_format'] || _format; var _c = this.options.pluginsConfig['tbfed-pagefooter']['copyright']; _copy = _c ? _c + ' all right reserved,' + _copy : _copy; } var _copy = ''+_copy+''; var str = ' \\n\\n' + _copy + '' + _label + '\\n{{file.mtime | date(\"' + _format + '\")}}\\n'; str += '\\n\\n'+ '\\n\\n'+ '\\n\\n'+ '\\n\\n'; page.content = page.content + str; return page; } }, filters: { date: function(d, format) { return moment(d).format(format) } } }; 来源于 gitbook-plugin-tbfed-pagefooter 插件的 index.js 文件,这里为了兼容 gitalk 插件而集成了相关代码,详情请参考 gitalk 评论插件 解决方案 根据邮件回复,定位到出错代码片段,真的没发现有什么问题啊? 既然已经确定不是我的问题,那很可能就是 github 的问题了,邮件中推荐我使用 Jekyll 进行构建网站,不不不! 既然已经选择 gitbook 搭建静态网站,那就没必要再使用 Jekyll ,我可不想那么麻烦! If you are not using Jekyll you can disable it by including a .nojekyll file in the root of your repository. 所以我不妨试试新增 .nojekyll 文件,说不定就好使了呢! $ touch .nojekyll $ git add .nojekyll $ git commit -m \"add .nojekyll\" $ git push 天不负我!竟然真的好使了,再也没有收到 github 的报错邮件反馈了,源码和网站都正常更新了! 小结 据我推测,可能是 github 误认为我的网站是使用 Jekyll 工具构建的,实际上,是使用 gitbook 构建的! 因此,增加 .nojekyll 文件禁用 Jekyll 工具,自然不会再受相关语法限制而报错了. 所以,遇到问题时,不仅要多思考,更应该寻求官方人员的帮助,即使不回你,你也要尝试一下! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/write/faq.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"bigDataWave/":{"url":"bigDataWave/","title":"大数据浪潮之巅:新技术商业制胜之道","keywords":"","body":"大数据浪潮之巅:新技术商业制胜之道 这本书讲述了大数据的发展史,描述了一幅波澜壮阔的宏大场面,有的公司迎难而上抓住机遇,有的公司错失良机失之交臂,看到的不是技术而是一系列的历史事件. 富有趣味性,让人爱不释手,通宵看完意犹未尽,值得推荐,好书! 作者简介 徐飞,著名大数据专家.拥有浙江大学本科学位,美国佛罗里达大学计算机博士学位,研究方向为数据库系统. 从事大数据的基础架构研发 10 余年,先后在微软和 Tableau 等知名公司的大数据核心团队工作.担任过首席架构师. 在国际顶尖会议和杂志上发表论文 10 余篇. 2016 年开通微信公众号\"飞总聊 IT\",并原创\"大数据那些事\"系列文章,系统阐述了大数据发展史,以及发展过程中的种种技术和商业决策案例,受到广泛好评. 内容简介 本书以各个企业在大数据浪潮中跌宕起伏的经历为核心来讲述大数据发展史,并分析各个大数据企业迥异的发展历程,探讨在新技术浪潮来临时应该如何应对. 本书主要分为两部分,前半部分讲述谷歌,微软,IBM,雅虎,亚马逊,阿里巴巴等大公司在大数据浪潮中的发展史,后半部分讲述各个大数据创业公司的发展历程和现状. 在每部分的后面,还通过专门的文章分析并总结了各企业在大数据浪潮中的作为和选择所产生的影响. 全书从公司的视角出发为大家呈现了一幅波澜壮阔的大数据领域发展史,读者不仅可以了解大数据技术,更能领略大数据领域的全貌,从各公司的故事中吸取教训,学习思路. 本书适合对大数据技术和商业思维有兴趣的读者阅读. 目录 谷歌的大数据路: 从拥有\"三驾马车\"到丧失先发优势 谷歌的“三驾马车”开启了大数据时代,然而在这个新时代里,谷歌却丧失了先发优势. 这是为什么呢?我认为是谷歌对待开放架构的态度相对保守导致的. 谷歌的大数据路: 一场影响深远的论战 在大数据发展史上,以迈克尔·斯通布雷克为代表的数据库元老级人物,针对MapReduce向谷歌提出了质疑. 这场著名的论战给整个业界带来了动荡,最后诞生了Spark. 谷歌的大数据路: 谷歌的\"黑科技\" 在大数据的上半场,谷歌以\"三驾马车\"引领时代,但后来因为决策失误丧失了先发优势; 而在大数据的下半场,谷歌带着\"黑科技\"Spanner数据库系统闪亮登场,效果如何呢? 如何读懂类似谷歌\"三驾马车\"这样的技术论文 读懂一篇技术论文,首先需要明白\"论文是写给谁看的\"和\"论文是怎么写出来的\"这两个基本问题,然后就可以有针对性地提升自己阅读论文的功力. 雅虎:大数据领域的\"活雷锋\" 雅虎,这个早已淡出我们视线的公司,却是大数据领域的\"活雷锋\",可以说正是它促成了今天的Hadoop生态圈.这篇文章就来说说它的故事. IBM的大数据路——起早贪黑赶了个晚集 作为历史悠久的计算机公司,IBM早早涉足了大数据领域,最终却只能寄希望于比自己的产品起步还要晚的Spark,我们来看看其中发生了什么. 三大社交媒体公司对Hadoop生态圈的贡献 雅虎把Hadoop开源以后,当时著名的三大社交媒体公司Facebook,LinkedIn和Twitter都加入了这个生态圈,并做出了巨大贡献.Hadoop生态圈给我们的启示是,抱团取暖才是生存之道. 微软的大数据发展史: 微软硅谷研究院 微软硅谷研究院曾经在微软的大数据发展历程中扮演了非常特殊的角色,它推出的Dryad和DryadLINQ可以说是两个另类的产品,虽然未曾大受欢迎,却对大数据的发展有着不可磨灭的贡献. 微软的大数据发展史: 必应的Cosmos Cosmos是微软必应搜索引擎下面的团队开发的大数据基础架构,代表了微软在大数据方面的最高成就. 微软的大数据发展史: Azure的发展 微软大数据发展史上的另一个分支是微软云计算平台下的大数据项目Azure. 这个项目产生了HDInsight,Azure Data Lake,CosmosDB三大平台,但最后只有CosmosDB取得成功. 亚马逊的大数据故事: 从先驱者到一味索取者 在大数据技术发展的早期,亚马逊发表了Dynamo系统的论文,成为和谷歌\"三驾马车\"的论文一样具有深远影响的论文. 然而随着大数据的发展和Hadoop生态圈的建立,亚马逊对大数据圈的贡献极少,但亚马逊自己却从中获得了巨大的利益. 亚马逊的大数据故事: 创新和\"拿来\"并存的云服务 亚马逊不仅在Hadoop生态系统里蓬勃发展,还推出了自己的数据分析产品.这些产品有些是亚马逊自己研发的,有些则只是对开源的产品进行了包装.但是,亚马逊一如既往地没有反哺开源项目. 阿里巴巴的大数据故事: 数据分析平台发展史 国内大数据平台做得最好的公司当属阿里巴巴. 本文就来介绍一下阿里巴巴数据分析平台的发展情况: 数据分析平台的叠加开发. 阿里巴巴的大数据故事: 流计算引擎发展史 在阿里巴巴的发展过程中,流数据处理一直是一项十分重要的技术,阿里巴巴也在这方面做了很多有意义的项目. 本文就来介绍一下阿里巴巴的流计算引擎JStorm与Blink的发展史. 大公司的大数据战略得失: 自建\"轮子\"成本高 大公司的大数据平台可分为两类,一类是自己搭的基础架构(自建\"轮子\"),另一类是抱团取暖所形成的Hadoop生态圈,两者各有利弊.本文将分析第1种情况,主要以谷歌,微软,阿里巴巴自己搭建的大数据平台架构为代表. 大公司的大数据战略得失: 抱团取暖的Hadoop生态圈 除了自建\"轮子\"的公司,其他各大公司走向了一条抱团取暖的道路,就是你搭一个模块,我搭一个模块,大家一起开源出来,最后组成了一个叫作Hadoop的生态圈. 其中有为社区积极做贡献的公司,也有以赚钱为目的的公司,还有一味索取的公司. Hadoop三国之\"魏国\"——Cloudera Hadoop领域曾经有三家发行商互相角逐,其中不乏各种战术与谋略,仔细琢磨,你会发现这三家公司的关系与三国时期的魏蜀吴之间的关系非常相似.本文讲述Hadoop三国之\"魏国\"——Cloudera的故事. Hadoop三国之\"吴国\"——MapR Hadoop三国之\"吴国\"MapR,实力强大却很少参与竞争,这篇文章就来说说它特立独行的故事. Hadoop三国之\"蜀国\"——Hortonworks Hadoop三国之\"蜀国\"Hortonworks始终坚持100%开源,本文讲述它的故事. Hadoop及其发行商的未来 Hadoop已诞生十多年,围绕其生态圈诞生了诸多企业,例如前面讲的社交媒体公司,三大发行商,而亚马逊却最终成为最大的受益者. 文档数据库的缔造者MongoDB(上) MongoDB的诞生像一场意外.它是一个文档型数据库,由10gen公司开发,以易用性闻名. 本文就来讲述MongoDB团队的开发重心,商业运作模式和产品盈利方式. 文档数据库的缔造者MongoDB(下) MongoDB的开发团队一向重视用户体验而不重视核心功能,其负面影响终于以一次安全危机的方式暴露. 加上公司曾经获得具有CIA背景的风投公司的投资,这一并引起了很多人的顾虑. 当然,这一切都挡不住MongoDB公司最终的成功上市. 以MongoDB为例,看基础架构类产品创业 作为一款基础架构类产品,MongoDB以其易用性闻名,然而MongoDB的开发者不注重系统的可靠性,只注重可用性,导致很多MongoDB的用户转向了其他产品. 基础架构类产品的创业者应该如何平衡可用性和可靠性?这是一个值得深思的问题. 直面MongoDB,谈微软的N0SQL战略 2013年,MongoDB在数据库市场中的占有率很高,成为很多创业者和初创企业的首选. 微软究竟做了哪些事情,将Cosmos DB变成能与MongoDB竞争的产品的呢? Palantir: 神秘的大数据独角兽公司 Palantir是一家神秘的大数据创业公司,由硅谷著名投资人彼得·蒂尔创办,其主要服务对象是美国政府部门,特情组织和军队,所以外界对其了解甚少. Splunk: 机器日志数据分析帝国 Splunk是大数据圈里少有的盈利并且蓬勃发展的企业. 它主要服务于机器日志数据分析领域,随后又不断拓展业务,演变开发了若干不同类型的软件. 在本文中我们就来好好聊聊Splunk的进阶史. Confluent: Kafka项目背后的公司 Kafka是LinkedIn开发的开源项目,它主要通过日志文件传输的方式在不同的数据源之间同步数据. 而Confluent公司是Kafka开源项目的创始人离开LinkedIn以后所创立的公司,主要致力于Kafka项目的商业化. 在本文中,我们来讲讲这家公司的故事. Powerset: HBase的\"老东家\" Powerset是一家在多年前被微软收购的创业公司,目前在语义搜索方面开疆拓土. 它为开源社区贡献了BigTable的Hadoop版实现. 本文就来讲讲这家公司的发展史. Cassandra和DataStax公司的故事 Cassandra是开源社区仿照Amazon Dynamo开发的产品,它最初由Facebook开发并开源,却又被公司内部弃用. 创业公司DataStax对Cassandra大力支持,造就了今日繁荣的Cassandra社区。 Databricks: Spark的数据\"金砖\"王国 Spark是Hadoop生态圈里大红大紫的项目,它甚至取代了Hadoop MapReduce的地位. Databricks是对这个项目进行商业化的企业. 本文就来聊聊这家企业的故事. Data Artisans和浴火重生的新一代大数据计算引擎Flink Data Artisans是对Flink进行商业化的公司. Apache Flink是一个年轻的新型处理引擎,是Hadoop社区里Spark的主要竞争对手. Flink设计理念先进,但是工程实现方面相对落后. Dremio: 基于Drill和Arrow的大数据公司 Dremio是另外一家大数据创业公司,其创始人是从MapR公司跳槽出来的. Dremio的主要产品就是Dremio项目,它吸收了MapR主导的开源项目Drill的精华,以开源项目Arrow为核心开发. 本文就来讲讲Dremio公司和Dremio平台的来龙去脉. Imply: 基于Druid的大数据分析公司 开源大数据项目Druid由Metamarkets开发. 开始时籍籍无名,后来被一些大公司,尤其是Airbnb使用和推广以后,受到了很多关注. Kyligence: 麒麟背后的大数据公司 麒麟(英文名字是Kylin)是第1个全部由中国人主导的Apache顶级开源项目,Kyligence则是对这个项目进行商业化的公司. 本文就来看看麒麟和Kyligence的故事. Snowflake: 云端的弹性数据仓库 Snowflake是一个构建在云端的弹性数据仓库,它背后的公司与之同名. Snowflake公司的创始人和管理层都有强大的背景,本文就来讲一下Snowflake及其公司的故事. TiDB:一个国产新数据库的创业故事 TiDB是位于北京的一家创业公司PingCAP的产品,它的目标是实现一个开源的类似谷歌Spanner的系统,这个产品非常有特色,本文就来聊聊TiDB和它背后的公司. 大数据创业公司的前景: 红海vs.蓝海 关于创业的市场,通常有红海和蓝海的说法,蓝海容易成功,红海相对艰难. 对大数据创业公司来说,蓝海多半指的是应用软件类的市场,而红海指的则是基础架构软件类的市场. 本文将对比分析一下这两类市场. 如何通过分析企业的技术积累来判断其发展前景 通过分析企业的技术积累,能够有效地判断企业的发展前景如何. 我们需要关注三个方面: 技术适用的场景是否有巨大的盈利空间,技术本身是否有领先和独到之处,以及技术的积累是否足够深和广. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/bigDataWave/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "},"greet/":{"url":"greet/","title":"晚安","keywords":"","body":"晚安 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/greet/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/":{"url":"other/","title":"其他","keywords":"","body":"其他 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/dev-env-install.html":{"url":"other/dev-env-install.html","title":"重装开发环境","keywords":"","body":"重装开发环境 重装系统的实验环境 由于莫名其妙的原因,系统总是在我正在敲代码敲得正入迷时意外重启,我以为这一次和往常一样只要关机再重新开机就能恢复正常状态. 遗憾的是,这一次竟然卡死在关机界面,登录界面之间无限循环,于是乎在苹果客服的电话支持下经过多重方式重试均无效,只能降级重装系统. 身在老家农村的我,哪来的 WiFi 也没有超大硬盘,只能含泪接受了抹除硬盘并用热点共享的方式重装系统的解决方案. ...... (内心崩溃中,已省略一万字,想要查看更多心理历程,请解锁: AES 对称加密并含有密钥!) U2FsdGVkX1+R6u5DTm0gElVut7ICgmOmOp6YA2L90vYOEB9T0D27maRPThnnsSGI VHW0N39zZ7dVaaOBxe1f3xsHQ3OwD6TDg78M1/xQ0Td68Y8NjKuN5hwqF43UHmHJ ueVAuBRvI9MSV3nJKoNoC12V+ZYHeAtxu6daHjOwGVtBAbXbWSJWbvfvor1tjAuy 热点共享一下午,用掉了我整整 10g 的流量,恐怖如斯,不敢回想,不过总算能正常开机了,此次降级后的系统版本真的好低,恢复出厂设置的系统感觉要被众多软件放弃了呢! 总结 恢复出厂设置的新电脑,不再是熟悉的开发环境,所以只能动手重新打造开发环境. 当然一起从流量出发,只能安装最核心软件,想不到的软件只能说明是暂时不需要的也可能是可有可无的工具. 先从输入法开始搞起 下载链接传送门: http://cdn2.ime.sogou.com/d902446f85c2478e05fb37d385817173/5e4ce413/dl/index/1574950329/sogou_mac_56b.zip 先用系统自带的浏览器输入关键字搜狗输入法并打开官网找到下载链接,下载完成后双击进行安装,稍后自行登陆账号以获取同步设置. 切换到搜狗输入法并手动删除系统自带的输入法,防止多个输入法之间来回切换,能删则删,干净利落! 总结 搜狗输入法是国内下载源,下载速度还是非常快的,大小在 50Mb 左右,非常良心,毕竟随随便便一款手机聊天软件都要一二百兆. 动手改造默认浏览器 下载链接传送门: https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg 没有 Chrome 浏览器,不是完整的 web 开发体验,Safari 浏览器虽好但还是不敌 Chrome 浏览器,嫌弃的同时还下载 Chrome 浏览器再说! 下载安装包后双击进行安装,并将其设置为默认浏览器,打开 Chrome 浏览器后更改默认搜索引擎为百度. 默认搜索引擎是谷歌,所以不更改默认行为的话,大概率无法正常上网,除非先解决 FQ 问题. 打开浏览器地址栏,搜索关键字\"谷歌助手\"下载插件帮助访问谷歌服务,进而登陆谷歌账号同步浏览器设置. 下载完成后双击解压文件夹,得到源码以及打包文件,两者任选其一进行安装,值得注意的是,需要打开扩展程序右上角的开发模式. 随后就可以无障碍访问谷歌服务,包括但不限于登录谷歌账号进行同步浏览器设置,同步后的浏览器基本上已经可以满足使用了,但是还有一些细节需要手动设置. 窗口控制台的设置并没有同步过来,因此这些细节一定要注意,调试程序时不要想当然,出问题了先检查工具是否正常. 总结 Chrome 浏览器虽然是国外网站,但是下载速度还是很快的,文件大小也只有 90MB 左右,安装后需要先解决上网问题再同步云设置,心有余力时再手动检查一遍设置是否完成. 开发环境最佳实践 开发环境主要包括基础环境以及语言环境,比如版本控制工具 git 或者 svn 就是管理源码的网盘,无论是 python 环境还是 java 环境,只要涉及到源码就需要做版本控制,所以先保证这一部分基础环境安装正常才能继续搭建语言环境. 基本工具开发环境 版本控制工具 git 默认情况下,Mac 自带 git 环境,因此不需要额外下载,只需要配置 git 账号保证能够正常查看并提交代码即可. $ git --version #git version 2.10.1 (Apple Git-78) Git 的配置过程包括本地开发环境以及远程开发环境,如果仅仅是个人单独使用 Git 不涉及多人合作,并不需要配置远程开发环境. step 1 : 配置本地开发环境 这里修改成自己的用户名和邮箱,例如我的用户名是: git config --global user.name \"snowdreams1006\" $ git config --global user.name \"your username\" # 配置 Git 账号用户名 $ git config --global user.email \"example@example.com\" # 配置 Git 账号邮箱 step 2 : 配置远程开发环境 这里要修改成自己的邮箱,例如我的邮箱是: ssh-keygen -t rsa -C \"snowdreams1006@163.com\",生成 ssh 密钥对的过程一路回车默认设置即可,最后生成的公钥文件位于 ~/.ssh/id_rsa.pub $ ssh-keygen -t rsa -C \"youremail@example.com\" # 配置 Git 账号邮箱 $ cat ~/.ssh/id_rsa.pub # 查看生成 ssh 公钥 step 3 : 上传公钥到远程服务器 如果远程服务器是 Github 网站的话,可以复制公钥内容粘贴到 Github 网站,完成后就可以使用 ssh 方式免密拉取或提交代码了. 如果远程服务器是自搭建的 Git 服务器,那么需要将公钥内容追加到服务器认证文件(~/.ssh/authorized_keys),实现免密使用 Git 协议的目的. nodejs 开发环境 https://registry.npmjs.org https://registry.npm.taobao.org python 开发环境 安装真实环境 下载链接传送门: https://www.python.org/ftp/python/3.8.1/python-3.8.1-macosx10.9.pkg 阿里云: http://mirrors.aliyun.com/pypi/simple/ 清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/ 中科大 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣: http://pypi.douban.com/simple/ pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ virtualenv pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt 安装虚拟环境 pip3 install virtualenv java 开发环境 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/dev-env-install.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-20 09:53:43 "},"other/dingtalk-reminder.html":{"url":"other/dingtalk-reminder.html","title":"钉钉签到提醒","keywords":"","body":"钉钉签到提醒 本文主要介绍了如何利用现成软件快速实现钉钉自动签到功能,核心思路非常简单,甚至无任何编程基础的小白也能轻松实现定时自动打卡功能. 通过本节课程,你将学习到以下内容: 怎么开启钉钉极速打卡功能; 如何唤醒手机中的知名软件; 如何自己给自己发推送提醒; 怎么运行24h不间断定时程序. 大纲 前言 关键词 + 提醒闹钟 + 极速打卡 如今钉钉早已经成为全国性的 app,不仅正在工作中的上班族离不开就连小学生也不逃不开被操控的宿命,不得不说钉钉真的是让人又爱又恨! 例如: 功能非常简单也是使用频率相当高的打卡签到功能就让我忍不住吐槽,提醒方式只有两种而且还是单选,要么设置闹钟要么设置极速打卡! 当然还有第三种方式: 你也可以选择关闭提醒,呵呵. 所以最简单操作流程如下: 上述流程中让我觉得不爽的是每次除了打开 app 响应时间过长之外,还有就是每次打卡都要自己心里盘算着打卡时间,浪费了为数不多的脑细胞. 因此需要一种更加傻瓜式操作流程来解放大脑,最好能实现自动打卡或者去掉打卡功能也可以,让我沉浸在工作的海洋中吧! 效果 上下班时间定时推送打卡通知,点击确认后自动完成打卡操作,省心省力不用记忆打卡时间,交给程序处理吧! 原理 关键词 + 极速打卡 + URL Scheme + 定时唤醒 由于钉钉签到打卡只有两种快捷方式可供选择,相比于提醒闹钟个人更偏爱极速打卡方式,因此开启极速打卡功能后,只要在打卡时间段内打开 app 就会自动打卡,所以只要简化某些流程就能实现傻瓜式打卡签到. 在众多唤醒手机中知名 app 的解决方案中有一种比较简单的方式,那就是 URL Scheme 方案. 自定义 URL Scheme 协议 常用的URL Scheme URL Scheme 是一种页面跳转协议,类似于网页中的 url 链接,常用于 h5 网站引流到 app 应用的跳转访问,不同于普通 url 的http/https 协议,URL Scheme 一般由 app 自定义协议头,例如 dingtalk . 例如: 钉钉的跳转协议是 dingtalk://具体什么内容由钉钉自主规定 ,如果是普通的 url 链接用户点击就会跳转到指定网页,而 URL Scheme 链接点击后就会跳转到相应的 app 界面. url 链接演示 : https://snowdreams1006.tech/ URL Scheme 链接演示 : dingtalk://snowdreams1006.tech/ 所以只要在浏览器中访问到类似于 dingtalk://snowdreams1006.tech/ 这样的链接就能实现自动跳转到钉钉 app 中,既然已经解决了如何打开 app 的问题,那么接下来的事情就是什么时候访问链接的问题了. 不知疲倦的crontab定时程序 Linux crontab 命令 对于编程开发者来说,实现定时程序的最简单方式之一当属于 crontab 命令了,属于 linux 环境的基本命令之一,可用来执行定时程序,类似于日常生活中的日程表的功能. # 早上 8 点半和下午 5 点半运行 dingtalk.sh 脚本文件并将运行结果写入到 dingtalk.log 文件 30 08,17 * * * sudo ~/reminder/dingtalk.sh >> ~/reminder/dingtalk.log 现在搞定了定时运行脚本程序的问题,接下来的事情就比较简单了,在 24h 不间断运行的服务器上需要定时执行什么命令才能实现用户实现访问到自定义链接呢? 狗吠狼吼bark给自己推送通知 Bark is an iOS App which allows you to push customed notifications to your iPhone : https://github.com/Finb/Bark 很显然,我们需要一款推送服务,服务器定时下发推送通知给注册手机用户,由用户主动点击确认后跳转到自定义协议网页,紧接着就会触发钉钉 app 的唤醒操作,从而实现自动打卡功能. 正常来说,手机上的 app 应用绝大多数都具备推送功能,然而推送内容都是由 app 自主控制,用户只能选择开启或关闭通知功能,并不能决定通知内容,而 bark 却是一款自己给自己发推送通知的软件,简单且开源. # 点击推送将跳转到url的地址(发送时,URL参数需要编码) https://api.day.app/yourkey/百度网址?url=https://www.baidu.com 只要访问指定链接就能发送推送通知给用户手机,进而用户主动点击通知就能跳转到指定网页,从而实现唤醒钉钉 app 的目的. # bark 客户端和服务器均开源,可以独立部署也可以使用默认服务. https://api.day.app/yourkey/钉钉打卡提醒/带我去极速打卡?url=dingtalk://snowdreams1006.tech 命令行curl网络请求神器 curl 的用法指南 现在已经构造出推送通知请求链接,只需要访问该链接就能完成推送通知,由用户直接操作的话可以复制到浏览器直接回车,但是如果要用程序运行的话就需要使用脚本命令 curl 神器. curl -i -X GET \\ \"https://api.day.app/yourkey/%E9%92%89%E9%92%89%E6%89%93%E5%8D%A1%E6%8F%90%E9%86%92/%E5%B8%A6%E6%88%91%E5%8E%BB%E6%9E%81%E9%80%9F%E6%89%93%E5%8D%A1?url=dingtalk://snowdreams1006.tech\" 现在总算实现了定时推送通知打开钉钉 app 实现自动签到的功能,其实整个流程并不复杂,解决的是钉钉 app 并没有提供定时自动签到的功能这一问题. 总结 关键词 + 前提条件 + 核心重点 + 实现步骤 + 主要技术 + 感谢支持 本文的主要目的是解决钉钉上下班签到打卡问题,提醒闹钟和极速打开只能二者选其一,并且也没有定时自动签到功能,所以本文的解决思路是利用 URL Scheme 定时唤醒 app,然后借助极速打卡实现自动签到功能. 前提条件 支持固定上下班时间且开启极速打卡功能 只有固定上下班时间才支持极速打卡功能,否则唤醒 app 后还是需要手动打卡岂不是多此一举? 拥有至少一台 24h 不间断运行的服务器 用于提供定时推送服务,如果有其他手段完成定时访问唤醒链接的任务也可以不需要服务器. 下载并注册 bark 软件或者其他类似软件 用于接收推送通知,点击推送通知内容确认后进而自动完成签到打卡操作,省去了劳心费神的记忆时间. 核心重点 浏览器打开类似于 dingtalk://snowdreams1006.tech/ 这样的带有 dingtalk:// 协议的自定义链接即可实现打开钉钉 app 操作. 实现步骤 crontab 定时推送唤醒链接到注册手机,用户点击推送通知确认后自动打开钉钉 app 进行极速签到,从而变相实现了自动签到的目的. 由于不需要心里暗自计算上下班时间,只需要接收到推送后手动点击进行签到确认,所以大大节省了不少脑细胞,剩下的时间又可以愉快敲代码了! 主要技术 高度自定义的网页跳转协议 : dingtalk 不知疲倦的定时任务命令: crontab 命令行下的网络请求命令: curl 自己给自己发推送通知软件: bark 感谢支持 如果本文对你有所帮助,欢迎点赞留言告诉我一声,你的支持和鼓励将会是我继续创作的动力! var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/dingtalk-reminder.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-04-09 22:53:15 "},"other/reminder.html":{"url":"other/reminder.html","title":"倒计时提醒","keywords":"","body":"倒计时提醒 #! /bin/sh leftDays=$((($(date +%s -d '20191215') - $(date +%s ))/86400)) title=\"***!\" body=\"***${leftDays}天,您真的准备好了吗?\" echo \"title=${title} body=${body}\" curl -i -X GET \\ \"https://bark.snowdreams1006.cn/***/${title}/${body}?automaticallyCopy=1©=${body}&url=https://blog.snowdreams1006.cn/\" curl -i -X GET \\ \"https://sc.ftqq.com/***.send?text=${title}---$(uuidgen)&desp=${body}\" whereis crontab crontab -e */1 * * * * sudo ~/reminder/tiaotiao.sh >> ~/reminder/tiaotiao.log 每一分钟执行一次 shell 脚本并输出到日志 crontab -l 00 08,10,14,17,22 * * * sudo ~/reminder/tiaotiao.sh >> ~/reminder/tiaotiao.log crontab 在线表达式 Shell 变量 shell 计算两个日期之间的天数 使用crontab定时执行shell脚本 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/reminder.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/anti-withdrawback-wechat.html":{"url":"other/anti-withdrawback-wechat.html","title":"微信防撤回","keywords":"","body":"微信防撤回 目录结构 微信目录 $ tree /Applications/WeChat.app/ -L 3 /Applications/WeChat.app/ └── Contents ├── Frameworks │ ├── AFNetworking.framework │ ├── CocoaLumberjack.framework │ ├── JietuFramework.framework │ ├── MMLibHooks.framework │ ├── WCDB.framework │ └── matrixreport.framework ├── Info.plist ├── MacOS │ ├── WeChat │ ├── WeChatExtension.framework │ └── WeChat_backup ├── PkgInfo ├── PlugIns │ └── WeChatMacShare.appex ├── Resources │ ├── AppIcon.icns │ ├── Assets.car │ ├── Base.lproj │ ├── zh-Hans.lproj │ └── zh-Hant.lproj ├── _CodeSignature │ └── CodeResources └── _MASReceipt └── receipt app_bundle_path : /Applications/WeChat.app/Contents/MacOS/ app_executable_path : /Applications/WeChat.app/Contents/MacOS/WeChat app_executable_backup_path : /Applications/WeChat.app/Contents/MacOS/WeChat_backup framework_path : /Applications/WeChat.app/Contents/MacOS/WeChatExtension.framework 插件结构 $ tree /Users/snowdreams1006/Documents/workspace/WeChatExtension-ForMac-master/WeChatExtension/Rely - L 3 /Users/snowdreams1006/Documents/workspace/WeChatExtension-ForMac-master/WeChatExtension/Rely ├── Install.sh ├── Plugin │ ├── WeChatExtension │ │ ├── Update.sh │ │ └── WeChatExtension.framework │ │ ├── Resources │ │ │ ├── Base.lproj │ │ │ │ ├── TKAboutWindowController.nib │ │ │ │ └── TKRemoteControlWindowController.nib │ │ │ └── zh-Hant.lproj │ │ │ ├── Localizable.strings │ │ │ ├── TKAboutWindowController.nib │ │ │ ├── TKAboutWindowController.strings │ │ │ ├── TKRemoteControlWindowController.nib │ │ │ └── TKRemoteControlWindowController.strings │ │ ├── Versions │ │ └── WeChatExtension │ └── WeChatExtension.zip ├── Uninstall.sh └── insert_dylib WeChatExtension.framework : ./Plugin/WeChatExtension/WeChatExtension.framework ${shell_path}/insert_dylib --all-yes \"${framework_path}/${framework_name}\" \"$app_executable_backup_path\" \"$app_executable_path\" install #!/bin/bash wechat_path=\"/Applications/WeChat.app\" if [ ! -d \"$wechat_path\" ] then wechat_path=\"/Applications/微信.app\" if [ ! -d \"$wechat_path\" ] then echo -e \"\\n\\n应用程序文件夹中未发现微信,请检查微信是否有重命名或者移动路径位置\" exit fi fi app_name=\"WeChat\" shell_path=\"$(dirname \"$0\")\" framework_name=\"WeChatExtension\" app_bundle_path=\"${wechat_path}/Contents/MacOS\" app_executable_path=\"${app_bundle_path}/${app_name}\" app_executable_backup_path=\"${app_executable_path}_backup\" framework_path=\"${app_bundle_path}/${framework_name}.framework\" # 对 WeChat 赋予权限 if [ ! -w \"$wechat_path\" ] then echo -e \"\\n\\n为了将小助手写入微信, 请输入密码 : \" sudo chown -R $(whoami) \"$wechat_path\" fi # 判断是否已经存在备份文件 或者 是否强制覆盖安装 if [ ! -f \"$app_executable_backup_path\" ] || [ -n \"$1\" -a \"$1\" = \"--force\" ] then # 备份 WeChat 原始可执行文件 cp \"$app_executable_path\" \"$app_executable_backup_path\" result=\"y\" else read -t 150 -p \"已安装微信小助手,是否覆盖?[y/n]:\" result fi if [[ \"$result\" == 'y' ]]; then cp -r \"${shell_path}/Plugin/WeChatExtension/${framework_name}.framework\" ${app_bundle_path} ${shell_path}/insert_dylib --all-yes \"${framework_path}/${framework_name}\" \"$app_executable_backup_path\" \"$app_executable_path\" fi uninstall # !/bin/bash wechat_path=\"/Applications/WeChat.app\" if [ ! -d \"$wechat_path\" ] then wechat_path=\"/Applications/微信.app\" if [ ! -d \"$wechat_path\" ] then echo -e \"\\n\\n应用程序文件夹中未发现微信,请检查微信是否有重命名或者移动路径位置\" exit fi fi app_name=\"WeChat\" framework_name=\"WeChatExtension\" app_bundle_path=\"${wechat_path}/Contents/MacOS\" app_executable_path=\"${app_bundle_path}/${app_name}\" app_executable_backup_path=\"${app_executable_path}_backup\" framework_path=\"${app_bundle_path}/${framework_name}.framework\" # 备份WeChat原始可执行文件 if [ -f \"$app_executable_backup_path\" ] then rm \"$app_executable_path\" rm -rf \"$framework_path\" mv \"$app_executable_backup_path\" \"$app_executable_path\" if [ -f \"$app_executable_backup_path\" ] then echo \"卸载失败,请到 /Applications/WeChat.app/Contents/MacOS 路径,删除 WeChatPlugin.framework、WeChat 两个文件文件,并将 WeChat_backup 重命名为 WeChat\" else echo \"\\n\\t卸载成功, 重启微信生效!\" fi else echo \"\\n\\t未发现微信小助手\" fi 初识逆向工程 下面是在iOS逆向分析中使用到的一些工具,大家可以先看看了解下,在后面的课程中会给大家讲解一些工具的使用及其原理。 一、砸壳工具: dumpdecrypted: https://github.com/stefanesser/dumpdecrypted Clutch: https://github.com/KJCracks/Clutch 二、界面分析工具: Reveal: https://revealapp.com/ 三、监控工具: snoop-it: https://code.google.com/archive/p/snoop-it introspy: https://github.com/iSECPartners/Introspy-iOS 四、静态分析工具 IDA: https://www.hex-rays.com/products/ida/support/download_demo.shtml Hopper: https://www.hopperapp.com/ 五、动态调试工具 ldb: http://lldb.llvm.org/ 六、动态脚本工具 cycript: http://www.cycript.org/ frida: http://www.frida.re 七、抓包工具 BurpSuite: https://portswigger.net/burp/download.html Charles: https://www.charlesproxy.com/ Wireshark: https://www.wireshark.org/download.html 八、Mac工具 MachOView: http://github.com/gdbinit/MachOView class-dump: https://github.com/nygard/class-dump thoes: https://github.com/theos/theos/wiki/installation iOSOpenDev: https://code.google.com/archive/p/iosopendev/downloads insert_dylib: https://github.com/Tyilo/insert_dylib iTerm: http://www.iterm2.com/ Alfred: https://www.alfredapp.com/ iTools: http:/wwW.itools.cn/ iFunBox: http://www.i-funbox.com/ lipo , otool 认识越狱设备 本节课中涉及Cydia里面的工具: 系统定制工具: Cloaky 命令行工具: adv-cmds 文件管理工具: iFile 越狱环境配置 本节课涉及到的一些工具: OpenSSH usbmuxd appsync Apple File Conduit'2' 一、OpenSSH安装使用 ssh root@ip password:alpine 修改密码 passwd passwd mobile 二、免密码登录 ssh-keygen -t rsa -P '' scp /Users/用户名/.ssh/id_rsa.pub root@Ip:/tmp cat /tmp/id_rsa.pub >> .ssh/authorized_keys 三、通过USB连接 下载地址: https://cgit.sukimashita.com/usbmuxd.git usbmuxd-1.0.8 python tcprelay.py -t 22:2222 ssh root@localhost -p 2222 scp -P 2222 ./test.txt root@localhost:/tmp 四、SSH中文 创建\".inputrc\" set convert- meta off set meta-flag on set output-meta on 导入文件到 var/root 下面 五、key不匹配的问题 打开 /Users/monkey/.ssh/know_hosts 文件,删除对应 IP 的 key 即可。 六、查看进程列表 ps aux | grep xXx 快速SSH登入设备的方法 这里给大家安利一个快速快速SSH登录设备的方法。 切换到目最 cd /Users/monkey/.ssh #monkey为当前用户名 如果没有 config 文件,新建一个。 touch config 编辑内容 Host 5c #这个名称是自定义的设备名,自己定义一个就行 Hostname localhost #我是通过USB端口映射,所以写localhost User root #以root用户登录 Port 2222 #指定端口号为映射的端口号 2222 IdentityFile /Users/monkey/.ssh/id_rsa #rsa key的路径 端口映射 python tcprelay.py 22:2222 一键登录 ssh 5c 设置启动时端口自动映射: 安装: brew install libimobiledevice 创建文件: -/Library/LaunchAgents/com.usbmux.iproxy.plist 写入内容: Label com.usbmux.iproxy ProgramArguments /usr/local/bin/iproxy 2222 22 RunAtLoad KeepAlive 执行命令: launchctl load -/Library/LaunchAgents/com.usbmux.iproxy.plist 安装 iterm2 , 点击 Preferences ,然后点击 Profiles Command 为 ssh 5c 以后只需要 Command + O ,打开对应的 Profile 即可。 App的结构与构建过程 示例运行方式:执行 make all,生成 ipa 文件 使用正确 Team ID 编辑 make 文件,在 plist 部分的 CFBundleldentifie 填写可用的 Team ID 编辑 entitlementsplist 文件,配置 Team ID 正确地签名: 编辑 make 文件,在 codesign 部分填写正确的开发名证书 ID; 将包含 device ID 的 provision 的文件放到 make 文件同级目录; itools: http://www.itools.cn/ 寻找控件背后的代码 lldb 打印页层级结构 po [UIApplteation abaredlpplication].keywindow.recursivaDeseription 获取 UIControl 的 action 的需要的方法: allTargets allControlEvents actionsForTarget:forControlEvent: 数据存储 获取沙盒目录: //沙盒根目录 NSHomaDirectory() //cache目录 NSSearchPathPorDirectoriesInDomainaNSCacheaDirectory, NSUsarDomainMaak, YES) plst文件格式转换: plutil -convert xnl1 plist_binary_file 或 pluti1 -convart pliat_binary_file -0 xx.plist keychain信息导出工具: Keychain-Dumper iOs文件系统 httpe:fideveloper.apple.camdibrarylcontent/documentatian FileManagemenUCor/. htn 类与方法 打印 instance size p(int)class_getInstanceSize([AppDelegate class]) arm64 从 isa 获 class 的方法 p/x (Class)(isa_content &(long)_debag_1sa_elaas_mask) Shell 教程 linux:shell脚本(逻辑判断和字符串比较) var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/anti-withdrawback-wechat.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/transformation.html":{"url":"other/transformation.html","title":"转型之路","keywords":"","body":"转型之路 视频来自腾讯课堂: 程序员转型之路 如何学习?什么叫理财? 01.如何正确认识学习? 知识: 靠记忆 (20%) 技能: 靠练习 (30%) 态度: 靠发心 (50%) 纸上谈兵一万次,不如战场来一遍 02.谈转型 graph LR product((产品)) -.- management((管理)) management -.- freelance((自由职业)) freelance -.- market((市场)) market -.- entrepreneurship((创业)) 03.如何更高效学习与运用 保持持续性学习 学会跳出自己的\"舒适区\" 思维转变,换个角度看世界 04.学习思路 graph LR empty_cup_mentality[空杯心态] --- closing_comments[关闭评论] closing_comments --- know_and_use[知用合一] know_and_use --- timely_output[及时输出] 如何让自己快速拥有10万+流量,并且快速变现 各大平台特点 抖音 今日头条 微信公众号 知乎 新浪博客 简书 大鱼号 百家号 搜狐自媒体 熊掌号 一点资讯 抖音分为商家版和个人版,成立专门团队,越专业越成功! 到底如何写内容 标题的重要性 字数在20字以内 出乎意料 带数字 给人感觉像故事或干货 人格化定位 引导转发和分享 如何把流量快速变现 如何写出可以直接收钱的文案 如何30分钟学会演讲,客服种种心里障碍 如何复制转型成功的程序员称为自有职业者,月收入是5万+ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/transformation.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/static.html":{"url":"other/static.html","title":"历史统计","keywords":"","body":"历史统计 全网统计 c3.generate({\"bindto\":\"#plugin-chart-6\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 110, 115, 115, 116, 134, 197, 285 ], [ \"阅读\", 49453, 53447, 53829, 54106, 71422, 91544, 115362 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 慕课手记 慕课手记 : https://www.imooc.com/u/5224488/articles c3.generate({\"bindto\":\"#plugin-chart-7\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 18, 18, 18, 18, 19, 24, 27 ], [ \"阅读量\", 16448, 18037, 18215, 18313, 24293, 29870, 34813 ], [ \"手记\", 61, 61, 61, 61, 64, 75, 79 ], [ \"推荐\", 78, 79, 79, 79, 85, 99, 105 ], [ \"积分\", 334, 341, 342, 342, 421, 555, 632 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 简书 简书 : https://www.jianshu.com/u/577b0d76ab87 c3.generate({\"bindto\":\"#plugin-chart-8\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 21, 24, 24, 24, 28, 41, 49 ], [ \"阅读量\", 2825, 3242, 3287, 3317, 4869, 6194, 8277 ], [ \"文章\", 61, 61, 61, 61, 64, 74, 79 ], [ \"喜欢\", 121, 125, 126, 127, 135, 151, 174 ], [ \"简书钻\", 107, 24, 24, 24, 26, 69, 95 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); CSDN CSDN : https://blog.csdn.net/weixin_38171180 c3.generate({\"bindto\":\"#plugin-chart-9\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 0, 0, 0, 0, 0, 11, 53 ], [ \"访问量\", 2872, 2846, 2858, 2858, 3306, 4097, 10484 ], [ \"原创\", 59, 60, 60, 60, 62, 72, 99 ], [ \"喜欢\", 55, 55, 55, 55, 59, 68, 90 ], [ \"积分\", 669, 669, 669, 669, 706, 820, 954 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 博客园 博客园 : https://www.cnblogs.com/snowdreams1006/ c3.generate({\"bindto\":\"#plugin-chart-10\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 21, 21, 21, 21, 21, 23, 25 ], [ \"阅读数\", 7756, 8256, 8298, 8329, 11240, 14397, 16256 ], [ \"随笔\", 61, 62, 62, 62, 64, 74, 78 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 掘金 掘金 : https://juejin.im/user/582d5cb667f356006331e586 c3.generate({\"bindto\":\"#plugin-chart-11\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"关注者\", 10, 10, 10, 10, 15, 33, 51 ], [ \"阅读数\", 1885, 2086, 2093, 2105, 3246, 4984, 7005 ], [ \"专栏\", 60, 60, 60, 60, 63, 74, 79 ], [ \"点赞数\", 89, 89, 90, 91, 107, 147, 168 ], [ \"掘力值\", 106, 109, 110, 110, 138, 194, 235 ] ], \"axes\": { \"关注者\": \"y2\" }, \"types\": { \"关注者\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"关注者\", \"position\": \"outer-middle\" } } } }); 思否 思否 : https://segmentfault.com/blog/snowdreams1006 c3.generate({\"bindto\":\"#plugin-chart-12\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 2, 2, 2, 2, 3, 4, 4 ], [ \"阅读量\", 6063, 6980, 7073, 7156, 10591, 15226, 18746 ], [ \"内容数\", 62, 62, 62, 62, 65, 75, 80 ], [ \"点赞数\", 23, 23, 23, 23, 27, 29, 37 ], [ \"声望\", 91, 91, 91, 91, 97, 101, 123 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 开源中国 开源中国 : https://my.oschina.net/snowdreams1006 c3.generate({\"bindto\":\"#plugin-chart-13\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 17, 17, 17, 17, 17, 22, 27 ], [ \"访问量\", 6307, 6374, 6376, 6383, 6593, 7756, 9389 ], [ \"博文\", 61, 61, 61, 61, 64, 74, 79 ], [ \"推荐\", 13, 13, 13, 13, 13, 15, 16 ], [ \"积分\", 13, 13, 13, 13, 13, 14, 17 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); 腾讯云社区 腾讯云社区 : https://cloud.tencent.com/developer/user/2952369/activities c3.generate({\"bindto\":\"#plugin-chart-14\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 13, 13, 13, 13, 13, 13, 13 ], [ \"阅读量\", 4809, 5062, 5065, 5076, 6324, 7642, 8573 ], [ \"文章\", 62, 62, 62, 62, 67, 77, 80 ], [ \"点赞\", 199, 199, 199, 199, 215, 248, 256 ], [ \"订阅\", 11, 12, 12, 12, 12, 12, 12 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); B站专栏 B站专栏 : https://member.bilibili.com/v2#/upload-manager/text c3.generate({\"bindto\":\"#plugin-chart-15\", \"data\": { \"x\": \"x\", \"columns\": [ [ \"x\", \"2019-03-31\", \"2019-04-30\", \"2019-05-31\", \"2019-06-30\", \"2019-07-31\", \"2019-08-31\", \"2019-09-30\" ], [ \"粉丝\", 10, 10, 10, 11, 18, 26, 36 ], [ \"阅读量\", 490, 564, 564, 569, 960, 1378, 1819 ], [ \"投稿\", 56, 56, 56, 56, 59, 70, 75 ], [ \"点赞\", 65, 65, 65, 65, 70, 78, 91 ], [ \"收藏\", 16, 22, 22, 23, 35, 46, 63 ], [ \"投币\", 1, 1, 1, 1, 1, 1, 1 ] ], \"axes\": { \"粉丝\": \"y2\" }, \"types\": { \"粉丝\": \"bar\" } }, \"axis\": { \"x\": { \"type\": \"timeseries\", \"tick\": { \"format\": \"%Y-%m-%d\" } }, \"y2\": { \"show\": \"true\", \"label\": { \"text\": \"粉丝\", \"position\": \"outer-middle\" } } } }); var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/static.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/me.html":{"url":"other/me.html","title":"关于作者","keywords":"","body":"关于作者 慕课网认证作者,今日头条优质科技领域创作者,微信公众号「雪之梦技术驿站」号主,目前专注于后端领域,主要技术栈有 Java,Go 和 NodeJs 等. var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/me.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"other/donate.html":{"url":"other/donate.html","title":"捐赠支持","keywords":"","body":"捐赠支持 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/other/donate.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"opensource/gitbook/":{"url":"opensource/gitbook/","title":"gitbook 插件项目","keywords":"","body":"gitbook 插件项目 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/opensource/gitbook/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"opensource/hexo/":{"url":"opensource/hexo/","title":"hexo 插件项目","keywords":"","body":"hexo 插件项目 var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/opensource/hexo/ 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:52 "},"GLOSSARY.html":{"url":"GLOSSARY.html","keywords":"","body":"markdown 简洁优雅的排版语言,简化版的 HTML,加强版的 TXT,详情请参考 https://snowdreams1006.github.io/markdown/ git 分布式版本控制系统,详情请参考 https://snowdreams1006.github.io/git/ var targetUl = document.getElementsByClassName('page-inner')[0].getElementsByTagName('ul')[0];if(targetUl.getElementsByTagName('a').length>0){targetUl.className='toc';} 作者: 雪之梦技术驿站 链接: https://snowdreams1006.github.io/GLOSSARY.html 来源: 雪之梦技术驿站 本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作! © snowdreams1006 all right reserved,powered by Gitbook文件修订时间: 2020-02-18 23:27:51 "}}