`元素。
+
+这些特殊的集合也都是动态的。
+
+#### DOM 一致性检测
+
+DOM 分为多个级别,也包含多个部分,因此检测浏览器实现了 DOM 的哪些部分就十分必要了。`document.implementation`属性就是提供检测方法的对象。
+
+#### 文档写入
+
+将输出流写入到网页中的能力已经存在很多年了,这个能力体现在四个方法中:`write()`、`writeIn()`、`open()`和`close()`。
+
+写入文本:
+
+* `write()`:接受一个参数,原样写入;
+* `writeIn()`:接受一个参数,添加换行符`\n`写入。
+
+```html
+
+ The current date and time is:
+
+
+```
+
+在页面呈现期间直接使用`document.write()`向页面添加内容是正常的,但是如果等页面渲染完毕了再使用`document.write()`添加内容就会**重写覆盖整个页面**。
+
+### Element 类型
+
+除了 Document 类型之外,Element 类型就是 Web 编程中最常用的类型了。它用于表现 XML 或 HTML 元素。
+
+* nodeType 为 1;
+* nodeName 为元素的标签名;
+* nodeValue 为 null;
+* parentNode 可能是 Document 或 Element;
+
diff --git a/source/_md/Gitea.md b/source/_md/Gitea.md
new file mode 100644
index 0000000..b363f15
--- /dev/null
+++ b/source/_md/Gitea.md
@@ -0,0 +1,5 @@
+```bash
+docker pull nginx:alpine
+docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
+docker run --rm nginx:alpine cat /etc/nginx/conf.d/default.conf > default.conf
+```
\ No newline at end of file
diff --git a/source/_md/Map与Set.md b/source/_md/Map与Set.md
new file mode 100644
index 0000000..9b3f381
--- /dev/null
+++ b/source/_md/Map与Set.md
@@ -0,0 +1,78 @@
+传统对象的键(key)只能是字符串
+
+Map 的键(key)可以是任何类型的,不像对象一样只能是字符串。
+
+```js
+let map = new Map([
+ [1, 'test'],
+ ['1', 'str']
+])
+```
+
+`map[key]`不是使用 Map 的正确方式,虽然`map[key]`也有效,例如我们可以设置`map[key] = 2`,这样会将 Map 视为 JavaScript 的 plain object,因此它暗含了所有相应的限制(没有对象键等)。所以我们应该使用 Map 方法:`set`和`get`等。
+
+`map.set`方法每次都会返回 Map 本身,所以可以使用“链式”调用:
+
+```js
+let map = new Map();
+
+map.set(obj, 'this is a object')
+ .set('test', 12312313123)
+ .set('obj', obj)
+```
+
+## Map 迭代
+
+Map 有三种迭代方法
+
+* `map.keys()` —— 遍历并返回所有的键(returns an iterable for keys),
+* `map.values()` —— 遍历并返回所有的值(returns an iterable for values),
+* `map.entries()` —— 遍历并返回所有的实体(returns an iterable for entries)`[key, value]`,`for..of`在默认情况下使用的就是这个。
+
+这三个方法都是能够将 Map 键值迭代出来,同时它们自身也是可迭代的:
+
+```js
+for (let i of map.entries()) {
+ console.log(i);
+}
+```
+
+> 迭代的顺序与插入值的顺序相同。与普通的 Object 不同,Map 保留了此顺序。
+
+### 从对象创建 Map
+
+对象方法`Object.entries()`的返回格式正好与创建 Map 构造函数相同,因此可以使用该方法使用对象创建 Map。
+
+```js
+let obj = {
+ name: 'xfy',
+ age: 18
+}
+
+console.log(Object.entries(obj));
+let map = new Map(Object.entries(obj));
+console.log(map.get('age'));
+```
+
+### 从 Map 创建对象
+
+`Object.fromEntries()`方法的作用是相反的,它可以从 Map 迭代返回的键值对中创建对象。并且具有各种类型的键会被转换为字符串。
+
+```js
+let test = 'xfy';
+
+let map = new Map([
+ [1, 3],
+ [test, 4]
+])
+
+let obj = Object.fromEntries(map.entries());
+console.log(obj);
+```
+
+Set
+
+Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。
+
+### Set 迭代
+
diff --git a/source/_md/回首那个jQuery一把梭的年代.md b/source/_md/回首那个jQuery一把梭的年代.md
new file mode 100644
index 0000000..c9652ea
--- /dev/null
+++ b/source/_md/回首那个jQuery一把梭的年代.md
@@ -0,0 +1,90 @@
+## 使用
+
+在页面中引入jQuery有多种方法,最常用的方法就是下载jQuery库到本地和使用公共CDN。jQuery 库是一个 JavaScript 文件,可以直接使用`script`标签引入到html页面中。
+
+```html
+
+
+
+```
+
+另外,现在的jQuery也支持在node上下载和使用。
+
+```js
+npm install jquery
+yarn add jquery
+```
+
+[下载]:(https://jquery.com/download/)
+
+## 语法
+
+jQuery简化了原生JS对DOM的操作,语法也与原生JS不尽相同。jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作。
+
+基础语法: $(selector).action()
+
+* 美元符号定义 jQuery
+* 选择符(selector)"查询"和"查找" HTML 元素
+* jQuery 的 action() 执行对元素的操作
+
+示例:
+
+* `$(this).hide()` - 隐藏当前元素
+* `$("p").hide()` - 隐藏所有 元素
+* `$("p.test").hide()` - 隐藏所有 class="test" 的
元素
+* `$("#test").hide()` - 隐藏 id="test" 的元素
+
+> jQuery 使用的语法是 XPath 与 CSS 选择器语法的组合。
+
+### 文档就绪函数
+
+文档就绪函数是问了防止jQuery在DOM没有加载完之前就开始运行的一个函数,它通常是这样的:
+
+```js
+$(document).ready(function() {
+ // code
+})
+```
+
+如果在文档没有完全加载之前就运行函数,操作可能失败。下面是两个具体的例子:
+
+* 试图隐藏一个不存在的元素
+* 获得未完全加载的图像的大小
+
+文档就绪函数还有一个简洁的写法,效果完全一样。
+
+```js
+$(function() {
+ // code
+})
+```
+
+原生JS也有类似的入口函数:
+
+```js
+window.onload = function () {
+ // code
+}
+```
+
+jQuery 入口函数与 JavaScript 入口函数的区别:
+
+* jQuery 的入口函数是在 html 所有标签(DOM)都加载之后,就会去执行。
+* JavaScript 的 window.onload 事件是等到所有内容,包括外部图片之类的文件加载完后,才会执行。
+
+
load和ready区别
+
+| | window.onload() | $(document).ready() |
+| -------- | ---------------------------------------------- | --------------------------------------- |
+| 执行时机 | 必须等待网页加载完才能执行(包括图片等) | 只需等待网页中的DOM结构加载完毕就能执行 |
+| 执行次数 | 只能执行一次,如果第二次,那么第一次就会被覆盖 | 可以执行多次,多次都不会被覆盖 |
+| 简写方式 | 无 | $(function() { // code }) |
+
+## 选择器
+
+jQuery 中所有选择器都以美元符号开头:`$()`。选择器基于元素的 id、类、类型、属性、属性值等"查找"(或选择)HTML 元素。 它基于已经存在的 CSS 选择器,除此之外,它还有一些自定义的选择器。
+
+## 事件
+
+jQuery 是为事件处理特别设计的。
+
diff --git a/source/_md/客户端检测.md b/source/_md/客户端检测.md
new file mode 100644
index 0000000..c292e7d
--- /dev/null
+++ b/source/_md/客户端检测.md
@@ -0,0 +1,205 @@
+浏览器发展至今,各种主流浏览器都实现了各自的长处,随之而来的就是各种不一致性的问题。
+
+## 能力检测
+
+最常用的一种客户端检测形式就是**能力检测**(特征检测)。能力检测不是去识别特定的浏览器,而是去识别浏览器的能力。只要确定了浏览器支持的特定能力,就可以给出特定的解决方案。检测手段也很简单,只需要用到简单的类型转换:
+
+```js
+if (Object.propertyInQuestion) {
+ // 使用特定能力
+}
+```
+
+来看一个简单的例子,在IE5.0之前不支持`document.getElementById()`这个DOM方法,但是可以使用非标准的`document.all`属性来实现相同的目的。所以:
+
+```js
+function getElementId(id) {
+ if (document.getElementById) {
+ return document.getElementById(id);
+ } else if (document.all) {
+ return document.all(id);
+ } else {
+ throw new Error('No way to get element id.')
+ }
+}
+```
+
+这里先判断标准方法是否存在,如果存在就直接使用。如果不存在,就使用IE5.0之前的非标准方法。如果二者都没有,则抛出一个错误。
+
+### 更可靠的能力检测
+
+仅仅靠简单的类型转换来检测是不够完善的,不仅仅要知道某个属性是否存在,还需要知道它是不是我们所需要的那个方法。如果仅使用类型转换来做判断,那么可能会遇到这样的问题:
+
+```js
+function isSortable(obj) {
+ return !!obj.sort;
+}
+
+let someObj = {
+ sort: 1
+}
+
+isSortable(someObj); //true
+```
+
+可以考虑善用`typeof`操作符,例如:
+
+```js
+function isSortable(obj) {
+ return typeof obj.sort == 'function';
+}
+```
+
+不过`typeof`操作符也不是完美的解决方案,在早期的IE中,某些DOM方法返回的是`object`而不是`function`。例如`document.createElement()`方法。
+
+除此之外,IE的ActiveX对象与其他对象的行为差异很大。例如:
+
+```js
+let xhr = new ActiveXObject('Microsoft.XMLHttp');
+if (xhr.open) { //发生错误
+ // do something...
+}
+
+typeof (xhr.open); //unknow
+```
+
+当然针对IE也是有解决办法的:
+
+```js
+//来自 Peter Michaux
+function isHostMethod(object, property) {
+ let t = object[property];
+ return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknow';
+}
+```
+
+## 怪癖检测
+
+怪癖检测,也就是Bug检测。通过确定浏览器以有的Bug来确定某一个特性不能正常工作。
+
+在IE8以及之前中有个Bug,将某个实例的属性设置为与标记了`[[Enumerbale]]`为`false`的某个原型属性同名,那么该属性就不会被枚举。可以这样来检测:
+
+```js
+(function hasEnumerableQuirk() {
+ let obj = {
+ toString: function () {}
+ }
+ for (let i in obj) {
+ if (i == 'toString') {
+ return false;
+ }
+ }
+ return true;
+})();
+```
+
+在Safari 3以前的版本中也有一个Bug,实例会枚举被隐藏的同名的原型属性。
+
+```js
+(function hasEnumerShadowQuirk() {
+ let obj = {
+ toString: function () {}
+ }
+ let count = 0;
+ for (i in obj) {
+ if (i == 'toString') {
+ count++;
+ }
+ }
+ return (count > 1);
+})()
+```
+
+## 用户代理字符串的历史
+
+这是一段很有趣的浏览器历史。
+
+// maybe later
+
+## 用户代理字符串检测
+
+用户代理字符串也就是常见的UA(UserAgent)。考虑到各个主流浏览器的发展历史,所以UA的判断也变的比较复杂。当然对于现代更加复杂的浏览器环境来说,识别出详细的浏览器还是需要更多的检测依据。
+
+### 识别呈现引擎
+
+呈现引擎,也就是浏览器的内核。每个引擎都有一些自己的特性,但是要正确的识别出引擎,关键还是识别顺序。
+
+为了不污染全局变量,这里使用局部变量的命名来命名。这个方法最终返回一个对象,这个对象就是根据检测到的引擎版本的键值对。
+
+```js
+let client = function () {
+ let engine = {
+ // 主流引擎
+ trident: 0,
+ gecko: 0,
+ webkit: 0,
+ khtml: 0,
+ opera: 0,
+
+ // 具体版本号
+ ver: null
+ }
+
+ return {
+ engine: engine
+ }
+```
+
+基本的变量命名都准备好了,接下来就是判断了。我们的第一步就是识别 opera,因为它的用户代理字符串有可能完全模仿其他浏览器。
+
+判断 opera 很简单,不需要去检测 ua 中的字符串,它有个全局变量`window.opera`供我们检测:
+
+```js
+ if (window.opera) {
+ engine.ver = window.opera.version();
+ engine.opera = parseFloat(engine.ver);
+ }
+```
+
+第二步就是 WebKit 了,WebKit 需要我们通过判断 ua 字符串内的特定内容来识别它。在客户端获取 UA 最好的办法就是通过`navigator.userAgent`属性。
+
+```js
+let ua = navigator.userAgent;
+let webkit = /AppleWebKit\/(\S+)/;
+
+if (webkit.test(ua)) {
+ engine.ver = ua.match(webkit)[1];
+ engine.webkit = parseFloat(engine.ver);
+}
+```
+
+KHTML 的用户代理字符串中也包含 Gecko,因此在排除 KHTML 之前,无法准确检测基于 Gecko 的浏览器。
+
+```js
+let khtml = /KHTML\/(\S+)/;
+let khtml1 = /Konqueror\/([^;]+)/;
+
+if (khtml.test(ua) || khtml1.test(ua)) {
+ engine.ver = ua.match(khtml)[1];
+ engine.khtml = parseFloat(engine.ver);
+}
+```
+
+在排除了 KHTML 与 WebKit 之后,就可以去检测 Gecko 了,Gecko 的版本号不一定会出现在 Gecko 关键字后面,而是会出现在`rv:`的后面。
+
+```js
+let gecko = /rv:([^\)]+)\) Gecko\/\d{8}/;
+if (gecko.test(ua)) {
+ engine.ver = ua.match(gecko)[1];
+ engine.gecko = engine.ver;
+}
+```
+
+在所有的大哥都被排除了之后,最后剩下的就是 IE 了。在最新版本的 IE11 中已经没有关键字`MSIE`,取而代之的是`rv:`。并且有个独有的关键字`WOW64`。
+
+```js
+let trident = {
+ wow: /WOW64/,
+ rv: /rv:([^\)]+)/
+};
+if (trident.wow.test(ua)) {
+ engine.ver = ua.match(trident.rv)[1];
+ engine.trident = engine.ver;
+}
+```
+
diff --git a/source/_md/容器化!nginx与certbot.md b/source/_md/容器化!nginx与certbot.md
new file mode 100644
index 0000000..64ce74c
--- /dev/null
+++ b/source/_md/容器化!nginx与certbot.md
@@ -0,0 +1,15 @@
+## Nginx 与反代
+
+在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
+
+不过这次水的部分不同,没想到过直接把小破站迁移到了 Hexo。这次准备搭建一些其他的服务,而有些东西对于 SSL 的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
+
+## Nginx 的安装与配置
+
+### 修改配置文件
+
+## 使用 certbot 获取证书
+
+### 为 Nginx 配置 SSL
+
+### 自动续期
\ No newline at end of file
diff --git a/source/_md/探索Node.js基本概念.md b/source/_md/探索Node.js基本概念.md
new file mode 100644
index 0000000..9a4286a
--- /dev/null
+++ b/source/_md/探索Node.js基本概念.md
@@ -0,0 +1,70 @@
+为了深入了解同步与异步编程,和充分理解这一理念。打算详细的了解与对比这两种编程模式。
+
+## Node编程风格
+
+随便搜寻几篇 Node.js 的文章便会发现,node 主要的编程风格便是使用非阻塞(异步)编码风格。先通过一个日常的举例来了解下阻塞(同步)编码与非阻塞(异步)编码之间的差异。
+
+## 同步与异步的对比 – 场景一
+
+Node.js 官网关于阻塞式 I/O 的定义如下:
+
+> “阻塞表示 Node.js 进程中其他 JavaScript 的执行必须等到非 JavaScript 操作完成后才能继续的情况。发生这种情况的原因在于,当发生阻塞操作时,事件循环无法继续运行 JavaScript。
+> 在 Node.js 中,由于 CPU 占用率高而不是等待非 JavaScript 操作(如 I/O)导致性能欠佳的 JavaScript 通常并不被称为阻塞。”
+
+### 同步
+
+普通的同步风格程序。它在 V8 线程上自上而下运行,仅占用少量 CPU,这是非技术层面的阻塞。
+
+```js
+'use strict'
+console.log(Date.now().toString() + ': 主进程开始;');
+console.log(Date.now().toString() + ': 创造一个延迟;');
+let start = Date.now();
+let end = start;
+while(end < start + 20) {
+ end = Date.now(); // 阻塞延迟
+}
+console.log(Date.now().toString() + ': 主进程结束;');
+```
+
+这里使用了`while`通过不断的检查系统时间来创建一个阻塞的延迟动作,根据最后打印的时间戳,总共运行时间在 21~23 毫秒之间。
+
+### 异步
+
+使用异步的方式创建了与上述同样效果的实例。
+
+```js
+'use strict'
+console.log(Date.now().toString() + ': 主进程开始;');
+setTimeout(() => {
+ console.log(Date.now().toString() + ': 事件循环回调');
+}, 20);
+console.log(Date.now().toString() + ': 主进程结束;');
+```
+
+这里的异步使用了`setTimeout`来将事件超时运行。根据最后打印的时间戳,总共运行时间在 25~30 毫秒之间。
+
+## 场景一小结
+
+经过上述的小实验,不难发现同步的运行时间比异步还短那么几毫秒。异步反而更慢,异步编程风格并不关乎单纯的速度,而是关乎可扩展性。
+
+## 模块系统
+
+模块化是现代软件开发中的一个关键概念。它使我们能够构建更健壮的代码,并在多处复用代码,而无需重复编写相同的代码。
+
+Node 模块化不仅为我们提供了上述所有好处,还提供了:
+
+* 自动封装。默认情况下,一个模块中的所有代码都被打包到函数包装程序中,以便对模块外部的其他 JavaScript 代码隐藏。
+
+* 一种公开模块接口的方式。模块中的函数、变量和其他构造必须通过 module.exports(或者其简短表示:exports)对模块外部的 JavaScript 模块显式公开。
+
+### 引入模块
+
+在 ES6 发布后,module 成为标准。曾经的我们采用的是 CommonJS 规范,使用`require()`来引入模块。目前的 ES6 标准使用`import`来引入模块。
+
+```js
+let fs = require('fs');
+import fs from 'fs';
+```
+
+ES2015 的 module
\ No newline at end of file
diff --git a/source/_md/正则表达式.md b/source/_md/正则表达式.md
new file mode 100644
index 0000000..26ea208
--- /dev/null
+++ b/source/_md/正则表达式.md
@@ -0,0 +1,28 @@
+正则,听到这个词大脑就会油然而生一阵疼痛。它是那么的令人头疼,却又是那么强大。无论在什么语言环境下都离不开正则表达式的匹配,而学习它的最佳办法就是多尝试,记住了它的语法就能轻松掌握用法了。
+
+匹配字符:`[abc]`,匹配`[...]`中的所有字符。
+
+排除字符:`[^abc]`,匹配除了`[...]`中字符的所有字符。
+
+区间匹配:`-`表示一个区间,`[0-9]`匹配0到9,`[a-z]`匹配a到z,区分大小写。
+
+`.`:匹配除换行符(\n、\r)之外的任何单个字符,相等于`[^\n\r]`。
+
+`\w`:匹配字母、数字、下划线。等价于`[A-Za-z0-9_]`。
+
+用`\d`可以匹配一个数字,`\w`可以匹配一个字母或数字
+
+用`*`表示任意个字符(包括0个),用`+`表示至少一个字符,用`?`表示0个或1个字符,用`{n}`表示n个字符,用`{n,m}`表示n-m个字符
+
+- `[0-9a-zA-Z\_]`可以匹配一个数字、字母或者下划线;
+- `[0-9a-zA-Z\_]+`可以匹配至少由一个数字、字母或者下划线组成的字符串,比如`'a100'`,`'0_Z'`,`'Py3000'`等等;
+- `[a-zA-Z\_][0-9a-zA-Z\_]*`可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
+- `[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}`更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
+
+`A|B`可以匹配A或B,所以`(P|p)ython`可以匹配`'Python'`或者`'python'`。
+
+`^`表示行的开头,`^\d`表示必须以数字开头。
+
+`$`表示行的结束,`\d$`表示必须以数字结束。
+
+你可能注意到了,`py`也可以匹配`'python'`,但是加上`^py$`就变成了整行匹配,就只能匹配`'py'`了。
\ No newline at end of file
diff --git a/source/_md/路由中的动态组件-keepAlive与路由.md b/source/_md/路由中的动态组件-keepAlive与路由.md
new file mode 100644
index 0000000..f85ae6e
--- /dev/null
+++ b/source/_md/路由中的动态组件-keepAlive与路由.md
@@ -0,0 +1,104 @@
+## 真正的动态组件
+
+``经常配合`componentIs`来动态的切换组件,当组件再次被切换回来的时候,组件的状态依然被保存。
+
+```js
+
+
+
+```
+
+## 在路由中的问题
+
+在研究动态组件``的时候发现,如果配置了默认路由跳转的情况下,再子路由上使用``就无法达到预期的效果了。因为就算子组件的状态缓存了,但再次访问该组件时,跳转的还是默认的路由。
+
+```html
+
+首页
+关于
+
+
+
+
+```
+
+```js
+// 默认子路由
+{
+ path: '/home',
+ component: Home,
+ meta: {
+ title: '首页'
+ },
+ children: [
+ {
+ path: '/',
+ redirect: '/home/news',
+ },
+ {
+ path: 'news',
+ component: HomeNews,
+ },
+ {
+ path: 'message',
+ component: HomeMessage,
+ }
+ ]
+},
+```
+
+```html
+
+新闻
+消息
+
+```
+
+在默认的情况下激活 Home 组件会跳转到`/home/news`。在第一次访问了【消息】这个子组件之后,组件的状态会被缓存下来,如果按照之前动态组件的案例来看,再次访问 Home 组件时,对应的依然是【消息】这个子组件。
+
+但仅仅只是缓存了组件的状态还不够,再次访问 Home 组件时,URL 还是会被默认的路由覆盖,不会达到动态组件的效果。
+
+### 手动推送 URL
+
+目前发现的最佳的解决办法就是使用一个变量来保存离开 Home 组件时的 URL,再次激活 Home 组件时,再将 URL 推送回去。
+
+```js
+data() {
+ return {
+ path: "/home/news",
+ };
+},
+```
+
+当使用了``时,组件生命周期中会被调用`activated()`和 `deactivated()`两个方法。
+
+使用`activated()`配合上路由的`beforeRouteLeave()`,就可以达到在离开组件时记录当前的 URL,并在重新激活组件时 push 新的 URL。
+
+```js
+activated() {
+ console.log("activated");
+ if (!(location.pathname === this.path)) {
+ this.$router.push(this.path);
+ }
+},
+beforeRouteLeave(to, from, next) {
+ console.log("leave");
+ (this.path = from.path), next();
+},
+```
+
+另外这个判断是必须的:`if (!(location.pathname === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
+
+并得到这样的错误:
+
+```js
+Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: ""
+```
+
+
+
+这个错误的主要原因是因为同一条路由被重写两遍:NavigationDuplicated。
+
+### 不缓存呢
+
+在组件被创建时,`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
\ No newline at end of file
diff --git a/source/_posts/ASCII在线视频流.md b/source/_posts/ASCII在线视频流.md
new file mode 100644
index 0000000..0eeeb0b
--- /dev/null
+++ b/source/_posts/ASCII在线视频流.md
@@ -0,0 +1,246 @@
+---
+title: ASCII 在线视频流
+date: 2019-06-29 12:12:41
+tags: Tools
+categories: 实践
+url: online-ascii-video
+index_img: /images/ASCII在线视频流/logo.webp
+---
+
+什么是ASCII?
+
+来自百度百科的解释:
+ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
+
+应该很多小伙伴们都非常熟悉ASCII码了,它也是现今最能玩的一套编码了吧(雾💊
+
+那么ascii视频流又是啥呢?
+
+这是来自某位大佬胡乱起的名字。🤣
+
+
+
+## 那么如何安装呢?
+
+根据[大佬的文章](https://file.aoaoao.me/2018/03/26/e6-9e-84-e5-bb-ba-e4-b8-80-e4-b8-aa-e5-9c-a8-e7-ba-bfascii-e8-a7-86-e9-a2-91-e6-b5-81-e6-9c-8d-e5-8a-a1/#如何搭建这么一个服务?)与开源项目。首先我们需要:
+
+1. ffmpeg
+2. [hit9/img2txt](https://github.com/hit9/img2txt)
+3. [HFO4/plus1s.live](https://github.com/HFO4/plus1s.live)
+4. node.js/Go/Python运行环境
+
+### 使用ffmpeg截取视频片段
+
+安装ffmpeg:
+
+**CentOS**
+由于CentOS没有官方FFmpeg rpm软件包。但是,我们可以使用第三方YUM源(Nux Dextop)完成此工作。
+
+```centos7
+sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
+sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
+sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
+sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
+```
+
+**Ubuntu**
+Ubuntu的源里默认就有ffmpeg的软件包,所以我们直接安装就ok了。
+
+```
+apt install ffmpeg
+```
+
+拥有了ffmpeg之后,我们可以使用如下命令:
+
+```
+ffmpeg -i demo.mp4 -r 5 -ss 00:01:13 -t 00:00:15 %03d.png
+```
+
+将demo视频的第1分13秒后的15秒以每秒5帧的速度保存为图像,图像名格式为001.png 002.png ……
+效果如下:
+
+
+
+```bash
+➜ ~ ls time
+001.png 005.png 009.png 013.png 017.png 021.png 025.png 029.png 033.png 037.png 041.png 045.png 049.png 053.png 057.png 061.png 065.png 069.png 073.png
+002.png 006.png 010.png 014.png 018.png 022.png 026.png 030.png 034.png 038.png 042.png 046.png 050.png 054.png 058.png 062.png 066.png 070.png 074.png
+003.png 007.png 011.png 015.png 019.png 023.png 027.png 031.png 035.png 039.png 043.png 047.png 051.png 055.png 059.png 063.png 067.png 071.png 075.png
+004.png 008.png 012.png 016.png 020.png 024.png 028.png 032.png 036.png 040.png 044.png 048.png 052.png 056.png 060.png 064.png 068.png 072.png
+```
+
+### 使用修改过的hit9/img2txt将图像转换为ASCII画
+
+> 原版hit9/img2txt只能单张转换,我稍微改了下,可以批量转换并保存为txt。修改后的版本:https://github.com/HFO4/img2txt/blob/gh-pages/img2txt.py
+
+可能大佬都是说改就改的吧。
+完事我们clone下来后修改img2txt.py第246行的目录为上一步存放图像的目录:
+
+```
+246 imgname = "/root/time/"+str(i).zfill(3)+".png"
+```
+
+然后再执行:
+
+```
+pip install img2txt.py
+python img2txt.py h
+```
+
+稍等片刻,ASCII字符文件便会存放到与img2txt.py同级的pic目录下。若提示无pic文件夹导致的错误,手动创建一个名为`pic`的文件夹再运行一次即可。
+
+### 部署在线服务
+
+最后,使用大佬的[HFO4/plus1s.live](https://github.com/HFO4/plus1s.live)来部署在线播放的服务。
+
+将上一步使用img2txt的pic文件夹中的图片放到改项目下的pic文件夹内,然后修改stream.go的第13行为你得到的单帧图像的总个数。保存后执行:
+
+```
+go build stream.go
+./stream
+```
+
+然后程序会默认开放一个暴力的端口,使用`curl 您的ip:1926`命令即可查看效果。
+
+## 另一款强大的软件
+
+> 📺ASCIIPlayer : Golang写的ASCII码播放器
+
+
+如同作者自己所说的,该软件是Go语言写的一款强大的Ascii码的转码加播放器。
+
+* [ASCIIPlayer : Golang写的ASCII码播放器](https://segmentfault.com/a/1190000016976239)
+
+* [asciiplayer](https://github.com/qeesung/asciiplayer)
+
+### 安装
+
+```
+go get -u github.com/qeesung/asciiplayer
+```
+
+安装后若提示:
+
+```
+zsh: command not found: asciiplayer
+```
+
+则在当前目录下会缓存一个`go`文件夹,在`go/bin/`文件夹内会有一个可执行的asciiplayer。我们将其copy至`/usr/bin/`目录下,并重连ssh即可解决。
+
+```
+cp -a asciiplayer /usr/bin
+```
+
+### 三种工作模式
+
+该软件强大的地方就是在此了,对于转换为ascii码,它拥有三个工作模式:
+
+- 输出到一个一般文件中(Encode模式): 这里我们只能逐帧,逐像素的将转化以后的ASCII图像写到文件中去。
+- 输出到终端(Play模式): 直接将转换以后的图像按照一定的频率输出到终端即可。
+- 输出到远端客户端(Server模式): 这里和输出到终端的原理类似,只是输出到了远端客户端所在的终端。
+
+```
+ +---------------+ +---------+
+ | | | |
+ +------> Gif Decoder | +---> Encoder +---> file
+ | | | | | |
+ | +---------------+ | +---------+
+ | +---------------+ +-------------+ | +---------+
+ | | | | | | | |
+Input File+------> Image Decoder +---> Frames +-->+ Image2ASCII +->ASCII Frames-+---> Player +---> stdout
+ | | | | | | | |
+ | +---------------+ +-------------+ | +---------+
+ | +---------------+ | +---------+
+ | | | | | |
+ +------> Video Decoder | +---> Server +---> socket
+ | | | |
+ +---------------+ +---------+
+```
+
+以至于它一款软件就能够直接实现我们是上述将视频中抽去图片再挨个转换为文本的ASCII码的工作了。除了不能将我们需要的输出为文本保存以外,其他都很完美。
+唯一一个缺点就是目前还不支持直接读取视频文件,只能先使用ffmpeg将视频转换为gif中,在用此软件读取。作者目前也说后续会支持视频的。🎉
+
+### 常用的命令
+
+- play
+
+通过适配屏幕的方式播放GIF
+
+```
+asciiplayer play demo.gif
+```
+
+缩小为原来的十分之一,然后播放GIF
+
+```
+asciiplayer play demo.gif -r 0.1
+```
+
+缩放成固定的长和宽,然后播放GIF
+
+```
+asciiplayer play demo.gif -w 100 -h 40
+```
+
+播放一个PNG图片
+
+```
+asciiplayer play demo.png
+```
+
+- encode
+
+将一个GIF文件demo.gif编码为ASCII的Gif文件output.gif
+
+```
+asciiplayer encode demo.gif -o output.gif
+```
+
+指定输出ASCII字符大小的情况下,讲一个GIF文件demo.gif编码成ASCII的GIF动图文件output.gif
+
+```
+asciiplayer encode demo.gif -o output.gif --font_size=5
+```
+
+将GIF动图demo.gif缩放为原来的十分之一,然后编码成ASCII的GIF动图文件output.gif
+
+```
+asciiplayer encode demo.gif -o output.gif -r 0.1
+```
+
+编码一个jpeg文件,然后输出一个ASCII的output.png文件
+
+```
+asciiplayer encode demo.jpeg -o output.png
+```
+
+- Server
+
+输入demo.gif,并以默认端口8080启动一个http服务器
+
+```
+asciiplayer server demo.gif
+```
+
+输入demo.gif,并以自定义端口8888启动一个http服务器
+
+```
+asciiplayer server demo.gif --port 8888
+```
+
+输入一个demo.png图片,并且启动http 服务器
+
+```
+asciiplayer server demo.png
+```
+
+## 大佬们
+
+[ASCIIPlayer : Golang写的ASCII码播放器](https://segmentfault.com/a/1190000016976239#articleHeader0)
+[构建一个在线ASCII视频流服务](https://file.aoaoao.me/2018/03/26/e6-9e-84-e5-bb-ba-e4-b8-80-e4-b8-aa-e5-9c-a8-e7-ba-bfascii-e8-a7-86-e9-a2-91-e6-b5-81-e6-9c-8d-e5-8a-a1/#如何搭建这么一个服务?)
+
+## Try it ?
+
+```
+curl time.defect.ink:1926
+```
\ No newline at end of file
diff --git a/source/_posts/AliOssForTypecho.md b/source/_posts/AliOssForTypecho.md
new file mode 100644
index 0000000..eedc5f8
--- /dev/null
+++ b/source/_posts/AliOssForTypecho.md
@@ -0,0 +1,43 @@
+---
+title: AliOssForTypecho
+date: 2019-06-26 16:42:41
+tags: typecho
+categories: 踩坑
+url: alioss-for-typecho
+index_img: /images/AliOssForTypecho/logo.webp
+---
+
+原作大佬:
+
+* [Typecho插件](https://zhoujie.ink/AliOssForTypecho.html)
+
+最近从辣鸡七牛换到了阿里云的oss,对于我们使用阿里云的ECS来说,oss支持直接内网访问还是很友好的。
+
+存储换了之后,于是找到了大佬的这款插件。可是大佬当初写插件的时候有些地方不太符合个人的使用习惯。比如存储的目录下都会给每张图片单独生成要一个文件夹。
+
+虽然看到大佬blog下已经有留言了,但是那都是去年的事了。
+
+当时是因为阿里云还没有检测object是否存在的sdk,大佬估计也是没有时间来跟这阿里云的sdk持续更新。就在18年10月份阿里云才更新了判断文件是否存在的php sdk。
+
+对于我这种0基础没入门的php玩家,修改太多也太麻烦,也不会。于是只做了一些简单的修改
+
+- 去除每个图片随机创建一个文件夹,但是没有是否存在的检测,上传时要确保文件不会重名。
+- 添加图片处理样式,支持自定义规则。
+- 更新了最新的OssClient(虽然我不知道它怎么用
+
+
+
+
+为什么不做object存在检测?
+
+- 当前文件夹是按“年-月-日”来分层的,也就是说存在重名的文件的时间段只有一天内上传的文件才有机会重名。
+- 不会
+- 主要是不会
+
+阿里云的[判断文件是否存在](https://help.aliyun.com/document_detail/88501.html?spm=a2c4g.11186623.6.938.33f015cdQHplrY)文档,有兴趣的大佬可以试试。
+
+
+
+下载地址:
+
+* [AliossForTypecho](https://github.com/DefectingCat/AliOssForTypecho-)
\ No newline at end of file
diff --git a/source/_posts/Can't install gifsicle.md b/source/_posts/Can't install gifsicle.md
new file mode 100644
index 0000000..497eefd
--- /dev/null
+++ b/source/_posts/Can't install gifsicle.md
@@ -0,0 +1,78 @@
+---
+title: Can't install gifsicle
+index_img: /images/Can't%20install%20gifsicle/index.webp
+date: 2020-08-04 14:03:00
+tags: network
+categories: 踩坑
+url: cant-install-gifsicle
+---
+
+## Hexo-all-minifier
+
+In a long time, i'm used to Hexo-all-minifier to optimization blog. But recently i can't even install it.
+
+The error logs with `npm install`:
+
+```bash
+ ‼ getaddrinfo ENOENT raw.githubusercontent.com
+ ‼ gifsicle pre-build test failed
+ i compiling from source
+ × Error: Command failed: C:\WINDOWS\system32\cmd.exe /s /c "autoreconf -ivf"
+'autoreconf' �����ڲ����ⲿ���Ҳ���ǿ����еij���
+�����������
+```
+
+In the beginning, i thought the problem is my windows can't run autoconf. So, i tried installing cygwin, And that is difficult for me. I never tried to installed cygwin.
+
+Anyway, i installed successfully. But the problem has not solved. There is still has the errors with npm install .
+
+## imagemin-gifsicle
+
+The problem appeared when installing gifsicle, The Hexo-all-minifier used it too. So, the best way is go to the gifsicle issues. As predicted, there is someone got the same errors.
+
+Be unexpected, It's not a problem with windows or autoconf. That is network problem🌚.
+
+```bash
+ ‼ getaddrinfo ENOENT raw.githubusercontent.com
+ ‼ gifsicle pre-build test failed
+```
+
+As in above two lines, the problem is can't connect with `githubusercontent.com`.
+
+## Best way
+
+Write domain with ip into the hosts. That is best way to connect with github and other domains.
+
+```
+52.74.223.119 github.com
+192.30.253.119 gist.github.com
+54.169.195.247 api.github.com
+185.199.111.153 assets-cdn.github.com
+151.101.76.133 raw.githubusercontent.com
+151.101.76.133 gist.githubusercontent.com
+151.101.76.133 cloud.githubusercontent.com
+151.101.76.133 camo.githubusercontent.com
+151.101.76.133 avatars0.githubusercontent.com
+151.101.76.133 avatars1.githubusercontent.com
+151.101.76.133 avatars2.githubusercontent.com
+151.101.76.133 avatars3.githubusercontent.com
+151.101.76.133 avatars4.githubusercontent.com
+151.101.76.133 avatars5.githubusercontent.com
+151.101.76.133 avatars6.githubusercontent.com
+151.101.76.133 avatars7.githubusercontent.com
+151.101.76.133 avatars8.githubusercontent.com
+```
+
+Then, try`npm cache clean -f`and`ipconfig/flushdns`.
+
+As long as can ping with github domains, the problem will be solved.
+
+The `Command failed` just write some ips for hosts, then `npm install` will be worked.
+
+So,
+
+
+
+
+
+enjoy it.
\ No newline at end of file
diff --git a/source/_posts/Docker-全面容器化.md b/source/_posts/Docker-全面容器化.md
new file mode 100644
index 0000000..7069ece
--- /dev/null
+++ b/source/_posts/Docker-全面容器化.md
@@ -0,0 +1,596 @@
+---
+title: Docker-全面容器化!
+date: 2019-12-19 11:11:33
+tags: Linux
+categories: 实践
+url: docker-container-all
+index_img: /images/Docker全面容器化/logo.webp
+---
+
+自上篇[Docker - 构建属于自己的镜像](https://www.defectink.com/defect/docker-build-own-images.html)以来,发现Docker非常的有意思。主要是非常的方便,并且在可以跨平台的情况下部署环境对于以后迁移也是一件极其有利的事。研究了Dockerfile的编写以及实践。一些基础的实践之后,对于Docker的工作方式以及操作命令都有了一些熟悉。也逐渐了发现了它的一些优点。
+
+翻开自己的旧机器里的多种环境交杂在一起的配置,时间长了连配置文件在哪都找不到了。管理起来比较复杂。那些服务器的管理面板并不是很喜欢,而且相对于Docker来说,管理面板只是简化了部署的操作,并没有达到方便管理的目的。到最后可能它的软件目录,镜像源都是按照它的想法去放的。对于自己并没有完全的掌控。当然不能完全拿管理面板与Docker来相比,二者完全是两种技术。只是相较于方便管理这方面来对比一下。
+
+而最近研究的Docker,无疑是最满意的了。在保持宿主机不乱的情况下,可以完全的掌控自己的运行环境。于是就有了将自己目前跑了挺长时间的一套blog环境都迁移到Docker上。对于以后若是迁移机器也会更加的方便。
+
+## 涉及到的操作
+
+* Dockerfile
+* docker-compose.yml
+* apache virtualhost
+* php-fpm
+* http2
+* apache https
+* certbot with docker
+* apache proxy
+
+## 目前环境
+
+先来简单看下当前跑在机器上的环境:
+
+基本的LAMP环境,加上一些自定义的应用,与一个服务器监控软件。其中apache有多个虚拟主机,全部都使用了https。
+
+咋一看是一套很简单的环境,其中apache配置稍多一点。但是实际在迁移到Docker的操作起来还是比较复杂的。并且为了镜像的最小化,apache基于的镜像都是alpine。配置与常用的Ubuntu略有不同。
+
+## 容器化
+
+### 思路
+
+将多个运行LAMP分别拆分出三个运行环境,使用docker-compose来捆绑运行。
+
+目录树
+
+```
+.
+├── apache
+│ ├── Dockerfile
+│ ├── httpd.conf
+│ └── sites
+│ ├── 000-default-ssl.conf
+│ └── 000-default.conf
+├── docker-compose.yml
+├── mysql
+│ └── backup
+├── php
+│ └── Dockerfile
+└── www
+ └── html
+ └── index.php
+```
+
+首先创建一个用于存放整个运行环境的`Docker`父文件夹。然后根据不同的镜像来划分不同的子文件夹,子文件夹内存放的就是各个镜像的`Dockerfile`与配置文件等。将docker-compose.yml存放与父目录下。
+
+apache与php-fpm通信借助Docker的网络,实现内部的通信。
+
+## Apahce
+
+在当前的apache目录下,主要文件夹的划分为`Dockerfile`、`httpd.conf`和sites文件夹。
+
+### Dockerfile
+
+虽然httpd有了一个单独的镜像,但是还是需要使用Dockerfile来对其进行自定义配置。为了尽量减小镜像的大小。这里使用基于alpine的apache。
+
+在Docker hub中的[httpd](https://hub.docker.com/_/httpd)当前支持的tag:
+
+
+
+整个Dockerfile:
+
+```dockerfile
+FROM httpd:alpine
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
+ && apk update \
+ && apk upgrade
+COPY sites/ /usr/local/apache2/conf/sites/
+COPY httpd.conf /usr/local/apache2/conf/httpd.conf
+```
+
+所以`FROM`里使用的就是带alpine的tag了。我还尝试过测试使用基于alpine的空载运行apache大概节约了1MB的内存。
+
+```
+176d166ee52a testa 0.00% 4.484MiB / 3.607GiB 0.12%
+3dac39c11385 test 0.00% 5.664MiB / 3.607GiB 0.15%
+```
+
+对于跑在国内的机器上,alpine也有国内的源。并且替换的也很简单,一句话就好了。这样在后续的更新源和安装软件就没有那么苦恼了。
+
+```
+sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
+```
+
+剩下的`COPY`就是复制自定义的配置文件到容器里去了。
+
+### 配置文件
+
+首先,之前的环境中apache是有多个虚拟主机,并且每个主机都启用了ssl以及一些其他的配置。所以第一步是需要修改容器的配置文件。也就是要先获取默认的配置文件。
+
+优雅的获取apache默认配置文件:
+
+```
+docker run --rm httpd:2.4 cat /usr/local/apache2/conf/httpd.conf > httpd.conf
+```
+
+默认的ssl配置文件:
+
+```
+docker run --rm httpd:2.4 cat /usr/local/apache2/conf/extra/httpd-ssl.conf > ssl.conf
+```
+
+容器的配置文件路径:
+
+```
+/usr/local/apache2/conf/httpd.conf
+```
+
+获取到了默认的配置文件之后,在apache的文件夹内可以先自定义`httpd.conf`。并且尝试启动一次,没用问题后可以继续配置虚拟主机。
+
+由于不同的站点都交给了虚拟主机的配置文件来处理。所以`httpd.conf`主要是负责一些mod的配置,和一些全局的配置了。还有就是将余下的配置文件`Include`进来了。
+
+后期还有需要添加更多的虚拟主机的配置文件,到时候一个一个的`Include`操作太过繁琐。所以创建个专门存放配置文件的文件夹,再在`httpd.conf`里将整个文件夹`Include`进去。这样就最简单的解决了操作繁琐的问题。
+
+创建一个`sites`文件夹用于存放配置文件,`COPY`到容器内相应的目录:
+
+```
+COPY sites/ /usr/local/apache2/conf/sites/
+```
+
+在`httpd.conf`中相应的引入:
+
+```
+Include /usr/local/apache2/conf/sites/*.conf
+```
+
+{*}这一操作方法还是学自Ubuntu下的apache,它的配置目录下有两个文件夹`sites-available`和`sites-enabled`。在主要的apache2.conf中引入配置文件。
+
+```http
+# Include generic snippets of statements
+IncludeOptional conf-enabled/*.conf
+
+# Include the virtual host configurations:
+IncludeOptional sites-enabled/*.conf
+```
+
+`httpd.conf`中的虚拟主机配置不需要修改了。所有的站点可以都在Include中的配置文件中准备。基本上`httpd.conf`就是为引入配置文件和启用mod所准备的。
+
+### Module
+
+在基于alpine中的apache,所有的mod加载都写在了配置文件`httpd.conf`里。只需要取消注释就可以加载/启用模组了。
+
+这次添加的module:
+
+```
+LoadModule deflate_module modules/mod_deflate.so
+LoadModule proxy_module modules/mod_proxy.so
+LoadModule proxy_connect_module modules/mod_proxy_connect.so
+LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
+LoadModule proxy_http_module modules/mod_proxy_http.so
+LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
+LoadModule setenvif_module modules/mod_setenvif.so
+LoadModule mpm_event_module modules/mod_mpm_event.so
+LoadModule http2_module modules/mod_http2.so
+LoadModule proxy_http2_module modules/mod_proxy_http2.so
+LoadModule ssl_module modules/mod_ssl.so
+LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
+LoadModule rewrite_module modules/mod_rewrite.so
+LoadModule headers_module modules/mod_headers.so
+#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+```
+
+这些mod都是作用于何?
+
+* mod_deflate是一个压缩算法。
+
+* mod_socache_shmcb共享对象缓存提供程序。
+
+* 因为需要配置反代和与php-fpm工作,所以需要启用多个proxy配置文件。
+
+* 因为需要用到http2,所以工作模式得修改为event。同时注释掉默认的工作模式prefork。自然也需要mod_http2
+
+* https是不可或缺的,所以mod_ssl不可缺少。
+
+* 后续的博客需要用到伪静态,mod_rewrite也不可少。
+
+* 在最近也添加了多个header头,需要用到mod_headers。
+
+> info:根据自己需要启用module是一个良好的习惯,过多的module会影响性能。
+
+### 虚拟主机
+
+前面提到,专门创建了一个sites文件夹来存放虚拟主机的配置文件,目前sites文件夹还是空的。既然`httpd.conf`以及准备就绪,那么接下来就是填满sites文件夹了。
+
+在还未添加虚拟主机时,默认的站点配置文件全部都写在`httpd.conf`里。默认的根目录在htdocs。所以在第一次启动测试时,访问的时这里的html文件。
+
+```
+DocumentRoot "/usr/local/apache2/htdocs"
+```
+
+这里的配置可以不用动,全部操作交给虚拟主机就好。
+
+整个虚拟主机(default.conf)配置文件:
+
+```
+
+ProtocolsHonorOrder On
+Protocols h2 h2c
+Header set X-Frame-Options "SAMEORIGIN"
+Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
+Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
+Header set X-Content-Type-Options nosniff
+Header always set Referrer-Policy "no-referrer-when-downgrade"
+Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
+ # Proxy .php requests to port 9000 of the php-fpm container
+ ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/$1
+ ServerName www.defectink.com
+ DocumentRoot /var/www/html/
+
+ DirectoryIndex index.php
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+ # Send apache logs to stdout and stderr
+ CustomLog /proc/self/fd/1 common
+ ErrorLog /proc/self/fd/2
+RewriteEngine on
+RewriteCond %{SERVER_NAME} =www.defectink.com
+RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
+
+```
+
+#### 虚拟主机优先级
+
+在apache中,虚拟主机的配置文件是拥有优先级的。优先级的意思就是,当一个域名指向当前机器的ip,而配置文件中没有绑定的ServerName时,默认被引导到的页面。
+
+优先级的顺序是根据虚拟主机的配置文件名来决定的。名称首字母越靠前,优先级越高。使用数字开头将大于子母开头。
+
+> 000-default will be the default, because it goes “numbers, then letters”.
+
+可以使用命令来查看当前的默认站点:
+
+```bash
+httpd -S
+```
+
+```bash
+apache2ctl -S
+```
+
+### SSL
+
+这里的ssl配置文件是来自于容器内的默认配置文件,使用上述的方法可以很方便的导出。
+
+整个ssl(default-ssl.conf)配置文件:
+
+```
+Listen 443
+
+SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
+SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
+SSLHonorCipherOrder on
+SSLProtocol all -SSLv3
+SSLProxyProtocol all -SSLv3
+SSLPassPhraseDialog builtin
+SSLSessionCache "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"
+SSLSessionCacheTimeout 300
+
+
+ProtocolsHonorOrder On
+Protocols h2 h2c
+Header set X-Frame-Options "SAMEORIGIN"
+Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
+Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
+Header set X-Content-Type-Options nosniff
+Header always set Referrer-Policy "no-referrer-when-downgrade"
+Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
+# Proxy .php requests to port 9000 of the php-fpm container
+ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000/var/www/html/$1
+# General setup for the virtual host
+DocumentRoot "/var/www/html/"
+ServerName www.defectink.com:443
+ServerAdmin i@defect.ink
+
+ DirectoryIndex index.php
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+ErrorLog /proc/self/fd/2
+TransferLog /proc/self/fd/1
+
+SSLEngine on
+SSLCertificateFile "/etc/letsencrypt/live/www.defectink.com/fullchain.pem"
+SSLCertificateKeyFile "/etc/letsencrypt/live/www.defectink.com/privkey.pem"
+
+ SSLOptions +StdEnvVars
+
+
+ SSLOptions +StdEnvVars
+
+BrowserMatch "MSIE [2-5]" \
+ nokeepalive ssl-unclean-shutdown \
+ downgrade-1.0 force-response-1.0
+CustomLog /proc/self/fd/1 \
+ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
+
+
+```
+
+这里的主要配置点就在`SSLCertificateFile`和`SSLCertificateKeyFile`。关于配合certbot申请证书,在前一篇水过。有兴趣的小伙伴可以去了解更多。[Docker - 构建属于自己的镜像](https://www.defectink.com/defect/docker-build-own-images.html#menu_index_8)。
+
+值得注意的是,在一个`httpd.conf`文件中只能有一个`Listen 443`字段。而默认的ssl配置文件中就包含一个`Listen 443`字段。当复制多个默认的配置文件时,会导致apache运行错误。因为所有的配置文件都会被引入到`httpd.conf`,而一个apache只能监听一次端口,也就是说只能有一个`Listen 443`在配置文件中。
+
+可以考虑将其写在监听80端口下面:
+
+```
+#Listen 12.34.56.78:80
+Listen 80
+Listen 443
+```
+
+### 日志
+
+这里虚拟主机的默认配置时将日志发送到stdout和stderr。可以理解为输出到终端上。
+
+```
+ # Send apache logs to stdout and stderr
+ CustomLog /proc/self/fd/1 common
+ ErrorLog /proc/self/fd/2
+```
+
+当然也可以实现日志的持久化保存。将其映射到宿主机的目录下就好了。
+
+```
+ CustomLog /var/log/access.log common
+ ErrorLog /var/log/error.log
+```
+
+## PHP
+
+PHP这里使用fpm,配合docker-compose的内部网络与apache进行通信。
+
+### Dockerfile
+
+```
+FROM php:7.4-fpm-alpine
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
+COPY php.ini $PHP_INI_DIR/conf.d/
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
+ && apk update \
+ && apk upgrade \
+ && docker-php-ext-install mysqli \
+ && docker-php-ext-install pdo_mysql
+```
+
+### php.ini
+
+优雅的获取容器内的php.ini文件:
+
+```
+docker cp somephp:/usr/local/etc/php/ /root
+```
+
+修改过后的配置文件可以在Dockerfile中copy,也可以在compose.yml中映射。只要到了宿主机的正确位置就可以生效。
+
+官方的描述是这样的方法:
+
+```dockerfile
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
+COPY php.ini $PHP_INI_DIR/conf.d/
+```
+
+### 拓展
+
+如果需要安装typecho这样的blog程序的话,再连接sql时需要安装mysql的拓展,写在Dockerfile就好了:
+
+```
+docker-php-ext-install pdo_mysql
+```
+
+如果还需要其他的拓展的话,还可以在Dockerfile里自定义安装。可以使用`pecl`来安装,然后使用`docker-php-ext-enable`来启用它。
+
+> use `pecl install` to download and compile it, then use `docker-php-ext-enable` to enable it:
+
+```
+FROM php:7.4-cli
+RUN pecl install redis-5.1.1 \
+ && pecl install xdebug-2.8.1 \
+ && docker-php-ext-enable redis xdebug
+```
+
+或者直接使用`docker-php-ext-install`来安装:
+
+ && docker-php-ext-install mysqli \
+ && docker-php-ext-install pdo_mysql
+至于更多的拓展,还可以编译安装:
+
+```
+FROM php:5.6-cli
+RUN curl -fsSL 'https://xcache.lighttpd.net/pub/Releases/3.2.0/xcache-3.2.0.tar.gz' -o xcache.tar.gz \
+ && mkdir -p xcache \
+ && tar -xf xcache.tar.gz -C xcache --strip-components=1 \
+ && rm xcache.tar.gz \
+ && ( \
+ cd xcache \
+ && phpize \
+ && ./configure --enable-xcache \
+ && make -j "$(nproc)" \
+ && make install \
+ ) \
+ && rm -r xcache \
+ && docker-php-ext-enable xcache
+```
+
+## MySql
+
+mysql主要修改的一些配置使用启动时的环境变量就可以了,不需要修改其配置文件的情况下,便用不到Dockerfile了。直接使用官方的镜像就好了。
+
+### Docker Secrets
+
+如果担心密码写在docker-compose.yml里不安全的话,可以考虑使用docker secret。不过secret需要使用swarm集群。单台主机只使用docker-compose可能会更加方便一点。
+
+并且在compose中生成的是两个虚拟网络,只有apache在前端网络映射了端口。mysql完全放在后端,除了虚拟网络和宿主机都无法与其通信。所以密码的安全并不用太过于担心。如果mysql需要对外提供服务,那就需要多担心一下了。
+
+```
+$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag
+```
+
+> 映射了目录后,配置的密码都会被持久化保存。再次修改docker-compose.yml中的变量将不会生效。
+
+### 数据持久化
+
+对于docker来说,尽量不要在容器内发生写的操作为好。此外,对于数据库来,数据肯定是需要持久化存储的。官方推荐的是:
+
+> Important note: There are several ways to store data used by applications that run in Docker containers. We encourage users of the `mysql` images to familiarize themselves with the options available, including:
+>
+> - Let Docker manage the storage of your database data [by writing the database files to disk on the host system using its own internal volume management](https://docs.docker.com/engine/tutorials/dockervolumes/#adding-a-data-volume). This is the default and is easy and fairly transparent to the user. The downside is that the files may be hard to locate for tools and applications that run directly on the host system, i.e. outside containers.
+> - Create a data directory on the host system (outside the container) and [mount this to a directory visible from inside the container](https://docs.docker.com/engine/tutorials/dockervolumes/#mount-a-host-directory-as-a-data-volume). This places the database files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly.
+
+说白了就是映射出来为最佳解决方案。如果在compose.yml中使用`-v`映射,而不添加宿主机的目录位置的话。文件将会被映射到一个随机的目录。
+
+推荐方案:
+
+```
+$ docker run --name some-mysql -v /my/own/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
+```
+
+### 备份与恢复
+
+针对单个或多个数据库都可以导出为sql文件。在docker中当然也是同样。甚至密码可以使用变量`$MYSQL_ROOT_PASSWORD`来代替。
+
+导入:
+
+```bash
+docker exec -i mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" typecho' < /data/docker/typecho.sql
+```
+
+导出:
+
+```bash
+docker exec mysql sh -c 'exec mysqldump typecho -uroot -p"$MYSQL_ROOT_PASSWORD"' > /data/docker/mysql/backup/typecho.sql
+```
+
+## Docker-compose
+
+整个配置文件:
+
+docker-compose.yml
+
+```dockerfile
+version: "3.2"
+services:
+ php:
+ container_name: php
+ build: './php/'
+ networks:
+ - backend
+ volumes:
+ - ./www/:/var/www/
+ apache:
+ container_name: apache
+ build: './apache/'
+ depends_on:
+ - php
+ - mysql
+ networks:
+ - frontend
+ - backend
+ ports:
+ - "80:80"
+ - "443:443"
+ volumes:
+ - ./www/:/var/www/
+ - /etc/letsencrypt/:/etc/letsencrypt/
+ - ./apache/logs/:/var/log/
+ mysql:
+ container_name: mysql
+ image: mysql:5.7
+ volumes:
+ - ./mysql/sqldata/:/var/lib/mysql
+ networks:
+ - backend
+ environment:
+ - MYSQL_ROOT_PASSWORD=password
+ command: ['mysqld', '--character-set-server=utf8mb4']
+ bark:
+ container_name: bark
+ build: './bark'
+ ports:
+ - "8181"
+ depends_on:
+ - apache
+ networks:
+ - backend
+networks:
+ frontend:
+ backend:
+```
+
+### 网络
+
+## 问题
+
+```
+It does not belong to any of this network's subnets
+```
+
+这个问题很有意思,在配置文件中设置了网络段。也在服务下写了相同的地址段,它依然说我的网段不一样。
+
+导致这个问题的原因是在自定义配置网段之前启动过相同的网络,在docker网络下它已经定义过网段了。再次重新手动指定网络地址时,会和之前的不一样。
+
+只需要删除之前的网络,在重新运行`docker-sompose up`一遍就可以了。
+
+查看网络:
+
+```bash
+docker network ls
+```
+
+删除:
+
+```bash
+docker network rm docker_backend
+```
+
+### 此外
+
+```
+ - backend
+ ipv4_address: 172.18.0.10
+```
+
+不同的语法写在一起也会导致错误。
+
+## 参考
+
+* [How to set the default virtual host on Apache 2?](https://www.digitalocean.com/community/questions/how-to-set-the-default-virtual-host-on-apache-2)
+* [docker-compose up error, Invalid address](https://stackoverflow.com/questions/45648821/docker-compose-up-error-invalid-address)
+* [Networking in Compose](https://docs.docker.com/compose/networking/)
+* [Containerize This! How to use PHP, Apache, MySQL within Docker containers](https://www.cloudreach.com/en/insights/blog/containerize-this-how-to-use-php-apache-mysql-within-docker-containers/)
+
+```
+docker run -it --rm --name certbot \
+ -v "/etc/letsencrypt:/etc/letsencrypt" \
+ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
+ -v "/data/docker/web/www/:/data/docker/web/www/" \
+ certbot/certbot certonly -n --webroot \
+ -w /data/docker/web/www/test -d test.defectink.com \
+ -w /data/docker/web/www/html -d www.defectink.com \
+ -w /data/docker/web/www/index -d index.defectink.com \
+ -w /data/docker/web/www/api -d api.defectink.com
+```
+
+```
+docker run --rm --name myadmin -d --network docker_backend --link mysql_db_server:mysql -e PMA_HOST=172.20.0.4 -p 3002:80 phpmyadmin/phpmyadmin
+```
+
+```
+certbot certonly -n --webroot -w /data/docker/web/www/html -d www.defectink.com --post-hook "docker restart apache"
+```
+
+```
+certbot certonly -n --webroot -w /data/docker/web/www/html -d www.defectink.com
+certbot certonly -n --webroot -w /data/docker/web/www/index -d index.defectink.com
+certbot certonly -n --webroot -w /data/docker/web/www/test -d test.defectink.com
+certbot certonly -n --webroot -w /data/docker/web/www/api -d api.defectink.com
+```
+
diff --git a/source/_posts/Docker-构建属于自己的镜像.md b/source/_posts/Docker-构建属于自己的镜像.md
new file mode 100644
index 0000000..bc60223
--- /dev/null
+++ b/source/_posts/Docker-构建属于自己的镜像.md
@@ -0,0 +1,448 @@
+---
+title: Docker-构建属于自己的镜像
+date: 2019-11-29 09:30:33
+tags: Linux
+categories: 实践
+url: docker-build-own-image
+index_img: /images/Docker-构建属于自己的镜像/logo.webp
+---
+
+以前一直在使用别人构建好的镜像来使用Docker容器,在一次想搭建一个完整的Web环境时,发现使用过多容器非常难以管理。并且容器之间的交互通信变的困难。当然,也可以使用Docker Compose来捆绑多个镜像运行;不过对于运行服务较少的来说,使用Dockerfile来构建成一个镜像也是件好事。
+
+## 需求
+
+首先,在构建一个镜像之前,需要先明白这个镜像将会包含哪些东西,运行哪些服务。目前主要是想在当前机器上跑一个hexo的blog。当然可以部署在Github,以前还写过一篇关于部署在Github的[水文](https://www.defectink.com/defect/set-up-the-hexo-blog.html)。不过现在的想法是Github放一份,在本地服务器上也跑一个Server。
+
+当然跑一个hexo是一件很简单的事情,使用Docker来部署也是为了想体验一下写`Dockerfile`。目前有两个思路:
+
+1. 把node.js和hexo都部署在当前的宿主机,用Docker的Web服务器来跑宿主机生成的静态文件。
+
+ > 但是这样的话就不需要用到Dockerfile了,直接pull一个http服务的镜像就好了。
+
+2. 只在宿主机上使用Git来和Github同步文件,每次的生成和运行Web服务都放在Docker容器里。
+
+ > 目前打算尝试的一种方式,可以在每次写完文章后使用Docker构建,并且也可以尝试Dockerfile了。
+
+具体需要什么使用软件,完全看自己的需求,需要用到什么,就安装什么。就像在当前的宿主机上安装软件一样。只不过是使用Dockerfile来构建时安装的而已。
+
+## 构建自己的镜像
+
+好在还可以使用Dockerfile来基于其他的镜像来构建属于自己的镜像。可以在其他的系统基础镜像上来在构建时就安装自己需要的软件服务等,这样就可以构建一个自己需要的镜像了。
+
+### 使用基础镜像
+
+构建时使用的第一个命令是`FROM`命令。它会指定一个用于构建的基础镜像。这样就可以在基础镜像中使用自己喜欢的发行版,也解决了继承其他 Docker 镜像的途径 。
+
+创建一个目录,或者`clone`一个hexo博客等,在目录内编写一个` Dockerfile `。
+
+```dockerfile
+#test
+
+FROM alpine:latest
+MAINTAINER Defectink
+```
+
+这里选择的是alpine系统作为基础镜像,主要原因是alpine是个超级轻量的系统,对于最为基础镜像可以有效的减少构建后镜像的大小。
+
+除此之外,还有个`MAINTAINER`命令,它是用来著名当前Dockerfile的作者的。Docker支持`#`作为注释,使用起来很方便。
+
+### 第一次的构建
+
+编写了一个最基本的` Dockerfile `之后,就是运行第一次的构建测试了。使用`Docker`加上`build`来构建指定的` Dockerfile `为镜像。添加`-t`参数来为构建后的镜像指定一个tag标签,也就是之后的镜像(REPOSITORY)名。最后命令指定的目录是包含刚刚写好的` Dockerfile `文件的目录,被称作为“构建目录”。
+
+当前系统下没有基础镜像alpine的话,在第一次运行时docker也会进行下载。
+
+```bash
+# docker build -t blog /data/github/DefectingCat.github.io/
+Sending build context to Docker daemon 64kB
+Step 1/2 : FROM alpine:latest
+latest: Pulling from library/alpine
+89d9c30c1d48: Pull complete
+Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
+Status: Downloaded newer image for alpine:latest
+ ---> 965ea09ff2eb
+Step 2/2 : MAINTAINER Defectink
+ ---> Running in d572ac48c8f8
+Removing intermediate container d572ac48c8f8
+ ---> b8296646acaa
+Successfully built b8296646acaa
+Successfully tagged blog:latest
+```
+
+第一次的镜像构建已经完成了,虽然什么都没有进行定制,但已经迈出了第一步。
+
+### 安装软件
+
+迈出第一步之后,就可以开始考虑定制属于自己的镜像了。使用`docker images`可以查看当前系统下的docker镜像。也能看到刚刚所构建的第一个镜像。
+
+```bash
+# docker images
+REPOSITORY TAG IMAGE ID CREATED SIZE
+blog latest b8296646acaa 19 minutes ago 5.55MB
+alpine latest 965ea09ff2eb 5 weeks ago 5.55MB
+```
+
+既然是定制属于自己的镜像,那么肯定是需要安装所需求的软件的。这里我想构建一个运行hexo的镜像,所以至少需要3款软件:
+
+* apache
+* node.js
+* hexo
+
+使用`RUN`命令来在基础镜像上执行命令,像是安装软件等操作。由于alpine默认时区不是国内,还可以顺便修改下时区。可以使用`RUN`来一次安装完所有需要的软件,不需要分开执行。
+
+使用alpine的另个原因就是在它本身体积小的情况下,它安装软件还可以使用`--no-cache`来减少缓存。
+
+在容器内使用npm来安装hexo时会出现一个`uid:0`的问题,npm会有生命周期,某个包会有生命周期来执行一些东西,安全起见会自动降级导致没有权限执行一些操作,通过``--unsafe-perm`参数来解锁该限制。
+
+```dockerfile
+#install
+RUN apk update \
+ && apk upgrade \
+ && apk add --no-cache \
+ apache2 \
+ nodejs \
+ npm \
+ tzdata \
+ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
+ && rm -rf /var/cache/apk/* \
+ && mkdir -p /data/DefectingCat.github.io \
+ && npm config set unsafe-perm true \
+ && npm install -g hexo
+```
+
+因为是基于一个操作系统上构建的镜像,所以在构建完成后可以使用Docker来运行一个“伪终端”,让我们可以直接在终端内进行一些修改和查看。值得注意的是,在“伪终端”里进行的操作只是在当前容器内的,不会被写入镜像。当前被关闭后,任何操作将不复存在。
+
+在构建完后可以使用“伪终端”进入系统内查看一些信息,测试软件能否正常工作等。
+
+```bash
+docker run -it --rm blog
+```
+
+关于这里的一些参数:
+
+* `-i`即使没有附加也保持STDIN 打开。
+
+* `-t`分配一个伪终端。
+* `--rm`在退出后立刻删除容器。
+
+### 缓存
+
+```bash
+# docker build -t blog /data/github/DefectingCat.github.io/
+Sending build context to Docker daemon 64kB
+Step 1/5 : FROM alpine:latest
+ ---> 965ea09ff2eb
+Step 2/5 : MAINTAINER Defectink
+ ---> Using cache
+ ---> 92cd04f91315
+```
+
+在构建的时候可以在某一步(Step)下看到`Using cache`。 当 Docker 构建镜像时,它不仅仅构建一个单独的镜像;事实上,在构建过程中,它会构建许多镜像。
+
+输出信息中的每一步(Step),Docker都在创建一个新的镜像。同时它还打印了镜像ID:` ---> 92cd04f91315`。这样的好处在于,我们修改`Dockerfile`后重新构建镜像时,那些没有被修改的部分可以将上次构建的镜像当作缓存,加快构建的速度。
+
+但是这也会有些小问题,Docker是根据`Dockerfile`来判断构建时的变化的。但如果需要执行更新软件等操作,而`Dockerfile`内的命令是没有变化时,Docker会继续使用以前的缓存,导致旧的软件还是被安装了。
+
+所有在执行某些必要的操作时,不使用缓存也是极有好处的。在构建镜像时,**使用`--no-cache=True`即可**。
+
+`RUN`命令推荐使用一条命令完成尽可能多的操作,` Dockerfile `中的每个命令都会被分为构建镜像的一步来执行,这样可以减少构建时的步数(Step)。Docker 镜像类似于洋葱。它们都有很多层。为了修改内层,则需要将外面的层都删掉。
+
+### 第一次的运行
+
+将所有的软件都安装、测试完后,就可以构建能够第一次运行的镜像了。在此之前,还需要配置需要运行的软件,例如使用hexo生成静态文件,启动apache等。
+
+```dockerfile
+COPY DefectingCat.github.io /data/DefectingCat.github.io
+WORKDIR /data/DefectingCat.github.io
+RUN hexo g \
+ && cp -a public/* /var/www/localhost/htdocs
+
+EXPOSE 80 443
+CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
+```
+
+* `COPY`将宿主机上的文件复制进容器内的目录。在安装软件时就已经使用`RUN`来创建过需要的目录了。
+* `WORKDIR`切换工作的目录,和`cd`类似;切换后`RUN`等命令都会在当前目录下工作。
+* `EXPOSE`暴露需要使用到的端口。
+* `CMD`和`RUN`类似,通常用于来启动容器服务。
+
+关于`CMD`:
+
+`CMD`只能存在一条,根据运行的软件,它将占据最后容器输出的终端。因为容器并不像虚拟化或者物理机那样,可以使用守护进程;容器本身就是一个进程,容器内没有后台服务的概念。正确的做法是使用`CMD`直接执行可执行文件,并且要求以前台形式运行。
+
+当前的操作很简单,就是复制宿主机上git克隆下来的文件到容器的制定文件夹,然后使用`hexo`来生成静态文件,最后复制到`apache`的工作目录下。
+
+到这里就可以来运行一个一次性的容器测试一下我们的服务是否运行正常了。如果上述都没有任何问题的话,现在打开浏览器就应该能看到hexo的blog了🎉。
+
+```bash
+docker run -p 80:80 --rm blog
+```
+
+到目前为止,Dockerfile应该是这样的:
+
+```dockerfile
+FROM alpine:latest
+MAINTAINER Defectink
+
+#install
+RUN apk update \
+ && apk upgrade \
+ && apk add --no-cache \
+ apache2 \
+ nodejs \
+ npm \
+ tzdata \
+ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
+ && rm -rf /var/cache/apk/* \
+ && mkdir -p /data/DefectingCat.github.io \
+ && npm config set unsafe-perm true \
+ && npm install -g hexo
+
+COPY DefectingCat.github.io /data/DefectingCat.github.io
+WORKDIR /data/DefectingCat.github.io
+RUN hexo g \
+ && cp -a public/* /var/www/localhost/htdocs
+
+EXPOSE 80 443
+CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
+```
+
+安装了一些必要的软件,同时也尽量的减少了镜像构建后的大小。
+
+## HTTPS
+
+现代的网站应该都不会少的了SSL,也就是我们常见的https。目前自己的网站用的是最简单的LetsEncrypt,使用他家的工具Certbot来申请证书及其方便。在宿主机的环境下甚至还能自动配置。但是目前用的是Docker环境,在使用Dockefile构建时,是没有交互环境的。自动配置也可能无法生效。
+
+### 生成证书
+
+Certbot生成证书很是方便,在Docker环境下也是如此。使用官方的镜像可以很方便的生成:
+
+```bash
+sudo docker run -it --rm --name certbot \
+ -v "/etc/letsencrypt:/etc/letsencrypt" \
+ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
+ certbot/certbot certonly
+```
+
+配合`certonly`只获取证书,并`-v`来将容器的目录映射到宿主机,这样就能在生成后把证书存到宿主机目录了。
+
+生成时,也会有两种工作模式选择:
+
+```bash
+How would you like to authenticate with the ACME CA?
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+1: Spin up a temporary webserver (standalone)
+2: Place files in webroot directory (webroot)
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
+```
+
+分别是:
+
+* standalone模式:启动一个临时的webserver;
+* webroot模式:将验证文件放到当前已有的webserver目录下;
+
+如果当前没有正在运行的webserver,使用standalone模式是最为方便的。Certbot将自己运行一个临时的webserver完成认证。但是如果使用standalone模式,在运行需要添加一个映射的端口:
+
+```bash
+sudo docker run -it -p 80:80 --rm --name certbot \
+ -v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \
+ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
+ certbot/certbot certonly
+```
+
+因为Certbot启用了一个临时的webserver来验证域名解析,如果不把容器的`80`端口映射出来的话,将无法完成验证。
+
+在一切都没有任何问题之后,就能看到Congratulations了:
+
+```bash
+IMPORTANT NOTES:
+ - Congratulations! Your certificate and chain have been saved at:
+ /etc/letsencrypt/live/domain/fullchain.pem
+```
+
+根据官网的说法,证书均链接在`/etc/letsencrypt/live`目录内。
+
+> `/etc/letsencrypt/archive` and `/etc/letsencrypt/keys` contain all previous keys and certificates, while `/etc/letsencrypt/live` symlinks to the latest versions.
+
+### Mod_ssl
+
+有了证书之后,apache还需要ssl的mod。alpine的镜像安装apache时是没有安装的ssl的mod。所以还需要在Dockerfile内添加一行,手动进行安装,包名为`apache2-ssl`:
+
+```dockerfile
+RUN apk update \
+ && apk upgrade \
+ && apk add --no-cache \
+ apache2 \
+ apache2-ssl \
+```
+
+在重新构建之前,还需要修改apache的`ssl.conf`。如何取得`ssl.conf`呢?我们只需要构建一个临时的alpine镜像,在容器内使用相同的命令安装一个apache与ssl mod,之后在`/etc/apache2/conf.d`目录内就有`ssl.conf`配置文件了。将其copy到宿主机内修改就好了。
+
+```bash
+apk add apache2-ssl
+```
+
+在启动命令内的`httpd.conf`配置文件会包含`ssl.conf`。所以只需要修改`ssl.conf`,再在构建时将其copy到镜像内就好了。
+
+`httpd.conf`内的已有配置:
+
+```
+IncludeOptional /etc/apache2/conf.d/*.conf
+```
+
+那么,如何优雅的将容器内的`ssl.conf`copy出来呢?
+
+可以在先将容器放在后台运行:
+
+```bash
+docker run -id test
+```
+
+然后使用docker自带的`docker cp`命令来copy到宿主机的目录:
+
+```bash
+docker cp 253d3ca34521:/etc/apache2/conf.d/ssl.conf /root
+```
+
+当然也可以直接打开,然后记录文件内容再复制出来。
+
+有了Mod_ssl组件之后,就可以配合SSL证书来对网站进行加密了。既然能将默认的`ssl.conf`复制出来,就可以对其修改然后在生成镜像时再复制会容器内的原目录。
+
+剩下对于SSL的配置就和给宿主机配置加密一样了,几乎没有什么不同。主要就是在`ssl.conf`中填上正确的证书目录:
+
+```
+SSLCertificateFile /etc/letsencrypt/live/defect.ink/fullchain.pem
+#SSLCertificateFile /etc/ssl/apache2/server-dsa.pem
+#SSLCertificateFile /etc/ssl/apache2/server-ecc.pem
+
+# Server Private Key:
+# If the key is not combined with the certificate, use this
+# directive to point at the key file. Keep in mind that if
+# you've both a RSA and a DSA private key you can configure
+# both in parallel (to also allow the use of DSA ciphers, etc.)
+# ECC keys, when in use, can also be configured in parallel
+SSLCertificateKeyFile /etc/letsencrypt/live/defect.ink/privkey.pem
+```
+
+Let's Encrypt生成的证书在路径下还会有个`fullchain.pem`,这是一整个证书链。在配置文件中只需要这个证书和一个私钥`privkey.pem`就好。
+
+### 跳转至443
+
+在有了https之后,如果不需要80端口还能继续访问。可以使用301跳转来将访问80端口的访客都跳转到443。Apache的mod_rewrite可以轻松的实现针对各种条件的跳转。
+
+mod_rewrite的作用很多,能设置的条件也可以很复杂。当然配置个简单的跳转不是非常的复杂。
+
+```
+RewriteEngine on
+RewriteCond %{SERVER_NAME} =defect.ink
+RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
+```
+
+* `RewriteEngine`打开跳转引擎;
+* `RewriteCond`跳转的条件;这里设置当域名为`defect.ink`时,执行下面的跳转动作;
+* `RewriteRule`跳转的动作;当符合上面的条件时,执行添加https`https://%{SERVER_NAME}%{REQUEST_URI}`。而后面的变量保持不动。
+
+这行配置是来自于certbot的自动配置中的,在配置宿主机的ssl时可以选择全部跳转。然后它就会帮我们自动配置了。对其进行简单的修改就可以作用与其他的配置文件了。
+
+这几行推荐是写在`httpd.conf`的末尾,也就是`IncludeOptional /etc/apache2/conf.d/*.conf`的上方。虽然ssl.conf也会被include进来,但是还是感觉写在这里要方便一点。
+
+然后将`httpd.conf`和`ssl.conf`一样在构建时复制到容器内就ok了。
+
+```dockerfile
+ && cp -a ssl.conf /etc/apache2/conf.d/ \
+ && cp -a httpd.conf /etc/apache2/
+```
+
+### Renew
+
+Let's Encrypt的证书虽然很方便,但是一次只能生成三个月有效期的证书。使用和生成差不多的方法renew证书就好了。
+
+```
+sudo docker run -it -p 80:80 --rm --name certbot \
+ -v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \
+ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
+ certbot/certbot renew
+```
+
+想要自动化执行话,可以使用crontab来定时运行。
+
+## 全部的Dockerfile
+
+这时候的配置文件看起来应该是这个样子的:
+
+```dockerfile
+#test
+
+FROM alpine:latest
+MAINTAINER Defectink
+
+#install
+RUN apk update \
+ && apk upgrade \
+ && apk add --no-cache \
+ apache2 \
+ apache2-ssl \
+ nodejs \
+ npm \
+ tzdata \
+ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
+ && rm -rf /var/cache/apk/* \
+ && mkdir -p /data/DefectingCat.github.io \
+ && npm config set unsafe-perm true \
+ && npm install -g hexo
+
+COPY DefectingCat.github.io /data/DefectingCat.github.io
+WORKDIR /data/DefectingCat.github.io
+RUN hexo g \
+ && cp -a public/* /var/www/localhost/htdocs/ \
+ && cp -a ssl.conf /etc/apache2/conf.d/ \
+ && cp -a httpd.conf /etc/apache2/
+
+EXPOSE 80 443
+CMD ["/usr/sbin/httpd","-f","/etc/apache2/httpd.conf","-DFOREGROUND"]
+```
+
+## 启动!
+
+```bash
+docker run -id --name="blog" -v /etc/letsencrypt/:/etc/letsencrypt/ -p 80:80 -p 443:443 blog
+```
+
+全部操作完了,启动命令也随着操作变得更加的复杂了。
+
+* `-id`扔到后台;
+* `--name`容器别名;
+* `-v`映射之前的ssl证书的目录;
+* `-p`80和443都需要映射;
+
+## 优化
+
+一些比较方便的命令。
+
+删除所有``的镜像:
+
+```bash
+docker rmi $(docker images -f "dangling=true" -q)
+```
+
+停止所有容器,删除所有容器:
+
+```bash
+docker kill $(docker ps -q) ; docker rm $(docker ps -a -q)
+```
+
+停止所有容器,删除所有容器,**删除所有镜像**:
+
+```bash
+docker kill $(docker ps -q) ; docker rm $(docker ps -a -q) ; docker rmi $(docker images -q -a)
+```
+
+## 参考
+
+* [How To Create an SSL Certificate on Apache for CentOS 7](https://www.digitalocean.com/community/tutorials/how-to-create-an-ssl-certificate-on-apache-for-centos-7)
+
+* [apache2-ssl](https://pkgs.alpinelinux.org/package/edge/main/x86/apache2-ssl)
+* [Certbot running with Docker](https://certbot.eff.org/docs/install.html#running-with-docker)
+
+* [Where my Certificate](https://certbot.eff.org/docs/using.html#where-certs)
\ No newline at end of file
diff --git a/source/_posts/Gitlab尝鲜.md b/source/_posts/Gitlab尝鲜.md
new file mode 100644
index 0000000..f282bee
--- /dev/null
+++ b/source/_posts/Gitlab尝鲜.md
@@ -0,0 +1,88 @@
+---
+title: Gitlab 尝鲜
+date: 2019-06-19 15:42:41
+tags: Linux
+categories: 实践
+url: try-the-gitlab
+index_img: /images/Gitlab尝鲜/52152339.webp
+---
+
+## Gitlab?
+
+**GitLab**是由GitLab Inc.开发,使用[MIT许可证](https://zh.wikipedia.org/wiki/MIT許可證)的基于[网络](https://zh.wikipedia.org/wiki/互联网)的[Git](https://zh.wikipedia.org/wiki/Git)[仓库](https://zh.wikipedia.org/wiki/仓库_(版本控制))管理工具,且具有[wiki](https://zh.wikipedia.org/wiki/Wiki)和[issue跟踪](https://zh.wikipedia.org/wiki/事务跟踪管理系统)功能。
+
+它是一款和常见的Github很像仓库管理工具,大体使用上和Github很像。前端页面也很好看,主要的是安装非常的方便,它集成了自身需要的nginx的服务端。
+
+起初是由Ruby写成,后来部分由Go语言重写。
+
+最早,它是完全免费的开源软件,按照 MIT 许可证分发。毕竟人家是公司,后来Gitlab被拆分成GitLab CE(社区版)和 GitLab EE(企业版)。和如今的模式一样,ce是完全免费使用的社区版,而ee是可以进行试用且更多功能的收费版。
+
+
+
+## 安装部署
+
+[官方](https://about.gitlab.com/install/)拥有详细的安装操作文档,并且对于不同的Linux发行版也有着不同的软件仓库源。除此之外,我们还可以选择其他的安装方式,如Docker等。
+
+我当前是部署在Ubuntu上的,系统信息:
+
+
+
+ 官方是推荐系统空闲内存在4GB以上的,对于类似我们这样的个人使用的较少的来说,推荐空闲内存是2GB以上。毕竟它会自己运行一套nginx、redis等服务端。
+
+自家的开源地址:[Gitlab](https://gitlab.com/gitlab-org/gitlab-ce/)
+
+相对于从源码安装来说,自家提供的相应的软件包更加的方便,也更不会容易出错。我们只需要选择相应的操作系统即可。
+
+这里仅以Ubunt示例:
+
+首先安装需要的相关依赖:
+
+```
+sudo apt-get update
+sudo apt-get install -y curl openssh-server ca-certificates
+```
+
+如果我们不使用外部的SMTP来发邮件的话,Gitlab可以使用postfix来进行发邮件。当然对我们完全不需要发邮件的这个需求的话,这步完全可以跳过。
+
+```
+sudo apt-get install -y postfix
+```
+
+基本依赖安装完后,随后可以添加Gitlab的源来进行安装软件了:
+
+```
+curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
+curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash
+```
+
+*注意ce和ee的区别*
+
+接下来,我们就可以使用`apt`来进行安装GItlab-ce了。修改下方命令的`https://gitlab.example.com`为自己Gitlab运行的域名。安装程序将自动配置该网址启动Gitlab
+
+对于需要启用`https`的小伙伴们,Gitlab可以自动请求[[Let's Encrypt](https://docs.gitlab.com/omnibus/settings/ssl.html#lets-encrypthttpsletsencryptorg-integration)]的证书,方便我们一步到位。当然我们也可以使用自己的证书。
+
+```
+sudo EXTERNAL_URL="https://gitlab.example.com" apt-get install gitlab-ce
+```
+
+到这里就安装的差不多了,此时我们可以打开自己的Gitlab。第一次访问时会被重定向到设定`root`密码的界面。设置完成后我们的Gitlab就安装完成了。初始管理员的账户就是`root`
+
+由官方给我们提供的安装方式是不是相对来说非常的简单呢?
+
+## 使用
+
+
+
+简洁多彩的界面也时非常的好看的。默认没有配置邮件的情况下是可以随意注册的,我们也可以在后台配置里关闭自动注册,作为一个私人的git仓库。也可以手动添加用户给想尝鲜的小伙伴们。
+
+当然,Gitlab只是一个仓库源的管理工具,提供了类似与Github的功能。对于我们终端使用git来说,还是和Github一模一样。并且我们可以将其部署在国内的主机上,来提升某些情况到Github速度奇慢无比的问题。
+
+## 启动与管理
+
+```
+$ sudo gitlab-ctl reconfigure
+$ sudo gitlab-ctl status
+$ sudo gitlab-ctl stop
+$ sudo gitlab-ctl restart
+$ sudo ps aux | grep runsvdir
+```
\ No newline at end of file
diff --git a/source/_posts/Header实践-得拿下这个A.md b/source/_posts/Header实践-得拿下这个A.md
new file mode 100644
index 0000000..579f9b0
--- /dev/null
+++ b/source/_posts/Header实践-得拿下这个A.md
@@ -0,0 +1,213 @@
+---
+title: Header 实践-得拿下这个 A
+date: 2019-12-18 16:42:53
+tags: HTML
+categories: 实践
+url: header-practice-have-to-win-this-a
+index_img: /images/Header实践-得拿下这个A/header-security.webp
+---
+
+[Header安全检测](https://securityheaders.com/)
+
+之前在学习HTML时候研究过`X-Frame-Options`,它也是header头中的一个安全策略。用于给浏览器指示是否允许一个页面能否嵌入`
起步 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
安装 从最基础的开始,可以在单html文件中引入vue。
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" ></script>
声明式渲染 Vue的核心是采用简介的模板语法来声明式地将数据渲染进DOM系统:
<div class ="app" > {{ message }}</div >
let app = new Vue({ el: '.app' , data: { message: 'Hello world!' }})
不得不说Vue的教程确实简单易懂,也可能是因为Vue本身的语法简洁,第一次看到教程里的这个实例时,大部分都是能够理解的。目前为止,已经成功的创建了第一个Vue应用。
现在数据和DOM已经被建立的关联,所有的东西都是响应式的,刚刚新建的app
实例拥有一个app.message
的值,在console中修改就能实时的看到相应的属性更新。
目前就不再和HTML直接进行交互了,一个Vue应用会将其挂在到一个DOM元素上:el: '.app'
,然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。
除此之外,Vue还能直接对DOM元素attribute进行绑定
<div class ="app" > <img v-bind:src ='src' v-bind:alt ='alt' v-bind:title ='message' > </div > <script > let app = new Vue({ el: '.app' , data: { src: 'https://cdn.defectink.com/images/file_4963947.png' , alt: 'ヽ(✿゚▽゚)ノ' , message: 'ヽ(✿゚▽゚)ノ' } })</script >
这样的操作方法被称之为指令 。指令带有v-
前缀,以表示他们是Vue提供的特殊attribute。它们会在渲染的 DOM 上应用特殊的响应式行为。
条件与循环 Vue提供了一个类似于条件语句的指令,切换一个元素的显示也非常的简单,使用v-if
语句。
<div class ="app" > <img v-if ='seen' v-bind:src ='src' v-bind:alt ='alt' v-bind:title ='message' > </div > <script > let app = new Vue({ el: '.app' , data: { src: 'https://cdn.defectink.com/images/file_4963947.png' , alt: 'ヽ(✿゚▽゚)ノ' , message: 'ヽ(✿゚▽゚)ノ' , seen: true } });</script >
继上一个例子,添加一个和绑定DOM attribute类似的指令:v-if
。相应的,它也类似于常见的if语句,当值为true
时,则显示这个DOM,反之亦然。当然,所有的内容还都是动态的,在console中继续使用app.seen = false
时,DOM元素将会隐藏。
既然有了if语句,那自然是不能少了for循环的。
<ol class ="app" > <li v-for ="todo in items" > {{ todo.txt }} </li > </ol > <script > let app = new Vue({ el: '.app' , data: { items: [ { txt : '小' }, { txt : '小小' }, { txt : '小小小肥羊' } ] } });</script >
Vue里的for循环可以用来创建列表等,并且是以数组的方式对其DOM进行控制的。指令v-for="todo in items"
中的items
就对应了data
中的items
数组,而DOM里的参数{{ todo.txt }}
就相当于items[i].txt
。
并且后续可以使用数组方法对DOM进行直接的操作:
app.items.push({txt : 'test' });app.items.shift();
处理用户输入 Vue可以使用指令v-on
来对DOM绑定一个事件监听器,通过它来调用在实例中定义的方法
<div class ="app" > <input type ="button" v-on:click ="disableImage" value ="切换!" > <br > <img v-bind:src ="src" alt ="" > </div > <script > let app = new Vue({ el: '.app' , data: { src: 'https://cdn.defectink.com/images/file_4963947.png' }, methods: { disableImage: function ( ) { if (this .src) { this .src = '' ; } else { this .src = 'https://cdn.defectink.com/images/file_4963947.png' ; } } } });</script >
这是对事件监听器的一个实例,通过在input
上绑定一个事件监听器来触发对实例中定义的方法。在实例中的方法中的this
指向于当前实例。
在实例方法中,我们更新了应用状态,但没有触碰DOM——所有的 DOM 操作都由 Vue 来处理,我们编写的代码只需要关注逻辑层面即可。
Vue还提供了v-model
指令,它能够轻松实现对表单的双向绑定
<div id ="app" > <input type ="text" v-model ="message" > <br > <p > {{ message }} </p > </div > <script > let app = new Vue({ el: '#app' , data: { message: 'Input something...' } });</script >
组件化应用构建 组件系统是Vue的另一个重要概念,它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。一个大型的页面应用将由几个可重复利用的组件构成。
在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:
Vue.component('todo-item' , { template: '<li>这是一个测试</li>' });let app = new Vue(...)
注册完成后就可以使用使用它来构建一个模板:
<ol id ="app" > <todo-item > </todo-item > </ol > <script > Vue.component('todo-item' , { template: '<li > 这是一个测试</li > ' }); let app = new Vue({ el: '#app' })</script >
这样一个组件简而易懂,定义一个特定内容的组件,然后在html中渲染出来其内容。但这样还不够,内容都是特定的,每次渲染的都是同样的文本。我们应该能从父作用域将数据传到子组件才对。
稍微修改一下定义的组件,使其能够接收一个prop。这类似于一个自定义的attribute。
Vue.component('todo-item' , { props: ['todo' ], template: '<li>{{ todo.text }}</li>' })
现在,我们可以使用 v-bind 指令将待办项传到循环输出的每个组件中:
<div id ="app" > <ol > <todo-item v-for ="item in list" v-bind:todo ="item" v-bind:key ="item.id" > </todo-item > </ol > </div > <script > Vue.component('todo-item' , { props: ['todo' ], template: '<li > {{ todo.text }} </li > ' }); let app = new Vue({ el: '#app' , data: { list: [ { id : 0 , text : '蔬菜' }, { id : 1 , text : '奶酪' }, { id : 2 , text : '随便其它什么人吃的东西' } ] } });</script >
这个实例中,父作用域中的数据通过组件的prop
接口进行了良好的解耦。在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。类似于这样:
<div id ="app" > <app-nav > </app-nav > <app-view > <app-sidebar > </app-sidebar > <app-content > </app-content > </app-view > </div >
与自定义元素的关系 Vue组件非常类似于自定义元素 ——它是 Web 组件规范的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is attribute。但是,还是有几个关键差别:
Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。
虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。
Vue 3! 对于一个初学者来说,同时学习两个版本可能有些吃力。但我依然想从最基本的开始时就了解了它的变化,并且还发现了一些有意思的收获。
更简洁的声明 第一次学习2.x版本时,发现确实如其介绍的那样:
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
用最直白的方式来看,2.x使用的方式是类似于构造函数来声明一个实例,并且有着固定的搭配:el
为DOM的element,data
为数据,后续还能继续添加方法:
let app = new Vue({ el: '.app' , data: { message: 'Hello world!' }})
而Vue3使用了另一种方法:
<div id ="test" > <p > {{ message }} </p > </div > <script > let count = { data ( ) { return { message: 'Hello world!' } } } Vue.createApp(count).mount('#test' ); </script >
Vue3首先使用一个对象字面量创建一个带有data()
函数的变量,该函数使用的是一种更简短的定义方法的方法 。data()
函数的返回值就是实例的数据。
当变量声明完成后,使用Vue的一个createApp()
方法传入,并接着使用mount()
方法传入DOM。这样一个Vue的实例就创建挂载完成了,相比较之下,我觉得这种方式对其生命周期有着更清晰的显示。
当然,也可以跳过创建变量这一步,直接传参,这样看上去更像Vue2。
<div id ="app" > <p > {{ message }} </p > </div > <script > let app = Vue.createApp({ data ( ) { return { message: 'xfy!' } }, mounted ( ) { let id = setInterval (() => { this .message = 'x' + this .message; }, 1000); setTimeout (() => { clearInterval (id); }, 10000) } }).mount('#app' ) </script >
就拿其官方文档的实例来看,再创建实例时还能方便的为其的添加其他方法,并且其方法名就是生命周期名。例如在挂载后执行mounted()
。
<div id ="test" > <p > {{ message }} </p > </div > <script > let count = { data ( ) { return { message: 'Hello world!' } }, mounted ( ) { let id = setInterval (() => { this .message += this .message; }, 1000); setTimeout (() => { clearInterval (id); },9000) } } Vue.createApp(count).mount('#test' );
入门到这里时,2和3目前接触到的只是写法不同。后续以3为基础学习,并和2做比较。
应用实例 所有的 Vue 组件都是实例,并且接受相同的选项对象。
创建一个实例 每个 Vue 应用都是通过用createApp
函数创建一个新的应用实例开始的,而2.x则是以一个构造函数开始的。
Vue.createApp(...);
创建实例后,我们可以挂载它,将容器传递给mount
方法。mount
方法接收DOM的选择器(class、ID等)。
Vue.createApp(...).mount('#id' );
根组件 Vue.createApp()
方法用于创建一个根组件,当我们挂载一个应用程序时,该组件将为渲染起点。
一个应用需要被挂载到一个DOM节点上。例如我们需要挂载实例到<div id="app"></div>
上,通常的步骤如下:
let rootComponent = { };let app = Vue.createApp(rootComponent);let vm = app.mount('#app' );
不像大多数的应用程序方法,mount
不会返回应用。相反,它会返回根节点实例。也就说变量vm
是根节点的实例。
Vue2和3虽然都没有完全遵循MVVM模型,但是 Vue 的设计也受到了它的启发。
一个 Vue 应用由一个通过 createApp 创建的根实例,以及可选的嵌套的、可复用的组件树组成。举个例子,一个 todo 应用的组件树可以是这样的:
根实例└─ TodoList ├─ TodoItem │ ├─ DeleteTodoButton │ └─ EditTodoButton └─ TodoListFooter ├─ ClearTodosButton └─ TodoListStatistics
节点实例属性 前面我们遇到了data
属性,data
中定义的属性通过节点实例暴露出来:
let app = Vue.createApp({ data ( ) { return { meg: 'greeting something...' } }});let vm = app.mount('#app' );console .log(vm.meg)
data
中暴露出的属性都会加如其响应式系统,整个实例会被设置一个Proxy代理拦截其行为,从而监听数据的变化并实时渲染到DOM上。
还有其他各种用户自定义属性的组件选项能够添加到实例,例如methods
, props
, computed
, inject
和setup
。
Vue同样也暴露了一些内建的属性,例如$attrs
和$emit
。他们都有$
前缀与用户自定义的属性区分开来。
生命周期钩子 每个组件在创建后都要经历一系列初始化的步骤,例如,它需要设置数据监控,编译模板,挂载实例到DOM节点和当数据变化时更新DOM。这一系列操作也被称之为生命周期钩子。
简单来说,就是一个实例在从最初始的声明到最后的卸载期间不同阶段对其操作的API。
例如,调用create()
钩子,在实例被创建后运行的操作:
let app = Vue.createApp({ data ( ) { return { meg: 'test' } }, created ( ) { console .log('instance created!' + this .meg); }});let vm = app.mount('#app' );
所有的一系列钩子,他们的this
都指向当前调用的活动实例。
注意,不要在一个组件属性或回调中使用箭头函数。例如:created: () => {console.log(this.a);};
或者vm.$watch('a', newValue => this.myMethod());
。箭头函数没有自己的this
,this
会和其他变量一样,向上层作用域中查找,直到找到为止。通常会遇到这样的报错:Uncaught TypeError: Cannot read property of undefined
或Uncaught TypeError: this.myMethod is not a function.
生命周期图 下图可以很清晰的看到Vue3的实例的一个生命周期。
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
- Vue
-
-
-
-
-
-
-
-
- Node.js之旅
-
- /defect/get-starting-for-node-js.html
-
- Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
Node.js不仅仅是服务器上的JavaScript。
并不熟悉的JavaScript 虽然说Node直接般来了个V8来运行JavaScript,但它毕竟不运行在浏览器上,并且是由事件驱动的异步程序,它的本来的目的就是用来搭建高性能的Web服务器。在极少情况下,编写的 JavaScript 代码没有按期望的方式运行。
在客户端的运行环境下,只要是在同一个浏览器窗口运行的JS环境就属于同一个全局环境。无论是在哪里引入的JS文件,都属于同一个运行环境,全局变量能够正常工作,例如有两个js被引入:
<script src ="a.js" > </script > <script src ="b.js" > </script >
他们分别有一段代码:
let a = 'xxxxxfy!' ;alert(a);
正常情况下全局变量a
是可以被正常访问的,而在node中,a.js和b.js分别是两个文件,不做其他操作的情况下,他们分别是两个全局变量。
基础架构 Node 运行时的基础架构由两大组件构成:
JavaScript引擎 众所周知,node使用的是Chrome大排自吸V8,它可以运行任何JS代码。启动时,它会启动一个V8的引擎实例,且node可以充分的利用这个引擎实例。
V8可嵌入(/绑定)到任何C++程序中,。这意味着,除了纯 JavaScript 库外,还可以扩展 V8 来创建全新的函数(或函数模板),方法是将其与 V8 绑定在一起。并且node还支持使用编译好的二进制的C++程序。
事件循环 JavaScript是一门单线程的语言,无论换到什么地方运行,它也是单线程语言。单线程语言面临的最大的问题就是阻塞,在前面的代码没有被执行完,后面的代码都将处于等待状态。
node使用libuv 来实现事件循环。要使用事件循环可以使用异步API,可将回调函数作为参数传递到该 API 函数,在事件循环期间会执行该回调函数。node异步编程的直接体现就是回调函数。异步编程基于回调函数来实现,但不能说是使用了回调后程序就异步化了。Node 所有 API 都支持回调函数。
┌───────────────────────────┐┌─>│ timers ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐│ │ pending callbacks ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐│ │ idle, prepare ││ └─────────────┬─────────────┘ ┌───────────────┐│ ┌─────────────┴─────────────┐ │ incoming: ││ │ poll │<─────┤ connections, ││ └─────────────┬─────────────┘ │ data, etc. ││ ┌─────────────┴─────────────┐ └───────────────┘│ │ check ││ └─────────────┬─────────────┘│ ┌─────────────┴─────────────┐└──┤ close callbacks │ └───────────────────────────┘
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
事件循环包含多个调用回调函数的阶段:
计时器阶段:将运行 setInterval() 和 setTimeout() 过期计时器回调函数 轮询阶段:将轮询操作系统以查看是否完成了所有 I/O 操作,如果已完成,将运行回调函数 检查阶段:将运行 setImmediate() 回调函数 一个常见的误区是,认为 V8 和事件循环回调函数在不同线程上运行。但事实并非如此。V8 在同一个线程上运行所有 JavaScript 代码。
非阻塞I/O 已经简单的了解过node基于v8的单线程来使用回调函数实现异步编程,从而达到高性能与高并发。
异步I/O 异步I/O能够大大的提升程序的工作效率,且不会影响剩下的代码执行。
同步读取文件 首先来看下同步读取文件
let fs = require ('fs' );console .log('starting process...' );let data = fs.readFileSync('test.txt' );console .log(data);console .log('the end' );
同步读取文件会按照从上到下的顺序来执行代码,到遇到读取I/O时,剩下的代码将处于等待状态。并且在读取I/O时遇到错误(例如文件不存在),将会直接返回错误,剩下的代码将不执行完。
异步读取文件 let fs = require ('fs' );console .log('starting process...' );fs.readFile('test.txt' , (err, data ) => { if (err) { console .log('something was wrong: ' + err); } else { console .log(data); }})console .log('the end' );
异步读取文件调用了回调函数,它设置了一个事件循环,在等待读取I/O的同时,将继续执行剩下的代码。当文件读取完成之后,将会返回回调函数,并输出结果。并且当读取文件遇到错误时,在等待读取期间执行的代码将正常执行。
异步的结果看起来像是这样的:
starting process...the end<Buffer 64 66 6 a 61 73 >
在等待其读取test.txt
的期间,JS将剩下的console.log
语句先执行了。
同步I/O node的设计理念就是使用异步编程,使用阻塞编程可无法写出一个高性能的web服务器。
但这不代表着同步I/O就一无是处,在某些情况下,同步 I/O 通常比异步 I/O 更快,原因在于设置和使用回调函数、轮询操作系统来获取 I/O 状态等操作都涉及到一定的开销。
如果需要写一个一次性的使用程序,仅用于处理一个文件,从命令行中启动 Node,并向其传递 JavaScript 实用程序的文件名。此时,该实用程序是唯一运行的程序,因此,即使它阻塞了 V8 线程,也没有任何影响。在此情况下,适合使用同步 I/O。
只要是谨慎使用同步 Node API 调用,就不会出现什么问题。
小结 V8是有八个气缸的V型发动机. JavaScript是单线程语言,它通过V8 引擎提供的异步执行回调接口实现了异步编程。 全文均是笔记
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
- Node
-
-
-
-
-
-
-
-
- 入坑IRC
-
- /defect/irc-getting-started.html
-
- IRCIRC的全称为Internet Relay Chat,是一种应用层的协议。主要用于聊天,是早期互联网中主流的聊天工具,在今天依然也有不少人活跃。要使用它需要使用客户端来连接到服务器。
IRC的组成 服务器 IRC是一个分布式的C/S架构。通过连接到一个服务器,就可以访问其连接的其他服务器上的频道。目前常见的有irc.freenode.net
。
频道 频道存在于一个IRC服务器上。一个频道类似于一个聊天室,频道名称必须以#符号开始,例如#irchelp。
客户端 客户端用于连接至服务器,目前有很多种基于字符/GUI的跨平台软件。我用的是HexChat,一款基于GUI的软件。
使用 简单的了解了IRC是一款用于聊天的应用协议之后,就是开始使用了。既然是基于C/S架构的,那么首先是准备好自己的客户端。我挑了一个常见的GUI客户端:HexChat。
昵称注册 IRC并不像现代的聊天软件一样,需要先注册账号才能使用。它可以输入一个昵称后就进入服务器的频道内与人聊天。而昵称任然是需要唯一的,所以想要使用自己的昵称而不被别人占用,就需要注册昵称。类似于注册账号,基于邮箱与密码。
注册过程很简单,首先需要在聊天窗口中输入注册的命令:
/msg nickserv register password email
因为是在聊天框里输入命令,所以一定要注意命令格式,否则一不小心可能就会将明文的密码发到频道里。在注册昵称时推荐不加入任何频道,这样就不会不小心发出去了。
输入注册命令后就会收到认证的邮件,邮件大概是这样的(freenode):
将邮件里的内容再输入一遍就注册完成了。
/msg NickServ VERIFY REGISTER Defectink hr**** **** *
认证 当注册昵称过后,下次再使用这个昵称登录的时候就需要认证了。使用同样的/msg
来进行认证:
/msg nickserv identify password
另一种说明身份的方法是设置服务器密码为您注册时提供的密码。
还可以对昵称设置进行保护,即在登录认证时,必须在30秒内向服务器表明身份,否则就强制改为其他昵称,并在一段时间内禁止此人使用此昵称(即便是在说明身份后)。在任意窗口中键入:/msg nickserv set enforce on
。如果你登陆时在30秒内未能表明身份且被改为其他名字,请在改回原有名称前,输入/msg nickserv release username password
以解除。
SASL 不少 IRC 客户端都支持使用 SASL 自动登录。只需找到相应的选项,在 SASL 用户名密码部分分别填入自己的昵称和NickServ密码,就可以获得自动登录的效果。
不同的客户端有不同的设置方法,文档也很多。这是对于HexCaht的:
Open the Network List (Ctrl + S) The freenode network may already exist; find it in the list then click on Edit In the User name
field, enter your primary nick Select SASL (username + password)
for the Login method
field In the Password
field, enter your NickServ password
频道 以#号开头的字符串就是频道名,可以使用命令/join
来加入频道。
/join #archlinux-cn
建立频道 创建频道同样也是使用/join
来创建,如果创建的频道已经存在,则直接进入。建立频道可用于与自己的小伙伴聊天,也可以用于熟悉频道命令。
如果频道成功建立,那么我们就会成为频道的管理员。因为频道名和昵称一样需要具有唯一性,所以频道也需要注册。
/msg ChanServ REGISTER <#channel> <passwd>
一般频道都通过ChanServ这个机器人管理.
/msg ChanServ SET <频道名> GUARD ON
在已注册的频道上委任管理员OP
/msg ChanServ op #xfy Defectink
设置频道简介
/topic <your_topic>
频道模式 频道的模式用于设置频道的一些功能与限制。通过/mode #channel
来列出频道模式,通过/msg chanserv info #channel
来列出有MLOCK的模式。
使用/mode #channel +(mode)
或者/mode #channel -(mode)
来增加或删除模式。带有MLOCK的模式需要使用/msg ChanServ SET #foo MLOCK +c
一些常用的模式:
Mode(name) Description b (channel ban) Prevent users from joining or speaking. Sending /mode #channel +b alone will return the current ban list. While on the channel, banned users will be unable to send to the channel or change nick. The most common form for a ban is +b nick!user@host. The wildcards * and ? are allowed, matching zero-or-more and exactly-one characters, respectively. Bans set on IP addresses will apply even if the affected user joins with a resolved or cloaked hostname. CIDR notation is supported in bans. The second form can be used for bans based on user data. You can append $#channel to any ban to redirect banned users to another channel. q (quiet) Works like +b (ban user), but allows matching users to join the channel. c (colour filter) Strip colour and formatting codes from channel messages. C (block CTCPs) Blocks CTCP commands (other than /me actions). i (invite only) Users are unable to join invite-only channels unless they are invited or match a +I entry. k (password) To enter the channel, you must specify the password on your /join command. Keep in mind that modes locked with ChanServ’s MLOCK command can be seen by anyone recreating the channel; this includes keys. Also keep in mind that users being on the channel when +k is set will see the key as well. n (prevent external send) Users outside the channel may not send messages to it. Keep in mind that bans and quiets will not apply to external users. l (join limit) Takes a positive integer parameter. Limits the number of users who can be in the channel at the same time. m (moderated) Only opped and voiced users can send to the channel. This mode does not prevent users from changing nicks. t (ops topic) Only channel operators may set the channel topic.
管理员 频道管理员称为operation channel。通常为:
sop (super operator) 频道的註册者,拥有操作频道所有权限,包括踢人。 aop (auto operator) 频道註册者信任的共同管理者,拥有部分权限,和第三个的差异在於,离开频道后再进入还是能拥有管理权限。 op 普通管理者,可能一旦离开频道就失去op。 weechat weechat是一款基于命令行的客户端。
添加服务器 /server add freenode chat.freenode.net
服务器选项 weechat的一些选项加油默认值,昵称默认为终端用户名。
/set irc.server .freenode .nicks "mynick,mynick2,mynick3,mynick4,mynick5"
设置用户和真实姓名:
/set irc.server .freenode .username "My user name" /set irc.server .freenode .realname "My real name"
在启动时启用自动连接到服务器:
/set irc.server.freenode.autoconnect on
使用SSL连接:
/set irc.server .freenode .addresses "chat.freenode.net/7000" /set irc.server .freenode .ssl on
SASL:
/set irc.server .freenode .sasl_username "mynick" /set irc.server .freenode .sasl_password "xxxxxxx"
nickserv:
/set irc.server .freenode .command "/msg nickserv identify xxxxxxx"
autojoin:
/set irc.server .freenode .autojoin "#channel1,#channel2"
连接服务器 /connect freenode
/disconnect freenode
窗口/缓冲区管理 /buffer /window
例如,将屏幕垂直分割为一个小窗口(1/3宽度)和一个大窗口(2/3),使用命令:
/window splitv 33
删除分割:
/window merge
一些常用的命令 示例命令 备注 /server irc.freenode.net
连接到 freenode
网络 /nick myName
更换昵称为 myName /msg nickserv register password me@163.com
注册昵称, 密码为 password, 邮箱为 me@163.com /join #java
进入 #java 聊天室 /exit
退出账户 /nick zhijia
登陆或切换用户名 /msg NickServ identify <password>
切换用户后登陆验证用户身份 /help
帮助 /quit
退出服务器 /whois 昵称
查看某人的资料 /part
离开频道 /query 昵称
和某人开小窗口私聊 /away 原因
离开 /away
取消离开。当您不写原因时,就会取消离开状态
坑中 在今天日异月新的IM中,IRC肯定是较小众。虽然几乎就是上个世纪流行的沟通方式了,不过在今天回味一下也很棒,如果有人在一起聊天的话就更棒了。
freenode有个web版,入门体验很好。webchat
另外,#xfy on freenode.
]]>
-
-
-
-
- 日常
-
-
-
-
-
-
- Linux
-
- tools
-
- IRC
-
-
-
-
-
-
-
-
- JavaScript的函数
-
- /defect/function-of-javascript.html
-
- 函数表达式是JavaScript中一个强大同时容易令人困惑的特性。定义函数的方式有两种:函数声明和函数表达式。
函数声明首先是function
关键字,其后是函数的名字,这就是指定函数名字的方式。
function test ( ) { console .log('Hi, there' );}
关于函数声明,其一个重要特征就是函数声明体提升 。它和用var
声明提升类似,但他是整体提升,所以可以在声明前使用。
test();function test ( ) { console .log('Hi, there' );}
函数表达式创建函数有一点不同,它类似于声明一个变量,使用var
或者let
来声明一个函数体。
let
关键字后的变量名就是函数的名字,而函数后没有名称,这个函数称为匿名函数。可以理解为将这个匿名函数赋值给了这个变量。
let test = function ( ) { console .log('Yo, there' );}
函数表达式与其他表达式一样,需要先声明才能使用。尽管var
创建的变量会声明提升,但是赋值并不会提升。
test();let test = function ( ) { console .log('something...' );}
在早期的环境里,函数声明和if语句可能还不能工作的和契合。不过现在多数浏览器与运行环境都能正常执行如下代码:
if (1 ) { function hi ( ) { console .log('hi' ); }} else { function hi ( ) { console .log('yo' ); }}
我的猜测可能是和函数声明提升有关,因为js没有块级作用域,所以在if语句里根据条件的函数声明都提升了,导致就算条件满足,最后声明的还是下面的函数声明。
如果真的有不能运行的环境,可以做这样的尝试。因为提前声明了变量,在将匿名函数赋值给变量,所以就不存在函数声明提升这个特性了。
let hi;if (1 ) { hi = function ( ) { console .log('hi' ); }} else { hi = function ( ) { console .log('yo' ); }}
递归 递归函数是在一个函数通过名字调用自身的情况下构成的:
function add (num ) { if (num <= 1 ) { return 1 ; } return num + add(num - 1 );}
这是一个经典的递归实例,但是如果将函数名更换一下。并将前一个函数名给解除引用,就会导致函数出错。这是因为函数内部的递归是通过函数名来引用这个函数的。如果更换了函数名,就会导致这种错误。
function add (num ) { if (num <= 1 ) { return 1 ; } return num + add(num - 1 );}let minus = add;add = null ;console .log(minus(5 ));
但这并不影响递归,可以换种方式来继续引用函数自身。arguments.callee
就是指向正在执行的函数的指针,因此可以用它来实现对函数递归的调用。从而与函数名拜托关系。
function add (num ) { if (num <= 1 ) { return 1 ; } return num + arguments .callee(num - 1 );}let minus = add;add = null ;console .log(minus(5 ));
但arguments.callee
不能在strict下使用。不过可以通过命名函数表达式来达到相同的效果。
在使用函数表达式时,使用小括号可以为函数体添加一个任然有效的名称。再将这个函数赋值给这个变量,无论函数怎么更换引用,函数名任然有效。这种方式再严格模式和非严格模式都行得通。
let add = (function a (num ) { if (num <= 1 ) { return 1 ; } return num + a(num - 1 );})
闭包 闭包就是在一个函数的内部返回另一个函数,返回的函数将还能访问外部函数的AO。
在compare这个函数里返回了另一个内部函数(一个匿名函数),这个匿名函数使用了compare函数的prep
参数。即使这个函数被返回到了外部使用,依然能访问到prep变量。这涉及到作用域链的细节。
当一个函数运行时(被调用时),会创建一个执行环境(execution context)及相应的作用域链,然后使用arguments和其他参数的值来初始化函数的活动对象(Activation Object)。这个AO会随着作用域链,链给内部的函数,并使内部函数可以访问外部函数的变量。
JavaScript的作用域与链
function compare (prep ) { return function (obj1, obj2 ) { let o1 = obj1[prep]; let o2 = obj2[prep]; if (obj1[prep] < obj2[prep]) { return -1 ; } else if (obj1[prep] > obj2[prep]) { return 1 ; } else { return 0 } }}
当创建compare函数时,会创建一个包含全局对象的作用域链,这个作用域链被保存在内部的[[scope]]属性中。当调用compare()
函数时,会为函数创建一个执行环境,然后赋值[[scope]]属性中的对象构建起执行环境的作用域链。
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候访问函数中的一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来说,当函数执行完后,局部活动对象就会被销毁,内存中仅保留全局作用域(全局执行环境的活动对象GO)。
但闭包的情况有所不同,当外部函数执行完后,其活动对象也不会被销毁,内部函数依然引用着外部函数的作用域链,。在将内部函数返回后,其执行环境的作用域链会被销毁,但是它的活动对象依然会留在内存中,直到匿名函数被销毁。
由于闭包会携带着包含它的函数的作用域,所以会占用更多的内存。虽然2020年不差内存,且像v8等js引擎会尝试回收闭包的内存,但还是推荐谨慎使用。
闭包与变量 作用域链的机制以及没有块级作用域的特性引出了一个副作用,即闭包只能取得外部函数AO中变量任何最后一个值。
function cycle ( ) { let arr = []; for (var i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { console .log(i); } } return arr;}let test = cycle();test[1 ]();
数组随着for循环将函数赋值到自身,变量i会随着循环的增加而增加。但是函数被赋值时并没有执行,等到函数被返回后在外部被执行时,访问到的i已经是AO里的10了。
可以使用立即执行函数,让i变量成为实参传入数组的函数内,因为参数是按值传递的 ,这样在外部执行时,每个匿名函数中的变量都有一个副本。
立即执行函数在执行后会被销毁,但是它的AO依然被内部的函数所引用。所以对应次数循环的函数内j就对应立即执行函数AO中的j,且每个立即执行函数AO中的j都对应i,因为每次的立即执行函数都不同。
可以使用立即执行函数来为内部的匿名函数再封装一个AO来解决此问题。
function cycle ( ) { let arr = []; for (var i = 0 ; i < 10 ; i++) { (function (o ) { arr[o] = function ( ) { console .log(o); } })(i); } return arr;}
function cycle ( ) { let arr = []; for (var i = 0 ; i < 10 ; i++) { arr[i] = (function (o ) { return function ( ) { console .log(o); } })(i); } return arr;}let test = cycle();
或者直接使用let来声明变量,产生块级作用域,在根本上解决问题。
function cycle ( ) { let arr = []; for (let i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { console .log(i); } } return arr;}
关于this对象 在闭包中使用this对象可能会出现一些小问题。由于闭包返回的匿名函数是在外部执行的,所以匿名函数的执行环境具有全局性,因此它的this对象通常指向window。
global .name = 'xfy' ;let obj = { name: 'xxxfy' , age: 18 , say: function ( ) { return function ( ) { console .log(this .name); } }}obj.say()();
这个例子在非严格模式下返回的是全局对象的属性。那为什么匿名函数没有取得外部函数的this的值呢?
每个函数在被调用时都会自动取得两个特殊的变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量(在匿名函数没有定义形参时可以访问外部函数的实参)。
可以将this实体化,保持在外部函数的AO中,再由匿名函数去访问外部AO中的变量。即使在函数返回之后,that也任然引用着obj。
global .name = 'xfy' ;let obj = { name: 'xxxfy' , age: 18 , say: function ( ) { let that = this ; return function ( ) { console .log(that.name); } }}obj.say()();
有几种特殊的情况,会导致this发生意外的变化。
let obj = { name: 'xfy' , feature: 'handsome' , say: function ( ) { console .log(this .name); }}global .name = 'dfy' ;
第一行就是平时最平常的调用了对象的方法,this得到正确的引用。第二行在方法执行前加上了括号,虽然加上了括号之后好像是在引用一个函数,但this的值得到了维持。而第三行代码先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,返回的就是全局对象里的属性了。
obj.say();(obj.say)();(obj.say = obj.say)();
内存泄漏 在高贵的IE9之前的版本,JScript对象和COM对象使用不同的辣鸡收集程序例程。那么在这些版本中的ie使用闭包就会导致一些特殊的问题。如果闭包的作用域中保存着一个HTML元素,那么就意味着该元素无法被销毁。
这样的一个简单的示例为element元素事件创建了一个闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了对getid函数的活动对象(AO)的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element引用数至少为1。所以它占用的内存就不会被回收。
function getid ( ) { let element = docuemnt.getElementById('test' ); element.onclick = function ( ) { alert(element.id); }}
不过只需要稍微改下就能够解决这个问题。将element.id
保存在一个变量中,并且在闭包中引用这个变量消除循环引用。但仅仅还不能解决内存泄漏的问题。闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭不引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把element设置为null。
function getid ( ) { let element = docuemnt.getElementById('test' ); let id = element.id; element.onclick = function ( ) { alert(id); } element = null ;}
模仿块级作用域 JavaScript是没有块级作用域的(let
与const
声明的变量/常量是有的)。这就意味着块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
在C艹、Java等语言中,变量i只会在for循环的语句块中有定义,一旦循环结束,变量i就会被销毁。而在JS中,变量i是由函数的活动对象所定义的,使用var
声明的变量将会在函数的AO里。并且在函数的任何位置都能访问它,即使重新声明一次,它的值也不会变。
function test (num ) { for (var i = 0 ; i < num; i++) { console .log(i); } var i; console .log(i); console .log(i);}test(10 );
在现在看来,当然是推荐使用let
来解决这个问题了。在for循环中使用let
声明变量就会产生块级作用域。
function test (num ) { for (let i = 0 ; i < num; i++) { console .log(i); } console .log(i); }test(10 );
不过在没有let
关键字的时候,可以使用立即执行函数来模拟块级作用域(私有作用域)。与let
的效果一样!
function test (num ) { (function ( ) { for (var i = 0 ; i < num; i++) { console .log(i); } })() console .log(i); }test(10 );
JS将function
关键字当作一个函数声明的开始,而函数声明后面不能跟园括号。然而函数表达式后面可以跟圆括号,要将函数声明转换为函数表达式,只需要加一对括号即可。
function ( ) {}();(function ( ) {})();
模仿的块级作用域通常作用于函数外部的全局作用域,从而向全局作用域添加变量和函数,而不污染全局作用域。
let name = 'xfy' ;(function ( ) { let name = 'yyy' ; console .log(name);})()
这种做法可以减少闭包占用内存的问题,没有匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域了。
私有变量 严格来说,JS是没有私有成员的概念;所有对象属性都是公开的。不过可以使用函数来实现私有变量的概念。任何在函数中定义的变量,都可以认为是私有化变量,因为在函数外部无法访问这些变量。私有变量包含函数的参数、局部变量和在函数内定义的其他函数。
在这个函数内部,有两个参数和一个局部变量。在函数的内部可以访问这些变量,而在函数外部除了主动返回之外则不能访问他们。如果在这个函数内创建一个闭包,那么这个闭包可以通过作用域链来访问到这些变量。利用这一点,就可以创建访问私有变量的共有方法。
function add (num1, num2 ) { let result = num1 + num2; return result;}
可以利用构造函数的模式来创建私有和公有属性。带有this的属性将因为构造函数被返回到外部,从而形成闭包。而构造函数内部其他的没有this的属性则不会被赋予到实例上。此时就只能通过定义的公有方法来访问私有的属性。
这种有权访问私有属性的公有方法称之为特权方法 (privileged method)。
function Person (name ) { let age = 18 ; function pv ( ) { age++; console .log(age); } this .say = function ( ) { console .log(name); pv(); }}let xfy = new Person('xfy' );console .log(xfy.name);xfy.say();
因为必须要使用构造函数,且还需要在构造函数内定义方法,所以这种方法一样有着和构造函数一样的缺点,每个实例都会创建一组同样的新方法。
静态私有变量 直接使用构造函数会导致方法被重新定义到每个实例。解决构造函数的这个问题的方法就是将方法定义在其原型上,而为了使原型上的方法能够访问到私有化的变量,可以通过在私有作用域中定义方法。
这个方法通过在私有作用域中创建一个全局的构造函数,并且将其方法定义在原型上。当构造函数在全局时,其原型的方法依然能访问私有作用域内的私有变量。
未经声明的创建变量可以提升到全局作用域,但是在严格模式下未经声明的变量则会报错。在目前的版本,可以在全局作用域中将变量先声明,再在私有作用域中赋值。可以到达同样的效果,其原型上的方法依然继承私有作用域的活动对象。
let Person;(function ( ) { 'use strict' let v = 10 ; function pv ( ) { v++; console .log(v); } Person = function ( ) {}; Person.prototype.say = function ( ) { console .log(v); } Person.prototype.cha = function ( ) { pv(); }})();let xfy = new Person();xfy.say();
除了上述的静态私有变量,还有一种写法就是在构造函数内为私有变量赋值。这样创建的实例就能够共享私有变量,并且在创建是就为其赋值。
这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己私有变量。要使用实例变量,还是静态私有变量。最终视需求而定。
let Person;(function ( ) { let name; Person = function (value ) { name = value; } Person.prototype.get = function ( ) { console .log(name); } Person.prototype.set = function (value ) { name = value; }})()let xfy = new Person('xfy' );xfy.get();xfy.set('dfy' );xfy.get();
模块模式 前面所述的方法都是为自定义类型创建私有变量和特权方法的。而道格拉斯所说的模块模式(module pattern)是为单例创建私有变量和特权方法的。所谓单例(singleton),指的就是只有一个实例的对象。
通常,JS都是以对象字面量来创建单例对象的。
let singleton = { name: value, method: function ( ) { return name; }}
模块模式通过为单例添加私有变量和特权方法能够让其得到增强。
这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。在返回的字面量中定义了公开的属性和方法。因为这个字面量是在匿名函数内定义的,所以它有权访问匿名的方法和属性。从本质上来讲,这个对象字面量定义的是单例的公共接口。
let singleton = function ( ) { let pv = 10 ; function pf ( ) { return false ; } return { pubp: true , prip: function ( ) { pv++; return pv; } }}()console .log(singleton);console .log(singleton.prip());
这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时时非常有用的。
let app = function ( ) { let con = []; con.push(new BaseCon()); return { getCon: function ( ) { return con; }, regCon: function (con ) { if (typeof con == 'obejct' ) { con.push(con) } } };}();
增强的模块模式 有人改进了模块模式,即在返回对象之前就加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例。同时还必须添加某些属性/方法对其加以增强的情况。
let app = function ( ) { let con = []; con.push(new BaseCon()); return { getCon: function ( ) { return con; }, regCon: function (con ) { if (typeof con == 'obejct' ) { con.push(con) } } };}();
小结 在JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式。以下总结了函数表达式的特点。
函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数。 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂; 递归函数应该始终使用arguments.callee
来递归地调用自身,不要使用函数名———函数名可能会发生变化。 当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下。
在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。 通常,当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。 使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域的概念),要点如下。
创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。 结果就是函数内部的所有变量都会被立即销毁——除非某些变量赋值给了包含作用域(即外部作用域)中的变量。 闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
即使JavaScript中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。 有权访问私有变量的公有方法叫做特权方法。 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。 JavaScript中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
-
-
-
-
-
-
-
- Can't install gifsicle
-
- /defect/cant-install-gifsicle.html
-
- Hexo-all-minifierIn a long time, i’m used to Hexo-all-minifier to optimization blog. But recently i can’t even install it.
The error logs with npm install
:
‼ getaddrinfo ENOENT raw.githubusercontent.com ‼ gifsicle pre-build test failed i compiling from source × Error: Command failed: C:\WINDOWS\system32\cmd.exe /s /c "autoreconf -ivf" 'autoreconf' �����ڲ����ⲿ���Ҳ���ǿ����еij������������ļ���
In the beginning, i thought the problem is my windows can’t run autoconf. So, i tried installing cygwin, And that is difficult for me. I never tried to installed cygwin.
Anyway, i installed successfully. But the problem has not solved. There is still has the errors with npm install .
imagemin-gifsicle The problem appeared when installing gifsicle, The Hexo-all-minifier used it too. So, the best way is go to the gifsicle issues. As predicted, there is someone got the same errors.
Be unexpected, It’s not a problem with windows or autoconf. That is network problem🌚.
‼ getaddrinfo ENOENT raw.githubusercontent.com‼ gifsicle pre-build test failed
As in above two lines, the problem is can’t connect with githubusercontent.com
.
Best way Write domain with ip into the hosts. That is best way to connect with github and other domains.
52.74.223.119 github.com192.30.253.119 gist.github.com54.169.195.247 api.github.com185.199.111.153 assets-cdn.github.com151.101.76.133 raw.githubusercontent.com151.101.76.133 gist.githubusercontent.com151.101.76.133 cloud.githubusercontent.com151.101.76.133 camo.githubusercontent.com151.101.76.133 avatars0.githubusercontent.com151.101.76.133 avatars1.githubusercontent.com151.101.76.133 avatars2.githubusercontent.com151.101.76.133 avatars3.githubusercontent.com151.101.76.133 avatars4.githubusercontent.com151.101.76.133 avatars5.githubusercontent.com151.101.76.133 avatars6.githubusercontent.com151.101.76.133 avatars7.githubusercontent.com151.101.76.133 avatars8.githubusercontent.com
Then, trynpm cache clean -f
andipconfig/flushdns
.
As long as can ping with github domains, the problem will be solved.
The Command failed
just write some ips for hosts, then npm install
will be worked.
So,
enjoy it.
]]>
-
-
-
-
- 踩坑
-
-
-
-
-
-
- network
-
-
-
-
-
-
-
-
- JavaScript面向对象的程序设计
-
- /defect/javascript-object-oriented-programming.html
-
- Standing on Shoulders of Giants.
本篇参考与《JavaScript高级程序设计》第六章:面向对象的程序设计。
面向对象(Object-Oriented,OO)的语言都有一个标志,那就是他们都有类的概念。通过类来创建任意多个具有相同属性和方法的对象。
ECMAScript中没有类的概念,所以它的对象也与基于类的语言中的对象有所不同。
ECMAScript-262的对象定义为:“无序属性的集合,其属性可以是基本值、对象和函数。”也就是说对象是一个没有属性的键值映射对,其值可以是数据和函数。
属性类型 虽然在JavaScript中不能直接访问属性类型,但是为了表示特性是内部值,ECMA-262将其放在了两对方括号中。
ECMAScript中有两种属性:数据属性和访问器类型属性。
[[Configurable]]:表示能否通过delete删除属性从而定义属性,能否修改属性特性,或者能否把属性修改为访问器属性。默认为true [[Enumerable]]:表示能否通过for-in循环返回属性。默认为true [[Writable]]:表示能否修改属性的值。默认为true [[Value]]:包含这个属性的数据值。读取属性的时候从这个位置读取,写入属性值的时候把新值保存在这个位置。默认值为undefined 使用对象字面量创建一个对象时,上述四个特性都为true。value则为属性的值。
修改属性的默认特性 要修改默认的特性必须要使用ECMAScritpt5的Object.prototype.defineProperty()
方法。
Syntax:
Object .defineProperty(obj, prop, descriptor)
let obj = { name: 'xfy' , age: 18 , saySomthing: function ( ) { console .log(this .name); }}Object .defineProperty(obj, 'name' , { writable: false , value: 'xxxxfy' , configurable: false })
将configurable修改为false之后就无法再进行配置了,也就说无法再修改回来了。其他特性都是可以再修改的。
访问器属性 为对象设置一个访问器的属性,这个属性不包含数据值,它包含一对setter和getter函数(不是必需的)。在读取这个访问器属性时,会调用getter函数。在写入这个访问器属性时,会调用setter函数。setter决定了如何处理数据。
访问器属性具有如下4个值:
[[Configurable]]:表示能否通过delete删除属性从而定义属性,能否修改属性特性,或者能否把属性修改为访问器属性。默认为true [[Enumerable]]:表示能否通过for-in循环返回属性。默认为true [[Get]]:在读取属性时调用的函数。默认为undefined [[Set]]:在写入属性时调用的函数。默认为undefined 访问器属性不能直接定义,必须使用Object.defineProperty()
方法来定义。
let arr = ['zero' , 'one' , 'two' , 'three' , 'four' , 'five' , 'six' , 'seven' , 'eight' , 'nine' , 'ten' ];let xbox = { _name: 'xbox ' , _year: 2020 , edition: arr[1 ], name: function ( ) { return this ._name + this .edition; }}console .log(xbox.name());Object .defineProperty(xbox, 'year' , { get: function ( ) { return this ._year; }, set: function (value ) { if (!(value < 2020 ) && !(value > 2030 )) { this .edition = arr[value - this ._year + 1 ]; this ._year = value; } }})xbox.year = 2021 ;console .log(xbox.year);console .log(xbox.name());let ps = { _name: 'PlayStation ' , _year: 2020 , edition: 4 , name: function ( ) { return this ._name + this .edition; }}Object .defineProperty(ps, 'year' , { get: function ( ) { return this ._year; }, set: function (value ) { if (!(value < 2020 ) && !(value > 2030 )) { this .edition = value - this ._year + 4 ; this ._year = value; } }})ps.year = 2021 console .log(ps.name());
访问器属性还有一个能够同时定义多个属性的方法。与单个定义对象相同,唯一区别是这里的属性都是同一时间创建的。
let ps = {};Object .defineProperties(ps, { _name: { value: 'PlayStation' , writable: true , configurable: true }, _year: { value: 2020 , writable: true , configurable: true }, edition: { value: 4 , writable: true , configurable: true }, name: { value: function ( ) { return this ._name + ' ' + this .edition; }, writable: true , configurable: true }, year: { get: function ( ) { return this ._year; }, set: function (value ) { if (!(value < 2020 ) && !(value > 2030 )) { this .edition = value - this ._year + 4 ; this ._year = value; } } }})
读取属性的特性 属性的特性可以很方便的设置,当然也可以方便的读取。使用ECMAScript5的Object.getOwnPropertyDescriptor()
方法就可以读取属性的特性。
继上述案例:
Object .getOwnPropertyDescriptor(ps, 'year' );
可以详细的看到属性的特性。
Object .getOwnPropertyDescriptor(ps, '_year' ).value
在JavaScript中可以针对任何对象–包括DOM和BOM对象使用Object.getOwnPropertyDescriptor()
方法。
创建对象 使用Object构造函数和对象字面量可以用来创建单个对象。但是这些方法都不能使用同一个接口创建大量的对象。
工厂模式 工厂模式应该是软件设计领域中一种广为人知的设计模式。但是在ECMAScript中无法创建类,早期的人们使用一种函数,来封装以特定接口创建对象的细节。
function createOne (name, age, sex ) { let o = {}; o.name = name; o.age = age; o.sex = sex; o.say = function ( ) { console .log(this .name); } return o;}let person1 = createOne('xfy' , 18 , 'male' );let person2 = createOne('dfy' , 81 , 'female' );console .log(person1);console .log(person2);
工厂模式在函数内显式的创建一个空对象,然后让函数的参数传为对象属性。最后返回函数。这样就可以多次调用这个函数来创建多个相似对象。但这样却无法解决对象试别的问题。
构造函数 像Object()
和Array()
都是原生的构造函数。在现在,可以创建自定义的构造函数,从而自定义对象类型的属性和方法。
重写上述工厂模式:
function One (name, age, sex ) { this .name = name; this .age = age; this .sex = sex;}let person1 = new One('xfy' , 18 , 'female' );let person2 = new One('dfy' , 81 , 'male' );console .log(person1);console .log(person2);
使用构造函数创建对象必须使用new操作符。此外构造函数的函数名通常为首字母大写,非构造函数首字母小写。这个做法借鉴自其他OO语言。
使用构造函数创建的对象,constructor都指向这个构造函数。
console .log(person1.constructor);
这就是构造函数胜过工厂模式的一个地方,工厂模式无法将其实例标记为一种特定的类型。在工厂模式下创建的实例,constructor都将指向Object()
这个构造函数,因为工厂模式返回的对象是显式创建的,它继承自Object。
内部原理 function Make ( ) { this .name = 'xfy' ;}
一个构造函数在生成时使用new
操作符,此时的函数内部隐式的声明了一个对象:
function Make ( ) { this .name = 'xfy' ;}
有了this这个对象之后,函数的作用域赋给新对象(所以this指向了这个对象):
function Make ( ) { this .name = 'xfy' ;}
最后再隐式的return this:
function Make ( ) { this .name = 'xfy' ; }
总结四步:
隐式的创建一个新对象; 将构造函数的作用域赋值给新对象(this指向); 执行构造函数内的代码(添加属性); 返回新对象; 也就是说可以不使用new
来使用一个构造函数(工厂模式):
function Xfy ( ) { let that = {}; that.name = 'xfy' ; that.age = 18 ; return that;}let xfy = Xfy();
不赋值给一个对象,直接返回执行的结果:
new Person().say();
若给构造函数显示的返回了一个空对象,则显示的返回优先级高于隐式的返回结果:
function Make ( ) { this .name = 'xfy' ; return {};}
但是如果显示的返回不是对象值,而是一个基本值,则构造函数不会收到影响:
function Make ( ) { this .name = 'xfy' ; return 123 ; }
普通函数 构造函数也是函数,它也能当作普通函数来使用。在全局作用域下,直接使用构造函数将会使其this指向全局对象。
function One (name, age, sex ) { this .name = name; this .age = age; this .sex = sex;}One('xfy' );window .name
所以可以将构造函数使用call/apply来调用:
let obj = {};One.call(obj, 'xfy' , 18 , 'female' );console .log(obj);
那么自然多个构造函数也可以互相利用,使用call/apply来改变调用函数的this为当前构造函数的this就可以利用上已经写过的代码。
function Two (name, age, sex, say ) { One.call(this , name, age, sex); this .say = function ( ) { console .log(say); }}let two = new Two('xfy' , 18 , 'female' , '嘤嘤嘤' );console .log(two.say());
构造函数的问题 构造函数虽然好用,但也并非没有缺点。构造函数的主要问题就是每个方法都要在实例上重新创建一遍。
function Two (name, age, sex, say ) { One.call(this , name, age, sex); this .say = function ( ) { console .log(say); } }let two = new Two('xfy' , 18 , 'female' , '嘤嘤嘤' );let two1 = new Two('xfy' , 18 , 'female' , '咕咕咕' );console .log(two.say === two1.say);
继上述例子中,每个构造函数内都包含一个不同的Function实例。以这种方式创建函数,会导致不同的作用域链和标识解析符。但创建Function新实例的机制任然是相同的。所以导致由构造函数创建的实例的方法只是同名而不相等。
console .log(two.say === two1.say);
创建这样两个完成相同任务的Function实例根本没有必要;况且有this对象存在,根本不用在执行代码前就把函数绑定到特定的对象上面。
所以可以考虑将特定的方法转移出构造函数内部来解决这个问题
function Two (name, age, sex, say ) { One.call(this , name, age, sex); this .say = say; this .saySome = saySome;}let two = new Two('xfy' , 18 , 'female' , '嘤嘤嘤' );let two1 = new Two('xfy' , 18 , 'female' , '咕咕咕' );console .log(two.saySome);function saySome ( ) { console .log(this .say);}
在构造函数的内部将方法指定为外部的全局函数,这样saySome包含的是一个函数的指针,因此创建出的对象的方法就共享了在全局作用域中定义的同一个函数。这样就能解决两个函数做同一件事的问题。当然这样还无法解决所有问题,如果对象需要很多的方法,那么就需要在全局作用域定义很多的函数,于是这个自定义的引用类型就毫无封装性可言了。
好在,原型模式可以帮我们解决这些问题。
原型 每创建一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。这个对象的好处就在于可以包含特定类型的所有实例共享的方法和属性。也就是说,构造函数的prototype(原型)这个对象的属性,可以包含到所有由这个构造函数创建的实例上。
这样,上述出现的全局函数不需要去污染全局环境,可以定义在原型上。另外,一些固有的属性也可以直接放在原型上。
Two.prototype.name = 'xfy' ;Two.prototype.age = 18 ;Two.prototype.saySome = function ( ) { console .log(this .say);}function Two (sex, say ) { this .sex = sex; this .say = say;}let two = new Two('female' , '嘤嘤嘤' );let two1 = new Two('male' , '咕咕咕' );console .log(two.saySome);
理解原型对象 无论在何时,只要创建了一个新的函数,就会根据一组特定规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,原型对象会自动获得一个constructor属性。这个属性指向prototype属性所在函数的指针。也就是说指向构造函数本身Two.prototype.constructor = Two
。
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性。其他的方法都是由Object继承而来。当使用构造函数创建新实例后,这个实例会包含一个指针(内部属性),指向构造函数的原型。ECMA-262第5版管这个指针叫做[[Prototype]]。虽然在js中没有标准访问[[Prototype]]的方法。但在多数浏览器中都支持一个属性:__proto__
。
不过真正要明确的是,这个连接只存在与实例与构造函数的原型对象之间,而不存在与实例与构造函数之间。
(Two Prototype)Two.prototype --> Two.prototypetwo1.__proto__ --> Two.prototype
所有在原型上的属性与方法都能被创建的实例所调用。这是通过查找对象属性的过程来实现的。就和作用域链类似,在实例上没有找到的属性会继续向上至原型链查找。
有两种方法来检测/访问到实例的原型对象:isPrototypeOf()
和Object.getPrototypeOf()
。
isPrototypeOf()
用来检测函数是否是实例的原型;Object.getPrototypeOf()
返回的就是实例的原型;console .log(Two.prototype.isPrototypeOf(two));console .log(Object .getPrototypeOf(two) == Two.prototype);
constructor属性也是共享的,可以通过实例对象访问。
无法重写 实例无法重写原型上的属性。虽然对象实例可以访问原型,但是对于原型上的属性的增、删操作都是不可以的。如果对象实例上设置了一个和原型属性同名的属性,那么就会优先访问实例本身的属性。
Two.prototype.name = 'xfy' ;Two.prototype.age = 18 ;Two.prototype.saySome = function ( ) { console .log(this .say);}function Two (sex, say ) { this .sex = sex; this .say = say;}let two = new Two('female' , '嘤嘤嘤' );let two1 = new Two('male' , '咕咕咕' );two.name = 'dfy' ;console .log(two.name);console .log(two1.name);
就相当于屏蔽了实例去访问原型上的同名属性,即使将实例本身的属性设置为null也不会恢复其指向原型的连接。不过使用delete操作符删除这个属性后,就能重新访问原型上的属性。
two.name = 'dfy' ;console .log(two.name);delete two.name;console .log(two.name);
当然它也有检测的方法:hasOwnProperty()
。
two.name = 'dfy' ;console .log(two.hasOwnProperty('name' ));delete two.name;console .log(two.hasOwnProperty('name' ));
in操作符 in操作符可以单独使用和在for-in循环中使用。在单独使用时,in用于检测给定的属性通过对象能否访问,无论是继承还是自有属性。
console .log('saySome' in two);
配合hasOwnProperty()
来使用就能检测属性是否是继承来的。
function inheritProperty (target, prep ) { return !target.hasOwnProperty(prep) && (prep in target);}
for-in循环会便利所有能访问、可枚举(enumerated)的属性。无论是否是继承来的属性。另外,屏蔽了原型中不能枚举的实例属性也能在for-in循环中返回。因为根据规定,所有开发人员定义的属性都是可枚举的。(只有IE8及更早的版本除外)
这里的two实例重写了toString()
方法,可以被for-in循环遍历出来。
若要更方便的取出实例的自有属性,可以使用Object.keys()
方法。它会返回所有自有的可枚举的属性。返回结果为一个数组,出现顺序与for-in循环相同。
Object .keys(two)
可以使用Object.getOwnPropertyNames()
来访问所有属性,包括不可枚举的属性
Object .getOwnPropertyNames(Two.prototype)
更简单的原型语法 前面给原型添加属性都是一行一行的写的,每添加一个属性都要Two.prototype
一遍。为了减少输入,常见的做法是使用对象字面量来重写整个原型对象。
Two.prototype = { name: 'xfy' , age: 18 , saySome: function ( ) { console .log(this .say); }}
将构造函数的prototype以对象字面量形式重新创建一个对象。最终结果没有任何变化,除了constructor不在指向这个构造函数以外。因为重写了prototype,所有constructor也就不存在了。
可以手动将constructor设置为正确是值,但此时的constructor就会变为可枚举的属性。在支持ECMAScript5的环境下,可以再手动设置为不可枚举的属性。
Two.prototype = { name: 'xfy' , age: 18 , saySome: function ( ) { console .log(this .say); }}Object .defineProperty(Two.prototype, 'constructor' , { enumerable: false , value: Two})
原型的动态性 由于在原型中查找值的过程是一次搜索,所以可以随时的修改原型的属性,并立即的在实例上反应出来。即使是先创建了实例再修改的原型也是如此。
实例与原型之间的连接不过是一个指针,而非一个副本。因此在后面的语句中可以找到新的属性。
Two.prototype = { name: 'xfy' , age: 18 , saySome: function ( ) { console .log(this .say); }}Object .defineProperty(Two.prototype, 'constructor' , { enumerable: false , value: Two})function Two (sex, say ) { this .sex = sex; this .say = say;}let two = new Two('female' , '嘤嘤嘤' );let two1 = new Two('male' , '咕咕咕' );console .log(two.name);Two.prototype.name = 'xxxfy' ;console .log(two.name);
尽管原型可以随时的修改属性和方法,并且能够在所有对象实例中立即反应出来。但是如果重写整个原型对象,那么情况就不一样了。
在调用构造函数时会为实例添加一个指向最初原型 的[[prototype]]指针,而重写整个原型对象之后,就相当于切断了构造函数与最初原型之间的联系。实例中的指向仅指向原型,而不指向构造函数。
所以在实例之后重写整个原型并不会生效,且实例的指针还是指向最初的原型 。
Two.prototype.name = 'xxxfy' ;function Two (sex, say ) { this .sex = sex; this .say = say;}let two = new Two('female' , '嘤嘤嘤' );let two1 = new Two('male' , '咕咕咕' );console .log(two.name);console .log(two.constructor);Two.prototype = { name: 'xfy' , age: 18 , saySome: function ( ) { console .log(this .say); }}Object .defineProperty(Two.prototype, 'constructor' , { enumerable: false , value: Two})
原生对象的原型 毫不意外的,原生的引用类似也是采用这种模式。可以像修改自定义对象的原型一样修改原生对象的原型。
由于声明了一个字符串变量,那么后台就会自动调用String来基本包装这个字符串,因此str这个变量可以直接调用String原型上的方法。
String .prototype.yyy = function ( ) { return '嘤嘤嘤' ;}let str = 'xfy' ;console .log(str.yyy());
原生对象的原型是可以被重写的。
原型对象的问题 原型虽然很大程度上解决了很多问题,但它也是有缺点的。首先它省略了为构造函数传递初始化参数这一环节,导致了所有实例再默认情况下都将取得相同的属性值。但这并不是主要问题,主要问题还是其共享性的特征导致的。
对于函数来说,这种共享性特别合适。对于包含基本值的属性也还行。但是对于引用值来说,就有很大的问题了。
通过该构造函数创建的两个实例,当一个实例修改了原型上的引用值时,另一个实例也被一起更改。
function Person ( ) {}Person.prototype = { name: 'xfy' , age: 18 , job: null , consoles: ['xbox' , 'playstation' ], sayName: function ( ) { console .log(this .name); }}Object .defineProperty(Person.prototype, 'constructor' , { enumerable: false , value: Person})let xfy = new Person();let dfy = new Person();xfy.consoles.push('nintendo' );console .log(xfy.consoles);console .log(dfy.consoles);
组合使用构造函数与原型 在了解构造函数的时候,了解到一个问题所在就是构造函数的每个属性/方法对于每个实例都是重新创建的。而原型则有着共享性。所以将二者组合使用就能互补其缺点。
这种方法的使用,每个实例就有自己的一份实例属性副本,但是同时又共享着对方法的引用,最大限度的节省了内存。
function Person (name, age, job ) { this .name = name; this .age = age; this .job = job; this .consoles = ['xbox' , 'playstation' ];}Person.prototype = { sayName: function ( ) { console .log(this .name); }}Object .defineProperty(Person.prototype, 'constructor' , { enumerable: false , value: Person})let xfy = new Person();let dfy = new Person();xfy.consoles.push('nintendo' );console .log(xfy.consoles);console .log(dfy.consoles);
动添原型模式 原型的动态性把所有信息都封装在了构造函数中。这样可以在构造函数中初始化原型(必要情况下),又保持了原型和构造函数的优点。也就是说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
对于这种模式创建的对象,还可以是使用instanceof
来确定它的类型。
function Person (name, age, job ) { this .name = name; this .age = age; this .job = job; this .consoles = ['xbox' , 'playstation' ]; if (typeof (sayName) != 'function' ) { Person.prototype.sayName = function ( ) { console .log(this .name); } }}let xfy = new Person('xfy' , 18 , null );let dfy = new Person();xfy.consoles.push('nintendo' );xfy.sayName();
如果在已经创建了实例的情况下重写原型,则会切断实例与新原型的联系。
寄生构造函数模式 在前面几种模式都不适用的情况下,可以适用寄生(parasitic)构造函数模式。在这个模式下会新建一个对象,并以相应的属性和方法初始化该对象,最后返回该对象。除了使用new操作符之外,这个模式与工厂模式一模一样。
构造函数在不返回值的情况下,默认会返回新对象实例。而在构造函数末尾添加一个return语句,可以重写调用构造函数时返回的语句。
这个模式可以用在特殊的情况下为对象创建构造函数。如果需要一个具有额外方法的特殊数组,而且不能直接Array构造函数,可以使用这个模式。
在这个模式下返回的对象与构造函数的原型直接没有任何关系。构造函数返回的对象与外部创建的对象没有任何的不同。无法用instanceof来确定对象类型。
function myArray ( ) { let val = new Array (); val.push.apply(val, arguments ); val.pid = function ( ) { return val.join('|' ); } return val}let arr = new myArray(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 );console .log(arr.pid());
稳妥构造函数模式 所谓稳妥对象,指的是没有公共属性,而且其方法也不能引用this的对象。稳妥对象最时候在一些安全的环境中(禁用this和new的环境)使用。
稳妥与寄生构造函数类似,但有两点不同:
创建新对象的实例方法不引用this; 不适用new操作符调用构造函数; 除了调用定义的方法外,没有其他方法去访问传递的参数。
function Person (name, age ) { let o = new Object (); o.sayName = function ( ) { console .log(name); } o.sayAge = function ( ) { console .log(age); } return o;}let xfy = Person('yyy' , 18 );console .log(xfy.name);xfy.sayName();
它和私有化变量也很相似,同样的也是不引用this和不使用new操作符。
它也和寄生构造函数一样,与构造函数的原型没有多大关系,也无法通过instanceof来确定类型。
私有化变量 闭包的另一种应用,在构造函数上的应用。
在使用构造函数构造对象时,属性中的函数会向闭包一样返回为对象的功能,因为闭包的特征,所以构造函数中AO中的变量能被属性内的函数保存和读取。
而构造函数中创建的变量不能使用通常的方法被对象直接访问,只有对象调用指定的方法才能访问。
function Xfy (name, consoled ) { let anotherConsole = 'ps4' ; this .name = name; this .consoled = consoled; this .changeConsole = function ( ) { this .console = anotherConsole; } this .buyConsole = function (target ) { anotherConsole = target; } this .sayTrueth = function ( ) { console .log(anotherConsole); }}let xfy = new Xfy('xfy' , 'xbox' );
console.log中的console也是关键字,和变量等不能重名……
继承 继承是OO语言中的一个最为人津津乐道的概念。许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于JS的函数没有签名,所有无法实现接口继承,只有实现继承。而实现继承主要依靠的是原型链。
原型链 ECMAScript中描述的原型链的概念,并将原型链作为实现继承的主要方法。其基本思想就是让一个引用类型继承另一个引用类型的属性和方法。
构造函数和原型与实例的关系:每个构造函数都一个原型对象,原型对象都包含一个指向构造函数的指针。而实例都包含一个指向原型对象的内部指针。
如果让构造函数的原型对象等于另一个类型的实例,另一个原型也包含另一个构造函数的指针,如果另一个原型又是另一个类型的实例,那么原型的关系就会层层递进,呈链式结构。这就是原型链的基本概念。
function SuperType ( ) { this .property = true ;}SuperType.prototype.getProperty = function ( ) { console .log(this .property);}function SubType ( ) {}SubType.prototype = new SuperType();let xfy = new SubType();xfy.getProperty();
由于SubType的原型对象是SuperType的实例,相当于重写了SubType的原型对象,所以通过SubType创建的实例对象的constructor指向的是SuperType。
xfy instanceof SubType;xfy instanceof SuperType;xfy.constructor;
默认原型 所有引用类型都继承了Object,这个继承也是通过原型链来实现的。所有函数的默认原型都是Object的实例 ,因此默认原型都会包含一个内部指针,指向Object.prototype。这也就是所有自定义类型都会继承toString()
、valueOf()
等方法的根本原因。
String instanceof Object ;
确定原型与实例的关系 可以使用instanceof,instanceof用于检测该对象的原型链中有没有该函数的原型。
String instanceof Object ;SuperType instanceof Object ;xfy instanceof SubType;xfy instanceof SuperType;xfy instanceof Object ;
isPrototypeOf()
方法与instanceof类似,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。
Object .prototype.isPrototypeOf(xfy);SuperType.prototype.isPrototypeOf(xfy);SubType.prototype.isPrototypeOf(xfy);
谨慎的定义方法 在原型链中,如果给子类型添加一个与超类型的同名的方法,那么子类型的实例就会继承覆盖超类型的方法。
function SuperType ( ) { this .property = true ;}SuperType.prototype.getProperty = function ( ) { console .log(this .property);}function SubType ( ) {}SubType.prototype = new SuperType();SubType.prototype.getProperty = function ( ) { return false ;}let xfy = new SubType();xfy.getProperty();
此外,重写原型链的方法时,不能使用对象字面量的方法来创建方法。这样会切断刚刚创建的原型链的联系。
function SuperType ( ) { this .property = true ;}SuperType.prototype.getProperty = function ( ) { console .log(this .property);}function SubType ( ) {}SubType.prototype = new SuperType();SubType.prototype = { getProperty: function ( ) { return false ; }}let xfy = new SubType();xfy.property
原型链的问题 原型链和原型一样非常的强大,但是它也有和原型一样的问题。那就是对引用值的问题。
修改子类型任何一个实例原型上的值,都是对SubType.prototype
这个原型对象的修改。并且会实时的反应到SubType创建的实例上去。
function SuperType ( ) { this .arr = [1 , 2 , 3 , 4 , 5 ]; this .property = true ;}SuperType.prototype.getProperty = function ( ) { console .log(this .property);}function SubType ( ) {}SubType.prototype = new SuperType();let xfy = new SubType();let dfy = new SubType();console .log(xfy.arr);console .log(dfy.arr);dfy.arr.push(6 , 7 , 8 , 9 )console .log(xfy.arr);console .log(dfy.arr);console .log(SubType.prototype.arr);
除此之外,原型链创建实例还有另外一个问题。在子类创建实例时无法向超类传递参数。实际上,无法在布影响所有对象实例的情况下,给超类的构造函数传递参数。
借用构造函数 原型链中所出现的问题可以配合构造函数使用来解决。这种方法称之为(constructor stealing)有时候也称为伪造对象或经典继承。
这种思想非常简单,即在子类型构造函数的内部调用超类型的构造函数。函数只不过是在特定环境中执行代码的对象,通过使用apply/call来调用另个构造函数也可以创建实例。
当然为了确保没有重写子类型的属性,可以先调用超类型的构造函数,在其后添加自定义属性。
这样创建出的实例就会有自己的属性副本了,而不是从原型链上继承得来的。所以修改其他实例的属性就不会影响到原型链,也就不会影响到其他实例了。
function SuperType (name, age ) { this .name = name; this .age = age; this .arr = [1 , 2 , 3 , 4 , 5 ];}function SubType (name, age ) { SuperType.call(this , name, age);}let xfy = new SubType('xfy' , 18 );let dfy = new SubType('dfy' , 188 );console .log(xfy);xfy.arr.push(6 , 7 , 8 , 9 );console .log(xfy.arr);console .log(dfy.arr);
不过借用构造函数也有问题,就和构造函数本身的问题一样,无法解决函数复用的问题。
组合继承 组合继承,有时候也称之为伪继承。指的是将原型链和构造函数的技术结合使用,从而发挥二者之长的一种模式。
这种方法将实例的属性通过借用构造函数来实现继承,让实例都有自己的属性副本,而不是在原型链上。为了解决方法能够复用的问题,将方法定义在原型链上,并通过原型链来继承。
组合继承避免了原型链和构造函数的缺点,融合了它们的优点。而且instanceof
和isPrototypeOf()
也能够识别基于组合继承创建的对象。
function SuperType (name, age ) { this .name = name; this .age = age; this .arr = [1 , 2 , 3 , 4 , 5 ];}SuperType.prototype.sayName = function ( ) { console .log(this .name);}function SubType (name, age ) { SuperType.call(this , name, age);}SubType.prototype = new SuperType;let xfy = new SubType('xfy' , 18 );let dfy = new SubType('dfy' , 188 );console .log(xfy.sayName());xfy.arr.push(6 , 7 , 8 , 9 );console .log(xfy.arr);console .log(dfy.arr);
原型式继承 道格拉斯·克罗克在06年写了一篇题为Prototypal Inheritance in JavaScript(JavaScript中的原型式继承)的文章。他介绍了一种实现继承的方法。这种方法没有使用严格意义上的构造函数,他的想法是借助原型可以基于已有的对象创建对象 ,同时还不必因此创建自定义类型。
在object函数内部先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时构造函数的一个新实例。从本质上来说,对传入的构造函数进行了一次浅复制。
function object (o ) { function F ( ) {}; F.prototype = o; return new F();}
ECMAScript5通过新增Object.create()
规范化了原型式继承。并且它接受两个参数:
作为新对象原型的对象; 为新对象定义额外属性的对象; 第二个参数与Object.defineProperties()
方法的第二个参数格式相同:每个属性都是通过自己的描述符定义了。以这种方式指定的属性都会覆盖原型对象上的同名属性。
let one = { name: 'xfy' , age: 18 }let two = Object .create(one, { name: { configurable: false , value: 'dfy' }})two.name = 123 ;console .log(two.name);console .log(two.age);
寄生式继承 寄生式继承式与原型式继承紧密相关的一种思路。它也是用克罗克福德推广而之的。寄生式继承的思路和寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后在真的像它做了所有工作一样返回对象。
基于寄生式继承的对象,不仅仅有了继承的对象属性方法,而且还有自己自定义的方法。
let person = { name: 'xfy' , age: 18 }function test (target ) { let clone = Object .create(target); clone.hello = function ( ) { console .log('hi(。・∀・)ノ゙' ); } return clone;}let xfy = new test(person);console .log(xfy.name);console .log(xfy.hello());
这种方法同样不能做到复用函数。
寄生组合继承 组合继承式一个常用的继承模式,不过它也有自己的不足。那就是无论在什么情况下,它都需要调用两次超类型的构造函数。
function SubType (name, age ) { SuperType.call(this , name, age);}SubType.prototype = new SuperType;
调用两次超类型构造函数就会导致同样的属性和方法会分别出现在子类型的prototype上和子类型创建的实例上。
寄生组合继承便可以解决这个问题:
function inherit (subtype, supertype ) { let prototype = Object .create(supertype.prototype); prototype.constructor = subtype; subtype.prototype = prototype;}function One (name ) { this .name = name;}One.prototype.sayName = function ( ) { console .log(this .name);}function Two (name, age ) { One.call(this , name); this .age = age;}Two.prototype.sayAge = function ( ) { console .log(this .age);}inherit(Two, One);let xfy = new Two('xfy' , 18 );console .log(xfy.name);
这种方法解决了多次调用超类型函数,已经在子类型原型上创建多余不必要的属性。同时原型链还能保持不变。
同样,所谓的圣杯模式也是同样的道理。这里直接就使用Object.create()
方法来代替寄生式继承的详细写法。
function inherit (target, origin ) { function F ( ) {}; F.prototype = origin.prototype; target.prototype = new F(); target.prototype.constructor = target; target.prototype.uber = origin;}
小结 ECMAScript支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象:
工厂模式,使用简单的函数传教对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。 构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。 原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。 JavaScript主要通过原型链实现继承。原型链的构建时通过将一个类的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过构造函数继承实例属性。
此外,还存在下列可供选择的继承模式:
原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质式执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。 寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。 寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方式。 我更加喜欢寄生组合式继承,也就是常说的“圣杯模式”。
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
-
-
-
-
-
-
-
- JavaScript的作用域与链
-
- /defect/javascript-scope-and-chain.html
-
- JavaScript是一门动态语言,也常称呼为弱类型/解释型的语言。除了不需要明确写出数据类型外,JavaScript的作用域也和其他类型的语言不同。并且作用域还以链的方式互相连接。预编译 要想彻底的了解作用域与变量提升,就得先了解js语言的执行过程。
JavaScript语言会分为三步来解释执行代码,分别是:
语法分析 预编译 解释执行 语法分析主要是检查整体代码中有没有明显的会导致程序无法执行的语法错误,在解释执行之前找出并提示错误。语法分析只是餐前甜点,真正发生变化的还是在预编译阶段。
由于预编译的阶段,存在了函数以及变量的提升。所谓的预编译就是在代码真正执行之前做好一些准备,例如声明变量,声明函数等。
函数的预编译需要经过几个阶段:
创建AO(执行期上下文,Activation Object)
AO { }
寻找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined。(变量声明提升 )
AO { a: undefined , b: undefined }
寻找函数声明,赋值为函数体(函数整体提升 )
AO { a : function a ( ) {}, b : undefined }
预编译阶段不会为变量赋值。
预编译阶段结束后就会进入解释执行阶段,此时函数就会真正准备执行了。
function test (a ) { console .log(a); var a = 123 ; console .log(a); function a ( ) {} console .log(a); let b = function ( ) {} console .log(b); }test(1 );
作用域 JavaScript也有作用域。不过和其他强类型语言相比,JavaScript并不是以花括号{}
来产生作用域的。
作用域可以直接的理解为一块独立的区域,在该区域内声明的函数、变量等都属于这个区域。使得在不同区域内的同重名变量和函数等不会冲突。在通常情况下作用域内的变量等不会被外部所访问到。
在预编译环境中,函数会创建一个执行期上下文的内部对象,这个AO就定义了一个函数执行时的环境。AO就可以理解为函数的作用域。
全局环境下也有一个执行环境:GO(Global Object),它存储了全局环境下的变量和函数等。也就是全局作用域。
执行期上下文:当函数运行时,会创建一个执行期上下文的内部对象(AO,Activation Object)。一个执行期上下文定义了一个函数执行时的环境。函数每次执行时对应的执行上下文是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文。当函数执行完毕,它所产生的执行期上下文会被销毁。
在函数的AO中保存的经过声明的变量 等,除了特意保存到外部,否则无论函数执行与否,在AO外部是无法被访问的:
function test ( ) { var a = 123 ; let b = 333 ; function c ( ) { console .log('xfy' ); }}console .log(a);c();
未经声明而直接赋值的变量将会转化为全局变量。
变量声明提升 除了新的关键字let
,cont
之外,JavaScript只在函数中产生作用域。并且和全局作用域一样,在函数作用域内使用var
声明和函数声明也会提升。
(function ( ) { a = 123 ; console .log(a); var a; }())
变量仅仅只是声明会提升到作用域顶部,变量的赋值还是在原有的位置被赋值。因为预编译环境不会为变量赋值。
函数声明提升 函数的声明提升与变量提升类似,都是经过预编译阶段将函数的声明提升到作用域的顶端。所以和使用var
声明的变量一样,可以将调用写在函数声明之前。
someFun()function someFun ( ) { console .log('xfy' );}
但必须是声明函数体才会在预编译中提升,将函数赋值给变量并不属于函数声明。此时在声明前调用函数就会得到ReferenceError: Cannot access 'foo' before initialization
的提示。
foo();let foo = function ( ) { console .log('xfy' );}
全局作用域 最外层的作用域,在代码的任何地方都能访问到的作用域。
let a = 123 ;function foo ( ) { let b = 333 ; return b;}
在window/global的属性也是全局作用域。
如果变量没有使用关键字声明,那么将会创建出一个全局变量。并且值得注意的是在连等赋值时,容易产生没有被声明的变量,因为关键字只能声明第一个变量,等号后的变量将不会被关键字声明。
function foo ( ) { a = 123 ; let b = c = 100 ; }foo();console .log(a); console .log(c);
块级作用域 新增的let
和const
关键字可以产生块级作用域。在其他的强类型语言中可能会经常容易见到块级作用域,常见的就是在大括号{}
中的代码为一个作用域。
js的大括号{}
是不会产生作用域的,也就是在没有这两个新增的关键字之前,js的块级作用域并不是那么容易产生。
块级作用域在日常的代码中也有很多方便的用途,例如最常见的for
循环就很容易因为没有块级作用域而导致在循环中调用的是同一个计数器的值。
在下面这段代码中,计数器i
使用var
来声明的,并不会产生块级作用域。也就是说:无论循环多少次,最后作用域里保存的i
只有一个值,那就是最后一次循环的值10
。 所以在这个数组内保存的十个函数在执行时,读取的到作用域里的i
都是10
let arr = [];for (var i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { console .log(i); }}arr[2 ]();
而将var
换成let
之后,由于块级作用域的存在,每次循环,都能将i
的值保存在作用域里。等到数组内的函数执行时,它所访问的i
就是那一次循环所保存在作用域里的值。所以使用了let
就不会出现所有的i
都是10的情况了。
let arr = [];for (let i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { console .log(i); }}arr[2 ]();
因为js中的函数是可以产生作用域的,所以除了使用let
来生成块级作用域,还能使用函数来模拟块级作用域。上述的例子中,可以使用立即执行函数来模拟出一个块级作用域。
由于立即执行函数是立即执行的,所以每次循环它都会执行一次,并产生一个AO(作用域)来保存参数。将i
作为参数传递给立即执行函数,就能把每次循环i
的值给保存下来。
let arr = [];for (var i = 0 ; i < 10 ; i++) { (function (j ) { arr[j] = function ( ) { console .log(j); } }(i))}arr[2 ]()
目前官方也是推荐使用let
替换var
。
链 [[scope]]:每个js函数都是一个对象,对象中有些属性可以访问,但有些不可以。这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。
[[scope]]中所存储的执行期上下文对象的集合 ,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
通俗的来说,作用域链就是将多个函数的AO包括GO呈链式的保存起来,并按照一定的顺序来访问。
作用域链将函数与其内部的函数和全局作用域串接在一起,呈一条链式的连接。在需要访问其变量或其他属性时,函数会顺着作用域依次向上查找,直到找到顶部GO。
let a = 123 ;function x ( ) { let b = 333 ; console .log(a, b); function y ( ) { let c = 321 ; console .log(a, b, c); function z ( ) { let d = 'xfy' ; console .log(a, b, c, d); } z(); } y();}x();
在作用域链内部的函数可以向上方位外部函数作用域内的变量,直至全局作用域。而作用域外的函数不能访问内部函数的变量。
父级作用域中的变量 由于作用域链的特性,子作用域内可以任意访问外部作用域,也就是父级作用域内的变量。但它也有一些值得注意的地方。
与this
的指向不同的是,它并不是谁调用就会去访问谁的作用域。下述代码,虽然fun调用了foo ,但是foo并不在fun作用域链内,也就是说foo并不能访问fun内的变量。
let a = 123 ;function foo ( ) { console .log(a);}function fun ( ) { let a = 'xfy' ; foo();}fun();
let a = 123 ;function foo ( ) { console .log(b);}function fun ( ) { let b = 'xfy' ; foo();}fun();
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
-
-
-
-
-
-
-
- 写作与协作
-
- /defect/write-and-cooperation.html
-
- 出于对速度无理的追求,最终还是放弃了使用动态内容。转战静态blog。以前也稍微尝试过hexo,所以决定还是主要为hexo为主了。在之前试过的typecho、wordpress之中,越是臃肿复杂的程序,1M的带宽越是不够。再详细的折腾了hexo之后,发现了最佳的解决方案。
在早期的一些常识之后,我也学会了很多。在刚入坑hexo的时候是盯上了阿里云的ECS+OSS和CDN的。虽然部署还是比较麻烦,但起码已经有了一套比较完善的流程了。
从前的写作流程✍ Typora + OneDrive + VScode
以前买过软软的365,于是就用Onedrive来同步写的东西了。Onedrive对windows用起来还好,虽然有时会出些莫名其的问题,但基本上的备份与同步都是正常的。
但对于这套流程的问题不只是Onedrive它卡,这三个软件的契合度并不是很高。写一段代码要切到VScode,写完了再切回来复制到Typora。Onedrive在后台的实时同步还占用一定的性能。
相比较而下,我的部署流程就更为复杂了。因为我的Markdown文件都是放在Onedrive里的,而Hxeo因为node_modules
的原因并不在Onedrive里,我需要写完了之后再将图片和MD放到Hexo的目录。之后再手动执行生成文章的命令。随后再将生成好的文章手动上传到ECS里挂载的OSS目录。
为什么要通过ECS再传到OSS呢?因为ECS和OSS再同一个区域是不会产生流量费用的。😅
Hexo –> ECS –> OSS
现在的流程 VScode + git
将步骤的简化带来的不只是效率,从之前的两个编辑器来回切换到现在的只需要一个编辑器,在文字多的情况下也能保证一定的性能。此外,整体流程的步骤也更加契合,写完了之后可以直接在VScode里打开Terminal进行push。
配上CI持续集成,只需要写完push,之后就可以等着全新的文章上线。
Hexo –> CI –> COS
Hexo的仓库直接push,之后CI持续集成就会按照预设好的步骤来进行部署,除了可以部署到几个仓库的Pages外,还可以直接部署到云存储。
VS Code - 不只是代码 软软的Visual Studio Code是一款很棒的编辑器。很早之前我就用它来尝试写一些东西,但只是用作于编辑器,主要功能就是代码补全和着色。
在早期的时候我比较喜欢用Typora来写作,它的风格我很喜欢,还能换主题,整体看上去很漂亮。但最近发现了一些比较难以容忍的毛病;就是当它的一篇文章字符超过10K的时候,性能略差一点的电脑就会很卡,打字都不出来的那种。我猜想可能和它是electron写的有关系。
就在我还没放弃它的时候,我无意中找到了VScode的Markdown插件。反正装插件也不是很麻烦是事情,于是就是尝试了一下。
没想到一时间我便爱上了它,虽然整体界面没有以前那么整洁、那么清新脱俗。但整体给人给感觉没有非常杂乱,反而看上去倒有点像剪辑软件?
除了外观从清新脱俗到繁重了一点,余下就只剩方便了。对于我这种才转到hexo的写作半吊子,一直很想找个与hexo契合度高的写作姿势。之前需要在Typora中写完,然后再将文章和单独的图片文件夹复制到hexo的souce/_post
目录。像我这种半年产出一篇文章的还好,要是天天写,那样会被麻烦死。况且,如果有某一篇文章出了点小差错需要改。那就要同时动两个md文件和两组图片文件夹,对着资源管理翻来覆去的找,极为麻烦!🌚
粘贴图片🖼 之前在使用Typora写作的时候,最为方便的莫非是粘贴图片了。目前Windows上的Typora也支持将粘贴的图片复制到指定的路径或者是云存储。我的图片存放路径都是相对于文章的目录下的images
目录:
../images/postName/
Typora可以设置将图片复制到指定文件夹,还能创建文件夹。不过好在在VScode里也有插件能够实现同样的操作。使得插图剩下了一大笔麻烦的操作。
我用的是开源的Paste Images 。
只需要将插件稍微改下设置,将path修改下就能达到想要的效果。
../images/${currentFileNameWithoutExt} /
一些小设置 Quick Suggestions
Editor > Suggest: Snippets Prevent Quick Suggestions
在代码段中依然显示建议。
自定义Suggestions
{"Print to console" : {"prefix" : "cl" ,"body" : ["console.log($1);" ,],"description" : "Log output to console" }}
Coding - 持续构建⏰ 之前最大的问题还是手动部署hexo的繁琐操作,每新增一篇文章都是几个重复的机械性操作。对于我这种半年才写一篇文章的咸鱼来说都感觉到烦了。
使用CI持续部署的好处就是,可以完全专心与创作,而不用再去管部署之类的问题。只需要第一次写好流程,剩下的就全部交给自动化吧。
之前的我从来没有用过Coding,对CI/CD也没有什么了解,从来没考虑过自动化部署这类操作。后来在研究静态化网站时发现了新大陆,完全可以将复杂重复的工作交给机器。并且随着后面文章的增加,渲染markdown文件肯定会越来越慢,于其手动繁琐的操作,不如完全交给CI。
Github Action 在我研究CI姿势的这段时间里,Github也推出了自己的CI(钞能力)。无论是谁家的CI,除了部署步骤不一样,其结果肯定是相同的。Github action也是能达到同样的效果,对于各个厂家的云存储,action也有同样的解决方法,甚至是比coding的jenkins还要灵活一点。
Hexo插件📥 压缩 我用的是Hexo-all-minifier ,可以静态文件以及图片。还可以分别设置压缩等级来权衡质量与大小。
npm install hexo-all-minifier --save``` 不过它使用到了已经编译好的二进制包gifsicle等,在安装时需要走个脚本编译一下,在网络不好的情况下大概率会安装失败。为此我还特地水过一次:[Can't install gifsicle](https://www.defectink.com/defect/cant-install-gifsicle.html) Hexo-all-minifier用到的也是gulp和一些图片压缩的工具,相对于gulp来说,它的配置更简单,更适合像我这种比较懒的咸鱼。 只需要在站点配置文件添加一段聚合好的配置文件就好了,像这样: ```yml # minifier all_minifier: ture html_minifier: enable: true ignore_error: false silent: false exclude: css_minifier: enable: true silent: false exclude: - ' *.min.css' js_minifier: enable: true mangle: true silent: false output: compress: exclude: - ' *.min.js' image_minifier: enable: true interlaced: false multipass: false optimizationLevel: 2 webpquant: false progressive: false silent: false
两个小功能吧,虽然也不会有人来订阅我的小破站🤣。
npm install hexo-generator-sitemap --savenpm install hexo-generator-feed --save
和压缩插件一样,都是在站点的_config.yml
里添上相应的配置文件就好了。
feed: type: - atom - rss2 path: - /xml/atom.xml - /xml/rss.xml limit: 20 hub: content: content_limit: 140 content_limit_delim: ' ' order_by: -date icon: icon.webp autodiscovery: true template: sitemap: path: /xml/sitemap.xml template: ./source/_data/sitemap_template.xml rel: false tags: true categories: true
另外,可以来试一下:
Git加速 使用Linux主机或者在Windows中使用git bash时,修改(新建)在用户目录下的~/.ssh/config
文件,新加如下内容。
host github.comHostName github.comPreferredAuthentications publickeyIdentityFile ~/.ssh/id_rsaProxyCommand connect -S 127.0.0.1:1080 %h %p
macos的connect可以用brew安装,而windows的git bash中已有:
brew install connect
对于Ubuntu:
apt-get install connect-proxy
某些发行版可能没有connect软件包,这个地址下载源码编译一下就好了。
https:// bitbucket.org/gotoh/ connect/src/ default/
对于http:
git config --global http.proxy "socks5://127.0.0.1:1080" git config --global https.proxy "socks5://127.0.0.1:1080"
node的淘宝源
npm config set registry https://registry.npm.taobao.org
npm config set registry https://registry.npmjs.org
早期计划 内容分发网络 之前就有在使用cdn来加速图片的访问,效果也还是不错的。但是整体blog的内容还都是有1M服务器上的php生成的。所以就算异地图片加载速度再快,终端也需要连接到身在华南的服务器。无论是人多还是人少,速度总是不理想。
最后打算使用纯静态的blog,直接部署到cdn上,速度肯定是无可比拟的。但是还是有一点弊端的,例如cdn节点可能更新不及时等问题。
解决方案 正好手头有个1M出口的ECS,嫌它太慢。而它的真正作用在于和阿里云的oss进行通信,因为走的是阿里云的内网,所以通过ECS上传文件到oss是不需要收流量费用的。而ECS仅仅只是出口1M而已,入口是不限速的。如果需要最大化节约的上传文件到oss,可以通过ECS传。
阿里云的oss在linux上有个可以连接oss的软件,但是那个操作并不是我想需要的。好在阿里云还有一款ossfs 软件。它可以将对象存储OSS的存储空间(Bucket)挂载到本地文件系统中,能够像操作本地文件一样操作OSS的对象(Object),实现数据的共享。
这对于写静态blog来说实在是太方便了,只需要将Bucket挂载到本地文件夹,就像挂载磁盘那样操作。随后就可以不用流量的将静态文件上传到oss。
开启OSS的静态网站托管,将hexo生成的静态blog都放到挂载的目录下即可。非常的方便,文章内的图片也可以使用相对路径,而不需要一张一张的插入外链那么麻烦了。
需要注意一点的是:使用OSS默认域名访问时,Response Header中会自动加上 Content-Disposition:'attachment=filename;'
。即从浏览器访问网页类型文件时,不会显示文件内容,而是以附件形式进行下载。也就是说需要绑定自己的域名才能静态托管。
多重备份 这种方案解决的好处就是,可以在多个地方实现多重的数据备份。且不需要在备份数据库了。
Github一份备份 CDN一份备份 ECS一份备份 自己的电脑还可以有多份备份 一步直接实现异地多备份。
OSSFS 目前有多个发行版的安装包:
下载到主机内后,根据不同的发行版进行安装就好了。而对于Ubuntu需要使用gdebi:
sudo apt-get updatesudo apt-get install gdebi-coresudo gdebi your_ossfs_package
配置账号信息 成功了安装了之后就可以配置oss的账号信息来登陆。使用AccessKeyId/AccessKeySecret来代替账号密码进行访问。如果担心安全问题还可以使用阿里云的子账号只赋予oss的访问权限,来最大程度的保护账户资产。在阿里云的RAM访问控制 中可以进行添加子账户并赋予特定的权限。
AccessKeyId/AccessKeySecret信息存放在/etc/passwd-ossfs
文件中。并且文件的权限必须正确设置,建议设为640。
格式为:bucket名:AccessKeyId:AccessKeySecret
echo my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-ossfschmod 640 /etc/passwd-ossfs
如果需要配置多个账号或者多个bucket,可以直接将账号信息写在后面,ossfs会根据挂载的存储空间名称匹配到正确的账号上。
echo my-second-bucket:my-access-key-id:my-access-key-secret >> /etc/passwd-ossfs
挂载到指定目录 账号信息填写的非常简单,写到指定文件里就可以了。填完之后就可以将oss挂载到本地的指定目录上。
格式为:ossfs bucket名 本地挂载点 -ourl=oss url
ossfs my -bucket my -mount-point -ourl=my -oss-endpoint
如果正好使用的是阿里云的ECS机器,可以走oss的内网,在上传文件时就不会产生多余的流量费用。通常oss的内网域名包含internal。例如:
oss-cn-beijing-internal .aliyuncs .com
只需要将近4条左右的命令就可以将oss成功挂载到本地上,就如同一个文件夹。
开机自动挂载 和开机自动挂载分区一样,Ubuntu需要在/etc/fstab
中进行操作。
ossfs
对应的填入了信息之后,可以使用mount -a
进行测试。如果没有任何报错,即代表成功。
阿里云子目录 阿里云oss的默认配置是不会去访问子目录下的首页的,子目录下的index.html
必须访问全部的静态链接。否则会跳转回主页的index.html
。
网上的大多数解决办法就是修改hexo的配置,把所有的子目录的绝对路径都生成出来。这是一种解决办法,但不能从根源上解决所有问题。
并且阿里云也早就支持了子目录首页了。只需要简单开一下就能解决这个问题。
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Tools
-
-
-
-
-
-
-
-
- JavaScript笔记-引用类型
-
- /defect/javascript-notes-reference-type.html
-
- 这是来自Professional JavaScript for Web Develops第五章的笔记。
基本类型和引用类型 基本类型值指的是简单的数据段。
引用类型值指那些可能由多个值构成的对象。
JS的五种基本数据类型就是基本类型值。这五种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。
引用类型的值是保存在内存中的对象。js不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在复制保存着对象的某个变量时,操作的是变量的引用。但为变量添加属性时,操作的是实际的对象。
传递参数只有按值传递,没有按引用传递:
let person = {};undefined function setName (ojb ) {ojb.name = 'xfy' ;}undefined setName(person);undefined person.name;"xfy"
当在函数内,将ojb2重新声明为一个新的对象。如果参数是按引用传递的,person对象应该自动修改name属性值指向'notxfy'
。
function setName2 (ojb2 ) {ojb2.name = 'xfy2' ;ojb2 = {};ojb2.name = 'notxfy' ;}undefined setName2(person);undefined person.name"xfy2"
js引用类型的值(对象)是引用类型的一个实例。引用类型是一种数据结构,用于将数据和功能组织在一起。虽然引用类型和类看起来类似,但是它们不是相同的概念。
对象是某个特定引用类型的实例。
Object类型 Object是目前ECMAScript中使用最多的一个语言。虽然Object实例不具备多少功能,对对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。
创建Object实例 创建方式有两种。第一种是new操作符后接Object构造函数。构造函数本身就是一个函数,只不过它时出于创建新对象的目的而定义的。
let person = new Object ();person.name = "Defectink" ;person.age = 8 ;
另外一种是使用对象字面量:
let person2 = { name = "xfy" , age = 88 }
左边的花括号表示对象字面量的开始,因为它出现在表达式上下文(expression context)中。同样的花括号如果出现在一个语句上下文(statement context)中,则表示一个语句块的开始。例如if语句的花括号。
在对象字面量中使用逗号来分隔不同的属性。最后一个属性不添加逗号。
在使用对象字面量语法时,属性名也可以使用字符串。
对象字面量还有另外一种写法:
let person3 = {};person3.name = 'xxx' ;person3.age = 3 ;
传递大量参数 对象字面量也是想函数传递大量可选参数的首选方式。
let output = "" ;function showInfo (args ) { if (typeof args.name == "string" ){ output += "the Name: " + args.name + "\n" ; } if (typeof args.age == "number" ){ output += "the Age: " + args.age + "\n" ; } alert(output);}showInfo({ name:"test" , age: 128 })
函数showInfo()接收一个名为args的参数。这个参数可能带有名为name或age的属性,又或者这两个属性都有或没有。每次都可以使用一个对象字面量来传递不同的可选数据。
通常访问对象属性都是用点表示法。在js中也可以使用方括号表示法来访问对象的属性。
console .log(personn.name);console .log(person["name" ]);
这两种方法没有任何区别。但方括号的优点时可以通过变量来访问属性。
let personAnotherName = 'test' ;console .log(person[personAnotherName]);
Array类型 js的数组是数据的有序列表。数组的每一项都都可以用来保存任何类型的数据。数组的大小也是可以动态调整的,即可以随着数据的添加自动增长以容纳新数据。
创建数组 使用Array构造函数:
let colors = new Array ();
Array构造函数可以传递数组的数量,创建十个数组:
let colors = new Array (10 );
传递的参数还能用于创建数组的内容,但是不能创建纯数字的内容:
let colors = new Array (3 ); let names = new Array ("Greg" );
可以省略new操作符:
let colors = Array (3 ); let names = Array ("Greg" );
使用字面量表示法:
let colors = ["red" , "blue" , "green" ]; let names = []; let values = [1 ,2 ,]; let options = [,,,,,];
读取数组 使用方括号并提供相应值的基于0的数字索引:
let colors = ['red' ,'blue' ,'green' ];colors[0 ];colors[2 ] = 'test' ;colors[3 ] = 'new one' ;
数组的项数保存在length属性中,这个属性始终都会返回0或更大的值。
let colors = ['red' ,'blue' ,'green' ];colors.length;3
length属性不是只读的,可以通过设置这个属性来向数组的末尾添加或移除内容。
let colors = ["red" , "blue" , "green" ]; colors.length = 2 ;alert(colors[2 ]); let colors = ["red" , "blue" , "green" ]; colors.length = 4 ;alert(colors[3 ]);
数组的最后一项索引始终都是length-1,所以可以使用length方便在末尾添加内容。
let colors = ["red" , "blue" , "green" ]; colors[colors.length] = "black" ; colors[colors.length] = "brown" ; alert(colors.length); alert(colors[3 ]); alert(colors[4 ]);
数组最多可以包含4 294 967 295个项
join()方法可以重现toString()的输出。定义数组分隔符。如果不给join()方法传入任何值,或者传入undefineed,则使用逗号。
let colors = ["red" , "green" , "blue" ];alert(colors.join("," )); alert(colors.join("||" ));
栈方法 栈是一种可以限制插入和删除项的数据结构。栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构。
栈项中的插入(推入)和移除(弹出)只发生在一个位置——栈的顶部。数组有push()和pop()方法实现了类似栈的行为。
push()将参数逐个添加到数组的末尾,并返回修改后数组的长度。
pop()将从数组末尾中移除一项,减少length的值,并返回移除的项。
let colors = new Array (); let count = colors.push("red" , "green" ); alert(count); count = colors.push("black" ); alert(count); let item = colors.pop(); alert(item); alert(colors.length);
栈方法可以和其他数组方法连用。
let colors = ["red" , "blue" ]; colors.push("brown" ); colors[3 ] = "black" ; alert(colors.length); let item = colors.pop();item;colors;
队列方法 队列数据结构的访问顺序是FIFO(First-In-First-Out,先进先出)。数组有shift()方法,它能够移除数组的第一个项,减少length值,并返回该项。它就像和pop()方法相反的操作。
结合shift()和push()方法,可以像队列一样使用数组。
let colors = new Array (); let count = colors.push("red" , "green" ); alert(count); count = colors.push("black" ); alert(count); let item = colors.shift(); alert(item); alert(colors.length);
数组还有个unshift()方法,它在数组前端添加添加任意个项,并返回修改后数组的长度。它就像和push()相反的操作。
结合unshift()和pop()方法可以反向模拟队列操作。
let colors = new Array (); let count = colors.unshift("red" , "green" ); alert(count); count = colors.unshift("black" ); alert(count); let item = colors.pop(); alert(item); alert(colors.length);
重排序方法 数组中有两个可以重排序的方法:reverse()和sort()。
reverse()对数组反向排序:
let values = [1 , 2 , 3 , 4 , 5 ];values.reverse();alert(values);
sort()按升序排列数组——即最小的值位于最前面。sort()会调用每个数组项的toString()转型方法。然后比较得到的字符串。
let values = [0 , 1 , 5 , 10 , 15 ];values.sort();alert(values);
由于1<5,所有10会被排在5前面。所以sort()可以接受一个比较函数来重新排序。
function compare (v1,v2 ) { if (v1 < v2){ return -1 ; }else if (v1 > v2 ) { return 1 ; }else { return 0 ; }}let values = [0 , 1 , 5 , 10 , 15 ];values.sort(compare);(5 ) [0 , 1 , 5 , 10 , 15 ]
reverse()和sort()返回的是经过排序之后的数组。
对于数值类型或者其valueOf()方法会返回数值类型的对象类型,可以使用一个更简单的比较函数。只要有第二个值减第一个值即可。
function compare2 (v1,v2 ) { v2 - v1;}values.sort(compare2);(5 ) [0 , 1 , 5 , 10 , 15 ]
操作方法 concat()基于当前数组中所有项目创建一个新的数组。它会先创建一个数组副本,然后将参数添加到这个副本的末尾,返回新构建的数组。没有传递参数时,它只是返回副本。
let colors = ["red" , "green" , "blue" ];let colors2 = colors.concat("yellow" , ["black" , "brown" ]);alert(colors); alert(colors2);
let colors = ['red' ,'blue' ,'green' ];undefined let color2 = ['1' ,'2' ,'3' ];undefined color2.concat(colors);(6 ) ["1" , "2" , "3" , "red" , "blue" , "green" ]
slice()基于当前数组中的指定位置创建一个新的数组。它接受两个参数,即起始位置和结束位置(不返回结束位置的项)。当只有 一个参数时,返回直到数组末尾的所有项。
let colors = ["red" , "green" , "blue" , "yellow" , "purple" ];let colors2 = colors.slice(1 );let colors3 = colors.slice(1 ,4 );alert(colors2); alert(colors3);
splice()是功能更全面的数组操作方法。
删除:可以删除任意数量的项,指定两个参数:要删除的第一项位置和要删除的项数。splice(0,2)
插入:可以向指定位置插入任意数量的项。指定三个参数:起始位置、0(要删除的项数)和需要插入的项。splice(2,0,'red','green')
替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项。指定三个参数:起始位置、要删除的项数和要插入的项。splice(2,1,'red','blcak')
插入和删除都在起始位开始。
let colors = ["red" , "green" , "blue" ];let removed = colors.splice(0 ,1 ); alert(colors); alert(removed); removed = colors.splice(1 , 0 , "yellow" , "orange" ); alert(colors); alert(removed); removed = colors.splice(1 , 1 , "red" , "purple" ); alert(colors); alert(removed);
位置方法 ECMAScript5为数组添加了两个位置方法:indexOf()和lastIndexOf()。他们都接受两个参数:要查找的项和(可选)起始位置的索引。
indexOf()从数组开头索引,lastIndexOf()从数组末尾开始索引。
接受两个参数:要查找的项和(可选)表示查找位置地点的索引。返回查找到的位置索引,没找返回-1。比较查找项时会使用全等操作。
let arr = [1 ,2 ,3 ,4 ,5 ,4 ,3 ,2 ,1 ];console .log(arr.indexOf(3 ));console .log(arr.lastIndexOf(3 ));console .log(arr.indexOf(3 ,3 ));console .log(arr.lastIndexOf(3 ,5 ));let person = { name : "Nicholas" };let people = [{ name : "Nicholas" }];let morePeople = [person];alert(people.indexOf(person)); alert(morePeople.indexOf(person));
迭代方法 every():对数组的每一项运行给定的函数,每一项都返回ture,则返回ture。 filter():对数组的每一项运行给定的函数,返回函数会返回ture组成的数组。 forEach():对数组的每一项运行给定的函数,没有返回值。 map():对数组的每一项运行给定的函数,返回函数调用结果。 some():对数组的每一项运行给定的函数,任意一项返回ture,返回ture。 所有方法都不会修改数组中的包含的值。
every()
let dd = input => input < 9 ;let arr = [1 ,2 ,3 ,4 ,5 ];console .log(arr.every(dd));
filter()
let ff = word => word.length > 5 ;let arr2 = ['spray' , 'limit' , 'elite' , 'exuberant' , 'destruction' , 'present' ];console .log(arr2.filter(ff));
forEach(),可以用来遍历数组
let arr3 = ['a' ,'b' ,'c' ,1 ,2 ,3 ];arr3.forEach(ele => console .log(ele));
map()
let arr4 = [1 ,2 ,3 ,4 ,5 ,6 ];console .log(arr4.map(sx => (sx + 1 ) * 2 / 3 ));
some()
let arr5 = [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ];console .log(arr5.some(qy => qy % 2 === 0 ));
归并方法 reduce()和reduceRight()。这两个方法都会迭代数组的所有项,然后构建一个最终返回值。reduceRgiht()从数组的最后一项开始遍历到开头。
他们都接受四个参数:
Accumulator (acc) (累计器) Current Value (cur) (当前值) Current Index (idx) (当前索引) Source Array (src) (源数组) let arr = [1 ,2 ,3 ,4 ,5 ,6 ];console .log(arr.reduce((a1,a2 ) => a1 + a2));console .log(arr.reduceRight((a1,a2 ) => a1 + a2));
Date类型 Date类型使用自1970年1月1日开始以来的毫秒数来保存日期。
创建日期对象,使用new操作符和Date构造函数。
let dd = new Date ();let d2 = new Date (2017 , 0 2, 0 1);let d3 = new Date (Date .UTC(2000 , 2 , 3 , 21 , 23 , 31 ));
Date()构造函数会假设第一个参数是年份,第二个参数是月份,以此类推。
Date.now()返回调用这个方法时日期和时间的毫秒数,可以用来做一个简单计时。
let start = Date .now();console .log(start);let arr = [1 ,2 ,3 ,4 ,5 ,666 ,745 ,23 ,441 ,323 ,123123 ,123 ,123 ,85858585 ,,3 ,23 ,1 ,23 ,123123123123 ];console .log(arr.reduce((a1, a2 ) => a1 + a2));let stop = Date .now();console .log(stop);let result = stop - start;console .log(result);
使用+操作符获取Date对象的时间戳也可以达到同样的目的。
let test = +new Date ();
继承的方法 与其他的引用类型一样,Date也重写了toLocalString()、toString()和valueOf()方法。
toLocaleString()
方法返回该日期对象的字符串,该字符串格式因不同语言而不同。
toString()
方法返回一个字符串,表示该Date对象。
let d4 = new Date ();console .log(d4.toLocaleString());console .log(d4.toString())
valueOf()
方法返回一个 Date 对象的原始值。即返回毫秒数。
RegExp类型 js通过RegExp类型来支持正则表达式。
创建使用字面量, 构造函数和工厂符号都是可以的:
/pattern/flagsnew RegExp (pattern [, flags])RegExp (pattern [, flags])
Flags:
g:全局匹配;找到所有匹配,而不是在第一个匹配后停止 i:忽略大小写 m:多行; 将开始和结束字符(^和$)视为在多行上工作(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不只是只匹配整个输入字符串的最开始和最末尾处。 使用构造函数创建时,参数需要使用字符串:
let pattern2 = new RegExp ("[bc]at" ,"g" );
所以在构造函数的情况下可能需要双重转义
let pattern3 = new RegExp ("\\[bc\\]at" ,"g" );
由于实例属性不会重置,所以在循环中再次调用test()方法会失败。第一次找到了cat,第二次会从上一次匹配的末尾开始寻找。
let re = null , i;for (i = 0 ; i < 10 ; i++) { re = /cat/g ; re.test('catastrophe' );}for (i = 0 ; i < 10 ; i++) { re = new RegExp ("cat" ,"g" ); re.test('catastrophe' )}
实例属性 RegExp.prototype.constructor
创建该正则对象的构造函数。
RegExp.prototype.global
是否开启全局匹配,也就是匹配目标字符串中所有可能的匹配项,而不是只进行第一次匹配。
RegExp.prototype.ignoreCase
在匹配字符串时是否要忽略字符的大小写。
RegExp.prototype.lastIndex
下次匹配开始的字符串索引位置。
RegExp.prototype.multiline
是否开启多行模式匹配(影响 ^ 和 $ 的行为)。
RegExp.prototype.source
正则对象的源模式文本。
console .log(pattern.global);console .log(pattern.ignoreCase);console .log(pattern.multiline);console .log(pattern.lastIndex);console .log(pattern.source);
实例方法 exec()和test()。
exec()设置了全局模式也只会返回一个匹配项,多次调用一次返回向后匹配到的值。而不设置全局模式则只返回第一次匹配到的匹配项。
var text = "cat, bat, sat, fat" ; var pattern1 = /.at/ ;var matches = pattern1.exec(text); alert(matches.index); alert(matches[0 ]); alert(pattern1.lastIndex);matches = pattern1.exec(text); alert(matches.index); alert(matches[0 ]); alert(pattern1.lastIndex);var pattern2 = /.at/g ;var matches = pattern2.exec(text); alert(matches.index); alert(matches[0 ]); alert(pattern2.lastIndex);matches = pattern2.exec(text); alert(matches.index); alert(matches[0 ]); alert(pattern2.lastIndex);
test()接受一个字符串参数,在模式与该参数匹配的情况下返回ture。通常与if语句一起使用。
let text = '123-00-12345' ;let pattern4 = /\d{3}-\d{2}-\d{4}/ ;if (pattern4.test(text)) { console .log('all matched' );}
Function类型 函数实际上是对象,函数名是指针。所以函数名与包装对象指针的其他变量没有什么不同。
function sum (a,b ) { return a + b;}let anotherSum = sum;console .log(anotherSum(1 ,2 ));sum = null ;console .log(anotherSum(1 ,2 ));
构造函数用来创建对象。
函数声明提升 解析器会率先读取函数声明,并使其在执行任何代码之前可以访问。至于函数表达式,则必须等到解析器执行到它所在的代码行,才会被真正的解析执行。
就像var的提升一样!
console .log(sum2(10 ,20 ));function sum2 (a,b ) { return a * b;}
但使用函数表达式就不存在这种情况了。
console .log(sum3(10 ,20 ));let sum3 = function (a,b ) { return a * b;}
由于函数在一个初始化语句中,(就算使用var,也只有var的声明会提升,语句并没有初始化)而不是函数声明。
作为参数传递 因为函数名本身就是变量,所以函数也可以作为值来使用。(回调)
function add (num ) { return num + 10 ;}function addd (ff,num ) { return ff(num);}console .log(addd(add,1 ));
另一种就是从一个函数中返回另一个函数,实际上就是一个函数嵌套了另一个函数。(闭包)
例如对对象数组进行按照对象属性排序:
let data = [ { name: 'xfy' , age: 18 }, { name: 'dfy' , age: 81 }]function com (propertyName ) { return function (object1,object2 ) { let value1 = object1[propertyName]; let value2 = object2[propertyName]; if (value1 < value2) { return -1 ; } else if (value1 > value2) { return 1 ; } else { return 0 ; } }}console .log(data.sort(com('name' )));
函数内部属性 在函数内部,有两个特殊的对象:arguments和this。arguments的主要作用是保存函数参数,但它还有一个callee的属性。该属性是一个指针,指针指向拥有这个arguments对象的函数。
一个经典的递归函数:
function test (num ) { if (num <= 1 ) { return 1 ; } return num + test(num - 1 );}console .log(test(100 ));
当遇到使用函数表达式重新指向函数时
function test (num ) { if (num <= 1 ) { return 1 ; } return num + test(num - 1 );}console .log(test(100 ));let test2 = test;test = function ( ) { return 1 ;}console .log(test2(100 )); console .log(test(100 ));
使用callee就能解决这个问题,类似于对象的this。
function cb (num ) { if (num <= 1 ) { return 1 ; } return num + arguments .callee(num - 1 );}console .log(cb(100 ));let cb2 = cb; cb = function ( ) { return 0 ;}console .log(cb2(100 ));console .log(cb(100 ));
函数的名字仅仅只是一个包含指针的变量而已。
ECMAScript5还定义了一个函数对象的属性:caller。它保存着调用当前函数的函数的引用。如果是在全局作用域中调用当前函数,它的值为null。
function outer ( ) { return inner();}function inner ( ) { return inner.caller;}console .log(outer());
甚至还能更进一步解耦合:
function outer ( ) { return inner();}function inner ( ) { return arguments .callee.caller;}
在严格模式下,访问arguments.callee
和arguments.caller
都会导致访问错误。且不能为函数的caller属性赋值,否则会导致错误。
函数属性与方法 js中的函数也是对象,所以函数也有属性和方法。每个函数都包含两个属性:length和prototype。
length属性表示函数希望接受的命名参数的个数。
function test (arg1,arg2 ) { return arg1 + arg2;}console .log(test.length);
对于引用类型而言,prototype是保存它们所有实例方法的真正所在。prototype属性是不可枚举的,所以用for-in是无法发现的。
每个函数都包含两个非继承而来的方法:apply()和call()。它们常用来动态改变this的值。call()与apply()相同,它们区别仅在接受参数的方式不同。第一个参数是this值,第二个参数分别是逐个传参和数组传参。
function fruits ( ) {};fruits.prototype = { color: 'red' , say: function ( ) { console .log('the color is : ' + this .color); }}let apple = new fruits();console .log(apple);console .log(apple.say());let banana = { color: 'yellow' }console .log(apple.say.apply(banana));
除了在对象中的应用,call和apply真正的用武之地是扩充函数的作用于。
window .color = 'red' ;let o = { color: 'blue' };function sayColor ( ) { return this .color;}sayColor()"red" sayColor.call(o)"blue" sayColor.call(window )"red"
除此之外还有一个方法:bind()。这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。
window .color = 'red' ;let o = { color: 'blue' };function sayColor ( ) { return this .color;}let sayAnotherColor = sayColor.bind(o);sayAnotherColor();"blue"
基本包装类型 三个特殊的引用类型:Boolean、Number和String。
包装对象都会经过三个步骤:
创建String或其他类型的一个实例; 在实例上调用指定的方法; 销毁这个实例; 也就是类似于这样的操作:
let s1 = new String ('some string' );let s2 = s1.length;s1 = null ;
引用类型与基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前一直都保存在内存中。而包装对象,则只存在执行代码的一瞬间,然后立即被销毁。所以不能在运行时为基本类型添加属性和方法。
let s1 = 'xfy' ;s1.color = 'pink' ;console .log(s1.color);
在第二行创建的String对象在执行第三行代码时就已经被销毁了,第三行代码则又创建自己String对象,而没有第二行创建color属性。
Object构造函数也会像工厂方法一样,根据传入值的类型来返回相应基本包装类型的实例。
let test = new Object ('xfy' );typeof test"object" test instanceof String true test instanceof Number false test instanceof Object true
使用new调用资本包装类型的构造函数,与直接调用同名的转型函数是不一样的。
let value = '25' ;let test = Number (value);typeof test"number" let test2 = new Number (25 );typeof test2;"object"
不建显式的创建基本包装类型的对象。
Boolean类型 Boolean有基本类型与引用类型。使用Boolean对象构造的值为引用类型。Boolean类型的实例重写了valueOf()与toString()。
let b = new Boolean (false ); let bb = true ;let b2 = false ; console .log(b && bb); console .log(b2 && bb);
布尔表达式中所有对象都会被转为true,因此引用类型的Boolean都会被转为true。
基本类型与引用类型的Boolean还有两个区别:
typeof对基本类型返回”Boolean“。对引用类型返回”Object“; Boolean对象是Boolean类型的实例,所以instanceof会返回true。而基本类型则返回false; 建议不要使用Boolean对象。
Number类型 Number类型也重写了valueOf()、toLocaleString()和toString()。
toString()可以传递一个表示基数的参数,将返回进制化数值的字符串形式。
let num = 123 ;console .log(num.toString(2 ));console .log(num.toString(8 ));console .log(num.toString(16 ));
除了继承的方法之外,Number类型还提供了一些用于将数值格式化为字符串的方法。toFixed()方法返回指定位数的小数。
当小数比指定位数还多一位的情况下,就会舍入。
let num = 123 ;console .log(num.toFixed(2 ));console .log(num.toFixed(20 ));num = 12.005 ;console .log(num.toFixed(2 ));
通常情况下toFixed()可以表示0-20位小数,但这只是标准实现的范围。有些运行环境可以支持更多。
也有用于格式化为指数表示法的方法,toExponential()(e表示法)。toExponential()接受一个参数,指定输出结果的小数的位数。返回也是字符串形式。
let num = 123 ;let num2 = num.toExponential(10 );console .log(num2);
还有toPrecision()会根据情况来使用toFixed()或者是toExponential()。
let num = 123 ;console .log(num.toPrecision(1 ));console .log(num.toPrecision(2 ));console .log(num.toPrecision(3 ));console .log(num.toPrecision(4 ));console .log(num.toPrecision(5 ));console .log(num.toPrecision(6 ));1e+2 1.2e+2 123 123.0 123.00 123.000
Number与Boolean一样,实例化Number类型在使用typeof和instanceof时,会有完全不同的结果。
let num = 123 ;let num2 = new Number (123 );console .log(typeof num);console .log(typeof num2);console .log(num instanceof Number );console .log(num2 instanceof Number );
String类型 String类型是字符串的对象包装类型。
let str = new String ('xfy' );
访问特定字符方法:charAt()和charCodeAt(),接受一个参数,从0开始的字符位置。
let str = 'xfy' ;console .log(str.charAt(1 ));console .log(str.charCodeAt(1 ));
还有另一个访问个别字符的方法,类似于访问数组
console .log(str[1 ]);
操作方法
除了+
拼接字符串,还有类似于数组的concat()方法。用于将一个或多个字符串拼接起来,返回新的字符串。
let str2 = 'yyy' ;console .log(str.concat('abc' ,str2));
还有三个基于字符串创建新字符串的方法,基于字符串修改或裁减。返回新的字符串。slice()、substr()和substring()。它们都接受两个参数,第一个参数指定字符串开始的位置,第二个参数(可选)表示字符串到哪里结束。
let str = 'xiaofeiyang' ;console .log(str.slice(4 ));console .log(str.substr(4 ));console .log(str.substring(4 ));console .log('---' );console .log(str.slice(4 ,7 ));console .log(str.substr(4 ,7 ));console .log(str.substring(4 ,7 ));feiyangfeiyangfeiyang---feifeiyangfei
]]>
-
-
-
-
- 笔记
-
-
-
-
-
-
- JavaScript
-
-
-
-
-
-
-
-
- Docker-全面容器化!
-
- /defect/docker-container-all.html
-
- 自上篇Docker - 构建属于自己的镜像 以来,发现Docker非常的有意思。主要是非常的方便,并且在可以跨平台的情况下部署环境对于以后迁移也是一件极其有利的事。研究了Dockerfile的编写以及实践。一些基础的实践之后,对于Docker的工作方式以及操作命令都有了一些熟悉。也逐渐了发现了它的一些优点。翻开自己的旧机器里的多种环境交杂在一起的配置,时间长了连配置文件在哪都找不到了。管理起来比较复杂。那些服务器的管理面板并不是很喜欢,而且相对于Docker来说,管理面板只是简化了部署的操作,并没有达到方便管理的目的。到最后可能它的软件目录,镜像源都是按照它的想法去放的。对于自己并没有完全的掌控。当然不能完全拿管理面板与Docker来相比,二者完全是两种技术。只是相较于方便管理这方面来对比一下。
而最近研究的Docker,无疑是最满意的了。在保持宿主机不乱的情况下,可以完全的掌控自己的运行环境。于是就有了将自己目前跑了挺长时间的一套blog环境都迁移到Docker上。对于以后若是迁移机器也会更加的方便。
涉及到的操作 Dockerfile docker-compose.yml apache virtualhost php-fpm http2 apache https certbot with docker apache proxy 目前环境 先来简单看下当前跑在机器上的环境:
基本的LAMP环境,加上一些自定义的应用,与一个服务器监控软件。其中apache有多个虚拟主机,全部都使用了https。
咋一看是一套很简单的环境,其中apache配置稍多一点。但是实际在迁移到Docker的操作起来还是比较复杂的。并且为了镜像的最小化,apache基于的镜像都是alpine。配置与常用的Ubuntu略有不同。
容器化 思路 将多个运行LAMP分别拆分出三个运行环境,使用docker-compose来捆绑运行。
目录树
.├── apache │ ├── Dockerfile │ ├── httpd .conf │ └── sites │ ├── 000-default-ssl .conf │ └── 000-default .conf ├── docker-compose .yml ├── mysql │ └── backup ├── php │ └── Dockerfile └── www └── html └── index .php
首先创建一个用于存放整个运行环境的Docker
父文件夹。然后根据不同的镜像来划分不同的子文件夹,子文件夹内存放的就是各个镜像的Dockerfile
与配置文件等。将docker-compose.yml存放与父目录下。
apache与php-fpm通信借助Docker的网络,实现内部的通信。
Apahce 在当前的apache目录下,主要文件夹的划分为Dockerfile
、httpd.conf
和sites文件夹。
Dockerfile 虽然httpd有了一个单独的镜像,但是还是需要使用Dockerfile来对其进行自定义配置。为了尽量减小镜像的大小。这里使用基于alpine的apache。
在Docker hub中的httpd 当前支持的tag:
整个Dockerfile:
FROM httpd:alpineRUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk update \ && apk upgrade COPY sites/ /usr/local /apache2/conf/sites/ COPY httpd.conf /usr/local /apache2/conf/httpd.conf
所以FROM
里使用的就是带alpine的tag了。我还尝试过测试使用基于alpine的空载运行apache大概节约了1MB的内存。
176d166ee52a testa 0 .00 % 4 .484 MiB / 3 .607 GiB 0 .12 % 3dac39c11385 test 0 .00 % 5 .664 MiB / 3 .607 GiB 0 .15 %
对于跑在国内的机器上,alpine也有国内的源。并且替换的也很简单,一句话就好了。这样在后续的更新源和安装软件就没有那么苦恼了。
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/ apk/repositories
剩下的COPY
就是复制自定义的配置文件到容器里去了。
配置文件 首先,之前的环境中apache是有多个虚拟主机,并且每个主机都启用了ssl以及一些其他的配置。所以第一步是需要修改容器的配置文件。也就是要先获取默认的配置文件。
优雅的获取apache默认配置文件:
docker run --rm httpd:2.4 cat /usr/local /apache2/conf /httpd.conf > httpd.conf
默认的ssl配置文件:
docker run --rm httpd:2.4 cat /usr/local /apache2/conf /extra/httpd-ssl.conf > ssl.conf
容器的配置文件路径:
/usr/ local/apache2/ conf/httpd.conf
获取到了默认的配置文件之后,在apache的文件夹内可以先自定义httpd.conf
。并且尝试启动一次,没用问题后可以继续配置虚拟主机。
由于不同的站点都交给了虚拟主机的配置文件来处理。所以httpd.conf
主要是负责一些mod的配置,和一些全局的配置了。还有就是将余下的配置文件Include
进来了。
后期还有需要添加更多的虚拟主机的配置文件,到时候一个一个的Include
操作太过繁琐。所以创建个专门存放配置文件的文件夹,再在httpd.conf
里将整个文件夹Include
进去。这样就最简单的解决了操作繁琐的问题。
创建一个sites
文件夹用于存放配置文件,COPY
到容器内相应的目录:
COPY sites/ /u sr/local/ apache2/conf/ sites/
在httpd.conf
中相应的引入:
Include /usr/ local/apache2/ conf/sites/ *.conf
{*}这一操作方法还是学自Ubuntu下的apache,它的配置目录下有两个文件夹sites-available
和sites-enabled
。在主要的apache2.conf中引入配置文件。
# Include generic snippets of statementsIncludeOptional conf-enabled/*.conf# Include the virtual host configurations:IncludeOptional sites-enabled/*.conf
httpd.conf
中的虚拟主机配置不需要修改了。所有的站点可以都在Include中的配置文件中准备。基本上httpd.conf
就是为引入配置文件和启用mod所准备的。
Module 在基于alpine中的apache,所有的mod加载都写在了配置文件httpd.conf
里。只需要取消注释就可以加载/启用模组了。
这次添加的module:
LoadModule deflate_module modules/mod_deflate.soLoadModule proxy_module modules/mod_proxy.soLoadModule proxy_connect_module modules/mod_proxy_connect.soLoadModule proxy_ftp_module modules/mod_proxy_ftp.soLoadModule proxy_http_module modules/mod_proxy_http.soLoadModule proxy_fcgi_module modules/mod_proxy_fcgi.soLoadModule setenvif_module modules/mod_setenvif.soLoadModule mpm_event_module modules/mod_mpm_event.soLoadModule http2 _module modules/mod_http2 .soLoadModule proxy_http2 _module modules/mod_proxy_http2 .soLoadModule ssl_module modules/mod_ssl.soLoadModule socache_shmcb_module modules/mod_socache_shmcb.soLoadModule rewrite_module modules/mod_rewrite.soLoadModule headers_module modules/mod_headers.so
这些mod都是作用于何?
mod_deflate是一个压缩算法。
mod_socache_shmcb共享对象缓存提供程序。
因为需要配置反代和与php-fpm工作,所以需要启用多个proxy配置文件。
因为需要用到http2,所以工作模式得修改为event。同时注释掉默认的工作模式prefork。自然也需要mod_http2
https是不可或缺的,所以mod_ssl不可缺少。
后续的博客需要用到伪静态,mod_rewrite也不可少。
在最近也添加了多个header头,需要用到mod_headers。
info:根据自己需要启用module是一个良好的习惯,过多的module会影响性能。
虚拟主机 前面提到,专门创建了一个sites文件夹来存放虚拟主机的配置文件,目前sites文件夹还是空的。既然httpd.conf
以及准备就绪,那么接下来就是填满sites文件夹了。
在还未添加虚拟主机时,默认的站点配置文件全部都写在httpd.conf
里。默认的根目录在htdocs。所以在第一次启动测试时,访问的时这里的html文件。
DocumentRoot "/usr/local/apache2/htdocs"
这里的配置可以不用动,全部操作交给虚拟主机就好。
整个虚拟主机(default.conf)配置文件:
<VirtualHost *:80 > ProtocolsHonorOrder On Protocols h2 h2 cHeader set X-Frame-Options "SAMEORIGIN" Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;" Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;" Header set X-Content-Type-Options nosniffHeader always set Referrer-Policy "no-referrer-when-downgrade" Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com" ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000 /var/www/html/$1 ServerName www.defectink.com DocumentRoot /var/www/html/ <Directory /var/www/html/> DirectoryIndex index.php Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> CustomLog /proc/self/fd/1 common ErrorLog /proc/self/fd/2 RewriteEngine on RewriteCond %{SERVER_NAME} =www.defectink.comRewriteRule ^ https://%{SERVER_NAME} %{REQUEST_URI} [END,NE,R=permanent] </VirtualHost>
虚拟主机优先级 在apache中,虚拟主机的配置文件是拥有优先级的。优先级的意思就是,当一个域名指向当前机器的ip,而配置文件中没有绑定的ServerName时,默认被引导到的页面。
优先级的顺序是根据虚拟主机的配置文件名来决定的。名称首字母越靠前,优先级越高。使用数字开头将大于子母开头。
000-default will be the default, because it goes “numbers, then letters”.
可以使用命令来查看当前的默认站点:
httpd -S
apache2ctl -S
SSL 这里的ssl配置文件是来自于容器内的默认配置文件,使用上述的方法可以很方便的导出。
整个ssl(default-ssl.conf)配置文件:
Listen 443 SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3 DESSSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3 DESSSLHonorCipherOrder on SSLProtocol all -SSLv3SSLProxyProtocol all -SSLv3SSLPassPhraseDialog builtinSSLSessionCache "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"SSLSessionCacheTimeout 300 <VirtualHost *:443 >ProtocolsHonorOrder On Protocols h2 h2cHeader set X-Frame-Options "SAMEORIGIN"Header always set Strict -Transport-Security "max-age=63072000; includeSubdomains;"Header set Content-Security -Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"Header set X-Content-Type -Options nosniffHeader always set Referrer-Policy "no-referrer-when-downgrade"Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"# Proxy .php requests to port 9000 of the php-fpm containerProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://php:9000 /var/www/html/$1 # General setup for the virtual hostDocumentRoot "/var/www/html/"ServerName www.defectink.com:443 ServerAdmin i@defect.ink <Directory /var/www/html/> DirectoryIndex index .php Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory>ErrorLog /proc/self/fd/2 TransferLog /proc/self/fd/1 SSLEngine on SSLCertificateFile "/etc/letsencrypt/live/www.defectink.com/fullchain.pem"SSLCertificateKeyFile "/etc/letsencrypt/live/www.defectink.com/privkey.pem"<FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars</FilesMatch><Directory "/usr/local/apache2/cgi-bin"> SSLOptions +StdEnvVars</Directory>BrowserMatch "MSIE [2-5]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 CustomLog /proc/self/fd/1 \ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"</VirtualHost>
这里的主要配置点就在SSLCertificateFile
和SSLCertificateKeyFile
。关于配合certbot申请证书,在前一篇水过。有兴趣的小伙伴可以去了解更多。Docker - 构建属于自己的镜像 。
值得注意的是,在一个httpd.conf
文件中只能有一个Listen 443
字段。而默认的ssl配置文件中就包含一个Listen 443
字段。当复制多个默认的配置文件时,会导致apache运行错误。因为所有的配置文件都会被引入到httpd.conf
,而一个apache只能监听一次端口,也就是说只能有一个Listen 443
在配置文件中。
可以考虑将其写在监听80端口下面:
#Listen 12.34 .56 .78 :80 Listen 80 Listen 443
日志 这里虚拟主机的默认配置时将日志发送到stdout和stderr。可以理解为输出到终端上。
CustomLog /proc/ self/fd/ 1 commonErrorLog /proc/ self/fd/ 2
当然也可以实现日志的持久化保存。将其映射到宿主机的目录下就好了。
CustomLog /var /log /access.log commonErrorLog /var /log /error .log
PHP PHP这里使用fpm,配合docker-compose的内部网络与apache进行通信。
Dockerfile FROM php:7.4-fpm-alpineRUN mv "$PHP_INI_DIR /php.ini-production" "$PHP_INI_DIR /php.ini" COPY php.ini $PHP_INI_DIR /conf.d/RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ && apk update \ && apk upgrade \ && docker-php-ext-install mysqli \ && docker-php-ext-install pdo_mysql
php.ini 优雅的获取容器内的php.ini文件:
docker cp somephp:/usr/ local/etc/ php/ / root
修改过后的配置文件可以在Dockerfile中copy,也可以在compose.yml中映射。只要到了宿主机的正确位置就可以生效。
官方的描述是这样的方法:
RUN mv "$PHP_INI_DIR /php.ini-production" "$PHP_INI_DIR /php.ini" COPY php.ini $PHP_INI_DIR /conf.d/
拓展 如果需要安装typecho这样的blog程序的话,再连接sql时需要安装mysql的拓展,写在Dockerfile就好了:
docker-php-ext-install pdo_mysql
如果还需要其他的拓展的话,还可以在Dockerfile里自定义安装。可以使用pecl
来安装,然后使用docker-php-ext-enable
来启用它。
use pecl install
to download and compile it, then use docker-php-ext-enable
to enable it:
FROM php:7.4 -cliRUN pecl install redis-5.1 .1 \ && pecl install xdebug-2.8 .1 \ && docker-php-ext-enable redis xdebug
或者直接使用docker-php-ext-install
来安装:
&& docker-php-ext-install mysqli \ && docker-php-ext-install pdo_mysql
至于更多的拓展,还可以编译安装:
FROM php:5.6 -cliRUN curl -fsSL 'https://xcache.lighttpd.net/pub/Releases/3.2.0/xcache-3.2.0.tar.gz' -o xcache.tar.gz \ && mkdir -p xcache \ && tar -xf xcache.tar.gz -C xcache --strip-components=1 \ && rm xcache.tar.gz \ && ( \ cd xcache \ && phpize \ && ./configure --enable-xcache \ && make -j "$(nproc)" \ && make install \ ) \ && rm -r xcache \ && docker-php-ext-enable xcache
MySql mysql主要修改的一些配置使用启动时的环境变量就可以了,不需要修改其配置文件的情况下,便用不到Dockerfile了。直接使用官方的镜像就好了。
Docker Secrets 如果担心密码写在docker-compose.yml里不安全的话,可以考虑使用docker secret。不过secret需要使用swarm集群。单台主机只使用docker-compose可能会更加方便一点。
并且在compose中生成的是两个虚拟网络,只有apache在前端网络映射了端口。mysql完全放在后端,除了虚拟网络和宿主机都无法与其通信。所以密码的安全并不用太过于担心。如果mysql需要对外提供服务,那就需要多担心一下了。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE =/run/secrets/mysql-root -d mysql:tag
映射了目录后,配置的密码都会被持久化保存。再次修改docker-compose.yml中的变量将不会生效。
数据持久化 对于docker来说,尽量不要在容器内发生写的操作为好。此外,对于数据库来,数据肯定是需要持久化存储的。官方推荐的是:
Important note: There are several ways to store data used by applications that run in Docker containers. We encourage users of the mysql
images to familiarize themselves with the options available, including:
Let Docker manage the storage of your database data by writing the database files to disk on the host system using its own internal volume management . This is the default and is easy and fairly transparent to the user. The downside is that the files may be hard to locate for tools and applications that run directly on the host system, i.e. outside containers. Create a data directory on the host system (outside the container) and mount this to a directory visible from inside the container . This places the database files in a known location on the host system, and makes it easy for tools and applications on the host system to access the files. The downside is that the user needs to make sure that the directory exists, and that e.g. directory permissions and other security mechanisms on the host system are set up correctly. 说白了就是映射出来为最佳解决方案。如果在compose.yml中使用-v
映射,而不添加宿主机的目录位置的话。文件将会被映射到一个随机的目录。
推荐方案:
$ docker run --name some-mysql -v /my/ own/datadir:/ var/lib/my sql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
备份与恢复 针对单个或多个数据库都可以导出为sql文件。在docker中当然也是同样。甚至密码可以使用变量$MYSQL_ROOT_PASSWORD
来代替。
导入:
docker exec -i mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" typecho' < /data/docker/typecho.sql
导出:
docker exec mysql sh -c 'exec mysqldump typecho -uroot -p"$MYSQL_ROOT_PASSWORD"' > /data/docker/mysql/backup/typecho.sql
Docker-compose 整个配置文件:
docker-compose.yml
version: "3.2" services: php: container_name: php build: './php/' networks: - backend volumes: - ./www/:/var/www/ apache: container_name: apache build: './apache/' depends_on: - php - mysql networks: - frontend - backend ports: - "80:80" - "443:443" volumes: - ./www/:/var/www/ - /etc/letsencrypt/:/etc/letsencrypt/ - ./apache/logs/:/var/log/ mysql: container_name: mysql image: mysql:5.7 volumes: - ./mysql/sqldata/:/var/lib/mysql networks: - backend environment: - MYSQL_ROOT_PASSWORD=password command: ['mysqld' , '--character-set-server=utf8mb4' ] bark: container_name: bark build: './bark' ports: - "8181" depends_on: - apache networks: - backendnetworks: frontend: backend:
网络 问题 It does not belong to any of this network's subnets
这个问题很有意思,在配置文件中设置了网络段。也在服务下写了相同的地址段,它依然说我的网段不一样。
导致这个问题的原因是在自定义配置网段之前启动过相同的网络,在docker网络下它已经定义过网段了。再次重新手动指定网络地址时,会和之前的不一样。
只需要删除之前的网络,在重新运行docker-sompose up
一遍就可以了。
查看网络:
docker network ls
删除:
docker network rm docker_backend
此外 - backend ipv4_address: 172.18 .0 .10
不同的语法写在一起也会导致错误。
参考 docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ -v "/data/docker/web/www/:/data/docker/web/www/" \ certbot/certbot certonly -n --webroot \ -w /data/ docker/web/ www/test -d test.defectink.com \ -w /data/ docker/web/ www/html -d www.defectink.com \ -w /data/ docker/web/ www/index -d index.defectink.com \ -w /data/ docker/web/ www/api -d api.defectink.com
docker run --rm --name myadmin -d --network docker_backend --link mysql_db_server:mysql -e PMA_HOST=172.20 .0 .4 -p 3002 :80 phpmyadmin/phpmyadmin
certbot certonly -n --webroot -w /data/ docker/web/ www/html -d www.defectink.com --post-hook "docker restart apache"
certbot certonly -n --webroot -w /data/ docker/web/ www/html -d www.defectink.comcertbot certonly -n --webroot -w /data/ docker/web/ www/index -d index.defectink.comcertbot certonly -n --webroot -w /data/ docker/web/ www/test -d test.defectink.comcertbot certonly -n --webroot -w /data/ docker/web/ www/api -d api.defectink.com
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- Header实践-得拿下这个A
-
- /defect/header-practice-have-to-win-this-a.html
-
- Header安全检测 之前在学习HTML时候研究过X-Frame-Options
,它也是header头中的一个安全策略。用于给浏览器指示是否允许一个页面能否嵌入<iframe>
等嵌入元素。
下述所有apache2的操作都需要先启用header
模块(Model)才能使用。
所有参考/摘录来自于MDN
X-Frame-Options https://www.defectink.com/defect/HTML-practice-x-frame-option.html
Strict-Transport-Security HTTP Strict Transport Security
(通常简称为HSTS)是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源,而不是HTTP。
一个网站接受一个HTTP的请求,然后跳转到HTTPS,用户可能在开始跳转前,通过没有加密的方式和服务器对话,比如,用户输入http://foo.com或者直接foo.com。
这样存在中间人攻击潜在威胁,跳转过程可能被恶意网站利用来直接接触用户信息,而不是原来的加密信息。
网站通过HTTP Strict Transport Security通知浏览器,这个网站禁止使用HTTP方式加载,浏览器应该自动把所有尝试使用HTTP的请求自动替换为HTTPS请求。
Apache配置 在配置文件中添加:
Header always set Strict -Transport-Security "max-age=63072000; includeSubdomains;"
浏览器工作方式 你的网站第一次通过HTTPS请求,服务器响应Strict-Transport-Security
头,浏览器记录下这些信息,然后后面尝试访问这个网站的请求都会自动把HTTP替换为HTTPS。
当HSTS头设置的过期时间到了,后面通过HTTP的访问恢复到正常模式,不会再自动跳转到HTTPS。
每次浏览器接收到Strict-Transport-Security头,它都会更新这个网站的过期时间,所以网站可以刷新这些信息,防止过期发生。
Chrome、Firefox等浏览器里,当您尝试访问该域名下的内容时,会产生一个307 Internal Redirect(内部跳转),自动跳转到HTTPS请求。
语法 Strict -Transport-Security : max-age=<expire-time >Strict -Transport-Security : max-age=<expire-time >; includeSubDomainsStrict -Transport-Security : max-age=<expire-time >; preload
max-age=<expire-time>
:设置在浏览器收到这个请求后的秒的时间内凡是访问这个域名下的请求都使用HTTPS请求。 includeSubDomains
:如果这个可选的参数被指定,那么说明此规则也适用于该网站的所有子域名。preload
:查看 预加载 HSTS 获得详情。不是标准的一部分。Content-Security-Policy 内容安全策略 (CSP ) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS ) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
Apache配置 在配置文件中添加:
Header set Content-Security-Policy "default-src 'self' https://cdn.defectink.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com; img-src *; style-src 'self' 'unsafe-inline' https://cdn.defectink.com https://maxcdn.bootstrapcdn.com https://fonts.googleapis.com/; font-src 'self' https://cdn.defectink.com https://fonts.gstatic.com/ https://maxcdn.bootstrapcdn.com; form-action 'self' https://cdn.defectink.com; upgrade-insecure-requests;"
说白了就是添加允许加载的脚本、样式等内容的白名单。配置相应的值,以控制用户代理(浏览器等)可以为该页面获取哪些资源。比如一个可以上传文件和显示图片页面,应该允许图片来自任何地方,但限制表单的action属性只可以赋值为指定的端点。一个经过恰当设计的内容安全策略应该可以有效的保护页面免受跨站脚本攻击。
跨站脚本攻击 CSP 的主要目标是减少和报告 XSS 攻击 ,XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行,因为浏览器信任其内容来源,即使有的时候这些脚本并非来自于它本该来的地方。
CSP通过指定有效域——即浏览器认可的可执行脚本的有效来源——使服务器管理者有能力减少或消除XSS攻击所依赖的载体。一个CSP兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略所有的其他脚本 (包括内联脚本和HTML的事件处理属性)。
作为一种终极防护形式,始终不允许执行脚本的站点可以选择全面禁止脚本执行。
实例 一个网站管理者想要所有内容均来自站点的同一个源 (不包括其子域名)
Content-Security-Policy: default-src 'self'
一个网站管理者允许网页应用的用户在他们自己的内容中包含来自任何源的图片, 但是限制音频或视频需从信任的资源提供者(获得),所有脚本必须从特定主机服务器获取可信的代码.
Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
在这里,各种内容默认仅允许从文档所在的源获取, 但存在如下例外:
图片可以从任何地方加载(注意 “*” 通配符)。 多媒体文件仅允许从 media1.com 和 media2.com 加载(不允许从这些站点的子域名)。 可运行脚本仅允许来自于userscripts.example.com。 X-Content-Type-Options X-Content-Type-Options
响应首部相当于一个提示标志,被服务器用来提示客户端一定要遵循在 Content-Type
首部中对 MIME 类型 的设定,而不能对其进行修改。
注意: nosniff
只应用于 “script
“ 和 “style
“ 两种类型。事实证明,将其应用于图片类型的文件会导致与现有的站点冲突 。
Apache配置 在配置文件中添加:
Header set X-Content-Type -Options nosniff
语法 nosniff
下面两种情况的请求将被阻止:
Referrer-Policy Referrer-Policy
首部用来监管哪些访问来源信息——会在 Referer
中发送——应该被包含在生成的请求当中。
Apache配置 在配置文件中添加:
Header always set Referrer-Policy "no-referrer-when-downgrade"
语法 注意 Referer
实际上是单词 “referrer” 的错误拼写。Referrer-Policy
这个首部并没有延续这个错误拼写。
Referrer-Policy : no -referrerReferrer-Policy : no -referrer-when -downgradeReferrer-Policy : originReferrer-Policy : origin-when -cross -originReferrer-Policy : same-originReferrer-Policy : strict -originReferrer-Policy : strict -origin-when -cross -originReferrer-Policy : unsafe-url
Feature-Policy 这是一个实验中的功能
**Feature-Policy
**响应头提供了一种可以在本页面或包含的iframe上启用或禁止浏览器特性的机制。
Apache配置 在配置文件中添加:
Header always set Feature-Policy "vibrate 'self'; sync-xhr 'self' https://cdn.defectink.com https://www.defectink.com"
语法 Feature -Policy: <directive> <allowlist>
<allowlist>
*
: 允许在当前文档和所有包含的内容(比如iframes)中使用本特性。
'self'
: 允许在当前文档中使用本特性,但在包含的内容(比如iframes)仍使用原值。
'src'
: (只在iframe中允许) 只要在src 中的URL和加载iframe用的URL相同,则本特性在iframe中允许,
'none'
: 从最上层到包含的内容都禁止本特性。 <origin(s)>: 在特定的源中允许,源URL以空格分割。
*
: 本特性默认在最上层和包含的内容中(iframes)允许。
'self'
: 本特性默认在最上层允许,而包含的内容中(iframes)使用源地址相同设定。也就是说本特性在iframe中不允许跨域访问。
'none'
: 本特性默认在最上层和包含的内容中(iframes)都禁止。
*
(在所有源地址启用)或'none'
(在所有源地址禁用)只允许单独使用,而'self'
和'src'
可以与多个源地址一起使用。
所有的特性都有一个如下的默认的allowlist
*
: 本特性默认在最上层和包含的内容中(iframes)允许。'self'
: 本特性默认在最上层允许,而包含的内容中(iframes)使用源地址相同设定。也就是说本特性在iframe中不允许跨域访问。'none'
: 本特性默认在最上层和包含的内容中(iframes)都禁止。测试
为什么没有A+? 因为CSP的一个报错,拒绝加载内联的JS脚本。可以使用unsafe-inline
来启用内联脚本。但是启用了unsafe-inline
之后,就得不到A+了。
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-eval' https://maxcdn.bootstrapcdn.com https://ajax.googleapis.com https://cdn.defectink.com" . Either the 'unsafe-inline' keyword
参考 ]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- HTML
-
-
-
-
-
-
-
-
- Docker-构建属于自己的镜像
-
- /defect/docker-build-own-image.html
-
- 以前一直在使用别人构建好的镜像来使用Docker容器,在一次想搭建一个完整的Web环境时,发现使用过多容器非常难以管理。并且容器之间的交互通信变的困难。当然,也可以使用Docker Compose来捆绑多个镜像运行;不过对于运行服务较少的来说,使用Dockerfile来构建成一个镜像也是件好事。需求 首先,在构建一个镜像之前,需要先明白这个镜像将会包含哪些东西,运行哪些服务。目前主要是想在当前机器上跑一个hexo的blog。当然可以部署在Github,以前还写过一篇关于部署在Github的水文 。不过现在的想法是Github放一份,在本地服务器上也跑一个Server。
当然跑一个hexo是一件很简单的事情,使用Docker来部署也是为了想体验一下写Dockerfile
。目前有两个思路:
把node.js和hexo都部署在当前的宿主机,用Docker的Web服务器来跑宿主机生成的静态文件。
但是这样的话就不需要用到Dockerfile了,直接pull一个http服务的镜像就好了。
只在宿主机上使用Git来和Github同步文件,每次的生成和运行Web服务都放在Docker容器里。
目前打算尝试的一种方式,可以在每次写完文章后使用Docker构建,并且也可以尝试Dockerfile了。
具体需要什么使用软件,完全看自己的需求,需要用到什么,就安装什么。就像在当前的宿主机上安装软件一样。只不过是使用Dockerfile来构建时安装的而已。
构建自己的镜像 好在还可以使用Dockerfile来基于其他的镜像来构建属于自己的镜像。可以在其他的系统基础镜像上来在构建时就安装自己需要的软件服务等,这样就可以构建一个自己需要的镜像了。
使用基础镜像 构建时使用的第一个命令是FROM
命令。它会指定一个用于构建的基础镜像。这样就可以在基础镜像中使用自己喜欢的发行版,也解决了继承其他 Docker 镜像的途径 。
创建一个目录,或者clone
一个hexo博客等,在目录内编写一个Dockerfile
。
FROM alpine:latestMAINTAINER Defectink <i@defect.ink>
这里选择的是alpine系统作为基础镜像,主要原因是alpine是个超级轻量的系统,对于最为基础镜像可以有效的减少构建后镜像的大小。
除此之外,还有个MAINTAINER
命令,它是用来著名当前Dockerfile的作者的。Docker支持#
作为注释,使用起来很方便。
第一次的构建 编写了一个最基本的Dockerfile
之后,就是运行第一次的构建测试了。使用Docker
加上build
来构建指定的Dockerfile
为镜像。添加-t
参数来为构建后的镜像指定一个tag标签,也就是之后的镜像(REPOSITORY)名。最后命令指定的目录是包含刚刚写好的Dockerfile
文件的目录,被称作为“构建目录”。
当前系统下没有基础镜像alpine的话,在第一次运行时docker也会进行下载。
Sending build context to Docker daemon 64kBStep 1/2 : FROM alpine:latestlatest: Pulling from library/alpine89d9c30c1d48: Pull complete Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5aStatus: Downloaded newer image for alpine:latest ---> 965ea09ff2ebStep 2/2 : MAINTAINER Defectink <i@defect.ink> ---> Running in d572ac48c8f8Removing intermediate container d572ac48c8f8 ---> b8296646acaaSuccessfully built b8296646acaaSuccessfully tagged blog:latest
第一次的镜像构建已经完成了,虽然什么都没有进行定制,但已经迈出了第一步。
安装软件 迈出第一步之后,就可以开始考虑定制属于自己的镜像了。使用docker images
可以查看当前系统下的docker镜像。也能看到刚刚所构建的第一个镜像。
REPOSITORY TAG IMAGE ID CREATED SIZEblog latest b8296646acaa 19 minutes ago 5.55MBalpine latest 965ea09ff2eb 5 weeks ago 5.55MB
既然是定制属于自己的镜像,那么肯定是需要安装所需求的软件的。这里我想构建一个运行hexo的镜像,所以至少需要3款软件:
使用RUN
命令来在基础镜像上执行命令,像是安装软件等操作。由于alpine默认时区不是国内,还可以顺便修改下时区。可以使用RUN
来一次安装完所有需要的软件,不需要分开执行。
使用alpine的另个原因就是在它本身体积小的情况下,它安装软件还可以使用--no-cache
来减少缓存。
在容器内使用npm来安装hexo时会出现一个uid:0
的问题,npm会有生命周期,某个包会有生命周期来执行一些东西,安全起见会自动降级导致没有权限执行一些操作,通过``–unsafe-perm`参数来解锁该限制。
RUN apk update \ && apk upgrade \ && apk add --no-cache \ apache2 \ nodejs \ npm \ tzdata \ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && rm -rf /var/cache/apk/* \ && mkdir -p /data/DefectingCat.github.io \ && npm config set unsafe-perm true \ && npm install -g hexo
因为是基于一个操作系统上构建的镜像,所以在构建完成后可以使用Docker来运行一个“伪终端”,让我们可以直接在终端内进行一些修改和查看。值得注意的是,在“伪终端”里进行的操作只是在当前容器内的,不会被写入镜像。当前被关闭后,任何操作将不复存在。
在构建完后可以使用“伪终端”进入系统内查看一些信息,测试软件能否正常工作等。
docker run -it --rm blog
关于这里的一些参数:
-i
即使没有附加也保持STDIN 打开。
-t
分配一个伪终端。
--rm
在退出后立刻删除容器。
缓存 Sending build context to Docker daemon 64kBStep 1/5 : FROM alpine:latest ---> 965ea09ff2ebStep 2/5 : MAINTAINER Defectink <i@defect.ink> ---> Using cache ---> 92cd04f91315
在构建的时候可以在某一步(Step)下看到Using cache
。 当 Docker 构建镜像时,它不仅仅构建一个单独的镜像;事实上,在构建过程中,它会构建许多镜像。
输出信息中的每一步(Step),Docker都在创建一个新的镜像。同时它还打印了镜像ID: ---> 92cd04f91315
。这样的好处在于,我们修改Dockerfile
后重新构建镜像时,那些没有被修改的部分可以将上次构建的镜像当作缓存,加快构建的速度。
但是这也会有些小问题,Docker是根据Dockerfile
来判断构建时的变化的。但如果需要执行更新软件等操作,而Dockerfile
内的命令是没有变化时,Docker会继续使用以前的缓存,导致旧的软件还是被安装了。
所有在执行某些必要的操作时,不使用缓存也是极有好处的。在构建镜像时,使用--no-cache=True
即可 。
RUN
命令推荐使用一条命令完成尽可能多的操作,Dockerfile
中的每个命令都会被分为构建镜像的一步来执行,这样可以减少构建时的步数(Step)。Docker 镜像类似于洋葱。它们都有很多层。为了修改内层,则需要将外面的层都删掉。
第一次的运行 将所有的软件都安装、测试完后,就可以构建能够第一次运行的镜像了。在此之前,还需要配置需要运行的软件,例如使用hexo生成静态文件,启动apache等。
COPY DefectingCat.github.io /data/DefectingCat.github.io WORKDIR /data/DefectingCat.github.io RUN hexo g \ && cp -a public/* /var/www/localhost/htdocs EXPOSE 80 443 CMD ["/usr/sbin/httpd" ,"-f" ,"/etc/apache2/httpd.conf" ,"-DFOREGROUND" ]
COPY
将宿主机上的文件复制进容器内的目录。在安装软件时就已经使用RUN
来创建过需要的目录了。WORKDIR
切换工作的目录,和cd
类似;切换后RUN
等命令都会在当前目录下工作。EXPOSE
暴露需要使用到的端口。CMD
和RUN
类似,通常用于来启动容器服务。关于CMD
:
CMD
只能存在一条,根据运行的软件,它将占据最后容器输出的终端。因为容器并不像虚拟化或者物理机那样,可以使用守护进程;容器本身就是一个进程,容器内没有后台服务的概念。正确的做法是使用CMD
直接执行可执行文件,并且要求以前台形式运行。
当前的操作很简单,就是复制宿主机上git克隆下来的文件到容器的制定文件夹,然后使用hexo
来生成静态文件,最后复制到apache
的工作目录下。
到这里就可以来运行一个一次性的容器测试一下我们的服务是否运行正常了。如果上述都没有任何问题的话,现在打开浏览器就应该能看到hexo的blog了🎉。
docker run -p 80:80 --rm blog
到目前为止,Dockerfile应该是这样的:
FROM alpine:latestMAINTAINER Defectink <i@defect.ink>RUN apk update \ && apk upgrade \ && apk add --no-cache \ apache2 \ nodejs \ npm \ tzdata \ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && rm -rf /var/cache/apk/* \ && mkdir -p /data/DefectingCat.github.io \ && npm config set unsafe-perm true \ && npm install -g hexo COPY DefectingCat.github.io /data/DefectingCat.github.io WORKDIR /data/DefectingCat.github.io RUN hexo g \ && cp -a public/* /var/www/localhost/htdocs EXPOSE 80 443 CMD ["/usr/sbin/httpd" ,"-f" ,"/etc/apache2/httpd.conf" ,"-DFOREGROUND" ]
安装了一些必要的软件,同时也尽量的减少了镜像构建后的大小。
HTTPS 现代的网站应该都不会少的了SSL,也就是我们常见的https。目前自己的网站用的是最简单的LetsEncrypt,使用他家的工具Certbot来申请证书及其方便。在宿主机的环境下甚至还能自动配置。但是目前用的是Docker环境,在使用Dockefile构建时,是没有交互环境的。自动配置也可能无法生效。
生成证书 Certbot生成证书很是方便,在Docker环境下也是如此。使用官方的镜像可以很方便的生成:
sudo docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly
配合certonly
只获取证书,并-v
来将容器的目录映射到宿主机,这样就能在生成后把证书存到宿主机目录了。
生成时,也会有两种工作模式选择:
How would you like to authenticate with the ACME CA?- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -1: Spin up a temporary webserver (standalone)2: Place files in webroot directory (webroot)- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
分别是:
standalone模式:启动一个临时的webserver; webroot模式:将验证文件放到当前已有的webserver目录下; 如果当前没有正在运行的webserver,使用standalone模式是最为方便的。Certbot将自己运行一个临时的webserver完成认证。但是如果使用standalone模式,在运行需要添加一个映射的端口:
sudo docker run -it -p 80:80 --rm --name certbot \ -v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot certonly
因为Certbot启用了一个临时的webserver来验证域名解析,如果不把容器的80
端口映射出来的话,将无法完成验证。
在一切都没有任何问题之后,就能看到Congratulations了:
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/domain/fullchain.pem
根据官网的说法,证书均链接在/etc/letsencrypt/live
目录内。
/etc/letsencrypt/archive
and /etc/letsencrypt/keys
contain all previous keys and certificates, while /etc/letsencrypt/live
symlinks to the latest versions.
Mod_ssl 有了证书之后,apache还需要ssl的mod。alpine的镜像安装apache时是没有安装的ssl的mod。所以还需要在Dockerfile内添加一行,手动进行安装,包名为apache2-ssl
:
RUN apk update \ && apk upgrade \ && apk add --no-cache \ apache2 \ apache2-ssl \
在重新构建之前,还需要修改apache的ssl.conf
。如何取得ssl.conf
呢?我们只需要构建一个临时的alpine镜像,在容器内使用相同的命令安装一个apache与ssl mod,之后在/etc/apache2/conf.d
目录内就有ssl.conf
配置文件了。将其copy到宿主机内修改就好了。
apk add apache2-ssl
在启动命令内的httpd.conf
配置文件会包含ssl.conf
。所以只需要修改ssl.conf
,再在构建时将其copy到镜像内就好了。
httpd.conf
内的已有配置:
IncludeOptional /etc/ apache2/conf.d/ *.conf
那么,如何优雅的将容器内的ssl.conf
copy出来呢?
可以在先将容器放在后台运行:
docker run -id test
然后使用docker自带的docker cp
命令来copy到宿主机的目录:
docker cp 253d3ca34521:/etc/apache2/conf.d/ssl.conf /root
当然也可以直接打开,然后记录文件内容再复制出来。
有了Mod_ssl组件之后,就可以配合SSL证书来对网站进行加密了。既然能将默认的ssl.conf
复制出来,就可以对其修改然后在生成镜像时再复制会容器内的原目录。
剩下对于SSL的配置就和给宿主机配置加密一样了,几乎没有什么不同。主要就是在ssl.conf
中填上正确的证书目录:
SSLCertificateFile /etc/ letsencrypt/live/ defect.ink/fullchain.pemSSLCertificateKeyFile /etc/ letsencrypt/live/ defect.ink/privkey.pem
Let’s Encrypt生成的证书在路径下还会有个fullchain.pem
,这是一整个证书链。在配置文件中只需要这个证书和一个私钥privkey.pem
就好。
跳转至443 在有了https之后,如果不需要80端口还能继续访问。可以使用301跳转来将访问80端口的访客都跳转到443。Apache的mod_rewrite可以轻松的实现针对各种条件的跳转。
mod_rewrite的作用很多,能设置的条件也可以很复杂。当然配置个简单的跳转不是非常的复杂。
RewriteEngine on RewriteCond %{SERVER_NAME} =defect.inkRewriteRule ^ https://%{SERVER_NAME} %{REQUEST_URI} [END,NE,R=permanent]
RewriteEngine
打开跳转引擎;RewriteCond
跳转的条件;这里设置当域名为defect.ink
时,执行下面的跳转动作;RewriteRule
跳转的动作;当符合上面的条件时,执行添加httpshttps://%{SERVER_NAME}%{REQUEST_URI}
。而后面的变量保持不动。这行配置是来自于certbot的自动配置中的,在配置宿主机的ssl时可以选择全部跳转。然后它就会帮我们自动配置了。对其进行简单的修改就可以作用与其他的配置文件了。
这几行推荐是写在httpd.conf
的末尾,也就是IncludeOptional /etc/apache2/conf.d/*.conf
的上方。虽然ssl.conf也会被include进来,但是还是感觉写在这里要方便一点。
然后将httpd.conf
和ssl.conf
一样在构建时复制到容器内就ok了。
&& cp -a ssl.conf /etc/apache2/conf.d/ \&& cp -a httpd.conf /etc/apache2/
Renew Let’s Encrypt的证书虽然很方便,但是一次只能生成三个月有效期的证书。使用和生成差不多的方法renew证书就好了。
sudo docker run -it -p 80 :80 --rm --name certbot \ -v "/data/docker/apache/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot renew
想要自动化执行话,可以使用crontab来定时运行。
全部的Dockerfile 这时候的配置文件看起来应该是这个样子的:
FROM alpine:latestMAINTAINER Defectink <i@defect.ink>RUN apk update \ && apk upgrade \ && apk add --no-cache \ apache2 \ apache2-ssl \ nodejs \ npm \ tzdata \ && cp -r -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && rm -rf /var/cache/apk/* \ && mkdir -p /data/DefectingCat.github.io \ && npm config set unsafe-perm true \ && npm install -g hexo COPY DefectingCat.github.io /data/DefectingCat.github.io WORKDIR /data/DefectingCat.github.io RUN hexo g \ && cp -a public/* /var/www/localhost/htdocs/ \ && cp -a ssl.conf /etc/apache2/conf.d/ \ && cp -a httpd.conf /etc/apache2/ EXPOSE 80 443 CMD ["/usr/sbin/httpd" ,"-f" ,"/etc/apache2/httpd.conf" ,"-DFOREGROUND" ]
启动! docker run -id --name="blog" -v /etc/letsencrypt/:/etc/letsencrypt/ -p 80:80 -p 443:443 blog
全部操作完了,启动命令也随着操作变得更加的复杂了。
-id
扔到后台;--name
容器别名;-v
映射之前的ssl证书的目录;-p
80和443都需要映射;优化 一些比较方便的命令。
删除所有<none>
的镜像:
docker rmi $(docker images -f "dangling=true" -q)
停止所有容器,删除所有容器:
docker kill $(docker ps -q) ; docker rm $(docker ps -a -q)
停止所有容器,删除所有容器,删除所有镜像 :
docker kill $(docker ps -q) ; docker rm $(docker ps -a -q) ; docker rmi $(docker images -q -a)
参考 ]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- 修改Windows端iCloud云盘存储位置
-
- /defect/modify-icloud-storage-location-on-windows.html
-
- 自从有了水果之后,除了天天被人嘲讽之外,还花了大手笔买了一个月6块的iCloud 50GB存储空间。但是光有手机来用还是略微有点大。以前一直以为iCloud就是手机上存个照片的,都忘了它其实也是一个云盘来着。于是决定利用一下剩下的空间,在Windows也用它来同步。果然在试用了一段时间过后,水果没有让我失望过。iCloud照片可以跟着Windows系统的【图片】文件夹跑,但是iCloud云盘就不那么听话了,它只会在User
目录下直接怼一个目录,并且无法修改 ,连个设置都没有。对于我这种C盘小的可怜的来说,就非常难受了。
所以只能用最基本的方法,创建符号链接 : 软链接来解决问题了。
操作 找到当前的iCloud云盘文件夹位置,并剪切到目的位置。当然先复制更加稳妥。 关掉不听话的iCloud。 以管理员身份运行cmd,创建符号链接到新的存储位置。需要删除已经存在c盘的文件夹。 C:\WINDOWS \system32 >mklink /D C :\Users \Defectink \iCloudDrive D :\iCloudDrive 为 C :\Users \Defectink \iCloudDrive <<===>> D :\iCloudDrive 创建的符号链接
重新运行iCloud。 没有退出按钮?
如果先退出再复制文件可能会导致意外
启动文件夹 我的iCloud不知道为什么就是无法开机自启,无奈只好放到Windows的启动文件夹里了。效果还可以
shell:startup
]]>
-
-
-
-
- 踩坑
-
-
-
-
-
-
- Windows
-
-
-
-
-
-
-
-
- ASCII在线视频流
-
- /defect/online-ascii-video.html
-
- 什么是ASCII?来自百度百科的解释: ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
应该很多小伙伴们都非常熟悉ASCII码了,它也是现今最能玩的一套编码了吧(雾💊
那么ascii视频流又是啥呢?
这是来自某位大佬胡乱起的名字。🤣
那么如何安装呢? 根据大佬的文章 与开源项目。首先我们需要:
ffmpeg hit9/img2txt HFO4/plus1s.live node.js/Go/Python运行环境 使用ffmpeg截取视频片段 安装ffmpeg:
CentOS 由于CentOS没有官方FFmpeg rpm软件包。但是,我们可以使用第三方YUM源(Nux Dextop)完成此工作。
sudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.rosudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpmsudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.rosudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
Ubuntu Ubuntu的源里默认就有ffmpeg的软件包,所以我们直接安装就ok了。
apt install ffmpeg
拥有了ffmpeg之后,我们可以使用如下命令:
ffmpeg -i demo.mp4 -r 5 -ss 00 :01 :13 -t 00 :00 :15 %03 d.png
将demo视频的第1分13秒后的15秒以每秒5帧的速度保存为图像,图像名格式为001.png 002.png …… 效果如下:
➜ ~ ls time001.png 005.png 009.png 013.png 017.png 021.png 025.png 029.png 033.png 037.png 041.png 045.png 049.png 053.png 057.png 061.png 065.png 069.png 073.png002.png 006.png 010.png 014.png 018.png 022.png 026.png 030.png 034.png 038.png 042.png 046.png 050.png 054.png 058.png 062.png 066.png 070.png 074.png003.png 007.png 011.png 015.png 019.png 023.png 027.png 031.png 035.png 039.png 043.png 047.png 051.png 055.png 059.png 063.png 067.png 071.png 075.png004.png 008.png 012.png 016.png 020.png 024.png 028.png 032.png 036.png 040.png 044.png 048.png 052.png 056.png 060.png 064.png 068.png 072.png
使用修改过的hit9/img2txt将图像转换为ASCII画 原版hit9/img2txt只能单张转换,我稍微改了下,可以批量转换并保存为txt。修改后的版本:https://github.com/HFO4/img2txt/blob/gh-pages/img2txt.py
可能大佬都是说改就改的吧。 完事我们clone下来后修改img2txt.py第246行的目录为上一步存放图像的目录:
246 imgname = "/root/time/" +str(i).zfill(3 )+".png"
然后再执行:
pip install img2txt.py python img2txt.py h
稍等片刻,ASCII字符文件便会存放到与img2txt.py同级的pic目录下。若提示无pic文件夹导致的错误,手动创建一个名为pic
的文件夹再运行一次即可。
部署在线服务 最后,使用大佬的HFO4/plus1s.live 来部署在线播放的服务。
将上一步使用img2txt的pic文件夹中的图片放到改项目下的pic文件夹内,然后修改stream.go的第13行为你得到的单帧图像的总个数。保存后执行:
go build stream .go./stream
然后程序会默认开放一个暴力的端口,使用curl 您的ip:1926
命令即可查看效果。
另一款强大的软件 📺ASCIIPlayer : Golang写的ASCII码播放器
如同作者自己所说的,该软件是Go语言写的一款强大的Ascii码的转码加播放器。
安装 go get -u github.com /qeesung/asciiplayer
安装后若提示:
zsh: command not found: asciiplayer
则在当前目录下会缓存一个go
文件夹,在go/bin/
文件夹内会有一个可执行的asciiplayer。我们将其copy至/usr/bin/
目录下,并重连ssh即可解决。
cp -a asciiplayer /usr/ bin
三种工作模式 该软件强大的地方就是在此了,对于转换为ascii码,它拥有三个工作模式:
输出到一个一般文件中(Encode模式): 这里我们只能逐帧,逐像素的将转化以后的ASCII图像写到文件中去。 输出到终端(Play模式): 直接将转换以后的图像按照一定的频率输出到终端即可。 输出到远端客户端(Server模式): 这里和输出到终端的原理类似,只是输出到了远端客户端所在的终端。 +---------------+ +---------+ | | | | +------> Gif Decoder | +---> Encoder +---> file | | | | | | | +---------------+ | +---------+ | +---------------+ +-------------+ | +---------+ | | | | | | | |Input File+------> Image Decoder +---> Frames +-->+ Image2ASCII +->ASCII Frames-+---> Player +---> stdout | | | | | | | | | +---------------+ +-------------+ | +---------+ | +---------------+ | +---------+ | | | | | | +------> Video Decoder | +---> Server +---> socket | | | | +---------------+ +---------+
以至于它一款软件就能够直接实现我们是上述将视频中抽去图片再挨个转换为文本的ASCII码的工作了。除了不能将我们需要的输出为文本保存以外,其他都很完美。 唯一一个缺点就是目前还不支持直接读取视频文件,只能先使用ffmpeg将视频转换为gif中,在用此软件读取。作者目前也说后续会支持视频的。🎉
常用的命令 通过适配屏幕的方式播放GIF
asciiplayer play demo .gif
缩小为原来的十分之一,然后播放GIF
asciiplayer play demo.gif -r 0 .1
缩放成固定的长和宽,然后播放GIF
asciiplayer play demo.gif -w 100 -h 40
播放一个PNG图片
asciiplayer play demo .png
将一个GIF文件demo.gif编码为ASCII的Gif文件output.gif
asciiplayer encode demo .gif -o output .gif
指定输出ASCII字符大小的情况下,讲一个GIF文件demo.gif编码成ASCII的GIF动图文件output.gif
asciiplayer encode demo.gif -o output .gif
将GIF动图demo.gif缩放为原来的十分之一,然后编码成ASCII的GIF动图文件output.gif
asciiplayer encode demo .gif -o output .gif -r 0.1
编码一个jpeg文件,然后输出一个ASCII的output.png文件
asciiplayer encode demo .jpeg -o output .png
输入demo.gif,并以默认端口8080启动一个http服务器
asciiplayer server demo.gif
输入demo.gif,并以自定义端口8888启动一个http服务器
asciiplayer server demo.gif --port 8888
输入一个demo.png图片,并且启动http 服务器
asciiplayer server demo.png
大佬们 ASCIIPlayer : Golang写的ASCII码播放器 构建一个在线ASCII视频流服务
Try it ? curl time .defect .ink :1926
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Tools
-
-
-
-
-
-
-
-
- bark-水果自定义通知大法🍎
-
- /defect/bark-custom-notification-for-apple.html
-
- ding~
用了一段时间的水果了,发现它的通知来的还是非常及时的。基本上只要连接了网络,通知都不会落下。简单了解过IOS的通知机制:APP→水果服务器→你的机器。也就是说这三个步骤都能够正常通信的情况下,我们的机器就能正常的收到通知。
在Android平台也有类似的通知机制,也是由服务端来推送通知到我们的机器,从而到达APP可以不挂后台的情况下收到推送。但是为什么国内的UI用不了我就不清楚了。
给自己发通知🐾 上述我们简单了解到,既然是由APP控制的推送通知,那么我们既然想要自定义通知就非常简单了。只需要一个能够由我们控制的APP就可以了。
App Store中有位大佬开发的Bark 就是为了这事存在的,它的存在目的就是为了让我们自己给自己推送通知。、
Github:Bark
默认它提供了自己的服务器,如果我们需要推送一些较为隐私的消息,可以使用自建服务端。它提供了http接口,后端简单调用即可给自己的水果设备发送推送。
服务端 服务端是一个开源项目,bark-server ,这是一个非常简单易用的服务端软件。
部署非常简单,并且也有docker的部署方式:
docker run -dt --name bark -p 8080:8080 -v `pwd `/bark-data :/data finab/bark-server
当然和我一样的传统用户喜欢直接部署在主机上的也很方便。相对来说,这种简单易用的服务端的部署和使用docker也差距不大,docker不一定会方便到哪去,说不定还会更加难用。
我们只需要简单的四步就可以部署完成。
1、Download precompiled binaries from the releases page 2、Add executable permissions to the bark-server binary: chmod +x bark-server
3、Start bark-server: ./bark-server -l 0.0.0.0 -p 8080 -d ./bark-data
4、Test the server: curl localhost:8080/ping
对于第三步来说,-l
定义是监听的地址,-p
为监听的端口,./bark-data
默认使用/data
目录,可以不定义。
当我们使用测试时,返回这样的结果{"code":200,"data":{"version":"1.0.0"},"message":"pong"}
就意味着我们的服务端已经运行成功了。
此时,我们可以在客户端软件中添加我们的服务器地址http://server-ip:8080
即可,正常通信后,软件界面上的服务器地址就会全部都变成我们自己刚刚搭建的服务器。
这个时候应该就已经能够正常的使用了。但是既然自己搭建服务端时为了推送一些较为隐私的消息。那么只使用http就显得有点白忙活了。
https 目前还不太清楚作者有没有直接在服务端添加证书的操作,从目前的文档来看,https需要我们使用其他的思路了。
我目前实现的方法是,既然bark也是基于http接口的,那么我就可以将其只监听127.0.0.1
,然后使用我的前端apache给它做反代。毕竟apache部署个证书是非常简单的操作。
这样可以达到apache和bark的交互只在机器内部工作,而对外开放的apache使用证书添加上https。达到传输加密的效果。
这样是可以实现了完全加密推送消息到我的设备上了,至于水果那段不太清楚,应该也是加密的吧。
我这里使用的是免费的Let’sEncrypt的证书,他家的不但免费,还有及其方便的脚本直接给apache或其他web服务端配置好证书以及配置文件。并且证书都是保存在相应的目录的,有其他需求时,可以随时使用。
唯一一个缺点就是一次的证书只有3个月时长,需要不停的续期,好在官方也有自动续期的脚本。不是特别的麻烦。
~ cert.pem chain.pem fullchain.pem privkey.pem README
反代 apache已经有了证书了,接下来直接反代到我们的bark服务端就ok了。
这是我的配置文件:
VirtualHost *:80 ProtocolsHonorOrder On Protocols h2 h2c http/1.1 Servername api2.defectink.com ServerAdmin webmaster@localhost ProxyRequests Off ProxyMaxForwards 100 ProxyPreserveHost On ProxyPass / http://127.0.0.1:8181/ ProxyPassReverse / http://127.0.0.1:8181/ Proxy * Order Deny,Allow Allow from all /ProxyRewriteEngine onRewriteCond %{SERVER_NAME} =api2.defectink.comRewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]/VirtualHost
只需要注意反代的地址就ok了,也就是我们bark监听的地址。
如果一切都没啥问题的话,我们直接打开刚刚配置好证书的apache站点,bark应该就是能够正常运行了。
Systemd 前不久才水过一篇systemd的水文,简单试了几个服务,发现还是非常方便的。尤其是像bark这样的占用一个终端,用screen又不太方便找的程序。给他使用systemd来控制更是非常的方便。
把配置文件直接放到/etc/systemd/system/
目录下就可以了。只需要修改一下ExecStart
字段的启动路劲就可以正常使用了。
[Unit]Description =bark note for ipDocumentation =https://www.defectink.comAfter =network.target[Service]Type =simplePIDFile =/run/bark.pidExecStart =/data/bark-server_linux_amd64 -l 127.0.0.1 -p 8181ExecReload =/bin/kill -s HUP $MAINPID ExecStop =/bin/kill -s QUIT $MAINPID PrivateTmp =true [Install]WantedBy =multi-user.target
具体的效果就和平时使用其他的systemd控制的软件一样了,并且不用再那么麻烦了。
]]>
-
-
-
-
- 踩坑
-
-
-
-
-
-
- Tools
-
-
-
-
-
-
-
-
- AliOssForTypecho
-
- /defect/alioss-for-typecho.html
-
- 原作大佬:最近从辣鸡七牛换到了阿里云的oss,对于我们使用阿里云的ECS来说,oss支持直接内网访问还是很友好的。
存储换了之后,于是找到了大佬的这款插件。可是大佬当初写插件的时候有些地方不太符合个人的使用习惯。比如存储的目录下都会给每张图片单独生成要一个文件夹。
虽然看到大佬blog下已经有留言了,但是那都是去年的事了。
当时是因为阿里云还没有检测object是否存在的sdk,大佬估计也是没有时间来跟这阿里云的sdk持续更新。就在18年10月份阿里云才更新了判断文件是否存在的php sdk。
对于我这种0基础没入门的php玩家,修改太多也太麻烦,也不会。于是只做了一些简单的修改
去除每个图片随机创建一个文件夹,但是没有是否存在的检测,上传时要确保文件不会重名。 添加图片处理样式,支持自定义规则。 更新了最新的OssClient(虽然我不知道它怎么用 为什么不做object存在检测?
当前文件夹是按“年-月-日”来分层的,也就是说存在重名的文件的时间段只有一天内上传的文件才有机会重名。 不会 主要是不会 阿里云的判断文件是否存在 文档,有兴趣的大佬可以试试。
下载地址:
]]>
-
-
-
-
- 踩坑
-
-
-
-
-
-
- typecho
-
-
-
-
-
-
-
-
- Gitlab尝鲜
-
- /defect/try-the-gitlab.html
-
- Gitlab?GitLab 是由GitLab Inc.开发,使用MIT许可证 的基于网络 的Git 仓库 管理工具,且具有wiki 和issue跟踪 功能。
它是一款和常见的Github很像仓库管理工具,大体使用上和Github很像。前端页面也很好看,主要的是安装非常的方便,它集成了自身需要的nginx的服务端。
起初是由Ruby写成,后来部分由Go语言重写。
最早,它是完全免费的开源软件,按照 MIT 许可证分发。毕竟人家是公司,后来Gitlab被拆分成GitLab CE(社区版)和 GitLab EE(企业版)。和如今的模式一样,ce是完全免费使用的社区版,而ee是可以进行试用且更多功能的收费版。
安装部署 官方 拥有详细的安装操作文档,并且对于不同的Linux发行版也有着不同的软件仓库源。除此之外,我们还可以选择其他的安装方式,如Docker等。
我当前是部署在Ubuntu上的,系统信息:
官方是推荐系统空闲内存在4GB以上的,对于类似我们这样的个人使用的较少的来说,推荐空闲内存是2GB以上。毕竟它会自己运行一套nginx、redis等服务端。
自家的开源地址:Gitlab
相对于从源码安装来说,自家提供的相应的软件包更加的方便,也更不会容易出错。我们只需要选择相应的操作系统即可。
这里仅以Ubunt示例:
首先安装需要的相关依赖:
sudo apt-get update sudo apt-get install -y curl openssh-server ca-certificates
如果我们不使用外部的SMTP来发邮件的话,Gitlab可以使用postfix来进行发邮件。当然对我们完全不需要发邮件的这个需求的话,这步完全可以跳过。
sudo apt-get install -y postfix
基本依赖安装完后,随后可以添加Gitlab的源来进行安装软件了:
curl https:// packages.gitlab.com/install/ repositories/gitlab/gi tlab-ce/script.deb.sh | sudo bashcurl https:// packages.gitlab.com/install/ repositories/gitlab/gi tlab-ee/script.deb.sh | sudo bash
注意ce和ee的区别
接下来,我们就可以使用apt
来进行安装GItlab-ce了。修改下方命令的https://gitlab.example.com
为自己Gitlab运行的域名。安装程序将自动配置该网址启动Gitlab
对于需要启用https
的小伙伴们,Gitlab可以自动请求[Let’s Encrypt ]的证书,方便我们一步到位。当然我们也可以使用自己的证书。
sudo EXTERNAL_URL ="https://gitlab.example.com" apt-get install gitlab-ce
到这里就安装的差不多了,此时我们可以打开自己的Gitlab。第一次访问时会被重定向到设定root
密码的界面。设置完成后我们的Gitlab就安装完成了。初始管理员的账户就是root
由官方给我们提供的安装方式是不是相对来说非常的简单呢?
使用
简洁多彩的界面也时非常的好看的。默认没有配置邮件的情况下是可以随意注册的,我们也可以在后台配置里关闭自动注册,作为一个私人的git仓库。也可以手动添加用户给想尝鲜的小伙伴们。
当然,Gitlab只是一个仓库源的管理工具,提供了类似与Github的功能。对于我们终端使用git来说,还是和Github一模一样。并且我们可以将其部署在国内的主机上,来提升某些情况到Github速度奇慢无比的问题。
启动与管理 $ sudo gitlab-ctl reconfigure$ sudo gitlab-ctl status$ sudo gitlab-ctl stop$ sudo gitlab-ctl restart$ sudo ps aux | grep runsvdir
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- systemd的基础操作
-
- /defect/basic-knowledge-of-systemd.html
-
- 什么是systemd?Systemd是我们常用的一些Linux发行版中常见的一套中央化系统及设置管理程序(init),包括有守护进程 、程序库 以及应用软件。
我们经常使用systemctl start apache2
来启动一些服务或者应用软件时,使用到的就是Systemd的一部分。
当前绝大多数的Linux发行版都已采用systemd代替原来的System V 。
学习它的作用? 它能够方便的对一些软件运行进行管理,经常使用systemctl
的同学们可能会比较了解,譬如查看运行状态,设置开机自启等操作。
最近尝鲜了Ubuntu,但是遇到个新的问题。在Ubuntu 18.10中,已经将以前的开机自启动的脚本/etc/rc.local
去掉了。无意中看到使用systemd来控制开机自启应用程序。
后来就顺便尝试进一步了解一下systemd,毕竟还是比较有用的。
开机运行启动的原理 一些支持systemd的软件,在安装时会在/usr/lib/systemd/system
目录下添加一个支持systemd的配置文件。当我们使用systemctl enable apache2
时,就相当于将/usr/lib/systemd/system
目录下的配置文件添加一个符号链接,链接到/etc/systemd/system
目录。
当我们的系统开机时,systemd会执行/etc/systemd/system
目录下的配置文件,以达到开机自启的效果。
最近发现当前较新的发行版,使用enable命令时,创建的链接目录为:
~ systemctl enable httpd Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib /systemd /system /httpd .service .
配置文件 几条常用且熟悉的sysemctl的命令这里就不在详细介绍了。这里直接了解一下最核心的部分,配置文件。
早在很久以前,对于Nyncat 这篇文章,里面就使用到自己编写systemd的配置文件来达到对nyancat这个服务的详细控制。(虽然当时我不理解配置文件说的啥…
上述了解到,配置文件一般情况下出现在两个地方:/usr/lib/systemd/system
目录和/etc/systemd/system/multi-user.target.wants
目录。对于完全不了解配置文件的情况下,我们可以先在这两个目录找个案例了解一下。
某些Ubuntu的发行版可能在/lib/systemd/system
目录下保存配置文件
例如,CentOS的httpd:
[Unit]Description =The Apache HTTP ServerAfter =network.target remote-fs.target nss-lookup.targetDocumentation =man:httpd(8)Documentation =man:apachectl(8)[Service]Type =notifyEnvironmentFile =/etc/sysconfig/httpdExecStart =/usr/sbin/httpd $OPTIONS -DFOREGROUNDExecReload =/usr/sbin/httpd $OPTIONS -k gracefulExecStop =/bin/kill -WINCH ${MAINPID} KillSignal =SIGCONTPrivateTmp =true [Install]WantedBy =multi-user.target
我们可以了解到,它基本上分为三个区块:Unit
、Service
和Install
区块。
Unit区域 Unit区域用于解释启动顺序与依赖关系。可以直观的看到它有几个字段:
Description After Documentation 第一眼看上去可能都是比较乱七八糟的,根本不知道它在说啥。但是我们拆开来一个一个了解,就会发现它意义非常的简单。
Description :用于给出当前服务的简单描述。通常我们查看服务状态时,都会在第一行看到这么一句话:
httpd.service - The Apache HTTP Server
这就是Descipton
字段的作用,一句话对当前服务的简单介绍。
After :该字段是有关于启动顺序的字段,从字面意思我们就应该大概了解到,这个值应该是定义当前服务应该启动在哪些服务之后。
上述配置文件该值解释就是:当network.target remote-fs.target nss-lookup.target
这些服务需要启动,那么当前的httpd.service
应该在他们之后启动。
相对的,和After
对应的还有个Before
字段。了解了After
这个Before
就应该很容易理解了。完全和After
相对的意思,定义httpd.service
应该在哪些服务之前启动。
After和Before只关乎到服务的启动顺序,并不关乎到依赖关系。
Documentation :该字段比较简单,和Descripton
作用差不多。它的值用于给出当前服务的文档的位置。
当前配置文件中并没有说明依赖关系的字段。依赖关系和启动顺序都是写在当前这个Unit区域的,它俩非常像象,但是作用不同。
依赖关系有两个字段进行控制:Wants
和Requiers
。
Wants :表示弱依赖关系,即使该值内的服务启动失败,也不影响当前服务的继续运行。
Requires :表示强依赖关系,如果该值内的服务无法运行,那么当前服务也将停止。
打个比方:
当前的httpd.service需要依赖mysql来存储数据。如果在配置文件中它只定义了在mysql之后启动。而没定义依赖关系,那么当mysql出现错误停止时,在重新启动期间,当前的httpd将无法与mysql建立链接。
这里只是打个比方帮助我们更好的了解,实际情况下httpd在通常和mysql是没有这样的依赖关系的🍥。
Service区域 Service区域是主要的一部分,主要控制软件的启动停止等,都是在此部分声明的。也就是定义了如何启动当前的服务。
许多软件都有环境参数文件,使用EnvironmentFile
字段便可以定义环境参数。
EnvironmentFile
字段:指定当前服务的环境参数文件。该文件内部的key=value
键值对,可以用$key
的形式,在当前配置文件中获取。
例如,启动sshd
,执行的命令是/usr/sbin/sshd -D $OPTIONS
,其中的变量$OPTIONS
就来自EnvironmentFile
字段指定的环境参数文件。
除此之外,Service区域还有一些关于控制软件行为的一些字段:
ExecStart
字段:定义启动进程时执行的命令。ExecReload
字段:重启服务时执行的命令ExecStop
字段:停止服务时执行的命令ExecStartPre
字段:启动服务之前执行的命令ExecStartPost
字段:启动服务之后执行的命令ExecStopPost
字段:停止服务之后执行的命令所有的启动设置之前,都可以加上一个连词号(-
),表示”抑制错误”,即发生错误的时候,不影响其他命令的执行。比如,EnvironmentFile=-/etc/sysconfig/sshd
(注意等号后面的那个连词号),就表示即使/etc/sysconfig/sshd
文件不存在,也不会抛出错误。
此外,Service中还有几个比较重要的字段
Type 字段,它有如下一些值:
simple(默认值):ExecStart
字段启动的进程为主进程 forking:ExecStart
字段将以fork()
方式启动,此时父进程将会退出,子进程将成为主进程 oneshot:类似于simple
,但只执行一次,Systemd 会等它执行完,才启动其他服务 dbus:类似于simple
,但会等待 D-Bus 信号后启动 notify:类似于simple
,启动结束后会发出通知信号,然后 Systemd 再启动其他服务 idle:类似于simple
,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合 Install区域 Install
区块,定义如何安装这个配置文件,即怎样做到开机启动。
WantedBy
字段:表示该服务所在的 Target。
Target
的含义是服务组,表示一组服务。WantedBy=multi-user.target
指的是,sshd 所在的 Target 是multi-user.target
。
这个设置非常重要,因为执行systemctl enable sshd.service
命令时,sshd.service
的一个符号链接,就会放在/etc/systemd/system
目录下面的multi-user.target.wants
子目录之中。
Systemd 有默认的启动 Target。
$ systemctl get-defaultmulti-user.target
一般来说,常用的 Target 有两个:一个是multi-user.target
,表示多用户命令行状态;另一个是graphical.target
,表示图形用户状态,它依赖于multi-user.target
。官方文档有一张非常清晰的 [Target 依赖关系图](https://www.freedesktop.org/software/systemd/man/bootup.html#System Manager Bootup)。
Target 也有自己的配置文件。
$ systemctl cat multi-user.target[Unit]Description=Multi-User SystemDocumentation=man:systemd.special(7)Requires=basic.targetConflicts=rescue.service rescue.targetAfter=basic.target rescue.service rescue.targetAllowIsolate=yes
详细的字段解释 [Unit]Description : 服务的简单描述Documentation : 服务文档Before 、After :定义启动顺序。Before =xxx.service,代表本服务在xxx.service启动之前启动。After =xxx.service,代表本服务在xxx.service之后启动。Requires:这个单元启动了,它需要的单元也会被启动;它需要的单元被停止了,这个单元也停止了。Wants:推荐使用。这个单元启动了,它需要的单元也会被启动;它需要的单元被停止了,对本单元没有影响。
[Service]Type =simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。Type =forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile =,以便systemd能够跟踪服务的主进程。Type =oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit =yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。Type =notify:与 Type =simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。Type =dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。Type =idle: systemd会等待所有任务(Jobs)处理完成后,才开始执行idle类型的单元。除此之外,其他行为和Type =simple 类似。PIDFile:pid文件路径ExecStartPre:停止服务时执行的命令ExecStart:指定启动单元的命令或者脚本,ExecStartPre和ExecStartPost节指定在ExecStart之前或者之后用户自定义执行的脚本。Type =oneshot允许指定多个希望顺序执行的用户自定义命令。ExecReload:指定单元停止时执行的命令或者脚本。ExecStop:指定单元停止时执行的命令或者脚本。ExecStopPost:停止服务之后执行的命令ExecStartPost:启动服务之后执行的命令PrivateTmp:True 表示给服务分配独立的临时空间Restart:这个选项如果被允许,服务重启的时候进程会退出,会通过systemctl命令执行清除并重启的操作。RemainAfterExit:如果设置这个选择为真,服务会被认为是在激活状态,即使所以的进程已经退出,默认的值为假,这个选项只有在Type =oneshot时需要被配置。
[Install]Alias :为单元提供一个空间分离的附加名字。RequiredBy:单元被允许运行需要的一系列依赖单元,RequiredBy列表从Require获得依赖信息。WantBy:单元被允许运行需要的弱依赖性单元,Wantby从Want列表获得依赖信息。Also :指出和单元一起安装或者被协助的单元。DefaultInstance:实例单元的限制,这个选项指定如果单元被允许运行默认的实例。
重启 $ sudo systemctl daemon-reload$ sudo systemctl restart foobar
]]>
-
-
-
-
- Linux
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- 公开密钥密码学🔑
-
- /defect/public-key-cryptgraphy.html
-
- GPG/PGP赛高!什么是非对称加密? 人类的历史上加密走了很长的一段路程。想尽了各种办法来保护自己那不想让不该知道的人知道的东西。 加密这东西,在密码学中最直白的解释就是将一般的明文信息改变为难以读取的内容,使其不可读的过程只有拥有解密方法的对象,经由解密过程,才能将密文还原为正常可读的内容。
大概在1970年代中期,所谓的“强加密”的使用开始从政府保密机构延申至公共的领域了,也就是说开始到我们大众都开始接触了。当今世界,加密已经是我们的日常生活中常常见到的东西了。
例如我们常常访问的带有SSL/TLS的网站,这也是非对称加密的一种。 所谓的对称加密,它也是密码学中的一种。但他与对称加密不同的是,它需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密,另一个则用作解密。使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。
由于加密和解密需要两个不同的密钥,故被称为非对称加密; 不同于加密和解密都使用同一个密钥的对称加密。虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个;因此其中一个可以公开,称为公钥,任意向外发布;不公开的密钥为私钥,必须由用户自行严格秘密保管,绝不透过任何途径向任何人提供,也不会透露给被信任的要通信的另一方。
加密 如果任何人使用公钥加密明文,得到的密文可以透过不安全的途径(如网络)发送,只有对应的私钥持有者才可以解密得到明文;其他人即使从网络上窃取到密文及加密公钥,也无法(在数以年计的合理时间内)解密得出明文。
典型例子是在网络银行或购物网站上,因为客户需要输入敏感消息,浏览器连接时使用网站服务器提供的公钥加密并上传数据,可保证只有信任的网站服务器才能解密得知消息,不必担心敏感个人信息因为在网络上传送而被窃取。
在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一条钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有信箱主人拥有钥匙可以打开信箱,这就视为私钥。
常见的公钥加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法(英语:Elliptic Curve Cryptography, ECC)。使用最广泛的是RSA算法(由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来)是著名的公开秘钥加密算法,ElGamal是另一种常用的非对称加密算法。
加密过程 直白的解释: Tom 和 Jerry想发送一些消息/文件,而不被隔壁的Spike知道文件的内容。于是它们机智的采用了非对称加密来保证内容的安全性。
Tom先生产非对称的两个密钥,分别为公钥A,私钥B 为了能让Jerry发过来的消息被加密了,Tom先将可公开的公钥A发给Jerry 因为公钥A是完全可公开的,所以Spike知道也没关系 Jerry收到Tom发的公钥A,并将自己的文件X使用公钥A进行加密 随后Jerry就可以将加密的文件A(X)正大光明的发送给Tom了 此时的Spike就算截取到加密过的文件A(X)也没有用 因为Tom收到的加密文件A(X)只有它自己的私钥B能够解密,于是它收到后可以使用私钥B正常解密 所以如果Tom丢失了它的私钥B,那么Tom and Jerry都无法读取加密的文件A(X)了 (没有私钥就无法解开公钥加密过的信息) 相反,Jerry也可以将自己的公钥发给Tom,使其加密要发给自己的信息。 数字签名 如果某一用户使用他的私钥加密明文,任何人都可以用该用户的公钥解密密文;由于私钥只由该用户自己持有,故可以肯定该文件必定出自于该用户。
公众可以验证该用户发布的数据或文件是否完整、中途有否曾被篡改,接收者可信赖这条信息确实来自于该用户,该用户亦无法抵赖,这被称作数字签名。 所以我们常常见到提示一定要保护好自己的私钥,因为不仅仅会使得加密失效,还会直接影响签名验证。
非对称加密的软件 对于软件来说,我们可能经常听说到GPG这一词。GPG的全称是GNU Privacy Guard(GnuPG或GPG)。它是一款非对称加密的软件,是PGP加密软件的满足GPL的替代物。 也就是说它相对于PGP加密来说,它是一款开源软件。
因为PGP的非对称的算法是开源的,所以GPG和PGP原理是完全一样的。通常我们会见到GPG/PGP。 所以PGP就可以简单了解到它是一款非开源的非对称加密软件了。 PGP(英语:Pretty Good Privacy,中文翻译“优良保密协议”)是一套用于讯息加密、验证的应用程序,采用IDEA的散列算法作为加密和验证之用。
多平台的安装与使用 既然上述已经介绍了它是自由软件,那么它跨平台的几率就很大了,支持的平台也非常的多。在官方网站里,我们可以看到它支持很多平台。
Windows GPG4win 安装就不再多说,GPG4win的官网有打包好的exe可执行程序,我们直接下载双击安装就好,安装过程也非常的简单,不需要进行任何配置。也就是常说的“无脑next☀”。
Download
GPG4win是GPG在Windows平台的一款可视化的非对称加密软件。对于可视化的软件来说,使用也非常的简单明了。 几乎常用的一些功能都非常直白的写在了刚开打的页面中。基本上只要使用者了解大概的非对称加密的运作原理,就可以很轻松的使用该软件了。
Ubuntu & CentOS 目前最新的Ubuntu与CentOS的发行版中都带有GnuPrivacyGuard。也就是GPG的一种,所以使用的方法也是大同小异了。 以Ubuntu为例: * 创建密钥
gpg
不知道为啥我的机器在生成密钥的时候会卡住很长时间,导致我没有生成出来。等以后再考虑填这个坑吧。
查看公钥:gpg --list -key 查看私钥:gpg --list -secret-keys
提取公钥:gpg -a --export new key > new key .asc提取私钥:gpg -a --export-secret-keys new key > new key_pirv .asc
导入公钥或私钥:gpg --import new key
gpg -ea -r new key filename
gpg -d test .asc > test
gpg --edit-key [导入的密钥ID] trust 您是否相信这位用户有能力验证其他用户密钥的有效性(查对身份证、通过不同的渠道检查 指纹等)? 1 = 我不知道或我不作答 2 = 我不相信 3 = 我勉强相信 4 = 我完全相信 5 = 我绝对相信 m = 回到主菜单
我的公钥 如果有小伙伴想和我扮演Tom and Jerry的话,或者想校验我的签名的文件的话。欢迎使用下述公钥
我的公钥🔒!
参考 ]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- 自动备份大法
-
- /defect/auto-backup.html
-
- 引入最近看到几个数据爆炸的可怕事件,虽然我平时偶尔有手动备份的,但还是不怎么放心。以前有用过lsyncd自动同步到其他机器。但昨天发生了一个更可怕的事情,我重启机器后发现mysql启动不了,apt也不能update了。当时就蒙了,后来发现是我的/var目录满了。mysql与apt都需要用到/var目录,所以爆炸了。但是为什么会满呢…
因为一个lsyncd的日志写了34GB。
操作 放弃lsyncd。
以前因为懒,写过一个自动压缩网页根目录的脚本,配合crontab在每天的凌晨自动执行一遍非常不错。
但是最重要的不是根目录,而是数据库。最近有了解到mysqldump,表示可以crontab一下。
dump为sql文件 导出整个数据库:
mysqldump -u 用户名 -p 数据库名 > 导出的文件名
例:
mysqldump -u root -p typecho > typecho_backup .sql
导出一个表
mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
例:
mysqldump -u root -p typecho users > users_backup.sql
导出一个数据库结构
mysqldump -u 用户名 -p -d 数据库名 > 导出的文件名
例:
mysqldump -u root -p -d typecho > typecho .sql
导入数据库 mysql -u 用户名 -p 数据库名 < 数据库名.sql
例:
mysql -u root -p typecho < typecho .sql
实际操作了一下,确实很简单方便好用。但问题是,对于我这种勤(lan)快的人肯定要脚本自动一体化啊。
感觉很厉害的Script 自我感觉,自我感觉。
#!/bin/bash USER="root" PASS="password" HOST="localhost" NAME="typecho" NAME2="wordpress" BAK_DIR="/root/backup/" TIME=`date +%F`mysqldump -u$USER -p$PASS -h$HOST $NAME > $NAME "_" $TIME .sqlmysqldump -u$USER -p$PASS -h$HOST $NAME2 > $NAME2 "_" $TIME .sqlrm -rf /root/backup/$NAME "_" $TIME .sql /root/backup/$NAME2 "_" $TIME .sqlrm -rf /root/$NAME "_" $TIME .sql /root/$NAME2 "_" $TIME .sqlfind /root/backup/tar.gz/sql -mtime +3 -name "*.*" -exec rm -rf {} \;
只要将其放到crontab中,并按时间进行执行。就能实现完美的sql备份了。
再加上以前写过的一些备份其他文件的Shell Script,就能实现最基本的收据备份了。并且七牛的云储存有个在Linux上的下载备份脚本。正好给了我不小的帮助。
(虽然喜欢写交互式的脚本,但是只要将命令挑出来放crontab就好了)
写入crontab 先来简单的介绍下可爱的crontab文件的时间格式吧。
星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。
然后就是写到Crontab里去了。第一次我也是以为直接找到并编辑crontab这个文件的,后来才发现,原来人家有编辑的命令的:
crontab -e
然后按照格式讲我们的脚本写进去就好了。
0 5 * * * /bin/ sh /root/ backup/c.sh0 4 * * * /bin/ sh /root/ backup/d.sh
结尾 进过超级简单的操作再配合定时任务,就能实现自动化的各种各样的操作了。对于备份这种操作,手动来做的话迟早会累死,就是不累也会感觉到烦。所以将其运用到定时任务上就是非常的人性化了。主要是方便,不需要任何的人工参与。
对于数据这方面的,还是经常性的备份比较重要。不光光是不本机的备份,也要经常性的实施多机备份。
]]>
-
-
-
-
- Linux
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- QinQ基础操作
-
- /defect/basic-knowledge-of-qinq.html
-
- QwQ♥
QinQ简介 QinQ技术(或称为IEEE 802.1ad、Vlan stacking)。是802.1q协议(Virtual Bridged Local Area Networks)为基础衍生出的一种通讯协议。
它是一项拓展vlan空间的技术,通过在原有的以太网帧中再堆叠一个802.1q的报头来达到拓展vlan空间的功能。使其vlan数量最多可以达4094(inner)*4094(outer)。即802.1Q-in-802.1Q,所以称之为QinQ协议。
目的 随着当前的以太网技术的发展,利用传统802.1q vlan来对用户进行隔离和标识收到很大限制。因为IEEE802.1Q中定义的VLAN Tag域只有12个比特,仅能表示4096个VLAN,无法满足以太网中标识大量用户的需求,于是QinQ技术应运而生。
而运用了QinQ协议之后,可以在原有的vlan标签中再堆叠一层vlan标签,使其vlan的数量达到翻倍,极大的拓展了vlan的空间。
优点 扩展VLAN,对用户进行隔离和标识不再受到限制。 QinQ内外层标签可以代表不同的信息,如内层标签代表用户,外层标签代表业务,更利于业务的部署。 QinQ封装、终结的方式很丰富,帮助运营商实现业务精细化运营。 解决日益紧缺的公网VLAN ID 资源问题 用户可以规划自己的私网VLNA ID 提供一种较为简单的二层VPN解决方案 使用户网络具有较高的独立性 实现方式 QinQ拥有两种实现方式:
基本QinQ
如果收到的是带有VLAN Tag的报文,该报文就成为带双Tag的报文。 如果收到的是不带VLAN Tag的报文,该报文就成为带有本端口缺省VLAN Tag的报文。 灵活QinQ
为具有不同内层VLAN ID的报文添加不同的外层VLAN Tag。 根据报文内层VLAN的802.1p优先级标记外层VLAN的802.1p优先级和添加不同的外层VLAN Tag。通过使用灵活QinQ技术,在能够隔离运营商网络和用户网络的同时,又能够提供丰富的业务特性和更加灵活的组网能力。 终结子接口 “终结”意思为设备对传过来的报文tag进行识别,然后根据后续的转发行为来对单层或双层的tag进行玻璃或继续传输。
“终结”一般作用于子接口上,故称之为:终结子接口
QinQ技术在和MPLS/IP核心网连接时,根据不同的情况,会用到不同的终结方法:
如果路由子接口是对报文的单层Tag终结,那么该子接口称为Dot1q终结子接口; 如果路由子接口是对报文的双层Tag终结,那么该子接口称为QinQ终结子接口。 Tips:Dot1q终结子接口和QinQ终结子接口不支持透传不带VLAN的报文,收到不带VLAN的报文会直接丢弃。
帧格式 QinQ报文有着固定的格式,就是在802.1Q的标签上再堆叠一层802.1Q标签。QinQ报文比普通的vlan标签多4个字节。vlan帧最小帧长为68字节。
字段 长度 含义 Destination address 6字节 目的MAC地址。 Source address 6字节 源MAC地址。 Type 2字节 长度为2字节,表示帧类型。取值为0x8100时表示802.1Q Tag帧。如果不支持802.1Q的设备收到这样的帧,会将其丢弃。对于内层VLAN tag,该值设置为0x8100;对于外层VLAN tag,有下列几种类型0x8100:思科路由器使用0x88A8:Extreme Networks switches使用0x9100:Juniper路由器使用0x9200:Several路由器使用 PRI 3比特 Priority,长度为3比特,表示帧的优先级,取值范围为0~7,值越大优先级越高。用于当交换机阻塞时,优先发送优先级高的数据包。 CFI 1比特 CFI (Canonical Format Indicator),长度为1比特,表示MAC地址是否是经典格式。CFI为0说明是经典格式,CFI为1表示为非经典格式。用于区分以太网帧、FDDI(Fiber Distributed Digital Interface)帧和令牌环网帧。在以太网中,CFI的值为0。 VID 12比特 LAN ID,长度为12比特,表示该帧所属的VLAN。在VRP中,可配置的VLAN ID取值范围为1~4094。 Length/Type 2字节 指后续数据的字节长度,但不包括CRC检验码。 Data 42~1500字节 负载(可能包含填充位)。 CRC 4字节 用于帧内后续字节差错的循环冗余检验(也称为FCS或帧检验序列)。
报文示例
TPID(Tag Protocol Identifier) TPID:标签协议标识ID(Tag Protocol Identifier)是Vlan tag中的一个字段,标识该vlan tag的协议类型。IEEE 802.1Q协议规定QinQ的外层vlan标签的type值为:(0x8100)。
IEEE802.1Q协议定义的以太网帧的VLAN Tag。802.1Q Tag位于SA(Source Address)和Length/Type之间。通过检查对应的TPID值,设备可确定收到的帧承载的是运营商VLAN标记还是用户VLAN标记。接收到帧之后,设备将配置的TPID值与帧中TPID字段的值进行比较。如果二者匹配,则该帧承载的是对应的VLAN标记。例如,如果帧承载TPID值为0x8100的VLAN标记,而用户网络VLAN标记的TPID值配置为0x8200,设备将认为该帧没有用户VLAN标记。也就是说,设备认为该帧是Untagged报文。 另外,不同运营商的系统可能将QinQ帧外层VLAN标记的TPID设置为不同值。为实现与这些系统的兼容性,可以修改TPID值,使QinQ帧发送到公网时,承载与特定运营商相同的TPID值,从而实现与该运营商设备之间的互操作性。以太网帧的TPID与不带VLAN标记的帧的协议类型字段位置相同。为避免在网络中转发和处理数据包时出现问题,不可将TPID值设置为下表中的任意值:
协议类型 对应值 ARP 0x0806 RARP 0x8035 IP 0x0800 IPV6 0x86DD PPPoE 0x8863/0x8864 MPLS 0x8847/0x8848 IPX/SPX 0x8137 LACP 0x8809 802.1x 0x888E HGMP 0x88A7 设备保留 0xFFFD/0xFFFE/0xFFFF
基本QinQ配置 拓扑:
如图示,SW2和SW3用于模拟运营商之间的Internet,SW1和SW4为客户内网。基本QinQ的配置就作用于SW2和SW3之间,将客户内网内的vlan10与vlan20封装上一层vlan100,用于再SW2和SW3之间传输。
sysname SW1vlan batch 10 20interface GigabitEthernet0/0/1 port link-type trunk port trunk allow-pass vlan 10 20interface GigabitEthernet0/0/2 port link-type access port default vlan 10interface GigabitEthernet0/0/3 port link-type access port default vlan 20
SW1和SW4只需做基本配置,用作普通二层交换。
sysname SW2 vlan batch 100 interface GigabitEthernet0 /0 /1 port link-type dot1 q-tunnel//开启基本二层QinQ功能 port default vlan 100 //并划分为vlan100 interface GigabitEthernet0 /0 /2 port link-type trunk//普通trunk port trunk allow -pass vlan 100
SW1的G 0/0/1
为trunk接口,对应连接的SW2的G 0/0/1
为基本二层QinQ接口,划分vlan为vlan 100。
使用PC1发送ICMP包到PC3,数据包内容为:
其中,可以看到内层的802.1q的vlan标签ID为10,type为(0x0800);外层的,也就是SW2封装的vlan标签ID为100,type为(0x8100)。
灵活QinQ配置 拓扑和上述一样:
我们在模拟internet的SW2和SW3之间添加了一个vlan 200,用于配置灵活的QinQ的vlan 20堆叠一个vlan 200的tag。
SW1和SW4的配置与普通的QinQ的配置相同,无需改变。
sysname SW2 vlan batch 100 200 interface GigabitEthernet0 /0 /1 port link-type hybrid //必须是hybrid接口模式 qinq vlan-translation enable//开启vlan转换 port hybrid untagged vlan 100 200 //出方向时剥离vlan100 和200 的标签 port vlan-stacking vlan 10 stack-vlan 100 //vlan10 堆叠vlan100 tag port vlan-stacking vlan 20 stack-vlan 200 ////vlan20 堆叠vlan200 taginterface GigabitEthernet0 /0 /2 port link-type trunk port trunk allow -pass vlan 100 200
从vlan10和vlan20的PC分别向各自的vlan发包,可以看到数据包内容:
当两台PC机正常通信的时候,可以看到不同vlan封装的外层vlan tag也是不一样的。这就是基于vlan的灵活QinQ。
上述拓扑 参考 ]]>
-
-
-
-
- 网络
-
-
-
-
-
-
- Network
-
-
-
-
-
-
-
-
- 解决inotify watch不够⌚
-
- /defect/fixed-inotify-watch-not-enough.html
-
- Failed to add /run/systemd/ask-password to directory watch: No space left on device
这当然不是磁盘空间不足。
曾经被这问题折腾了很长时间,在磁盘空间充足的情况下,一直提示设备剩余空间不足,导致许多服务无法启动。该问题所在的根源是Inotify watch被占用光了导致的。
inotify watch Inotify 到底是什么?
Inotify 是一种文件变化通知机制,或者称之为监控文件系统,Linux 内核从 2.6.13 开始引入。在 BSD 和 Mac OS 系统中比较有名的是kqueue ,它可以高效地实时跟踪 Linux 文件系统的变化。近些年来,以fsnotify 作为后端,几乎所有的主流 Linux 发行版都支持 Inotify 机制。
可以简单的理解为,inotify就是监控我们当前系统上的文件变化。在日常工作中,人们往往需要知道在某些文件 (夹) 上都有那些变化,比如:
通知配置文件的改变 跟踪某些关键的系统文件的变化 监控某个分区磁盘的整体使用情况 系统崩溃时进行自动清理 自动触发备份进程 向服务器上传文件结束时发出通知 检查当前系统内核是否支持inotify机制:
grep INOTIFY_USER /boot/ config-$(uname -r)
如果输出为:CONFIG_INOTIFY_USER=y
,那么当前的系统内核便是支持inotify了。
解决watch不够 经常打开服务无法启动,提示:
Failed to add /run/systemd/ask-password to directory watch: No space left on device
便是inotify watch不够导致的服务无法启动,很多程序的进程都需要使用inotify watch来监控文件系统。当某些进程使用的太多的时候,就会导致watch不够,导致一些程序直接无法启动。
遇到这种情况解决办法非常的简单,毕竟不是磁盘的空间不够,我们不需要删除任何的文件,只需要放大足够的watch数量就ok了。
临时的解决办法:
echo 1048576 > /proc/ sys/fs/i notify/max_user_watches
直接在终端echo
一个大量的watch数量到指定的路径,不出意外的话就能够直接解决问题。但这只是个临时的解决办法,再重启机器后将会还原。
该临时解决办法的好处就是方便快捷,有次我的sshd因为watch数量的不够倒是无法启动时,唯一的解决办法就是连接vnc来解决,然后网页的vnc是不支持粘贴的,所以使用这一行命令也就非常的方便了。
长期解决方法:
vim /etc/ sysctl.conf fs.inotify.max_user_watches=1048576
长期的解决方法也很简单,我们直接在/etc/sysctl.conf
文件的末尾添加一句话就可以了。
参考 ]]>
-
-
-
-
- 踩坑
-
-
-
-
-
-
- Linux
-
-
-
-
-
-
-
-
- 开黑之路-Teamspeak Server搭建
-
- /defect/teamspeak-server.html
-
- 与小伙伴的开黑之路🤞
Teamspeak?
Teamspeak是一套专有的VoIP软件。所谓VoIP软件,就是基于网络协议的语音通话。而Teamspeak就是和现在市面上大多数即时通讯软件差不多,可以发送即时消息以及多人语音通话。
它是以S(erver) -> C(lient)架构基于Internet的运作方式,但是与其他多数软件不同的是,它允许自己搭建服务器。也就是说它的服务端也是可以下载安装的。
而且Teamspeak是一款多平台的软件,这就意味着,如果我们想,可以让在任何机器上运行成为服务端。它甚至支持离线/局域网的通信。
除此之外,它对自己的通信频道有着完全自定义的控制。设置官方称可以为整个服务器或只是特定通道启用基于AES的加密。
我主要喜欢它的地方在于,完全不需要注册账号。可以建立在私人服务器上并有着很完成的控制权限。
服务端 废话了半天,我们了解它是一款可以搭建在自己私人服务器上的一款即时通讯软件。所以接下来就是安装操作了。
服务器环境 首先,我用做服务端的环境:
Server:阿里云ECS Bandwidth:1M OS:Ubuntu 18.04 bionic . WM: .++ .++.o+++oo+:` /dddhhh. .+.o+oo:. ` oddhhhh+ \+.++o+o`` -`` `` .:ohdhhhhh+ `:o+++ ` ohhhhhhhhyo++os: .o:`.syhhhhhhh/.oo++o` /osyyyyyyo++ooo+++/ `` `` ` +oo+++o\: ` oo++.
(主要是内存有点多的空闲……
下载地址 既然是安装,首先是下载用于作为服务端的软件。据我所知,TS是不开源的。
→官方下载地址
客户端平台:
LINUX MACOS WINDOWS ANDROID IOS 听说两个移动端都不怎么样。
当然,我们主要搭建的是服务端,服务端的平台也不少:
这些都是我们常见的服务器操作系统。也就是说搭建服务端对系统没有多大挑剔性了。
开始动手 从上述的下载地址中,我们下将服务端的软件下载到我们的服务器上:
wget https://files.teamspeak-services.com/releases/server/3 .6 .1 /teamspeak3 -server_linux_amd64 -3 .6 .1 .tar.bz2
(这条命令的软件版本可能会随着TS的更新而失效)
由于下载下来的是tar.bz2
的压缩格式,所以我们使用-xjvf
来进行解压操作:
tar -xjvf teamspeak3 -server_linux_amd64 -3 .6 .1 .tar.bz2
解压之后的文件夹目录:
CHANGELOG libts3 db_mariadb.so libts3 _ssh.so LICENSE-THIRDPARTY serverquerydocs ts3 server ts3 server_startscript.shdoc libts3 db_sqlite3 .so LICENSE redist sql ts3 server_minimal_runscript.sh tsdns
我们可以了解到它有一堆各种各样的文件,我们不需要去一个个的了解。据说是专有软件,也就是不开源。所以我们也更不需要去编译等操作了。这些文件肯定都是已经编译好的了。
要运行服务端的软件,首先我们要同意它的许可协议。(如果有人愿意看的话
touch .ts3 server_license_accepted
touch
这样一个文件在刚刚解压出的目录就意味着我们同意License了。
同意过后,我们执行它的启动脚本:
./ts3server_startscript.sh start
如果是root用户运行的话,会在启动时提示为了”安全起见,不要使用root用户运行“:
WARNING ! For security reasons we advise: DO NOT RUN THE SERVER AS ROOT!!!!!!!!!!!
当然等最后几个感叹号出现完之后,服务端软件就会正常运行了。就可以继续快乐的当root敢死队了🎉。当然使用什么用户运行取决于你自己。我这边是和自己的小伙伴开黑用的,几乎就是私用,不会将服务器做为公用。所以我并不怕死😈。
警告⚠我们过后,就是正常启动了
Starting the TeamSpeak 3 serverTeamSpeak 3 server started, for details please view the log file ------------------------------------------------------------------ I M P O R T A N T ------------------------------------------------------------------ Server Query Admin Account created loginname= "serveradmin", password= "dxxxxxxAa" ------------------------------------------------------------------ ○ ------------------------------------------------------------------ I M P O R T A N T ------------------------------------------------------------------ ServerAdmin privilege key created, please use it to gain serveradmin rights for your virtualserver. please also check the doc/privilegekey_guide.txt for details. token=HxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxRL ------------------------------------------------------------------
机会将等会会在客户端用的admin账户以及token显示在我们的终端上了。serveradmin账户和token都是TS的最高权限账户,可修改服务器设置。下文会用到
进程:
○ ps -aux | grep ts3 root 4031 0.4 1.1 689336 22820 pts/0 Sl 10 :44 0 :07 ./ts3server
防火墙 如果我们的服务器有启用防火墙来限制网络的话,ts需要一些TCP/UDP的端口来和客户端进行通信。
目前的Teamspeak 3需要这些端口:
UDP: 9987 TCP: 10011 TCP: 30033 可以在iptables
中添加:
iptables -A INPUT -p udp iptables -A INPUT -p tcp iptables -A INPUT -p tcp
亦或者使用firewalld
:
rewall-cmd --zone =public --add-port =9987/udp --permanentfirewall-cmd --zone =public --add-port =10011/tcp --permanentfirewall-cmd --zone =public --add-port =30033/tcp --permanentfirewall-cmd --reload
对于一些特殊的发行版可能需要特殊操作,例如我当前的ECS机器,它的防火墙(iptables)的工作方式不是以进程方式运行的。(虽然我也不懂……
但是新建防火墙的规则与上述不同,具体可以了解我很早以前水过的一篇文章
开机启动 当前已经添加过了端口并且已经成功启动了,我们基本上就可以正常连接了。当然还少了一个重要的一步,那就是开机自启了。 由于是使用脚本启动了,而没有使用systemd
来进行控制,所以服务端需要使用Crontab
来进行开机启动的控制
@reboot /root/ teamspeak/teamspeak3-server_linux_amd64/ ts3server_startscript.sh start
在crontab文件中添加对应的ts脚本启动位置,来实现开机启动ts服务端的效果。 添加完成后我们可以使用crontab -l
来查看添加完成后的crontab文件,确认是否添加成功。 至此,运行于Ubuntu上的Teamspeak服务端就运行成功了。
客户端 上述我们介绍过客户端软件支持的平台以及服务端的安装搭建。客户端的下载地址 于服务端是同一个地址,可以找的适用于自己的平台的安装包来进行下载安装。
连接服务端 前面有介绍过TS使用的是C/S架构,我们搭建好了服务端当然是为了连接它。连接它比我们想象的要简单的多,打开软件后直接在工具栏就能找到连接这一选项。 单击连接,就可以根据服务器地来连接我们搭建好的服务端了。
我们可以看到有三个选项框,第一个是服务器地址,其次是服务器密码,最后是用于展示给其他人的昵称 默认新安装的服务端是没有密码的,如果我们是连接一个新服务器的话,是可以将密码留空登陆。这里的昵称只用于展示于其他人,不等于我们的用户名。
Token 在上述我们的安装服务端的操作中,第一次启动后会给我们一个serveradmin账户一段token码。ts这个软件不是那么的强调用户。我们登陆至服务器是不需要创建一个拥有密码的账户的。这和我们目前常用的微信、QQ等软件是略有不同的。
但是ts它也是有用户身份的,因为ts大部分对于服务器的控制操作都是可以在客户端完成的。这时候我们就需要一个有高等级权限的用户身份来完成这个操作。同样需要识别出其他连接进来的用户没有修改服务器的权限。
如果是第一次连接至ts的新服务器,那么我们连接成功后就会立马弹出一个用于输入token的对话框。我们将刚刚创建服务器时给我们的token填入即可。这样就可以直接在客户端修改自己的服务器了。
因为ts默认在使用客户端时会自动创建一个用户身份,每个身份都是不相同的。那如果我们更换电脑连接自己的服务器时,或者想给其他人一个修改服务器的权限时该怎么办呢?
在工具栏的“工具-身份”这个标签中,我们可以看到自己当前账户身份。直接右击便可以执行导出操作。在其他地方使用相同方法导入就可以继续使用这个身份了。
当我们想给其他身份的用户修改服务器的权限的时候,我们可以使用新建权限码的方式来提升其他用户的权限。 在工具栏的“权限-权限码清单”中就可以找的新建权限码的按钮以及已经新建过的权限码清单。新建时也可以选择不同的权限来进一步控制。使用权限码就和我们第一次使用时一样操作即可。
翻译插件 如何安装软件这里就不再做赘述。ts默认是英文版本的,可以自己在其他地方下载拥有中文汉化的第三方做的包来使用。也可以自己在原版的基础上添加汉化文件。亦或者是自己安装中文翻译的插件(目前中文插件仅有繁体中文)。
打开软件后,打开“工具-选项”(Alt+P),找到“插件(add-ons)”这一选项卡。
默认看到的是当前本地的插件,我们可以选择“Browse online”来查看在线可以下载安装的插件。选择筛选器为”翻译“然后输入”Chinese“就可以找到一款繁体中文的插件。点击进入插件的详情页面就可以看到”install“。单击安装即可。
Install完成之后重新打开软件就会应用上翻译了。如果没有成功应用,可以再去刚刚插件地方看看有没有启用。 除了翻译插件之外,TS还有很多种类的插件,以及界面皮肤等。和刚刚安装翻译插件的方法一摸一样。
参考 ]]>
-
-
-
-
- Player
-
-
-
-
-
-
- Player
-
-
-
-
-
-
-
-
- Resilio Sync多平台实时同步
-
- /defect/multi-platform-real-time-synchronization-by-resilio-sync.html
-
- 备份,同步一步到位。🏹
什么是Resilio Sync Resilio Sync 是一款多平台的文件同步工具,能够实现几乎实时的同步效果。原名BitTorrent Sync,看到的原名就能想到,它的运作原理类似于我们的BitTorrent。
它是由BitTorrent 开发的一款基于P2P的协议来进行传输文件的。
它和我以前用过的一款运作于Linux发行版之上的同步软件lsyncd 类似,都能达到几乎实时监控指定文件夹变动的一款同步工具。
相比较二者之间的不同的是,Resilio Sync在Windows平台是拥有GUI的(亦是使用WebUI),但是在其他服务端的操作系统上,类似于各种Linux发行版与Windows Server。它的GUI是以WebUI的方式提供给我们使用的。
这也是它的优点之一了,在不用反复的修改配置文件的情况下,我们可以使用基于WebUI的一种交互方式来对其进行操作。
这也是我很喜欢它的地方之一了❤
优点 如果我们用作同步工具来使用的话,它还是有很多的优点的:
可以纯内网工作(P2P) 多人实时同步,原理是人越多速度越快(BitTorrent) 存储空间及流量等不受限制 多平台、多网络环境同步 Free for home WebUI 缺点 事物都是拥有两面性的,既然它拥有不少优点。那么缺点肯定也不会缺席:
闭源软件(Free for home) 公共网络可能会使用中转服务器 类似BT的原理,机器需要保持在线才能保证传输 多平台 两个移动设备上目前还是一款免费软件,不知道功能是什么样的。但是身为一个闭源软件在多平台都有免费使用的方案还是很欣慰的。
同类 Syncthing 几乎就是Resilio Sync的另一面了,有人说它就是Resilio Sync的替代品。它不但能够实现相应的实时同步的所有功能,并且还是一款开源的自由软件。
对于我们来说,一款开源软件不仅仅是不收费这么简单。当一款开源软件由社区进行驱动时,人人都可以完全查看它的源代码以及改进。因此,一款开源软件对我们最大的益处就是真正安全与尊重隐私。
但闭源软件也有它存在的合理处,如果我们未来需要可靠稳定的支持,与更多的付费服务。Resilio Sync肯定也是一个不错的选择。毕竟顾客即是上帝🍡
总之各个实时同步的软件之间都是各有各的优缺点,具体想要使用哪一款,完全根据我们自身的需要与心情就OK了。
安装 上述我们有提到Resilio Sync支持多种平台,这里的我只使用到了Windows与Linux的两个发行版之间。所以其他平台的安装没有相应的记录。
Windows Windows是我们平常接触最多的一款GUI操作系统了,对于安装软件来说,差距都不会太大的。Resilio Sync也是同样。直接前往官网 选择合适的授权下载安装即可。
安装界面仅仅只需要一步
合适的选择后,对于较新的Windows平台可是直接打开一个软件窗口
接受了几个隐私政策与EULA之后(有空还是要多留意条款之类的),我们就可以正常使用了。
打开后的简洁的界面
配置之类的稍后再说,再将自己的其他平台的机器也给完成安装。
Linux 对于在各个Linux平台的安装,官方有给一个完整的帮助文档 其中有各个版本的不同数位版的下载链接。以及一些发行版的手动添加源的二进制安装方式。
对于网络环境不好的时候,可以考虑使用离线安装包的方式来安装。官方给了两个包,分别是deb和rpm。
DEB:
sudo dpkg -i <resilio-sync .deb >
RPM
sudo rpm -i <resilio-sync.rpm >
由于我的服务器因为网络环境无法与其官方源通信,所以只好选择使用下载离线安装包的方式来进行安装了。
相比较之下,个人感觉Linux平台的安装甚至比带有GUI的Windows更加方便。对于此软件,至需要一条命令即可。速度也很快。
使用软件仓库安装,我们需要简单的三个步骤:
添加仓库源 添加GPG公钥用于验证 安装✨ 对于Debian-based Linux(Debian, Ubuntu, Mint, Zorin, Elementary)
创建用于安装的仓库源列表:
echo "deb http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free" | sudo tee /etc/ apt/sources.list.d/ resilio-sync.list
添加公钥:
curl -LO http://linux-packages.resilio.com/resilio-sync/key .asc && sudo apt-key add ./key .asc
更新源与安装:
sudo apt-get updatesudo apt-get install resilio-sync
对于RPM-based Linux(Red Hat, Fedora, CentOS)
创建用于安装的仓库源列表:
printf "[resilio-sync]\n name=Resilio Sync\n baseurl=https://linux-packages.resilio.com/resilio-sync/rpm/\$basearch\n enabled=1\n gpgcheck=1\n " | sudo tee /etc/yum.repos.d/resilio-sync.repo
添加公钥:
sudo rpm --import https:// linux-packages.resilio.com/resilio-sync/ key.asc
安装:
sudo yum install resilio-sync
更新:
sudo yum check-updatesudo yum update resilio-sync
运行与配置 Windows上的打开软件不需要任何多嘴了,双击快捷方式打开就好了。
对于Linux发行版上,几款目前常用的较新的发行版运行软件的方式都是差不多的。
可以使用Systemd进行控制
自动运行服务:
sudo systemctl enable resilio-sync
启动与停止:
sudo systemctl start resilio-sync sudo systemctl stop resilio-sync
对于在没有GUI的机器的环境下,是可以使用WebUI通过其他机器的浏览器访问来进行控制操作。
所以我们可以修改其配置文件来进行启动操作。
默认的配置文件 /etc/ resilio-sync/config.json
而对于基本的使用,配置文件也是非常的简单易读的。
{ "storage_path" : "/var/lib/resilio-sync/" , "pid_file" : "/var/run/resilio-sync/sync.pid" , "webui" : { "listen" : "127.0.0.1:8888" }}
对于,官方文档称为了安全选择,软件默认监听127.0.0.1
。如果需要在其他机器上直接访问的话,我们需要修改监听地址为:
"listen " : "0.0 .0 .0 :8888"
并且可以根据自己的需求修改监听的端口。
其中storage_path
为软件的一些设置,log等文件的存放目录。也可以根据自身的需要修改。
╰─# ls /var/lib/resilio-sync /debug .txt history .dat http.port settings.dat.old storage.db-wal sync .dat.old sync .log FileDelayConfig history .dat.old settings.dat storage.db sync .dat sync .lng
示例配置文件 上述运行的配置文件那么的简单易懂,同时可自定义的功能也就是更少了。而对于软件的一些其他功能在配置文件中的写法,官方是有留给我们示例的配置文件的。
我们只需要使用一条命令就可以将示例的配置文件导出到当前目录下:
rslsync --dump-sample -config > sync.conf
并且其中注释说明等都是非常完善的:
╰─{ "device_name" : "My Sync Device" ,// "listening_port" : 0 , // 0 - randomize port/* storage_path dir contains auxilliary app files if no storage_path field: .sync dir created in current working directory */ // "storage_path" : "/home/user/.sync" ,/* set location of pid file */ // "pid_file" : "/var/run/resilio/resilio.pid" ,/* use UPnP for port mapping */ "use_upnp" : true,/* 这里只截取部分配置 */
当我们修改的差不多的使用,我们就可以使用上述命令启动了。启动后,可以在进程中看到默认读取的配置文件的路径:
╰─# ps -aux | grep rslsync rslsync 88730 0.5 1.5 772560 15316 ? Ssl 01 :12 0 :05 /usr/bin/rslsync --config /etc/resilio-sync/config.json
用户 同样是为了安全原因,软件默认是使用最小权限的rslsync 的用户来运行的。这样就会导致一个问题,在rslsync 这个用户没有权限的目录就无法打开,就会导致无法使用该目录了。
最简便的解决办法就是将需要的文件夹给予用户rslsync 可使用的权限,我们可以将rslsync 添加到当前的用户组,并保证需要同步的文件夹用于上述组的权限:
sudo usermod -aG user_group rslsync sudo chmod g+rw synced_folder
亦或者直接修改rslsync用户为当前使用的用户来运行程序,编辑文件**/usr/lib/systemd/user/resilio-sync.service**将文件中的:
WantedBy = multi-user.target
修改为:
WantedBy = default .target
随后需要reload systemd并重启程序:
systemctl daemon-reloadsystemctl --user start resilio-sync
此处的--user
与普通启动可以分别守护两个进程,默认的分别为:
rslsync 27519 0 .4 0 .8 670144 16928 ? Ssl 15 :36 0 :01 /usr/bin/rslsync --config /etc/resilio-sync/config.jsonroot 29385 0 .3 0 .4 589864 9676 ? Ssl 15 :43 0 :00 /usr/bin/rslsync --config /root/.config/resilio-sync/config.json
且--user
的用户成功被更改为当前用户root。当然配置文件也是独立的。
除此之外 软件还可以直接在终端中使用rslsync
命令来进行运行等其他控制。上述的打印示例配置文件就是一个例子。关于该方式的更多可以在官方的帮助文档Guide To Linux, And Sync Peculiarities 中找到。
成后运行后的界面与Windows完全一样。毕竟二者是同一种方式展示的UI界面。
同步 安装均已经完成,接下来就是简单从操作来实现需要的同步效果了。
在保证权限都是正常的情况下,添加在A机器添加我们需要进行同步的文件夹。选择好了之后就是三种分享链接的方式。
分别是“链接”、“密钥”和二维码三种方式。
随后我们就可以使用三种方式的其中一种,例如使用我最喜欢的密钥,选择好读写权限后,在需要同步的B机器上输入复制过来的密钥。
然后等着他们自己开始同步就OK了。在多平台的环境下也是不会影响正常工作的。
除此之外,对于同步的文件夹还有一些其他的选项可以配置。具体就看自己的需要来配置了🌭
参考&推荐阅读 ]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Tools
-
-
-
-
-
-
-
-
- Minecraft bedrock服务端🥂
-
- /defect/minecraft-bedrock-server.html
-
- MinecraftMINECRAFT 是什么?
这是一个有关放置方块与探险的游戏。 游戏设定在一片可以无限生成的世界里,这里有广袤而开阔的土地——由冰雪覆盖的山峰、潮湿的河口、辽阔的牧场等等组成——它们充满着奥秘、奇迹与危险。
这个游戏应该是多数玩家都已不陌生,开发商为Mojang AB(Mojäng Aktiebolag),是瑞典的一家电子游戏开发商。他家也发行过其他的小游戏,但是名声都不怎么样。最后Minecraft一炮走红。2014年9月,财大气粗的软软以25亿美元收购Mojang以及游戏的知识产权。
最早的时候我们在PC上接触的应该都是开发商的Java版本,Java版本的好处就是它有各种各样的Mod,以及材质包等。对于玩家来说使用也非常的方便。虽然性能不是那么的卓越,但是现在依然是很受玩家的欢迎了。
在被微软收购以后,微软结合自家的Win10应用商店也出了一个Bedrock版本,与其不同的是,这次使用C语言写出来的。在自家的应用商店内,玩家们安装也变的更加的方便了。除此之外,性能方面肯定是要比Java好的多的。
Bedrock Server 相比较以前的Java版本自建服务器来说,Bedrock版本的Server要比以前方便简单的多。虽然目前还处于Bate版本,但是支持的功能也比较完善了。
主要是简单的多,对于以前的Java,服务端搭建起来非常的麻烦,得力于Java,还非常吃服务器的资源。目前已经编译好的C语言来说,不仅省了很多事,还降低了对服务器的要求。当然,唯一一个缺点就是跨平台肯定没有Java方便了。
搭建服务端 服务端我们可以免费的在官方网站 下载,目前只支持Windows与Ubuntu版本。在Windows上运行推荐使用Windows10/Windows Server 2016及以后的版本。
Windows版本与Ubuntu版本的文件几乎差不多,下载后直接解压,我们就能够看到一个可执行的bedrock_server.exe
文件。当没有任何需求时,直接执行它就可以启动并正常使用服务端了。
启动后我们可以看到一个类似这样的命令提示符的界面:
此时的服务器端就已经启动完成了,若能直接访问服务器,就可以直接开始游戏了🥓。
需要使用在Windows商店下载的Minecarft作为客户端
配置文件 当然,默认的配置文件往往大多数时间都是不能满足我们的需求的。还有很多种情况需要我们根据自己的想法去自定义。例如修改个监听的端口。
目前的Bedrock服务端也有个较为完善的配置文件,可读性也非常的高。官方下载的包里不仅有个“How to”,并且配置文件内都有着很详细的注释,例如修改游戏模式:
gamemode=survival# Sets the # Allowed
配置文件的修改入门很低,只要能打开几乎针对服务器的一些选项都可以自定义。除此之外,我们还可以在服务器允许中的命令行/shell内直接使用一些命令,例如提出某个在线的玩家:
kick <player name or xuid > <reason >
或者是关闭服务器:
stop
当然你喜欢的话也可以直接终止这个进程,当然不推荐这么做。
压缩包内的“bedrock_server_how_to.html”写的非常的详细,功能也非常的全。对于一些日常的使用,或者是入门的话,可以多参考参考该文件。
为了方便,我还放了个在线版本的📢bedrock_server_how_to 。是当前最新的1.11.2.1版本的,后续可能不会持续更新。当前写的非常的详细了。
权限级别 配置文件可以修改大多数服务端的配置,主要是针对游戏服务器的修改。像是对于游戏内的具体修改并没有写在配置文件内,例如对玩家的修改以及修改世界的选项。这些操作选项需要我们手动赋予一个玩家“操作员”的权限,这样,该玩家就会有对目前游戏的整个世界的完整操作权限。
在游戏的目录下有个名为permissions.json
的json文件,在默认情况下它是空的,我们可以根据帮助文件提供的格式直接赋予某个玩家权限:
[ { "permission" : "operator" , "xuid" : "451298348" }]
需要注意的是,我们需要对应的玩家的xuid
值,这在玩家连接时会显示在终端上。
一次赋予多个玩家的格式:
[ { "permission" : "operator" , "xuid" : "451298348" }, { "permission" : "member" , "xuid" : "52819329" }, { "permission" : "visitor" , "xuid" : "234114123" }]
当玩家被赋予operator
的权限时,重启服务端,再次进入游戏时该玩家就拥有了对世界的完整控制。
某些世界生成后就不能修改的选项除外
备份世界 最简单也是最直接的备份方式就是直接备份当前服务端的整个文件夹,如果这个操作太过于麻烦的话,或者说文件夹已经达到了臃肿的地步了。我们可以使用预留的备份命令,可以生成.db
的文件用于copy。文档的详细解释:
Command Description save hold This will ask the server to prepare for a backup. It’s asynchronous and will return immediately. save query After calling save hold
you should call this command repeatedly to see if the preparation has finished. When it returns a success it will return a file list (with lengths for each file) of the files you need to copy. The server will not pause while this is happening, so some files can be modified while the backup is taking place. As long as you only copy the files in the given file list and truncate the copied files to the specified lengths, then the backup should be valid. save resume When you’re finished with copying the files you should call this to tell the server that it’s okay to remove old files again.
我们可以直接使用save hold
来生成备份文件,然后再使用save query
来查询文件的位置。注意:当我们的世界名称使用中文时,可能会出现在终端中文乱码的情况 ,例如:
此时最佳解决办法就是换个世界名称。但是直接在配置文件中更换名称后,会导致重新创建一个新的世界。为了避免这个现象,达到给旧世界更换名称的操作。我们需要同时修改三个地方的名称,并保持一致:
server.properties
文件内的名称world
文件夹内的世界文件夹名称世界文件夹内的levelname.txt
内的文字 当这三处名称统一修改时,再重新启动服务器就会达到修改世界名称的效果了。
目前推荐的最佳的备份方法就是直接备份world
文件夹的所有内容,在服务器运行时对文件夹进行复制操作不会影响服务器的正常运行。
网易代理 网易代理的Minecraft对游戏的本质来说或许没有太多的不同,对于收费模式也是符合目前国内的游戏较为普遍的免费进入游戏,后续再根据自己的选择购买一些增值服务。也就是非买断制。但是网易拥有租赁服务器的服务,可能是想方便提供给玩家们一个更加方便的购买与启用服务器的渠道,但是他直接禁止使用其他的第三方服务器。对于某些玩家来说可能不是个好的选择。
虽然微软在Xbox上也有这类似的操作,但是Windows商店的Minecraft也不是天价,对于不想玩个mc就把身份证给网易的小伙伴们,Windows商店可能是个更好的选择。
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Minecraft
-
-
-
-
-
-
-
-
- 使typecho支持emoji🎈
-
- /defect/make-typecho-support-the-emoji.html
-
- Emoji?emoji是我们身边常见的且神奇的表情符号,它被称为绘文字(えもじ emoji )。最初是日本在无线通信中所使用的视觉情感符号。与我们常发的表情包不同的是,它并不是图片。
Emoji的编码是Unicode字符集中的一部分,特定形象的Emoji表情符号对应到特定的Unicode字节。也就是说emoji是unicode编码。好处是无论在什么地方使用都不像是图片那么难处理,以及可以直接写在数据库内。
词语发音 絵文字/えもじ(emoji)的发音是 /emoꜜdʑi/(此处为国际音标)。 2
/e/:即汉语拼音 ye 中ê 的发音,英语单词 be d 中e 的发音。 /dʑ/:与汉语拼音 j 对应的浊音。与潮州话拼音方案的 r 相似,如潮州话“字”(ri⁷)字的声母。 3 /mo/ 为重读音节。 在英语中,emoji 常被读作 /ɪˈmoʊdʒi/。
在typecho中使用emoji 现在多数的软件、网站等都已经广泛的支持emoji表情了。自己也是非常的喜欢这类表情,特别喜欢微软家的,那种扁平的风格真的很招人喜爱。
但是最近使用typecho的时候遇到点小问题,发现新安装的typecho居然不支持使用emoji。在文章等页面使用了emoji之后,保存会提示数据库查询错误。
这是因为数据库默认使用的是utf8
编码,在utf8
的编码中最多只支持3个字节,而我们可爱的emoji是4个字节,如上述所说的,emoji并非图片,是直接存储在数据库内的。所以就出现了数据库查询错误导致无法使用emoji的问题。
修改数据库 解决办法也非常的简单,我们直接使用phpMyAdmin或者sql,修改数据库charset
为utf8mb4
就ok了
alter table typecho_comments convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_contents convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_fields convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_metas convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_options convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_relationships convert to character set utf8mb4 collate utf8mb4_unicode_ci;alter table typecho_users convert to character set utf8mb4 collate utf8mb4_unicode_ci;
如果有没有涉及的表,按照同样的语句修改就可以了。
修改后就可以看到表的‘排序规则’(charset)为可以使用emoji的utf8mb4
了。
修改typecho配置文件 当数据库修改完成之后,到typecho的目录下找到其配置文件config.inc.php
。并且修改为刚刚设置编码就ok了
$db->addServer(array ( 'host' => localhost, 'user' => 'root' , 'password' => 'my_password' , 'charset' => 'utf8mb4' , 'port' => 3306 , 'database' => '喵喵喵' )
全部修改完成后就能正常的在typecho中使用emoji了
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- typecho
-
-
-
-
-
-
-
-
- 想起来当年还折腾过hexo
-
- /defect/hexo-again.html
-
- Hexo曾经的水文:
Hexo ✔Hexo and Github ✔
谁还不喜欢水呢(小声
hexo估计了解的人有很多了,在业界也是很知名的一款blog框架。说到blog程序,可能很多人都听说过知名的wordpress、typecho等。
那么hexo相对于他们的优势有什么呢?
全静态化站点 可部署于GitHub 一键部署 同样有丰富的插件 原生支持Markdown 曾经的曾经,那时的我刚开始研究hexo,还不够了解它的工作机制。谁让我以前比较笨。
以前我以为它是和部署普通的blog程序一样的,只是不需要用到数据库而已。于是我直接将其装在了自己的VPS上,虽然它也支持使用hexo-server来启用网页服务,直接部署在当前机器上。但是在我那个卡的要死的机器上使用ssh+vim来写markdown肯定不好受。
就算是在本地写完再上传也是比较麻烦的,尤其是后来研究了hexo与GitHub共同工作之后。发现它完全就可以部署在本地计算机上。写起来也更加的方便。 这就是这篇文章的作用了。
部署于Windows 所需:
Windows 用户 对于windows用户来说,建议使用安装程序进行安装。安装时,请勾选Add to PATH选项。 另外,您也可以使用Git Bash,这是git for windows自带的一组程序,提供了Linux风格的shell,在该环境下,您可以直接用上面提到的命令来安装Node.js。打开它的方法很简单,在任意位置单击右键,选择“Git Bash Here”即可。由于Hexo的很多操作都涉及到命令行,您可以考虑始终使用Git Bash来进行操作。
Gitbash与Node.js均有在Windows下的独立安装程序,就和安装其他软件一般,非常简单。不再赘述。当然也可以使用gitbash来安装node.js,都是同样的简单。
当git与node.js全部安装完成后,我们就可以使用一条命令直接安装hexo了。
$ npm install -g hexo-cli
建站🏘 当hexo以及其他所需要的环境都安装在我们的Windows上后,就可以开始配合GitHub来搭建一个托放在GitHub上的静态化blog了。
$ hexo init <folder>$ cd <folder>$ npm install
仅需三条命令,就可以部署一个文件夹为我们的站点跟目录了。当然这个文件夹需要是空的,必须要是新建的一个全空的文件夹,才能正常执行 hexo init 。 正常安装完成后,可以在目录下看到如下的文件树了。
.├── _config.yml├── package.json├── scaffolds├── source | ├── _drafts| └── _posts└── themes
日后我们新建的文章都会存放在source/_posts中,以便于hexo的渲染。
_config.yml 该配置文件用于修改一些站点的配置。可以修改大多数站点的参数。例如:站点标题,时区等。
# Site title: Defectinksubtitle: Another Defectink?description: Just Blogkeywords: author: DefectingCat
更多:配置
部署至GitHub🛰 部署至GitHub是非常简单且方便的一个操作了。相对于自己建设于VPS上的站点来说,优势于:
安装用于部署至GitHub的工具 hexo-deployer-git
$ npm install hexo-deployer-git --save
修改_config.yml中的deploy配置。
deploy: type: git repo: <repository url> #https: branch: [branch] #published message: [message]
如果要利用多个分支实现,一个分支用于存放hexo的文件,一个分支用于部署hexo生成的网页。那么就需要修改branch中的分支了。hexo会根据配置文件中的分支来创建并提交到分支中。
这一切是如何发生的? 当初次新建一个库的时候,库将自动包含一个master分支。请在这个分支下进行写作和各种配置来完善您的网页。当执行hexo deploy时,Hexo会创建或更新另外一个用于部署的分支,这个分支就是_config.yml配置文件中指定的分支。Hexo会将生成的站点文件推送至该分支下,并且完全覆盖该分支下的已有内容。因此,部署分支应当不同于写作分支。(一个推荐的方式是把master作为写作分支,另外使用public分支作为部署分支。)值得注意的是,hexo deploy并不会对本地或远程的写作分支进行任何操作,因此依旧需要手动推送写作分支的所有改动以实现版本控制。此外,如果您的Github Pages需要使用CNAME文件自定义域名,请将CNAME文件置于写作分支的source_dir目录下,只有这样hexo deploy才能将CNAME文件一并推送至部署分支。
首先需要满足:
仓库名(用户名.github.io)✔ 用于存放网页的必须是master分支✔ 如果不满足呢?当然也可以。
但是当你使用其他仓库名来创建网页(GitHub Pages),也可以使用“用户名.github.io”这个域名。但是会在域名后面添加一个仓库名。例如:“defectingcat.github.io/test”。 就好像是子目录一样。且不知道为什么分支只能使用gh-pages。
当所有条件都准备好了,配置文件也准备好了。那么现在就可以部署了。
hexo clean && hexo deploy
前者清除站点文件,后者重新生成站点文件并将之推送到指定的库分支。 每次都需要这么长的命令吗?不,通过markdown写完文章后。直接使用
hexo g -d
来部署至GitHub。
INFO Deploy done: git
看到这条消息,就说明我们已经向GitHub部署成功了。 此时访问GitHub的域名就可以打开刚刚部署好的hexo了。
恢复 部署至GitHub最大的好处就在于这里了,那就是恢复。刚刚上述有说过,我们利用两个分支,将生成的静态站点放在master分支,再额外创建一个分支用于存放hexo的核心文件。并使用git同步。
这样,当我们的本地的hexo的核心文件遭受损坏,或者误删的时候,我们就可以使用git。很轻松的获取一份曾经的备份。 例:
git clone https://gi thub.com/DefectingCat/ DefectingCat.github.io/tree/ file
另外,hexo默认是没有后台的面板的。毕竟是纯静态化的站点。貌似使用某些插件可以实现拥有后台面板。没有后台就意味着我们不在自己的电脑环境下更新自己的hexo文章就比较麻烦了。或者说更换电脑、操作系统等。我们的环境都会被更改。
此时,亦可以使用git恢复备份的文件。并再通过上述几个简单的步骤安装hexo。值得注意的是,使用以前的命令若安装不成功,可以试试:
npm install hexo
主题⛺ 无论是什么程序,那可能离不开主题。hexo也有很多很棒的主题,其中大部分都是开源主题。用起来也是很方便。 大红大紫的NexT应该是很多人都有了解了。找了半天也没有找到啥合适心意的主题。就决定试试这款主题了。 主题官方网站也有很完善的安装文档?使用文档
安装与启用 开源主题,直接clone。
$ cd your-hexo-site$ git clone https://gi thub.com/iissnan/ hexo-theme-next themes/next
亦或者使用其他方法:
curl -s https:// api.github.com/repos/ii ssnan/hexo-theme-next/ releases/latest | grep tarball_url | cut -d '"' -f 4 | wget -i - -O- | tar -zx -C themes/ next --strip-components=1
ok,无论是什么方法下载下来。都会在同一个文件夹themes/next
文件夹下。若不在,还是要主动移动到指定的文件较内。
安装完成后,可以通过修改配置文件来进行启用了。hexo很多的操作都是通过修改配置文件来实现的。虽然时修改配置文件,但是配置文件都是很人性化的,修改起来也非常的简单。并且NexT这款主题也有很完善的配置文档。
在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于站点根目录下,主要包含 Hexo 本身的配置;另一份位于主题目录下,这份配置由主题作者提供,主要用于配置主题相关的选项。
与其他所有hexo主题一样,启用方法都是:在站点配置文件中找到theme
字段,如下修改:
theme: next
完成只后推荐使用hexo clean来清除下缓存
菜单 菜单配置包括三个部分,第一是菜单项(名称和链接),第二是菜单项的显示文本,第三是菜单项对应的图标。 NexT 使用的是 Font Awesome 提供的图标, Font Awesome 提供了 600+ 的图标,可以满足绝大的多数的场景,同时无须担心在 Retina 屏幕下 图标模糊的问题。
也就相当于我们常见的独立页面了。 编辑主题配置文件,修改如下:
设定菜单内容,对应的字段是 menu。 菜单内容的设置格式是:item name: link。其中 item name 是一个名称,这个名称并不直接显示在页面上,她将用于匹配图标以及翻译。
menu: home: / || home about: /about/ || user tags: /tags/ || tags categories: /categories/ || th archives: /archives/ || archive #schedule: /schedule/ || calendar sitemap: /sitemap.xml || sitemap commonweal: /404 / || heartbeat
这里与官方配置文档写的不同的是,随着新版的更新,配置文件也更加的方便了。上述提到的 Font Awesome图标就是在menu配置后直接写的。例如:
home : / || home
这个|| home
就是Font Awesome的图标名啦。 而启用这个图标也非常的简单,就在上述配置的下方就有一个开关。
menu_icons: enable: true
除了主页与归档,其他页面都需要手动创建
创建独立页面 开启菜单的话,就需要创建一些独立页面来使用了。创建独立页面使用的就是hexo所说的“模板”了。官方文档模板
创建一个独立页面和创建一个新的文章的方式是一样的简单,但是使用到对应的模板创建成功后才能算是一个独立页面。
hexo new page about
使用这样的命令与创建文章的页面有什么不同呢?它也是生产一个新的index.html
。但它会在source
文件下创建对应的文件夹。例如:
$ ls source /_posts/ about/
创建about页面后就会有个about文件夹。其他页面同理。
菜单页面 上述我们启用了主题的菜单选项,但是菜单对应的都是一个独立页面。也就是类似于about所建立的独立页面。创建方法一样。不同的是根据主题的配置。
所有的菜单的页面配置都类似,在新建好的独立页面中配置类型“type”。例如tags:
title: 标签 date: 2014-12-22 12:39:04 type: "tags" ---
或者是categories:
title: 分类 date: 2014-12-22 12:39:04 type: "categories" ---
配和上述开启所需要的菜单后,我们就能在菜单栏中打开并访问对应的页面了。虽然是修改配置文件,但是也是非常的简单呢。?
若要禁止使用评论功能:
comments: false
CNAME 虽然GitHub送给我们了一个二级域名,但那个二级域名是需要配置自己的GitHub用户名使用的,往往我们的用户名可能就很长。本来就是二级域名了,再加上很长的域名,可能有时候自己都懒得输。
所以最佳、最方便的解决办法就是添加一个自己域名的cname解析到GitHub白送我们的域名上。 除了解析,hexo也要做相应的配置。也是非常的简单呢。
在网页的根目录下的source/
文件下新建一个名为CNAME
的空文件,在文件内写入我们cname过来的域名。
$ cat source/CNAMEdefect.ink
只需要写上域名就可以了,不需要戴上http等。
预览 记录的虽然不是太多,也可能不是那么详细。但是还是大致的顺着搭建成功这么一个放向来的的。 所以就留下一个截图的纪念吧?
图丢了
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
- HTML
-
-
-
-
-
-
-
-
- Hexo and Github
-
- /defect/hexo-and-github.html
-
- 这是一篇写于较早期的文章,当时水平有限,文章质量不高。
上次搭建hexo的时候是直接在服务器上使用hexo -server仍在服务器发布的。这次决定配合github,将hexo生成的静态页面部署至github,不仅有了github.io的域名,还能在github上做备份、版本控制等。
关于hexo安装的,可以去参考上一篇文章 →
安装完成后就是将hexo与github关联起来了
创建Repositories 为了将网页部署到github并发布,需要先创建一个仓库
在个人资料页面选择仓库(Repositories),并单击New来创建一个新的仓库:
• Repository name:仓库名称(需要使用格式为”Your_github_name.github.io”。Your_github_name一定要为你的github昵称,否则出现404状况) • Description:仓库描述(选填) • Public/Private:仓库类型(公开/私有) • Initialize this repository with a README:是否生成一个README文件初始化仓库(可选)
所有选项都填写完成后,点击Create Repository来创建仓库
随后便能看到自己刚刚创建的仓库了(下图为未创建一个README文件来初始化仓库)
关联Github 创建好仓库后,需要配置Github信息,以便于等会部署。
git config --global user.name "name" git config --global user.email "mail"
生成SSH密钥 ssh-keygen -t rsa
在生成时可以全部保持默认路径即可。
生成完毕后可以看到公钥的默认路径为:
/root/.ssh/id_rsa.pub
直接编辑并将所有内容复制至Github
vim .ssh/id_rsa.pub
部署至Github 直接修改_config.yml文件,并找到Deployment,修改为如下内容
deploy: type: git repo : git@github.com:DefectingCat/DefectingCat.github.io.git branch: master
repo:仓库的路径
branch:分支(默认为master)
hexo ghexo dhexo d -g
部署前生成静态页面
Deployer not found:git?
尝试如下命令:
npm install hexo-deployer-git --save
成功部署
查看刚刚所部署的仓库
访问域名测试
到此就成功部署到Github并运行成功了呢。
Over 这个只是最基本的部署发布页面,hexo还有很多高级操作。官方文档写的也非常详细,更多操作可以去参考官方文档 https://hexo.io/zh-cn/
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
- HTML
-
-
-
-
-
-
-
-
- hexo
-
- /defect/hexo.html
-
- 这是一篇写于较早期的文章,当时水平有限,文章质量不高。
好久不见
Hexo? Hexo是一款博客页面框架。特点是简洁、高效。使用markdown语法渲染文章。
其大概原理将本地markdown编写的.md文件经过多次渲染为html等静态页面文件。再由hexo服务发布。
可参考:
安装 Ubuntu
hexo的安装过程简便,工作原理与其他blog程序都大不相同。
安装前提
安装 Hexo 相当简单。然而在安装前,必须检查电脑中是否已安装下列应用程序:
• Node.js • Git
官方文档中提到安装所需要的上述应用程序,在ubuntu环境下Git在默认情况下为已经安装的。
若无git,可使用如下命令进行安装
apt install git
首先安装npm与node.js
apt install npm nodejs-legacynpm install -g hexo-cli
界面可能会如下
等待npm install -g hexo-cli命令完成后,hexo就已经安装在系统中了
接下来就是使用hexo建立站点了,可参考官方文档: https://hexo.io/zh-cn/docs/setup.html
$ hexo init <folder>$ cd <folder>$ npm install
配置 hexo init web
过程略长,稍等即可
init文件夹完成后,进入并安装
cd web && npm install
完成后文件目录为如下。
新建完成后,指定文件夹的目录如下:
.├── _config .yml├── package.json├── scaffolds├── source| ├── _drafts | └── _posts └── themes
_config.yml:站点的配置文件 package.json:应用程序信息 scaffolds:模版文件夹 source:用户资源存放文件夹 themes:主题文件夹 关于_config.yml的详细配置说明可参考官方文档: https://hexo.io/zh-cn/docs/configuration.html
服务端hexo-server Hexo 3.0 把服务器独立成了个别模块,需要安装才能够使用。
npm install hexo-server --save
安装完毕后,启动服务。(_config.yml需要先配置好)
hexo server -p 80 -s
正常情况下便可以直接进行访问,为如下页面。
Cannot GET / ?
如出现运行服务端后访问提示为”Cannot GET / “,请尝试:
• 确保在init的目录下运行过npm install • 添加了-s 参数,需要运行”hexo generate”命令生成静态文件。
无法打开?
• Hexo server默认端口号为4000,可用-p参数进行修改 • 需要在_config.yml配置文件中配置域名。
• 也可以使用-i命令修改监听的ip地址。默认为0.0.0.0 • 需要在所init的目录下运行server命令
Hexo server会占用整个shell,对于ssh连接来说,断开后便会中断服务。
我们可以使用screen命令搭配运行hexo server
screen hexo s -p 80 -s
运行后按下Ctrl+a+d
将当前窗口放置后台运行
查看screen -ls
重新连接会话screen -r 25211
写作 可以执行下列命令来创建一篇新文章。
$ hexo new [layout] <title>
可以直接使用命令来创建文章,默认文件名为title
hexo new TEST
可以看到创建的文章会被创建为source目录下的.md文件,可以直接使用markdown语法写作修改。
若是静态运行,每次修改文章后需要使用hexo generate命令渲染。
更多方式可以了解官方说明: https://hexo.io/zh-cn/docs/writing.html
结尾 Hexo是一款不同与其他的博客框架,其简洁、高效和多样性的用法也是吸引人的一大特点。无需数据库,所有文章都在统一文件夹内,真正纯静态化的站点。新颖、特殊的工作方式让人眼前一亮。由于与众不同的特点,hexo与markdown的方式还得日后慢慢学习使用。
此文完全参照与官方说明文档。
]]>
-
-
-
-
- 实践
-
-
-
-
-
-
- Linux
-
- HTML
-
-
-
-
-
-
-
-
- Hello World
-
- /defect/hello-world.html
-
- Welcome to Hexo ! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub .Quick Start Create a new post $ hexo new "My New Post"
More info: Writing
Run server $ hexo server
More info: Server
Generate static files $ hexo generate
More info: Generating
Deploy to remote sites $ hexo deploy
More info: Deployment
]]>
-
-
-
-
-
-
-
-
-
diff --git a/xml/rss.xml b/xml/rss.xml
deleted file mode 100644
index edc4e56..0000000
--- a/xml/rss.xml
+++ /dev/null
@@ -1,589 +0,0 @@
-
-
-
- 🍭Defectink
- https://www.defectink.com/
-
-
- https://www.defectink.com/icon.png
- 🍭Defectink
- https://www.defectink.com/
-
-
-
-
-
- Mon, 02 Nov 2020 02:17:16 GMT
- http://hexo.io/
-
- -
-
Vue3中的响应数据
- https://www.defectink.com/defect/response-data-in-Vue3.html
- https://www.defectink.com/defect/response-data-in-Vue3.html
- Mon, 02 Nov 2020 10:01:55 GMT
-
-
-
-
-
- <h2 id="实时渲染"><a href="#实时渲染" class="headerlink"
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
- Vue
-
-
- https://www.defectink.com/defect/response-data-in-Vue3.html#disqus_thread
-
-
-
- -
-
JavaScript-可迭代对象与for-of
- https://www.defectink.com/defect/javascript-iterable-object-and-for-of.html
- https://www.defectink.com/defect/javascript-iterable-object-and-for-of.html
- Thu, 29 Oct 2020 17:15:48 GMT
-
-
-
-
-
- <h2 id="Iterable-object(可迭代对象)"><a href="#Iterable-object(可迭代对象)" class="headerlink" title="Iterable object(可迭代对象)"></a>Iterable
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
-
- https://www.defectink.com/defect/javascript-iterable-object-and-for-of.html#disqus_thread
-
-
-
- -
-
Vue.js-起步!
- https://www.defectink.com/defect/Vue-js-get-started.html
- https://www.defectink.com/defect/Vue-js-get-started.html
- Thu, 22 Oct 2020 11:21:22 GMT
-
-
-
-
-
- <p>在我打算学习vue的时候,正是其3.0版本发布不久的时候。很庆幸生活在这个时代,但困扰我的是是否应该由旧版本的2.x开始学习?一向选择困难的我最终打算两个版本一起学习,从2.x开始入门,顺便还能一睹其与3.0版本的变化。</p>
-<h2 id="起步"><a
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
- Vue
-
-
- https://www.defectink.com/defect/Vue-js-get-started.html#disqus_thread
-
-
-
- -
-
Node.js之旅
- https://www.defectink.com/defect/get-starting-for-node-js.html
- https://www.defectink.com/defect/get-starting-for-node-js.html
- Thu, 03 Sep 2020 16:09:29 GMT
-
-
-
-
-
- <blockquote>
-<p>Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
- Node
-
-
- https://www.defectink.com/defect/get-starting-for-node-js.html#disqus_thread
-
-
-
- -
-
入坑IRC
- https://www.defectink.com/defect/irc-getting-started.html
- https://www.defectink.com/defect/irc-getting-started.html
- Sat, 22 Aug 2020 18:37:36 GMT
-
-
-
-
-
- <h2 id="IRC"><a href="#IRC" class="headerlink" title="IRC"></a>IRC</h2><p>IRC的全称为Internet Relay
-
-
-
-
-
-
- 日常
-
-
- Linux
-
- tools
-
- IRC
-
-
- https://www.defectink.com/defect/irc-getting-started.html#disqus_thread
-
-
-
- -
-
JavaScript的函数
- https://www.defectink.com/defect/function-of-javascript.html
- https://www.defectink.com/defect/function-of-javascript.html
- Fri, 07 Aug 2020 11:26:10 GMT
-
-
-
-
-
- <p>函数表达式是JavaScript中一个强大同时容易令人困惑的特性。</p>
-<p>定义函数的方式有两种:函数声明和函数表达式。</p>
-<p>函数声明首先是<code>function</code>关键字,其后是函数的名字,这就是指定函数名字的方式。</p>
-<pre><c
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
-
- https://www.defectink.com/defect/function-of-javascript.html#disqus_thread
-
-
-
- -
-
Can't install gifsicle
- https://www.defectink.com/defect/cant-install-gifsicle.html
- https://www.defectink.com/defect/cant-install-gifsicle.html
- Tue, 04 Aug 2020 14:03:00 GMT
-
-
-
-
-
- <h2 id="Hexo-all-minifier"><a href="#Hexo-all-minifier" class="headerlink" title="Hexo-all-minifier"></a>Hexo-all-minifier</h2><p>In a long
-
-
-
-
-
-
- 踩坑
-
-
- network
-
-
- https://www.defectink.com/defect/cant-install-gifsicle.html#disqus_thread
-
-
-
- -
-
JavaScript面向对象的程序设计
- https://www.defectink.com/defect/javascript-object-oriented-programming.html
- https://www.defectink.com/defect/javascript-object-oriented-programming.html
- Mon, 27 Jul 2020 16:42:32 GMT
-
-
-
-
-
- <blockquote>
-<p>Standing on Shoulders of
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
-
- https://www.defectink.com/defect/javascript-object-oriented-programming.html#disqus_thread
-
-
-
- -
-
JavaScript的作用域与链
- https://www.defectink.com/defect/javascript-scope-and-chain.html
- https://www.defectink.com/defect/javascript-scope-and-chain.html
- Fri, 03 Jul 2020 15:13:05 GMT
-
-
-
-
-
- <p>JavaScript是一门动态语言,也常称呼为弱类型/解释型的语言。除了不需要明确写出数据类型外,JavaScript的作用域也和其他类型的语言不同。并且作用域还以链的方式互相连接。</p>
-<h2 id="预编译"><a href="#预编译"
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
-
- https://www.defectink.com/defect/javascript-scope-and-chain.html#disqus_thread
-
-
-
- -
-
写作与协作
- https://www.defectink.com/defect/write-and-cooperation.html
- https://www.defectink.com/defect/write-and-cooperation.html
- Mon, 29 Jun 2020 12:12:41 GMT
-
-
-
-
-
- <p>出于对速度无理的追求,最终还是放弃了使用动态内容。转战静态blog。以前也稍微尝试过hexo,所以决定还是主要为hexo为主了。</p>
-<p>在之前试过的typecho、wordpress之中,越是臃肿复杂的程序,1M的带宽越是不够。再详细的折腾了hexo之后,发现了最佳
-
-
-
-
-
-
- 实践
-
-
- Tools
-
-
- https://www.defectink.com/defect/write-and-cooperation.html#disqus_thread
-
-
-
- -
-
JavaScript笔记-引用类型
- https://www.defectink.com/defect/javascript-notes-reference-type.html
- https://www.defectink.com/defect/javascript-notes-reference-type.html
- Mon, 06 Jan 2020 09:14:53 GMT
-
-
-
-
-
- <blockquote>
-<p>这是来自Professional JavaScript for Web Develops第五章的笔记。</p>
-</blockquote>
-<p><img
-
-
-
-
-
-
- 笔记
-
-
- JavaScript
-
-
- https://www.defectink.com/defect/javascript-notes-reference-type.html#disqus_thread
-
-
-
- -
-
Docker-全面容器化!
- https://www.defectink.com/defect/docker-container-all.html
- https://www.defectink.com/defect/docker-container-all.html
- Thu, 19 Dec 2019 11:11:33 GMT
-
-
-
-
-
- <p>自上篇<a href="https://www.defectink.com/defect/docker-build-own-images.html">Docker -
-
-
-
-
-
-
- 实践
-
-
- Linux
-
-
- https://www.defectink.com/defect/docker-container-all.html#disqus_thread
-
-
-
- -
-
Header实践-得拿下这个A
- https://www.defectink.com/defect/header-practice-have-to-win-this-a.html
- https://www.defectink.com/defect/header-practice-have-to-win-this-a.html
- Wed, 18 Dec 2019 16:42:53 GMT
-
-
-
-
-
- <p><a
-
-
-
-
-
-
- 实践
-
-
- HTML
-
-
- https://www.defectink.com/defect/header-practice-have-to-win-this-a.html#disqus_thread
-
-
-
- -
-
Docker-构建属于自己的镜像
- https://www.defectink.com/defect/docker-build-own-image.html
- https://www.defectink.com/defect/docker-build-own-image.html
- Fri, 29 Nov 2019 09:30:33 GMT
-
-
-
-
-
- <p>以前一直在使用别人构建好的镜像来使用Docker容器,在一次想搭建一个完整的Web环境时,发现使用过多容器非常难以管理。并且容器之间的交互通信变的困难。当然,也可以使用Docker
-
-
-
-
-
-
- 实践
-
-
- Linux
-
-
- https://www.defectink.com/defect/docker-build-own-image.html#disqus_thread
-
-
-
- -
-
修改Windows端iCloud云盘存储位置
- https://www.defectink.com/defect/modify-icloud-storage-location-on-windows.html
- https://www.defectink.com/defect/modify-icloud-storage-location-on-windows.html
- Mon, 18 Nov 2019 12:53:53 GMT
-
-
-
-
-
- <p>自从有了水果之后,除了天天被人嘲讽之外,还花了大手笔买了一个月6块的iCloud
-
-
-
-
-
-
- 踩坑
-
-
- Windows
-
-
- https://www.defectink.com/defect/modify-icloud-storage-location-on-windows.html#disqus_thread
-
-
-
- -
-
ASCII在线视频流
- https://www.defectink.com/defect/online-ascii-video.html
- https://www.defectink.com/defect/online-ascii-video.html
- Sat, 29 Jun 2019 12:12:41 GMT
-
-
-
-
-
- <p>什么是ASCII?</p>
-<p>来自百度百科的解释:<br>ASCII(American Standard Code for Information
-
-
-
-
-
-
- 实践
-
-
- Tools
-
-
- https://www.defectink.com/defect/online-ascii-video.html#disqus_thread
-
-
-
- -
-
bark-水果自定义通知大法🍎
- https://www.defectink.com/defect/bark-custom-notification-for-apple.html
- https://www.defectink.com/defect/bark-custom-notification-for-apple.html
- Fri, 28 Jun 2019 12:12:41 GMT
-
-
-
-
-
- <blockquote>
-<p>ding~</p>
-</blockquote>
-<p>用了一段时间的水果了,发现它的通知来的还是非常及时的。基本上只要连接了网络,通知都不会落下。简单了解过IOS的通知机制:APP→水果服务器→你的机器。也就是说这三个步骤都能够正常通信的情况下,我
-
-
-
-
-
-
- 踩坑
-
-
- Tools
-
-
- https://www.defectink.com/defect/bark-custom-notification-for-apple.html#disqus_thread
-
-
-
- -
-
AliOssForTypecho
- https://www.defectink.com/defect/alioss-for-typecho.html
- https://www.defectink.com/defect/alioss-for-typecho.html
- Wed, 26 Jun 2019 16:42:41 GMT
-
-
-
-
-
- <p>原作大佬:</p>
-<ul>
-<li><a
-
-
-
-
-
-
- 踩坑
-
-
- typecho
-
-
- https://www.defectink.com/defect/alioss-for-typecho.html#disqus_thread
-
-
-
- -
-
Gitlab尝鲜
- https://www.defectink.com/defect/try-the-gitlab.html
- https://www.defectink.com/defect/try-the-gitlab.html
- Wed, 19 Jun 2019 15:42:41 GMT
-
-
-
-
-
- <h2 id="Gitlab"><a href="#Gitlab" class="headerlink" title="Gitlab?"></a>Gitlab?</h2><p><strong>GitLab</strong>是由GitLab Inc.开发,使用<a
-
-
-
-
-
-
- 实践
-
-
- Linux
-
-
- https://www.defectink.com/defect/try-the-gitlab.html#disqus_thread
-
-
-
- -
-
systemd的基础操作
- https://www.defectink.com/defect/basic-knowledge-of-systemd.html
- https://www.defectink.com/defect/basic-knowledge-of-systemd.html
- Fri, 14 Jun 2019 14:42:41 GMT
-
-
-
-
-
- <h2 id="什么是systemd?"><a href="#什么是systemd?" class="headerlink"
-
-
-
-
-
-
- Linux
-
-
- Linux
-
-
- https://www.defectink.com/defect/basic-knowledge-of-systemd.html#disqus_thread
-
-
-
-
-
diff --git a/xml/sitemap.xml b/xml/sitemap.xml
deleted file mode 100644
index cc7e58b..0000000
--- a/xml/sitemap.xml
+++ /dev/null
@@ -1,400 +0,0 @@
-
-
-
-
- https://www.defectink.com/pgp/index.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/get-starting-for-node-js.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/basic-knowledge-of-qinq.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/multi-platform-real-time-synchronization-by-resilio-sync.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/teamspeak-server.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/response-data-in-Vue3.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/hello-world.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/hexo.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/minecraft-bedrock-server.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/basic-knowledge-of-systemd.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/make-typecho-support-the-emoji.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/modify-icloud-storage-location-on-windows.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/irc-getting-started.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/public-key-cryptgraphy.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/write-and-cooperation.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/hexo-again.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/bark-custom-notification-for-apple.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/auto-backup.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/fixed-inotify-watch-not-enough.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/javascript-notes-reference-type.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/Vue-js-get-started.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/javascript-object-oriented-programming.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/about/index.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/online-ascii-video.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/alioss-for-typecho.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/cant-install-gifsicle.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/try-the-gitlab.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/header-practice-have-to-win-this-a.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/hexo-and-github.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/javascript-iterable-object-and-for-of.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/javascript-scope-and-chain.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/docker-container-all.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/docker-build-own-image.html
-
- 2020-11-02
-
-
-
-
- https://www.defectink.com/defect/function-of-javascript.html
-
- 2020-11-02
-
-
-
-
-
- https://www.defectink.com/
- 2020-11-02
- daily
- 1.0
-
-
-
-
- https://www.defectink.com/tags/Tools/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/typecho/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/network/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Linux/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/HTML/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/JavaScript/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Node/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Network/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Player/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Vue/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Minecraft/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/Windows/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/tools/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/tags/IRC/
- 2020-11-02
- daily
- 0.6
-
-
-
-
-
- https://www.defectink.com/categories/%E5%AE%9E%E8%B7%B5/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/%E8%B8%A9%E5%9D%91/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/%E7%AC%94%E8%AE%B0/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/%E7%BD%91%E7%BB%9C/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/Player/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/Linux/
- 2020-11-02
- daily
- 0.6
-
-
-
- https://www.defectink.com/categories/%E6%97%A5%E5%B8%B8/
- 2020-11-02
- daily
- 0.6
-
-
-
\ No newline at end of file