-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.json
1 lines (1 loc) · 90.2 KB
/
search.json
1
[{"title":"django 与 mysql 勾结指南","url":"/2019/04/14/djangoandmysql/","content":"\n<hr>\n\n> 📖 阅读本文大概需要 26 分钟。\n\n参考文章:\n\nhttps://blog.51cto.com/eagle6899/2146972\n\nhttps://blog.csdn.net/qq_36963372/article/details/82558085\n\n#### 第一步:配置 setting.py\n\n```python\n# Database\n# https://docs.djangoproject.com/en/2.2/ref/settings/#databases\n\nDATABASES = {\n 'default': {\n # 'ENGINE': 'django.db.backends.sqlite3',\n # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),\n \n 'ENGINE': 'django.db.backends.mysql',\n 'NAME': 'mydatabase',\n 'USER': 'root',\n 'PASSWORD': 'root',\n 'HOST': '127.0.0.1',\n 'PORT': '3306',\n }\n}\n```\n\n#### 第二步:执行 migrate \n\n```\n$ python manage.py migrate\n```\n\n不出意外会让你安装 `mysqlclient`\n\n```\n$ pip install mysqlclient\n```\n\n你能下载成功,但可能安装失败。提示类似 **`“_mysql.c(29): fatal error C1083: 无法打开包括文件: “mysql.h”: No such file\nor directory”`** 的信息。\n\n> 总而言之,这是 window 开发者需要背负的穷罪。\n\n解决方案都在这里:https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient\n\n我们的目标是手动选择一个适合的 mysqlclient.whl ,然后编译。\n\n---\n\n#### 1、先安装 wheel,才可以编译 *.whl 文件\n\n```\n$ pip install wheel\n```\n\n#### 2、安装Microsoft Visual C++\n\nPython 2.7:Microsoft Visual C++ 2008 ([x64](https://www.microsoft.com/en-us/download/details.aspx?id=15336), [x86](https://www.microsoft.com/en-us/download/details.aspx?id=29), and [SP1](https://www.microsoft.com/en-us/download/details.aspx?id=26368)) \n\nPython 3.x:Visual C++ 2017 ([x64 or x86](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)) \n\n#### 3、查看 pip 支持的版本\n\n```\n# AMD64\nimport pip._internal\nprint(pip._internal.pep425tags.get_supported())\n\n# WIN32\nimport pip\nprint(pip.pep425tags.get_supported())\n```\n\n环境不同,输出不同,我的输入如下:\n\n```\n[('cp37', 'cp37m', 'win32'), ('cp37', 'none', 'win32'), ('py3', 'none', 'win32'), ('cp37', 'none', 'any'), ('cp3', 'none', 'any'), ('py37', 'none', 'any'), ('py3', 'none', 'any'), ('py36', 'none', 'any'), ('py35', 'none', 'any'), ('py34', 'none', 'any'), ('py33', 'none', 'any'), ('py32', 'none', 'any'), ('py31', 'none', 'any'), ('py30', 'none', 'any')]\n```\n\n根据我的支持表,我找到了文件: [mysqlclient-1.4.2-cp37-cp37m-win32.whl](https://download.lfd.uci.edu/pythonlibs/u2hcgva4/mysqlclient-1.4.2-cp37-cp37m-win32.whl)\n\n你可以在这里查找:[https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient](https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient)\n\n也可以在pip仓库查找各种历史版本:[https://pypi.org/project/mysqlclient/#files](https://pypi.org/project/mysqlclient/#files)\n\n下载之后,进行安装\n\n```\n$ pip install mysqlclient-1.4.2-cp37-cp37m-win32.whl\n```\n\n成功了你会看到如下输出:\n```\nProcessing c:\\users\\lee\\downloads\\mysqlclient-1.4.2-cp37-cp37m-win32.whl\nInstalling collected packages: mysqlclient\nSuccessfully installed mysqlclient-1.4.2\n```\n\n如果是不正确的版本,你会出现如下报错:\n\n**`mysqlclient-1.3.11-cp36-cp36m-win32.whl is not a supported wheel on this platform.`**\n\n不需要担心,慢慢找到匹配 pip 的即可。\n\n---\n\n一切尘埃落定之后,重新执行一下最初的 migratemigrate 命令。\n\n```\n$ python manage.py migrate\n```\n\n如果你的 mysql 版本是 5.5(笔者用 phpstudy 最新版也只有5.5)。还会出现一个 SQL 错误的信息:\n\n```\ndjango.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table ((1064, \"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax\nto use near '(6) NOT NULL)' at line 1\"))\n```\n\nMySQL5.5并不支持Django2.1生成的这种SQL语句。我选择安装了 mysql lastest 版本。既 (mysql8.0.15)[https://dev.mysql.com/downloads/mysql/]\n\n如果不会安装,请参考我的另一篇建议笔记:[mysql 编译安装 window篇](https://www.cnblogs.com/CyLee/p/7421949.html)\n\n或者参考网站 mysql 安装教程。总之要确保运行中的 mysql 服务版本是 5.5 以上。\n\n在确保你的 mysql 是最新且能访问之后。重新执行一下该命令。\n\n```\npython manage.py migrate\n```\n\n如果成功会看到如下信息:\n\n\n\n再看看你的数据库,django 生成了不少实用的表。\n\n\n\n(完)","tags":["django","mysql"],"categories":["django"]},{"title":"npm 发布包和删除包(2019最新攻略)","url":"/2019/03/20/npmpublish/","content":"\n<hr>\n> 📖 阅读本文大概需要 6 分钟。\n\n### 操作概览\n1. 验证你的包名是否重复。\n2. npm 官网注册账号(略)。\n3. npm init 初始化你的包。\n4. 发布 npm publish。\n5. 如何发布新版本?\n6. 如何删除你的发布包?\n7. 什么是 2FA?什么是 Authenticator App?什么是 One-time Password?\n8. (后记)补充说明\n\n<!--more--> \n\n#### 一、验证你的包名是否重复\n\n有两种方案:\n\n(推荐)1、直接上 [npmjs.com](https://www.npmjs.com/) 官网搜索\n\n\n\n2、也可以用一些工具库查找,虽然有点画蛇添足,但某些场景还是适用的。比如动态发布包。\n\n- [npm-name-cli](https://github.com/sindresorhus/npm-name-cli)\n\n\n\n\n---\n\n> 二、[npm 官网](https://www.npmjs.com/login)注册账号(略)\n\n\n---\n\n#### 三、npm init 初始化你的包。\n\n```bash\n$ npm init -y\n```\n\n#### package.json\n\n重点关注和修改以下三项:\n\n- name:你的包名\n- version:(推荐)用 jQuery 的版本规范:**0.0.1**\n- main:你的入口文件\n\n```json\n{\n \"name\": \"chuanghui-vue-portal\",\n \"version\": \"0.0.1\",\n \"main\": \"src/components/chuanghui-portal.vue\",\n \"description\": \"ChuangHui Vue Components\",\n \"author\": \"lizhaohong <928532756@qq.com>\"\n}\n```\n\n#### 四、发布 npm publish\n\n先添加 npm 账号\n```bash\n$ npm adduser \nUsername: ...\nPassword: ...\nEmail: (this IS public) 928532756@qq.com\nLogged in as cylee on https://registry.npmjs.org/.\n```\n\n\n\n正式发布,就一句话。\n\n```\n$ npm publish\n```\n\n正常的话,在 npm 个人 package 页面中可以看到上传的包:\n\n\n\n#### 五、迭代新版本\n\n只需要把你 `package.json` 的 `version` 版本号改变,如 **0.0.1 -> 0.0.2**,再执行 `$ npm publish` 即可。\n\n\n---\n\n\n#### 六、删除发布包\n\n> 如果你和我一样有强迫症,仅仅是修复一个 bug 就要把版本号从 0.0.1 升级到 0.02。\n>\n> 心里肯定很纠结,更多的可能是选择删掉包重新上传。\n\n网上介绍删除发布包的方法倒也简单。执行以下即可:\n\n```bash\n$ npm unpublish --force\n```\n\n但你可能出现 `ERR:2FA` 之类的错误信息?那你可能要先进行一大堆设置了,看下去吧。\n\n#### 七、什么是 2FA?什么是 Authenticator App?什么是 One-time Password?\n\n简单概括:\n- **2FA**: NPM 发布包管理的权限设置,可以在 NPM 后台配置;\n- **Authenticator App**:是微软 Microsoft 出品的一款实时密码App,请自行到App商店搜索下载;\n- **One-time Password**:Authenticator App 输出的实时密码。\n\n具体设置步骤:[官方教程](https://docs.npmjs.com/configuring-two-factor-authentication)\n\n1、到 App 商店搜索并且下载 Microsoft Authenticator App.\n\n\n2、进入 npm 后台,找到如图所示:\n\n\n3、选择 [Authorization and Publishing] - [submit]\n\n\n\n4、打开 Authenticator App,选择 “添加账户” - “其他账户(Google、Facebook 等)”\n\n\n\n5、扫描 `步骤3` 后的二维码。\n\n6、体验 One-time Password。如图所示\n\n\n\n7、使用 One-time Password 删除发布包。需要加上 --otp <One-time Password>\n\n```bash\n$ npm unpublish chuanghui-portal --force --otp 863613\n```\n\n#### 八、(后记)\n\n开通了 2FA 以后,你的账号发布包`$ npm publish` 都是需要使用 One-Time Password的。\n\n```bash\n$ npm publish --otp 863613\n```","tags":["npm","nodejs"],"categories":["npm"]},{"title":"flutter Android 调试指南","url":"/2019/03/12/flutterdebuger/","content":"\n<hr>\n\n操作预览:\n1. 准备一条数据线,并连接电脑和手;\n2. 使用 `flutter devices` 查看设备能否找到;\n3. 在 `Android studio` 中选择你的真机,然后点击 `[debug]`;\n4. 真机自动安装App。\n\n<!--more--> \n\n#### 一、准备一条数据线,并连接电脑和手机\n\n> 注意:切记不是充电线\n\n如果正常连接成功,你的手机和电脑都有提示。注意手机会提示你选择【USB 传输方式】,必须选择【传输文件】(或者MTP(多媒体传输))\n\n\n\n#### 二、使用 `flutter devices` 查看设备能否找到\n\n\n\n#### 三、在 `Android studio` 中选择你的真机,然后点击 `[debug]`\n\n\n\n#### 四、真机自动安装App(略)。\n\n","tags":["flutter"],"categories":["flutter"]},{"title":"androidssr","url":"/2019/03/10/androidssr/","content":"\n<hr>\n\n看上去比windows客户端多了很多选项,但实际上只需要设置这五个:\n\n<!--more-->\n\n> 链接:https://pan.baidu.com/s/1PKL0ViJJRJw9zkG8AlvEdQ \n> \n> 提取码:p175 \n\n### 操作步骤:\n\n0. 下载安装安卓ssr\n1. 输入ip\n2. 输入密码\n3. 输入端口\n4. 选择混淆方式:plain\n5. 选择加密方法:aes-256-cfb(和你的服务器加密一样)\n6. 点击右上角的飞机\n\n\n\n","tags":["ssr"],"categories":["ssr"]},{"title":"请求缓存策略","url":"/2019/03/06/requestjs/","content":"\n<hr>\n\n该代码摘抄自 [ant-design-pro](https://github.com/ant-design/ant-design-pro/blob/master/src/utils/request.js#L66) \n\nhttps://github.com/ant-design/ant-design-pro/blob/master/src/utils/request.js#L66\n\n只要不是实时数据的接口,基本上都可以充分利用请求缓存的特性,节约客户端网络资源的浪费,也减少服务器的请求压力。\n\n特别是我的项目中的数据,最短的迭代周期也是“每日一更”。所以缓存特性可以放心大胆使用。但为了保险,缓存生命周期还是设置短一点。比如1分钟。\n\n原理大致是这样的:\n1. 每次请求都先判断是否存在缓存,如果没有则存起来,如果有则需要判断缓存是否过期,如果过期则还是要请求,否则才返回缓存;\n2. 每次存之前,判断缓存是否满了?(sessionStoreage的容量大概5M),如果满了则需要清空再存入。值得注意的是,如果数据体积超过5M(几十万数据),那你清空存入还是会报错。所以我们存入之前要判断数据体积是否大于5M,如果是则不加入缓存;\n\n<!--more--> \n#### 问题一:如何判断你浏览器的缓存容量,可以手动执行以下代码。不出意外肯定是5120kb。\n\n```JavaScript\n// 获取localStorage最大容量\n(function() {\n if(!window.sessionStorage) {\n console.log('当前浏览器不支持sessionStorage!')\n } \n var test = '0123456789';\n var add = function(num) {\n num += num;\n if(num.length == 10240) {\n test = num;\n return;\n }\n add(num);\n }\n add(test);\n var sum = test;\n var show = setInterval(function(){\n sum += test;\n try {\n window.sessionStorage.removeItem('test');\n window.sessionStorage.setItem('test', sum);\n console.log(sum.length / 1024 + 'KB');\n } catch(e) {\n console.log(sum.length / 1024 + 'KB超出最大限制');\n clearInterval(show);\n }\n }, 0.1)\n })()\n\n\n// 获取sessionStorage的剩余容量\n(function(){\n if(!window.sessionStorage) {\n console.log('浏览器不支持sessionStorage');\n }\n var size = 0;\n for(item in window.sessionStorage) {\n if(window.sessionStorage.hasOwnProperty(item)) {\n size += window.sessionStorage.getItem(item).length;\n }\n }\n console.log('当前sessionStorage剩余容量为' + (size / 1024).toFixed(2) + 'KB');\n})()\n```\n\n\n\n#### 问题二:以什么作为缓存键(sessionStorage keys)?\n\n1. Url:请求地址,含 “?” 后面的 GET 参数;\n2. Options:包含 headers 和 params、body 等;\n3. Other:譬如有一些公共参数通常是在 `interceptors request` 请求拦截器中才添加的。如果没有则不计入。\n\n```\nimport hash from 'hash.js'\n\n// 缓存键(指纹) = 请求url + 请求配置 + 其他特殊参数\nconst fingerprint = Url + Options + Other;\n// 加密混淆\nconst hashcode = hash( fingerprint )\n```\n\n\n#### 问题三:sessionStorage 是没有过期和生命周期的概念的,需要怎么实现?\n\n需要新建一个特殊的键来记录该缓存录入的时间,比如:\n\n```JavaScript\n// 设置缓存\nsessionStorage.setItem(hashcode, JSON.stringify(content))\n// 同时,设置该缓存的录入时间\nsessionStorage.setItem(`${hashcode}:timestamp`, Date.now())\n```\n\n判断缓存是否过期(这里省略了判断缓存是否存在的代码):\n```JavaScript\n// 我们约定缓存的过期时间是60秒\nconst expirys = 60 \n\n// 获取该缓存的录入时间\nconst whenCached = sessionStorage.getItem(`${hashcode}:timestamp`)\n\n// 判断缓存过去了多久时间了\nconst age = (Date.now() - whenCached) / 1000\n\n// ...如果缓存没有过期\nif (age < expirys) {\n console.log('use cache')\n} else {\n console.log('no cache')\n}\n```\n\n#### 问题四:如何捕捉 sessionStorage 超出?\n错误名为 `\"QuotaExceededError\"`\n> 值得注意的是,尽管你清空缓存再重新存入,内容体积如果是大于5M依然会再度报错。所以最好限制缓存的大小。如果大于某个体积(譬如2M),则不存入缓存。\n\n```JavaScript\n/**\n * say something ...\n *\n * @hashcode {String} 缓存键\n * @content {String} 缓存值\n */\nconst cachedSave = (hashcode, content) => {\n try {\n // 返回code500是后端固定的报错反馈 && 不能为空对象 && 数据的小于2M\n if (content.code != 500 && !isEmptyObject(content) && (JSON.stringify(content).length / 1024).toFixed(2) < 2048) {\n // 设置缓存\n sessionStorage.setItem(hashcode, JSON.stringify(content))\n // 设置缓存时间\n sessionStorage.setItem(`${hashcode}:timestamp`, Date.now())\n }\n } catch (err) {\n // 超出缓存大小\n if (err.name === 'QuotaExceededError') {\n // 清空所有缓存\n sessionStorage.clear()\n // 重新设置缓存\n sessionStorage.setItem(hashcode, JSON.stringify(content))\n // 重新设置缓存时间\n sessionStorage.setItem(`${hashcode}:timestamp`, Date.now())\n }\n }\n\n // 返回Promise\n return content\n}\n```\n\n#### 问题五:如何加密指纹(sessionStorage keys)?\n可以使用开源的第三方库: `hash.js`\nhttps://github.com/indutny/hash.js\n\n使用示例:\n```JavaScript\nvar hash = require('hash.js')\nhash.sha256().update('你需要加密的数据').digest('hex')\n```\n\n\n## 最终代码\nrequest.js:\n```JavaScript\nimport axios from 'axios'\nimport hash from 'hash.js'\n\n// 判断是否为一个空对象:{}\nconst isEmptyObject = obj => {\n if (Object.getOwnPropertyNames) {\n return (Object.getOwnPropertyNames(obj).length === 0);\n } else {\n var k;\n for (k in obj) {\n if (obj.hasOwnProperty(k)) {\n return false;\n }\n }\n return true;\n }\n}\n\n// 检查状态码\nconst checkStatus = (response) => {\n // 判断请求状态\n if (response.status >= 200 && response.status < 300) {\n // 返回Promise\n return response.data\n } else {\n // 服务器响应异常\n throw new Error(response.statusText)\n }\n}\n\n// 缓存到sessionStorage\nconst cachedSave = (hashcode, content) => {\n try {\n // 返回code500是后端固定的报错反馈 && 不能为空对象 && 数据的小于2M\n if (content.code != 500 && !isEmptyObject(content) && (JSON.stringify(content).length / 1024).toFixed(2) < 2048) {\n // 设置缓存\n sessionStorage.setItem(hashcode, JSON.stringify(content))\n // 设置缓存时间\n sessionStorage.setItem(`${hashcode}:timestamp`, Date.now())\n }\n } catch (err) {\n // 超出缓存大小\n if (err.name === 'QuotaExceededError') {\n // 清空所有缓存\n sessionStorage.clear()\n // 重新设置缓存\n sessionStorage.setItem(hashcode, JSON.stringify(content))\n // 重新设置缓存时间\n sessionStorage.setItem(`${hashcode}:timestamp`, Date.now())\n }\n }\n\n // 返回Promise\n return content\n}\n\n// 公共请求\nexport const request = (url, options = {}) => {\n // 指纹\n const fingerprint = url + JSON.stringify(options)\n // 加密指纹\n const hashcode = hash.sha256().update(fingerprint).digest('hex')\n // 预设值指纹\n const _cachedSave = cachedSave.bind(null, hashcode)\n // 过期设置\n const expirys = options && options.expirys || 60\n // 本请求是否禁止缓存?\n if (expirys !== false) {\n // 获取缓存\n const cached = sessionStorage.getItem(hashcode)\n // 获取该缓存的时间\n const whenCached = sessionStorage.getItem(${hashcode}:timestamp)\n // 如果缓存都存在\n if (cached !== null && whenCached !== null) {\n // 判断缓存是否过期\n const age = (Date.now() - whenCached) / 1000\n // 如果不过期的话直接返回该内容\n if (age < expirys) {\n // 新建一个response\n const response = new Response(new Blob([cached]))\n // 返回promise式的缓存\n return new Promise((resolve, reject) => resolve(response.json()))\n }\n // 删除缓存内容\n sessionStorage.removeItem(hashcode)\n // 删除缓存时间\n sessionStorage.removeItem(${hashcode}:timestamp)\n }\n }\n return axios(url, options).then(checkStatus).then(_cachedSave)\n}\n```\n\n\n### 如何使用 request.js?\n\nLogin.js 示例\n\n```JavaScript\nimport { request } from '@/utils/request.js'\nimport qs from 'qs'\n\nrequest('/admin/user/sysUser/login', {\n method: 'POST',\n headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'},\n data: qs.stringify({ userAccount, userPwd, type: 'account' })\n}).then(data => {\n // ...\n})\n```\n","tags":["JavaScript","axios"],"categories":["JavaScript"]},{"title":"善用 JavaScript 特性:闭包与IIFE","url":"/2019/03/05/bibaoIIFE/","content":"\n<hr>\n\n一、使用 `IIFE` 优雅的解决 `setInterval` 首次不执行的尴尬。\n```JavaScript\n// 你的函数\nconst f = () => { ... }\n\n// 立即执行并且轮询\nconst timer = (function(fn, t) {\n // 为了解决 setInterval 首次不执行的尴尬\n fn && fn()\n // 返回计时器timer\n return setInterval(fn, t)\n})(f, 1500)\n```\n<!--more--> \n\n二、善用闭包,就可以轻松实现缓存模式、单例模式。\n\n下面几个例子来体验闭包在实战中的作用。\n\n> 这种也被称为 `“模块模式”` —— 现代模块化实现的基石\n\n##### 1. 轻量级极简的蒙版层mask,十分方便扩展:\n```JavaScript\nvar Mask = function (cb) {\n\tvar div = document.createElement('div')\n\tdiv.style = 'background-color: rgba(255, 255, 255, 0.7);position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 199307100337; display:none;'\n\tdiv.addEventListener('click', cb)\n\tdocument.body.append(div)\n\n\tvar img = new Image()\n\timg.src = \"http://wx3.sinaimg.cn/small/006ar8zggy1g0isbtuj2kg300w00wq2p.gif\"\n\timg.style = 'position: absolute; top: 50%; left: 50%;'\n\tdiv.append(img)\n\n\tvar show = function (showcb) {\n\t\tdiv.style.display = 'block'\n\t\tshowcb && showcb()\n\t}\n\n\tvar close = function (showcb) {\n\t\tdiv.style.display = 'none'\n\t\tshowcb && showcb()\n\t}\n\n\treturn { show, close }\n}\n\n// 创建一个蒙版\nconst mask = new Mask()\n// 打开蒙版\nmask.show()\n// 三秒后关闭\nsetTimeout(() => {\n\tmask.close()\n}, 3500);\n```\n\n##### 2. 巧妙使用闭包实现去重复\n\n我有一个这样的需求:需要从指定区间(比如-7 ~ 7)随机取 5 个数,虽然说是随机,但却不想重复。用闭包缓存已经取过的数,每次取的时候递归过滤一下即可。\n\n```JavaScript\n'use strict';\n\n// 缓存函数\nvar singeFn = function (fn, maxPollTime = 20) {\n\t// 缓存\n\tvar cache = []\n\t// 轮询次数\n\tvar pollTime = 0\n\t// 返回随机数生成器\n\treturn function _ () {\n\t\t// 获取随机数\n\t\tvar data = fn.apply(this, arguments)\n\t\t// 如果存在则递归\n\t\tif (~cache.indexOf(data)) {\n\t\t\t// 递归调用(如果递归次数大于阈值,那么直接返回False)\n\t\t\treturn ++pollTime > maxPollTime ? false : _.apply(this, arguments)\n\t\t} else {\n\t\t\t// 重置轮询次数\n\t\t\tpollTime = 0\n\t\t\t// 添加缓存并且返回data\n\t\t\treturn cache.push(data), data\n\t\t}\n\t}\n}\n\n// 我的随机函数\nvar random = function(min, max) {\n if (max == null) {\n max = min;\n min = 0;\n }\n return min + Math.floor(Math.random() * (max - min + 1));\n};\n\n// 从-7,7取随机数\nvar rangeRadom = random.bind(null, -7, 7)\n\n// 返回一个新的函数\nvar singeRangeRadom = singeFn(rangeRadom);\n\n// 获取返回值(每次都不一样)\nfor (var i = 0; i < 5; i++) {\n const randnum = singeRangeRadom()\n console.log(randnum)\n}\n```\n\n##### 3. 用闭包可以实现缓存模式,减少不必要的重复计算消耗。\n譬如比较实用的 `memoized`,我称之为 `参数标记缓存器`,源码和使用示例如下:\n```\nconst memoized = fn => {\n\tconst lookupTable = {};\n\t// 可以通过解释这个来观察缓存的变化\n\t// setInterval( () => console.log(lookupTable) , 1000); \n\treturn arg => lookupTable[arg] || (lookupTable[arg] = fn(arg));\n}\n\n// 阶乘的demo\nlet fastFactorial = memoized(n => {\n\tif (n === 0) {\n\t\treturn 1;\n\t}\n\t// 这是一个递归,并且每一次递归都具有缓存过程\n\treturn n * fastFactorial(n -1);\n});\n\nfastFactorial(5)\n```\n\n我的博客中`站内静态搜索功能`,就是使用了 `memoized` 的特性来优化性能,减少重复的搜索。\n\n\n\n如图,当我输入 “centos” 的时候,实际上是分别对 \n> \"c\", \"ce\", \"cen\", \"cent\", \"cento\", \"centos\" \n\n关键字分别进行了:\n\n> 搜索 -> 过滤 -> 模板引擎 -> 渲染UI\n\n那么问题来了,如果按下六次“BackSpace”。也就是变成了 \n\n> \"cento\", \"cent\", \"cen\", \"ce\", \"c\", \"\"\n\n是否又得重复进行上述的运算操作呢?\n\n显然是不必的,因为每一次输入关键词,我都会搜索一下缓存是否存在相关的内容,如果不存在则会缓存起来。如果存在则拿来即用。这样就减少了大量的计算消耗。直接跳到最后一步“渲染UI”了。","tags":["JavaScript"],"categories":["JavaScript"]},{"title":"更优雅的防止请求(XHR)重复 —— 请求队列","url":"/2019/03/05/norepeatxhr/","content":"\n<hr>\n问题:重复请求的问题在开发中很常见,浪费网络资源倒是其次,最怕的是前一次的点击逻辑,把后面的逻辑覆盖了。\n\n> 譬如,你在列表中点击了“吴彦祖”,然后再快速点击了“吴孟达”。假设“吴彦祖”的数据请求需要3秒,而“吴孟达”只需要2秒,那么结果就是,页面会先渲染出“吴彦祖”,而后又马上“吴孟达”的数据覆盖。\n>\n> **用户本来是想查看“吴彦祖”的照片,而界面展示的却是“吴孟达”……**\n\n<!--more--> \n\n\n\n常见的防止重复请求的解决方案,就是展示一个蒙版或者Loading层,拦截用户进一步操作。直到逻辑全部跑通为止。\n\n但在大数据屏幕中,如果拦截用户操作,并且赫然出现一个菊花图,是很Fu*k的体验。作为一个开发我自己看到菊花都烦:\n\n\n\n不过,这种不好的体验并不是源于菊花图,而是拦截了用户操作。让用户放不开。所以需求是:\n\n> 你随便瞎几把乱点,重复请求了算我输。\n\n### 请求队列\n\n1. 把所有请求都塞入一个队列;\n2. 每当一个请求进入队列之前,先清空并取消(Cancel)队列中**相同的请求**;\n3. 当一个请求完成,要将自己从队列中移除;\n\n\n\n\n重点在于如何 **取消(Cancel)** 已经发送的请求。实际上还真有,原生的 XHR 就有提供 abort() 可以中断请求。jQuery 的 Ajax 也提供了:https://github.com/jquery/jquery/blob/master/src/ajax/xhr.js#L82\n\n```JavaScript\nconst xhr = ajax({ ... })\nxhr.abort()\n```\n\naxios 也提供了类似的API,不过用起来比上面的麻烦一点,详情使用在后续的demo中:\n```JavaScript\nnew axios.CancelToken(_ => { ... })\n```\n\n虽然大部分项目已经是采用 axios、fetch等现代XHR请求技术了。但还是用传统的jQuery Ajax更容易说明,反正原理是一样的:\n\nsingeAjax.html:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>SingleAjax</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- jquery -->\n <script src=\"https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js\"></script>\n <style>\n </style>\n</head>\n\n<body>\n</body>\n<script>\n\n// 获取纯Url,不包含?后面的参数\nvar getPureUrl = url => {\n\tconst index = url.indexOf('?')\n\treturn url.substr(0, ~index ? index : url.length)\n}\n\n//(核心)以url相同作为重复条件,你可以根据自己的情况编写自己的重复条件\nvar SingleAjax = function () {\n // 缓存的队列\n var pending = [];\n\n // 返回单例模式ajax\n return function (opts) {\n \t// 获取纯Url(不包含?后面的参数)\n \tconst pureUrl = getPureUrl(opts.url)\n // 中止队列中所有相同请求地址的xhr\n pending.forEach(_ => _.url === pureUrl && _.xhr.abort());\n // 获取 success 回调函数\n const _success = opts.success\n // 装饰成功回调函数\n opts.success = function (...rest) {\n \t// 从队列过滤掉已经成功的请求\n \tpending = pending.filter(_ => _.url != pureUrl)\n \t// 继续执行它的成功\n \t_success && _success(...rest)\n }\n // 移除所有中止的请求,并且将新的请求推入缓存\n pending = [...pending.filter(_ => _.url != pureUrl), { url: pureUrl, xhr: $.ajax(opts) }]\n }\n}\n\n// 生成单例ajax\nvar singleAjax = new SingleAjax()\n\nfor (var i = 0; i < 10; i++) {\n singleAjax({\n url: \"https://api.github.com/users/dragon8github\",\n success: function (data) {\n // 其实在成功之后,可以考虑扩展把成功的xhr从队列中移除,但其实也不影响。已经成功的xhr就算再次被执行abort也不会怎么样,更不会从200变成cannel状态,更不会触发error。\n console.log('请求成功', data);\n },\n error: function(e, m){\n console.log('数据接口请求异常(请放心这是正常现象,由于请求被中止Cancel,所以会回调error。只需要判断一下m === \"abort\" 即可,你还可以在 abort() 时在 _.xhr 中加入任意属性来判断深入判断)', e, m, m === \"abort\");\n }\n })\n}\n</script>\n</html>\n```\n\n代码中我们模拟了十次重复的请求,发现前9次都被abort了。只留下最后一条。\n\n\n\n\n下面是axios的示例 singeAxios:\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Document</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js\"></script>\n</head>\n<body>\n</body>\n<script>\n// 请求队列\nlet pending = []\n\n// 获取纯Url,不包含?后面的参数\nvar getPureUrl = url => {\n\tconst index = url.indexOf('?')\n\treturn url.substr(0, ~index ? index : url.length)\n}\n\n// 请求拦截器\naxios.interceptors.request.use(config => {\n // 获取纯Url(不包含?后面的参数)\n const pureUrl = getPureUrl(config.url)\n // 中止队列中所有相同请求地址的xhr\n pending.forEach(_ => _.url === pureUrl && _.cancel('repeat abort'));\n // 配置取消令牌\n config.cancelToken = new axios.CancelToken(cancel => {\n // 移除所有中止的请求,并且将新的请求推入缓存\n pending = [...pending.filter(_ => _.url != pureUrl), { url: pureUrl, cancel }]\n })\n return config\n}, error => {\n return Promise.reject(error)\n})\n\n// 响应拦截器\naxios.interceptors.response.use(res => {\n // 成功响应之后清空队列中所有相同Url的请求\n pending = pending.filter(_ => _.url != getPureUrl(res.config.url))\n // 返回 response\n return res\n}, error => {\n return Promise.reject(error)\n});\n\nfor (var i = 0; i < 10; i++) {\n axios({url: 'https://api.github.com/users/dragon8github'}).then(console.log).catch(_ => {\n if (_.message === 'repeat abort') return console.info(_.message)\n // other error handler...\n // something code...\n throw new Error(_.message)\n })\n}\n</script>\n</html>\n```\n\naxios 也是一样道理,只是做法不一样。前9个请求都被abort了。\n\n\n\n不过奇怪的是,并没有cancel的过程。就像从来没有发起请求过一样。\n\n\n","tags":["xhr","axios"],"categories":["xhr"]},{"title":"Dart(Flutter) pub 包管理","url":"/2019/02/21/Dartpub/","content":"\n<hr>\n\nPub(https://pub.dartlang.org/ )是Google官方的Dart Packages仓库,类似于node中的npm仓库,android中的jcenter,我们可以在上面查找我们需要的包和插件,也可以向pub发布我们的包和插件。我们将在后面的章节中介绍如何向pub发布我们的包和插件。\n\n<!--more--> \n\n# 示例\n接下来,我们实现一个显示随机字符串的widget。有一个名为“english_words”的开源软件包,其中包含数千个常用的英文单词以及一些实用功能。我们首先在pub上找到english_words这个包,确定其最新的版本号和是否支持Flutter。\n\n\n\n我们看到“english_words”包最新的版本是3.1.3,并且支持flutter,接下来:\n\n将english_words(3.1.3版本)添加到依赖项列表,如下:\n\n```yaml\ndependencies:\n flutter:\n sdk: flutter\n\n cupertino_icons: ^0.1.0\n # 新添加的依赖\n english_words: ^3.1.3\n```\n\n### 下载包\n\n在Android Studio的编辑器视图中查看pubspec.yaml时,单击右上角的 Packages get 。\n\n\n这会将依赖包安装到您的项目。您可以在控制台中看到以下内容:\n```\nflutter packages get\nRunning \"flutter packages get\" in flutter_in_action...\nProcess finished with exit code 0\n```\n\n## 使用\n1、引入english_words包\n```dart\nimport 'package:english_words/english_words.dart';\n```\n\n在输入时,导入后该行代码将会显示为灰色,请放心,这并不一定是安装失败导致的。它表示导入的库尚未使用。\n\n\n\n2、使用english_words包来生成随机字符串。\n```dart\nclass RandomWordsWidget extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n // 生成随机字符串\n final wordPair = new WordPair.random();\n return Padding(\n padding: const EdgeInsets.all(8.0),\n child: new Text(wordPair.toString()),\n );\n }\n}\n```\n3、我们将 RandomWordsWidget 添加到\"计数器\"示例的首页MyHomePage 的Column的子widget中。\n\n\n\n\n\n## 热更新\n\n如果应用程序正在运行,请使用热重载按钮【⚡】更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。 这是因为单词对是在 build 方法内部生成的。每次热更新时,build方法都会被执行。\n\n\n","tags":["Dart","Flutter","pub"],"categories":["Dart","Flutter"]},{"title":"Dart for the web 一小时入门","url":"/2019/02/20/DartWeb/","content":"\n<hr>\n\n官方网站:https://webdev.dartlang.org/guides/get-started\n\n#### 1、Dart-window 安装\nhttp://www.gekorm.com/dart-windows/\n\n#### 2、安装开发工具 webstorm\nhttps://webdev.dartlang.org/tools/webstorm\n\n#### 3、安装使用 pub 安装两个必备插件:activate,webdev\n```bash\n> pub global activate webdev\n> pub global activate stagehand\n```\n\n<!--more--> \n\n#### 4、新建一个Dart项目\n\n\n\n#### 5、启动应用程序\n\n\n\n\n\n","tags":["Dart"],"categories":["Dart"]},{"title":"在 Windows 上搭建 Flutter 开发环境","url":"/2019/02/20/在Windows上搭建Flutter开发环境/","content":"\n<hr>\n1、获取Flutter SDK:https://flutter.io/sdk-archive/#windows\n\n2、解压 **flutter_windows_v1.0.0-stable.zip**,并将 flutter/bin 加入到环境变量\n\n3、安装Android Studio :https://developer.android.google.cn\n\n4、在 Android Studio 的 **Browse repositories** 安装 **Flutter** 和 **Dart** 插件\n- Flutter插件: 支持Flutter开发工作流 (运行、调试、热重载等)。\n- Dart插件: 提供代码分析 (输入代码时进行验证、代码补全等)。\n<!--more--> \n欢迎页 -> configure -> Plugins -> Browse repositories... -> Search\n\n\n\n\n\n\n\n5、安装完 flutter 插件后,重启一下Android Studio,然后我们就可以新建 flutter 模板了。\n\n欢迎页 -> Start a new Flutter project -> Flutter application -> 输入项目名称和选择flutter的sdk目录 -> finish\n\n\n\n\n\n\n\n\n\n6、运行应用程序\n\n\n\n---\n\n可能遇到的问题:\n##### 0. 任何访问不了网站,自动安装失败、下载失败等网络问题,自己想办法搞vpn fq。\n\n- [阿里云 香港服务器 Centos7 3分钟搞定vpn](https://www.cnblogs.com/CyLee/p/10401766.html)\n\n- [加速度](https://dc.36fy.com/)\n\n##### 1. 启动Android Studio时,出现“Unable to access Android SDK add-on list”\n点击 **\"Cancel\"**, 稍后再根据指引,自动安装即可。\n\n##### 2. 其他常见配置问题\n[https://book.flutterchina.club/chapter1/configuration.html](https://book.flutterchina.club/chapter1/configuration.html)","tags":["flutter"],"categories":["flutter"]},{"title":"阿里云 香港服务器 Centos7 3分钟搞定vpn","url":"/2019/02/19/aliyun/","content":"\n<hr>\n\n\n教程传送门:\n[阿里云主机搭建VPN服务](https://blog.csdn.net/ztx114/article/details/80423705)\n[在免费EC2上搭建自己的VPN](https://my.oschina.net/imcf/blog/659230)\n\n<!--more--> \n\n#### 1、安装python 和 Pip\n```\n$ yum install python-setuptools && easy_install pip\n```\n\n#### 2、安装 shadowsocks\n```\n$ pip install shadowsocks\n```\n\n#### 3、添加 shadowsocks 的配置文件\n```\n$ vi /etc/shadowsocks.json\n{\n \"server\": \"0.0.0.0\",\n \"server_port\": 443,\n \"local_address\": \"127.0.0.1\",\n \"local_port\": 1080,\n \"password\": \"daweiyixiangshihao\",\n \"timeout\": 300,\n \"method\": \"aes-256-cfb\",\n \"fast_open\": false,\n \"workers\": 1\n}\n```\n\n- server_port:是开放端口,阿里云默认开放了443;\n- password:是连接密码;\n- server:就是本机,保持0.0.0.0 即可;\n- method:是传输方式,保持\"aes-256-cfb\" 即可;\n\n#### 4、启动 shadowsocks 服务\n```\n$ ssserver -c /etc/shadowsocks.json -d start\n```\n\n#### 5、关闭防火墙\nCentOS 7.0默认使用的是firewall作为防火墙。需要使用命令关闭防火墙,否则无法使用代理。\n\n查看防火墙状态命令:firewall-cmd --state\n\n停止firewall命令:systemctl stop firewalld.service\n\n禁止firewall开机启动命令:systemctl disable firewalld.service\n\n#### 6、本机下载 shadowsocks.exe \n链接:https://pan.baidu.com/s/17y-v40jPGIHcftuE7gGiEA \n提取码:5gzb \n\n#### 7、配置 shadowsocks.exe \n\n\n#### 8、访问google.com 测试\n\n\n# 后记\n\ntest.sh\n> $ vi test.sh\n> $ chmod 777 test.sh\n\n```\n#!/bin/bash\n\nyum install python-setuptools && easy_install pip\n\npip install shadowsocks\n\ncat>/etc/shadowsocks.json<<EOF\n{\n \"server\": \"0.0.0.0\",\n \"server_port\": 443,\n \"local_address\": \"127.0.0.1\",\n \"local_port\": 1080,\n \"password\": \"daweiyixiangshihao\",\n \"timeout\": 300,\n \"method\": \"aes-256-cfb\",\n \"fast_open\": false,\n \"workers\": 1\n}\nEOF\n\nssserver -c /etc/shadowsocks.json -d start\n\nsystemctl stop firewalld.service\n\nsystemctl disable firewalld.service\n```\n\n","tags":["centos","aliyun","vpn","shadowsocks"],"categories":["centos","vpn"]},{"title":"《Docker每天五分钟》八:Dockerfile 中的命令三兄贵","url":"/2019/02/11/Docker每天五分钟8/","content":"\n<hr>\n\n# RUN vs CMD vs ENTRYPOINT\n\n简单地说:\n(1)RUN: 该命令会创建一个镜像层,适合在安装软件包的时候使用。\n(2)CMD: 配置容器启动后默认执行的命令及其参数,但 CMD 会被 `$ docker run` 后面跟的命令行参数替换。比如 `$ docker run -it [image] /bash/bin`, 那么 CMD 指令会被忽略掉。\n(3)ENTRYPOINT:配置容器启动时运行的命令。\n\n<!--more--> \n\n# RUN\nRUN 指令会创建新的镜像层。 通常用于安装应用和软件包。 Dockerfile 中常常包含多个 RUN 指令。\n\nRUN 有两种格式:\n(1) **Shell 格式(推荐): `RUN yum update && yum install gcc-c++\\vim\\git -y`**\n(2) Exec 格式: `RUN [\"yum\", \"install\", \"gcc-c++\", \"vim\", \"git\", \"-y\"]`\n\n> 注意,我们反复提到 RUN 指令会创建新的镜像层。镜像层的概念就类似缓存。会在各个地方的dockerfile被反复使用。\n> 这里 yum update 放在和安装同一个指令中。能保证每次安装都是最新的包。如果 yum update 单独的RUN。则会创建一个 yum update 的镜像层。当其他地方的d ockerfile 使用 yum update 的时候,就会直接使用该镜像层,而这一层很可能是很久以前缓存的了。\n\n# CMD\n此命令会在容器启动后运行。 前提是 `$ docker run` 没有指定其他命令。\n- 如果 docker run 指定了其他命令, CMD 指定了默认命令将被忽略。\n- 如果 Docker file 中有多个 CMD 指令,只有最后一个 CMD 有效。\n\nCMD 有三种格式:\n(1) **Exec 格式(推荐): CMD [\"/bin/echo\", \"Hello World\"]**\n(2) 嫁衣格式: CMD [\"param1\", \"params2\"]\n(3) Shell格式: CMD echo \"Hello World\"\n\n> (2)嫁衣格式是为 ENTRYPOINT 提供参数,此时 ENTRYPOINT 必须使用 Exec 格式。\n\n举例说明 CMD 和 `$ docker run` 的关系,Dockerfile 片段如下:\n\n```dockerfile\nCMD echo \"Hello world\"\n```\n运行容器 `$ docker run -it [image]` 将输出:\n```\nHello world\n```\n但如果加上命令: `$ docker run -it [image] /bin/bash`, CMD 会被忽略掉。也就没有输出 `Hello world` 了\n\n# ENTRYPOINT\n\nENTRYPOINT 指令可以让容器以应用程序或者服务的形式运行。\n\nENTRYPOINT 与 CMD 很相似,它们都可以指定执行的命令和参数。不同的地方在于 ENTRYPOINT 不会被 `$ docker run` 时指定的命令忽略。\n\nENTRYPOINT 有两种格式:\n(1) **Exec 格式(推荐): ENTRYPOINT [\"executable\", \"param1\", \"param2\"]**\n(2)Shell 格式:ENTRYPOINT command param1 param2\n\n> ENTRYPOINT 不同的格式效果差别巨大。 选择使用时必须小心。\n> ENTRYPOINT 的 Exec 格式可以接受 CMD 或 `$ docker run` 提供的参数。\n> ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 `$ docker run` 提供的参数。\n\n### ENTRYPOINT 的 Exec 格式\n\nENTRYPOINT 的 Exec 格式可以接受 CMD 或 `$ docker run` 提供的参数。 比如下面的 Dockerfile 片段:\n```dockerfile\nENTRYPOINT [\"/bin/echo\", \"Hello\"] CMD [\"world\"]\n```\n当容器通过 `$ docker run -it [image]` (无命令)启动时,输出为:\n```\nHello world\n```\n\n而如果通过 `$ docker run -it [image] CloudMan` 启动时,则输出为:\n```\nHello CloudMan\n```\n\n# Exec 格式 与 变量 shell 解析\n\n> 请注意,当需要解析变量时,应该使用 Shell 解析,如/bin/sh(bash) \n\n例如下面的 Dockerfile 片段,我们用 ENV 指令设置了环境变量 $name 并不会被解析:\n\n```dockerfile\nENV name Cloud Man ENTRYPOINT [\"/bin/echo\", \"Hello, $name\"]\n```\n\n运行容器将输出: \n```shell\nHello, $name\n```\n\n> 注意:环境变量 $name 没有被替换。必须使用 shell 解析。\n\n如果希望使用环境变量,如下修改 Dockerfile:\n```dockerfile\nENV name Cloud Man ENTRYPOINT [\"/bin/sh\", \"-c\", \"echo Hello, $name\"]\n```\n\n运行容器将输出: \n```shell\nHello, Cloud Man\n```","tags":["Docker"],"categories":["Docker"]},{"title":"碎片化经验广场","url":"/2019/02/04/碎片化经验广场/","content":"\n<hr>\n\n### 如何在 dockerhub 找到合适的镜像?\n\n1. 确定你的 base 镜像,如`centos`\n3. 确定你的开发环境和依赖,比如`php + nginx + mysql + redis`\n3. 找到官方镜像仓库\n","tags":["碎片化","经验"],"categories":["碎片化","经验"]},{"title":"《Docker每天五分钟》七:调试 dockerfile","url":"/2019/02/03/Docker每天五分钟7/","content":"\n<hr>\n\n总结一下 `dockerfile` 的构建镜像的过程:\n1. 从 base 镜像运行一个容器。\n2. 执行一条指令,对容器做修改。\n3. 执行类似 `docker commit` 的操作,生成一个新的镜像层。\n4. Docker 再基于刚刚提交的镜像运行一个新容器。\n5. 重复 2 ~ 4 步,直到 `dockerfile` 中所有的指令执行完毕。\n\n从这个过程可以看出,**如果 `dockerfile` 由于某种原因执行到某个指令失败了,我们也能够得到前一个指令成功执行构建出的镜像。**\n\n这样当我们修复完bug再次执行构建的时候,也能依靠docker的缓存特性跳过已完成的镜像。这对开发和调试 `dockerfile` 是非常友好且有帮助的。我们可以通过控制台的打印和测试,快速定位错误和分析原因。\n\n<!--more--> \n\n<hr>\n\n我们来看一个调试示例,以 buysbox 为 base 镜像:\n\n> BusyBox 是一个集成了三百多个最常用Linux命令和工具的软件。BusyBox 包含了一些简单的工具,例如ls、cat和echo等等,还包含了一些更大、更复杂的工具,例grep、find、mount以及telnet。有些人将 BusyBox 称为 Linux 工具里的瑞士军刀。\n\n新建一个 `Dockerfile` 和一个测试文件 `testfile`:\n```bash\n[root@10-255-0-217 dc2-user]# echo test > testfile\n[root@10-255-0-217 dc2-user]# vim Dockerfile\n```\nDockerfile 内容如下:\n> FROM busybox\n> RUN touch tmpfile\n> RUN /bin/bash -c echo \"continue to build...\"\n> COPY testfile /\n\n开始构建调试\n```bash\n[root@10-255-0-217 dc2-user]# docker build -t image-debug .\n```\n\n\n我们可以轻松发现在执行第三句指令: `RUN /bin/bash -c echo \"continue to build...\"` 的时候发生了异常,\n\n错误信息是: `/bin/sh: /bin/bash: not found`\n\n很显然我们得知了 buysbox 系统中不包含 /bin/bash 程序。然后再进一步的修正。\n\n<hr>\n\n上述这种错误是比较简单且显而易见的,大部分的bug都是难以肉眼观察,我们希望进一步的调试。\n\n根据 `Dockerfile` 构建镜像的特性,尽管由于某种原因执行到某个指令失败了,我们也能够得到前一个指令成功执行构建出的镜像。\n\n在此例子中,就是由 `RUN touch tmpfile` 指令构成的镜像 `6fa9f3938b8b`:\n\n\n\n我们也可以从 `docker images` 中查看最后成功的镜像:\n\n\n\n然后我们进入镜像之中进一步的操作体验和调试:\n```bash\n[root@10-255-0-217 dc2-user]# docker run -it 6fa9f3938b8b\n/ # /bin/bash -c echo \"continue to build...\"\nsh: /bin/bash: not found\n/ # \n```\n\n依然能得出 `/bin/bash: not found` 的错误定位结果。","tags":["Docker"],"categories":["Docker"]},{"title":"《Docker每天五分钟》六:第一个 Dockerfile","url":"/2019/02/02/Docker每天五分钟6/","content":"\n<hr>\n\nDockerfile 是一个文本文件,记录了镜像构建的所有步骤。\n\n```bash\n$ vim Dockerfile\n```\nDockerfile 内容如下:\n\n> FROM centos\n> RUN yum install -y vim\n\n开始构建\n\n<!--more--> \n\n```bash\n$ docker build -t centos-with-vim .\n```\n- -t:指明镜像的名字,该示例为 `centos-with-vim`;\n- 请注意最后一个`.` 它指明在当前路径寻找 Dockerfile;\n\n经过漫长的安装之后。我们查看一下镜像列表 `$ docker images`:\n\n\n\n### docker 的`缓存`特性:\n\n可以看到 Docker 分为了两个镜像: `centos` 和 `centos-with-vim`。\n\n其中 `centos` 镜像我们称为base镜像。而 `centos-with-vim` 就是基于base镜像构建的。\n\n如果在构建之前你的本地镜像已经存在base镜像。那么会直接使用,无须重新下载和构建。这也是 docker 的重要`缓存`特性。\n","tags":["Docker"],"categories":["Docker"]},{"title":"《Docker每天五分钟》五:docker commit 制作镜像","url":"/2019/02/02/Docker每天五分钟5/","content":"\n<hr>\n\ndocker commit 命令是创建新镜像最直接的方法,其过程包含三个步骤:\n- 运行容器\n- 修改容器\n- 将容器保存为新镜像\n\n###### (1)运行容器\n```bash\n[root@10-255-0-217 dc2-user]# docker run -it centos\n\nUnable to find image 'centos:latest' locally\nlatest: Pulling from library/centos\na02a4930cb5d: Pulling fs layer \nlatest: Pulling from library/centos\na02a4930cb5d: Pull complete \nDigest: sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426\nStatus: Downloaded newer image for centos:latest\n\n[root@ea9b3dcd88ad /]# \n```\n\n自动下载了最新版本的 Centos 镜像,并且运行和进入了容器(ea9b3dcd88ad)。\n\n<!--more--> \n\n###### (2) 修改容器\n\n这里示例安装 vim \n\n```bash\n[root@ea9b3dcd88ad /]# vim\nbash: vim: command not found\n\n[root@ea9b3dcd88ad /]# yum install -y vim\n...\nComplete!\n```\n\n###### (3) 保存为新镜像\n\n让容器继续运行着,然后 `新建窗口` 来查看当前运行的镜像。\n```bash\n[root@10-255-0-217 dc2-user]# docker ps\n```\n\n\n`silly_aryabhata` 是 Docker 为我们的容器随机分配的名字。\n\n执行 `docker commit` 命令将容器保存为镜像。\n\n```bash\n[root@10-255-0-217 dc2-user]# docker commit silly_aryabhata centos-with-vi\n\nsha256:3badf3a0b2d0a59bd3cd106e70d088c32da2e59676746e7613a30f7eb5d3e2b0\n```\n\n\n\n对比两个镜像,从 `SIZE` 上看到镜像因为安装了 `vim` 软件而变大了。\n\n<hr>\n\n以上演示了如何用 `docker commit` 创建新镜像。\n\n> 然而,Docker 并不建议用户通过 `docker commit` 这种方式构建镜像。\n\n### 原因如下:\n\n1. 这是一种手工创建镜像的方式,容易出错,效率低且重复性弱。比如要在ubuntu镜像中也加入vim,还得重复前面所有步骤。\n2. 使用者并不知道镜像是如何创建出来的,里面提供什么服务,包含什么程序,里面是否有恶意程序?也就是说无法对镜像进行审计,存在安全隐患。\n\n既然 `docker commit` 不是推荐的方法,我们为什么还要花时间学习呢?\n\n> 即便是用 dockerfile(推荐方法)构建镜像,底层也是 `docker commit` 一层一层构建镜像的。学习 `docker commit` 能够帮助我们更加深入的理解构建过程和镜像的分层结构。","tags":["Docker"],"categories":["Docker"]},{"title":"碎片化知识广场","url":"/2019/02/01/碎片化知识广场/","content":"\n<hr>\n\n###### (1) 如何调试 node_modules 中的代码?\n\n如果你尝试直接修改 `node_modules` 中的源码,如加上断点 `debugger;` 或者 打印 `console.log` 是没有效果的。\n\n这是为什么?\n\n这是因为你引用的是它编译好的内容。(通常在目录下 `package.json` 中 `main` 字段指向)。而你修改的是它的源码,并没有实时编译。当然你可以修改源码后重新编译他们,但没什么必要。这样很容易玩崩……_(:3」∠)_\n\n>(仔细想想本来就不可能实时编译 `node_modules` 中所有的依赖。平时开发 `webpack` 热编译的 `src` 下的文件都需要耗费几秒钟了。更不可能去兼顾编译 `node_modules` 中的内容)\n\n正确的做法是:将**入口文件**迁移到项目目录,而入口文件中的其他依赖直接指向 `node_modules` 中的源码目录即可。 \n\n原理是什么? 就像上文说的,`webpack` 热编译的只有 `src` 下的项目,如果你将要调试的文件迁移到你的项目 `src` 中,`webpack` 就能编译了,而入口的依赖文件哪怕指向 `node_modules` 也没关系,`webpack` 也会自动收集并且编译。这样我们就能愉快的在 `node_modules` 中修改源码了。\n\n\n","tags":["cpmposer"],"categories":["cpmposer"]},{"title":"《Docker每天五分钟》四:常用api","url":"/2019/01/31/Docker每天五分钟4/","content":"\n<hr>\n\n\n|\t命令 |\t\t功能 |\n|----------|:------------:|\n| $ docker images | 查看所有镜像 |\n| $ docker ps -a | 查看所有容器 |\n| $ docker run -it <镜像> | 运行本地镜像,如果镜像不存在则下载最新版本 |\n| $ docker exec -it <已启动容器> /bin/bash | 以交互的方式进入已启动的容器内部 |\n| $ docker stop <容器> | 停止容器 |\n| $ docker start <容器> | 启动容器 |\n| $ docker rm <容器> | 删除容器,必须先停止容器 |\n| $ docker rmi <镜像> | 删除镜像,必须先删除所有依赖该镜像的容器 |\n| $ docker build -t <新建容器名> . | 通过 Dockerfile 构建镜像,<br>请注意最后一个`.` 指明在当前路径寻找 Dockerfile |\n| $ docker history <镜像> | 查看镜像的构建层级 |\n\n\n\n> Dockerfile 支持以 ‘#’ 开头注释\n\n<!--more--> \n\n|\t命令 |\t\t功能 |\n|----------|:------------:|\n| FORM | 指定 base 镜像 |\n| MAINTAINER | 设置镜像的作者,可以是任意字符串 |\n| RUN | (命令三兄弟之一)在容器中运行指定命令。 |\n| CMD | (命令三兄弟之一)在容器启动时运行指定的命令。 |\n| ENTRYPOINT | (命令三兄弟之一)设置容器启动时运行的命令。 |\n| COPY | 将文件复制到镜像,`COPY src desc` 与 `COPY [\"src\", \"desc\"]` |\n| ADD | 与COPY类似,将文件复制到镜像。不同的是如果src是归档文件(zip、taz、xz等),文件会被自动解压 |\n| ENV | 设置环境变量,环境变量可被后面的指令使用如: <br> `ENV MY_VERSION 1.9.1 RUN yum install -y mypackage=$MY_VERSION` |\n| WORKDIR | 设置镜像中当前工作目录(服务于COPY、ADD、CMD、RUN、ENTRYPOINT等指令)。 |\n| EXPOSE | 指定容器中的进程会监听某个端口, Docker 可以将该端口暴漏出来。 |\n| VOLUME | 将文件或目录声明为 volume。 |\n\n注意点:\n- Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。\n- Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。\n- CMD 指令会被 `$ docker run [image] ...` 之后的参数替换掉。 如 `$ docker run [image] /bash/bin`\n- CMD 或 docker run 之后的参数会被当做参数传递给ENTRYPOINT。\n\n","tags":["Docker"],"categories":["Docker"]},{"title":"《Docker每天五分钟》三:hello-world、初识dockerfile","url":"/2019/01/31/Docker每天五分钟3/","content":"\n<hr>\n\nhello-world 是 Docker 官方提供的一个镜像。通常用来验证 Docker 是否安装成功。 我们先通过 `$ docker pull` 从 Docker Hub 下载它。\n```bash\n$ docker pull hello-world\n```\n\n<!--more--> \n\n使用 `$ docker images` 查看镜像是否下载成功。 发现才不到2kb! \n\n\n\n通过 `$ docker run` 运行它。\n\n```bash\n$ docker run hello-world\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n...\n```\n\n<hr>\n\nDockerfile 是镜像的描述文件,定义了如何构建 Docker 镜像。\n\n> 注:可以在 [dockerhub](https://hub.docker.com/_/hello-world?tab=description) 中查看 Dockerfile 内容\n\n\n\n","tags":["Docker"],"categories":["Docker"]},{"title":"《Docker每天五分钟》二:切换 DaoCloud 镜像源","url":"/2019/01/31/Docker每天五分钟2/","content":"\n<hr>\n\n切换国内 [DaoCloud](https://dashboard.daocloud.io/settings/profile) 镜像服务。 [免费注册](https://account.daocloud.io/signup)后进入控制台,找到右上角的[加速器图标](https://www.daocloud.io/mirror)。\n\n<!--more--> \n\n\n\n然后找到Linux的配置命令。\n\n\n\n你的配置命令也许和我的不一样哦。\n\n```bash\n$ curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io\n\n$ sudo systemctl restart docker\n```\n\n\n\n","tags":["Docker"],"categories":["Docker"]},{"title":"《Docker每天五分钟》一:启动第一个httpd容器","url":"/2019/01/31/Docker每天五分钟1/","content":"\n<hr>\n\n```bash\n$ sudo docker run -d -p 8080:80 httpd\n```\n\n1)从 Docker Hub 下载 httpd 镜像。镜像中已经安装好了 Apache HTTP Server.\n2)启动容器,并将容器的80端口映射到宿主机的8080端口。\n\n这样当我们访问宿主机的8080端口时,就能看到HTTP服务页面了。\n\n<!--more--> \n\n> 如果外网ip访问不了。请确保你服务器的安全组,以及本机的防火墙配置。\n\n```bash\n$ curl localhost:8080\n\n<html><body><h1>It works!</h1></body></html>\n```\n\n\n\n\n","tags":["Docker"],"categories":["Docker"]},{"title":"Nginx与php结合","url":"/2019/01/25/Nginx与php结合/","content":"\n<hr>\n\n### 传送门:\n- [Centos7 PHP-lastest 安装](/2019/01/25/php安装/)\n- [centos7 nginx 安装](/2019/01/24/nginx安装/)\n\n### 操作预览:\n1. 将 php.ini 文件中的配置项 cgi.fix_pathinfo 设置为 0\n2. 修改 `php-fpm.conf` 路径引用错误的bug\n3. 添加并且修改 `www.conf`\n4. 启动 php-fpm\n5. 配置 Nginx 使其支持 PHP 应用\n6. 配置 .php 文件的请求将被传送到后端的 PHP-FPM 模块\n7. 重启 Nginx。\n8. 创建测试文件\n\n<!--more--> \n\n<hr>\n\n> 补充:搭建环境最怕的就是路径不同,建议配合 `$ find / -name \"你要搜索的文件名\"` 来辅助。\n> 建议结合[官网教程](http://php.net/manual/zh/install.unix.php](http://php.net/manual/zh/install.unix.php)使用。\n> 但实际上官网的内容部分也是过时的无效的。所以要结合第三方文章来排坑。\n\n\n###### 1. 将 php.ini 文件中的配置项 cgi.fix_pathinfo 设置为 0\n```bash\n$ vim /usr/local/php/php.ini\n```\n定位到 `cgi.fix_pathinfo` 并将其修改为如下所示:\n\n> cgi.fix_pathinfo=0\n\n###### 2. 修改 `php-fpm.conf` 路径引用错误的bug\n\n```bash\n$ vim /usr/local/etc/php-fpm.conf\n```\n\n找到最后一行 `include=NONE/etc/php-fpm.d/*.conf`,改为 `include=etc/php-fpm.d/*.conf`\n\n> include=NONE/etc/php-fpm.d/*.conf\n> 改为\n> include=etc/php-fpm.d/*.conf\n\n###### 3. 添加 www.conf,并且修改权限\n```bash\n$ cd /usr/local/etc/php-fpm.d\n$ cp www.conf.default www.conf\n$ vim www.conf\n```\n\n找到并修改以下内容:\n\n```bash\n; Unix user/group of processes\n; Note: The user is mandatory. If the group is not set, the default user's group\n; will be used.\nuser = nobody\ngroup = nobody\n```\n\n###### 4. 启动 php-fpm\n\n```bash\n$ /usr/local/bin/php-fpm\n```\n\n\n###### 5. 配置 Nginx 使其支持 PHP 应用\n\n`$ vim /usr/local/nginx/conf/nginx.conf`,修改默认的 location 块,使其支持 .php 文件:\n\n```nginx\nlocation / {\n root html;\n index index.php index.html index.htm;\n}\n```\n\n###### 6. 配置 .php 文件的请求将被传送到后端的 PHP-FPM 模块\n```nginx\nlocation ~* \\.php$ {\n fastcgi_index index.php;\n fastcgi_pass 127.0.0.1:9000;\n include fastcgi_params;\n fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n fastcgi_param SCRIPT_NAME $fastcgi_script_name;\n}\n```\n\n\n###### 7. 重启 Nginx。\n```bash\n$ sudo /usr/local/nginx/sbin/nginx -s stop\n$ sudo /usr/local/nginx/sbin/nginx\n```\n\n###### 8. 创建测试文件\n```bash\n$ rm /usr/local/nginx/html/index.html\n$ echo \"<?php phpinfo(); ?>\" >> /usr/local/nginx/html/index.php\n```\n\n\n","tags":["nginx","php"],"categories":["nginx"]},{"title":"hexo的认知","url":"/2019/01/25/hexo的认知/","content":"\n<hr>\n\n### 预览:\n0. 如何使用摘要?加入 `<!--more--> `即可\n1. 自定义文章模板\n2. 加入 `<script>`\n3. 加入全局样式\n4. hexo 内置全局变量\n5. 如何打印全局变量\n6. 本地搜索功能\n7. 部署的git和项目的git\n\n<!--more--> \n\n#### 1. 自定义文章模板\n> \\themes\\landscape\\layout\\_partial\\after-footer.ejs\n\n#### 2. 加入 `<script>`\n> \\scaffolds\\post.md\n\n#### 3. 加入全局样式\n> \\my\\themes\\landscape\\source\\css\\style.styl\n\n#### 4. hexo 内置全局变量\n> [https://hexo.io/zh-cn/docs/variables](https://hexo.io/zh-cn/docs/variables)\n\n#### 5. 如何打印全局变量\n打开任意一个ejs文件如:`my\\themes\\landscape\\layout\\_partial\\article.ejs`,顶部加入JavaScript代码:\n```ejs\n<% \n\tconsole.log(20190125172431, post) \n%>\n```\n不过并不会打印到页面,而是打印到控制台。\n\n\n\n#### 6.本地搜索功能\n\n需要结合插件 [hexo-generator-search](https://github.com/wzpan/hexo-generator-search) 来使用:\n\n```bash\n$ cnpm install https://github.com/wzpan/hexo-generator-search\n```\n配置 `_config.yml`\n\n> search:\n> path: search.json\n> field: post\n> content: true\n\n当你使用 `$ hexo g` 之后,publish 目录下就生成了search.json了。我们就可以根据这个json来做本地搜索。\n\n#### 7. 部署的git和项目的git\n\n我们希望部署/publish到github.io和项目本身的git不一样。只需要加入一个 [hexo-deployer-git](https://github.com/hexojs/hexo-deployer-git) 插件即可。\n\n```bash\n$ cnpm install hexo-deployer-git\n```\n\n配置 `_config.yml`\n\n```yml\n# Deployment\n## Docs: https://hexo.io/docs/deployment.html\ndeploy:\n type: git\n repository: https://github.com/dragon8github/dragon8github.github.io.git\n branch: master\n```\n\n这样我们就可以使用 `$ hexo g && hexo d` 来部署到github.io了。就不会和我们项目本身的git冲突了。","tags":["hexo"],"categories":["hexo"]},{"title":"网页无法播放MP4的解决方案","url":"/2019/01/25/网页无法播放MP4的解决方案/","content":"\n<hr>\n\n### 操作预览:\n1. 下载 `格式工厂`\n2. 选择 MP4 导入你需要转换的视频\n3. 右键列表中的视频选择 `输出配置`,选择 `视频编辑:AVC(H264)`\n4. 点击开始\n\n<!--more--> \n\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Document</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n #myVideo {\n position: fixed;\n left: 0;\n right: 0;\n top: 0;\n z-index: 199307100337;\n }\n </style>\n</head>\n\n<body>\n <video autoplay loop muted id=\"myVideo\">\n <source src=\"./saber.mp4\" type=\"video/mp4\">\n </video>\n</body>\n</html>\n```\n\n","tags":["MP4","video","HTML"],"categories":["HTML","video","MP4"]},{"title":"Centos7 PHP-lastest 安装","url":"/2019/01/25/php安装/","content":"\n<hr>\n\n参考官方 UNIX 编译教程: [http://php.net/manual/zh/install.unix.php](http://php.net/manual/zh/install.unix.php)\n\n###### 1、安装依赖\n```bash\n$ sudo yum install libxml2 libxml2-devel\n```\n###### 2、下载php(China): http://php.net/get/php-7.3.1.tar.gz/from/a/mirror\n```bash\n$ wget -O php-7.3.1.tar.gz http://cn2.php.net/get/php-7.3.1.tar.gz/from/this/mirror\n```\n\n###### 3、编译三兄贵\n```bash\n$ ./configure --disable-fileinfo --enable-fpm\n$ make && make install \n```\n<!--more--> \n加入--disable-fileinfo是为了避免make时出现如下错误:\n> virtual memory exhausted: Cannot allocate memory \n> make: *** [ext/fileinfo/libmagic/apprentice.lo] Error 1\n\n其中 `--enable-fpm` 是支持fpm扩展\n\n安装完成之后,会出现如下提示语:\n\n> Wrote PEAR system config file at: /usr/local/etc/pear.conf\n> You may want to add: /usr/local/lib/php to your php.ini include_path\n> /home/dc2-user/php-7.3.1/build/shtool install -c ext/phar/phar.phar /usr/local/bin\n> ln -s -f phar.phar /usr/local/bin/phar\n> Installing PDO headers: /usr/local/include/php/ext/pdo/\n\n请放心这是正常的,实际上此时你的php已经安装成功了:\n```bash\n$ php -v\n\nPHP 7.3.1 (cli) (built: Jan 24 2019 21:37:18) ( NTS )\nCopyright (c) 1997-2018 The PHP Group\nZend Engine v3.3.1, Copyright (c) 1998-2018 Zend Technologies\n```\n\n###### 4、创建配置文件,并将其复制到正确的位置。\n```bash\n$ sudo cp php.ini-development /usr/local/php/php.ini\n$ sudo cp /usr/local/etc/php-fpm.conf.default /usr/local/etc/php-fpm.conf\n$ sudo cp sapi/fpm/php-fpm /usr/local/bin\n```\n\n接下来你可能想要了解:\n- [nginx 与 php 结合使用](/2019/01/25/Nginx与php结合/)\n- nginx 与 apache 结合使用","tags":["centos","PHP"],"categories":["centos","PHP"]},{"title":"Centos7 依赖全家桶","url":"/2019/01/24/Centos7依赖全家桶/","content":"\n<hr>\n\n该文收集我安装 Centos7 环境中所有依赖的第三方包。只需要执行以下一句代码即可:\n```bash\n$ sudo yum install \\\ngcc-c++ \\\nzlib-devel \\\npcre-devel \\\nlibxml2 \\\nlibxml2-devel \\\nyum-utilsdevice-mapper-persistent-data \\\nlvm2 \\\nyum-utils \\\nautoconf \\\n```\n\n<!--more--> ","tags":["centos","yum"],"categories":["centos"]},{"title":"Centos7 Nodejs 安装","url":"/2019/01/24/Nodejs安装/","content":"\n<hr>\n\n### 操作预览\n1. 下载nodejs: https://nodejs.org/en/download/\n2. 检查配置是否健康\n3. make 编译(大概需要半小时)\n4. 安装(root)\n5. 验证\n\n###### 1. 下载nodejs: https://nodejs.org/en/download/\n```bash\n$ wget https://nodejs.org/dist/v10.15.0/node-v10.15.0.tar.gz\n```\n\n###### 2. 检查配置是否健康\n```bash\n$ ./configure\n```\n\n###### 3. make 编译(大概需要半小时)\n```bash\n$ make\n```\n\n###### 4. 安装(root)\n```bash\n$ sudo make install\n```\n\n###### 5. 验证安装是否成功\n```bash\n$ node -v \nv10.15.0\n```","tags":["centos","Nodejs"],"categories":["centos","Nodejs"]},{"title":"centos7 nginx 安装","url":"/2019/01/24/nginx安装/","content":"\n<hr>\n\n### 操作预览:\n1. 安装依赖\n2. 下载nginx : http://nginx.org/en/download.html\n3. 解压\n4. 检查配置是否健康\n5. 开始安装\n6. 检查是否安装成功,默认安装目录是在/usr/local/nginx\n7. 启动nginx服务\n8. 查看服务是否开启\n9. 使用 `$ curl localhost` 查看 nginx 欢迎页 \n\n<!--more--> \n\n//////////////////////////////////////////////\n\n\n##### 1、安装依赖\n```bash\n$ yum -y install gcc-c++ zlib-devel pcre-devel\n```\n\n##### 2、下载nginx : http://nginx.org/en/download.html\n```bash\n$ wget http://nginx.org/download/nginx-1.15.8.tar.gz\n```\n\n##### 3、解压\n```bash\n$ tar zxvf nginx-1.15.8.tar.gz\n```\n\n//////////////////////////////////////////////\n\n##### 4、检查配置是否健康\n```bash\n$ ./configure\n```\n\n##### 5、开始安装\n```bash\n$ make && make install\n```\n\n//////////////////////////////////////////////\n\n##### 6、检查是否安装成功,默认安装目录是在/usr/local/nginx\n```bash\n$ /usr/local/nginx/sbin/nginx -t\n\nnginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok\nnginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful\n```\n\n##### 7、启动nginx服务\n```bash\n$ /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf\n```\n\n//////////////////////////////////////////////\n\n##### 8、查看服务是否开启\n```bash\n$ ps -ef | grep nginx\n```\n\n##### 9、使用 `$ curl localhost` 查看 nginx 欢迎页 \n```html\n<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n```\n\n","tags":["centos","nginx"],"categories":["centos","nginx"]},{"title":"Centos 7 Docker 安装","url":"/2019/01/24/Docker安装/","content":"\n### 操作预览:\n1. 安装一些基本依赖软件\n2. 为了使用 yum-config-manager 需要先安装一下 yum-utils\n3. 设置稳定版仓库\n4. 更新仓库(可选)\n5. 查看所有仓库中所有docker版本(可选)\n6. 正式下载安装\n7. 启动 (并开机启动)\n8. 检查是否安装成功\n\n<!--more--> \n\n////////////////////////////////////////////// \n前戏准备 \n//////////////////////////////////////////////\n\n\n###### 1、安装一些基本依赖软件\n```bash\n$ sudo yum install -y yum-utilsdevice-mapper-persistent-data lvm2\n```\n\n###### 2、为了使用 yum-config-manager 需要先安装一下这个 yum-utils\n```bash\n$ sudo yum -y install yum-utils\n```\n\n###### 3、设置稳定版仓库\n```bash\n$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo\n```\n\n###### 4、更新仓库(可选)\n```bash\n$ sudo yum-config-manager --enable docker-ce-edge\n```\n\n////////////////////////////////////////////// \n正式开始 \n//////////////////////////////////////////////\n\n\n###### 5、查看所有仓库中所有docker版本(可选)\n```bash\n$ sudo yum list docker-ce --showduplicates | sort -r\n```\n\n###### 6、正式下载安装\n```bash\n$ sudo yum install docker-ce -y\n```\n\n###### 7、启动 (并开机启动)\n```bash\n$ sudo systemctl start docker\n$ sudo systemctl enable docker\n```\n\n###### 8、检查是否安装成功\n```bash\n$ docker -v\n```\n\n","tags":["centos","docker"],"categories":["docker"]},{"title":"Laravel Eloquent 入门","url":"/2019/01/20/laravel-Eloquent/","content":"\n# 一、数据建模\n\n创建一个名为 `Article` 的 Eloquent Model,Laravel 会自动将其与 Article 数据表关联。\n<!--more--> \n```php\n$ php artisan make:model Article\n```\n\n\n\n<hr>\n\n# 二、tinker 命令行交互\n\n输入以下命令进入交互模式:\n\n```bash\n$ php artisan tinker\n```\n\n#### 示例 1 - Model与插入数据:\n\n```bash\n>>> $article = new App\\Article;\n=> App\\Article {#2909}\n\n>>> $article->title='My first Title';\n=> \"My first Title\"\n\n>>> $article->content='content'\n=> \"content\"\n\n>>> $article->published_at=Carbon\\Carbon::now();\n=> Carbon\\Carbon @1547995347 {#2913\n date: 2019-01-20 14:42:27.721241 UTC (+00:00),\n }\n\n>>> $article;\n=> App\\Article {#2909\n title: \"My first Title\",\n content: \"content\",\n published_at: Carbon\\Carbon @1547995347 {#2913\n date: 2019-01-20 14:42:27.721241 UTC (+00:00),\n },\n }\n\n>>> $article->save();\n=> true\n>>>\n```\n\n代码说明:\n- `$article = new App\\Article` : 新建一个 Article Model;\n- `$article->title='My first Title'`:填充title字段;\n- `$article->content='content'`:填充content字段;\n- `$article->published_at=Carbon\\Carbon::now()`: 使用 `Carbon\\Carbon` 时间库来获取并设置当前时间;\n- `$article`:打印出当前的Model;\n- `$article->save()`:保存到数据库,如果返回true则表示成功;\n\n\n\n\n\n\n也可以使用 `$article->toArray()` 打印出 Model 信息:\n```bash\n>>> $article->toArray()\n=> [\n \"title\" => \"My first Title\",\n \"content\" => \"content\",\n \"published_at\" => Carbon\\Carbon @1547995347 {#2913\n date: 2019-01-20 14:42:27.721241 UTC (+00:00),\n },\n \"updated_at\" => \"2019-01-20 14:43:05\",\n \"created_at\" => \"2019-01-20 14:43:05\",\n \"id\" => 1,\n ]\n>>>\n```\n\n#### 示例 2 - 查找数据\n\n查找刚刚插入 `id` 为 **1** 的数据:\n\n```bash\n>>> $data = App\\Article::find(1);\n=> App\\Article {#2924\n id: 1,\n title: \"My first Title\",\n content: \"content\",\n published_at: \"2019-01-20 14:42:27\",\n created_at: \"2019-01-20 14:43:05\",\n updated_at: \"2019-01-20 14:43:05\",\n introl: \"\",\n }\n>>>\n```\n\n也可以使用 `App\\Article::all()` 方法无条件查找所有数据:\n\n```bash\n>>> $data=App\\Article::all()\n\n=> Illuminate\\Database\\Eloquent\\Collection {#2924\n all: [\n App\\Article {#2906\n id: 1,\n title: \"My first Title\",\n content: \"content\",\n published_at: \"2019-01-20 14:42:27\",\n created_at: \"2019-01-20 14:43:05\",\n updated_at: \"2019-01-20 14:43:05\",\n introl: \"\",\n },\n ],\n }\n>>>\n\n\n```\n\n#### 示例 3 - 更新数据\n\n和插入很相似,依然是使用 `save()` 方法:\n\n```bash\n>>> $data->title=\"Update\";\n=> \"Update\"\n\n>>> $data->save();\n=> true\n>>>\n```\n\n#### 示例 4 - where 查询\n\n```bash\n>>> $data=App\\Article::where('content', '=', 'content')->get()\n\n=> Illuminate\\Database\\Eloquent\\Collection {#2911\n all: [\n App\\Article {#2929\n id: 1,\n title: \"Update\",\n content: \"content\",\n published_at: \"2019-01-20 23:02:45\",\n created_at: \"2019-01-20 14:43:05\",\n updated_at: \"2019-01-20 15:02:45\",\n introl: \"\",\n },\n ],\n }\n>>>\n\n```\n\n`get()` 方法代表获取所有行,返回的是一个Collection数据类型。可以理解为一个加强版的数组。\n\n通常我们只想返回一条数据,那么可以使用 `first()` 方法,它只会直接返回一个对象:\n\n```bash\n>>> $data=App\\Article::where('content', '=', 'content')->first()\n\n=> App\\Article {#2910\n id: 1,\n title: \"Update\",\n content: \"content\",\n published_at: \"2019-01-20 23:02:45\",\n created_at: \"2019-01-20 14:43:05\",\n updated_at: \"2019-01-20 15:02:45\",\n introl: \"\",\n }\n\n>>> $data->title\n=> \"Update\"\n```\n\n#### 示例 5 - `create()` / `update()` \n\n使用 `App\\Article::create([...])` 和 `$model->udate([...])` 可以更便捷的创建和更新数据。\n\n先来 `\\app\\Article.php` 中添加代码,设置字段允许被填充:\n\n```php\n<?php\n\nnamespace App;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Article extends Model\n{\n protected $fillable = ['title', 'content', 'published_at'];\n}\n```\n\n**必须重新进入命令行交互!!**\n**必须重新进入命令行交互!!**\n**必须重新进入命令行交互!!**\n\n使用 `App\\Article::create([...])` 来创建数据:\n\n```bash\n>>> $article=App\\Article::create(['title'=>'Second Title', 'content'=>'Second Content', 'published_at'=>Carbon\\Carbon::now() ]);\n\n=> App\\Article {#2913\n title: \"Second Title\",\n content: \"Second Content\",\n published_at: Carbon\\Carbon @1547998078 {#2923\n date: 2019-01-20 15:27:58.713977 UTC (+00:00),\n },\n updated_at: \"2019-01-20 15:27:58\",\n created_at: \"2019-01-20 15:27:58\",\n id: 2,\n }\n>>>\n\n```\n\n使用 `$model->udate([...])` 来更新数据:\n\n```bash\n>>> $article->update(['title'=>'Change Title']);\n=> true\n```\n","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"Laravel Mysql 数据库配置 与 Migration 基础","url":"/2019/01/20/laravel-migration/","content":"\n# 一、MySql 数据库配置\n\n先来配置一下数据库,打开 Laravel 的环境配置 `\\.env` 文件,简单配置你的数据库信息:\n\n```\nDB_CONNECTION=mysql\nDB_HOST=127.0.0.1\nDB_PORT=3306\nDB_DATABASE=laravel\nDB_USERNAME=root\nDB_PASSWORD=root\n```\n\n再打开数据库配置文件 `\\config\\database.php`,检查当前的 Mysql 数据库配置:\n<!--more--> \n```php\n<?php\n\n'default' => env('DB_CONNECTION', 'mysql'),\n\n'mysql' => [\n 'driver' => 'mysql',\n 'host' => env('DB_HOST'),\n 'port' => env('DB_PORT'),\n 'database' => env('DB_DATABASE'),\n 'username' => env('DB_USERNAME'),\n 'password' => env('DB_PASSWORD'),\n 'prefix' => '',\n],\n\n// ... otherthing\n\n```\n\n<hr>\n\n# 二、migrate 初体验\n\nLaravel 最强大工具之一:**migration**\n\n如果对 `migration` 的概念不清晰,可以简单理解为数据库的版本控制工具。有了它我们就可以对数据库进行方便的管理和愉快地开发了。\n\n\n找到 `\\database\\migrations\\`,可以看到Laravel默认帮我们准备了两个 migrations 示例文件。\n\n\n\n我们就来学习和使用一下 `2014_10_12_000000_create_users_table.php` 文件:\n\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass CreateUsersTable extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('users', function (Blueprint $table) {\n $table->increments('id');\n $table->string('name');\n $table->string('email')->unique();\n $table->timestamp('email_verified_at')->nullable();\n $table->string('password');\n $table->rememberToken();\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('users');\n }\n}\n\n```\n\n代码说明:\n- up: 负责创建一个 `users` 表;\n- down: 为了避免重复新建,负责删除 `users` 表,**但这也说明每次执行都会清空该表的数据**;\n\n\n\n```bash\n$ php artisan migrate\n```\n\n\n\n\n\n<hr>\n\n# 三、创建 migration 文件\n\n刚刚我们使用了 Laravel 示例的 migration 文件,现在我们新建一个自己的文章表 `articles`,依然可以通过 `artisan` 命令来创建:\n\n```bash\n$ php artisan make:migration create-articles_table --create=articles\n```\n\n\n\n\n\n自动生成文件 `\\database\\migrations\\2019_01_20_042528_create-articles_table.php `,代码如下:\n\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass CreateArticlesTable extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('articles', function (Blueprint $table) {\n $table->increments('id');\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('articles');\n }\n}\n\n```\n\n其中 `$table->timestamps();` 建议保留,Laravel 会自动创建并维护 `created_at` 和 `updated_at` 字段。\n\n我们试着添加一些基本字段,代码如下:\n\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass CreateArticlesTable extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('articles', function (Blueprint $table) {\n $table->increments('id');\n $table->string('title');\n $table->text('content');\n $table->timestamp('published_at');\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('articles');\n }\n}\n\n```\n\n再次执行 migrate 命令 \n\n```bash\nphp artisan migrate\n```\n\n\n\n<hr>\n\n# 四、添加字段\n\n假设我们现在需要为 `articles` 表添加一个简介字段 `intro`。\n\n```bash\n$ php artisan make:migration add_intro_column_to_articles --table=articles\n```\n\n`\\database\\migrations\\2019_01_20_060259_add_intro_column_to_articles.php` 默认代码如下:\n\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass AddIntroColumnToArticles extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::table('articles', function (Blueprint $table) {\n //\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::table('articles', function (Blueprint $table) {\n //\n });\n }\n}\n```\n\n添加代码:\n\n```php\n<?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass AddIntroColumnToArticles extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::table('articles', function (Blueprint $table) {\n $table->string('introl');\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::table('articles', function (Blueprint $table) {\n $table->dropColumn('introl');\n });\n }\n}\n```\n\n又又又执行命令: `$ php artisan migrate`\n\n刷新数据表 `articles` ,成功添加了 `introl` 字段:\n\n\n\n事实上,如果真的要执行 `$table->dropColumn` 需要依赖一个第三方包,这里安装一下吧:\n\n```bash\n$ composer require doctrine/dbal\n```","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"Laravel 入门示例(三):常用的blade语法","url":"/2019/01/20/laravel-demo3/","content":"\n# @yield 语法\n\n中文理解为区域,在母版页(layouts)中会使用。类似 Vue 中的 `<slot name='content'>` 标签。 \n\n新建 **\\resources\\views\\layouts\\app.blade.php**\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js\"></script>\n <link rel=\"stylesheet\" href=\"https://cdn.bootcss.com/twitter-bootstrap/3.3.6/css/bootstrap.min.css\">\n <script src=\"https://cdn.bootcss.com/twitter-bootstrap/3.3.6/js/bootstrap.min.js\"></script>\n</head>\n<body>\n @yield('content')\n</body>\n</html>\n```\n\n\\resources\\views\\sites\\about.blade.php\n<!--more--> \n```html\n@extends('layouts/app')\n@section('content')\n <h1>about me {{$first}} {{$last}}</h1>\n@stop\n```\n\n### 语法说明:\n- **@extends**:继承 `\\resources\\views\\layouts\\app.blade.php` 模板;\n- **@section**:将内容填充到 `@yield('content')` 中;\n- **@stop**:通常一个 `@section` 对应一个 `$stop`;\n\n可以发现 **bootstrap** 的样式起到效果了:\n\n\n\n<hr>\n\n# 特定的页面加载特定的js文件\n\n其实非常简单,只需要在 `layouts` 中新增多个@yield,在特定的页面选择性填充这个区域即可。\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js\"></script>\n <link rel=\"stylesheet\" href=\"https://cdn.bootcss.com/twitter-bootstrap/3.3.6/css/bootstrap.min.css\">\n <script src=\"https://cdn.bootcss.com/twitter-bootstrap/3.3.6/js/bootstrap.min.js\"></script>\n</head>\n<body>\n @yield('content')\n @yield('footer')\n</body>\n</html>\n```\n\n```html\n@extends('layouts/app')\n@section('content')\n <h1>about me {{$first}} {{$last}}</h1>\n@stop\n\n@section('footer')\n <script>alert('yesh!!')</script>\n@stop\n```\n\n\n\n<hr>\n\n# @if 语法\n```html\n@extends('layouts/app')\n@section('content')\n @if ($first == \"Zhou\")\n <h1>about me {{$first}} {{$last}}</h1>\n @else\n <h2>nothing</h2>\n @endif\n@stop\n```\n\n<hr>\n\n# @foreach 语法\n\n先在控制器中导出数组 \n\n\\app\\Http\\Controllers\\SitesController.php\n\n```php\npublic function about () {\n $people = ['Taylor Otwell', 'Jeffray Way', 'Happy Petter'];\n return view('sites/about', compact('people'));\n}\n```\n\n通常 @foreach 会结合 @if 使用,否则会出现多余的html标签的情况\n\n```html\n@extends('layouts/app')\n@section('content')\n @if (count($people) > 0)\n <ul>\n @foreach ($people as $p)\n <li>{{ $p }}</li>\n @endforeach\n </ul>\n @endif\n@stop\n```\n\n","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"Laravel 入门示例(二):初识控制器与视图","url":"/2019/01/19/laravel-demo2/","content":"\n1、新建 **resources\\views\\sites\\about.blade.php**\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n</head>\n<body>\n <h1>about me</h1>\n</body>\n</html>\n```\n<!--more--> \n2、新增 **about** 路由\n\n\\routes\\web.php\n\n```php\n<?php\n\nRoute::get('/', 'SitesController@index');\n\nRoute::get('/about', 'SitesController@about');\n```\n\n3、控制器新增 **about** 方法\n\n\\app\\Http\\Controllers\\SitesController.php\n\n```php\n<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\n\nclass SitesController extends Controller\n{\n // Route::get('/', 'SitesController@index');\n public function index () {\n return view('welcome');\n }\n\n // Route::get('/about', 'SitesController@about');\n public function about () {\n return view('sites.about');\n }\n}\n```\n\n要访问 **resources/views/sites/about.blade.php** 页面,可以有两种方式:\n- return view('sites/about');\n- return view('sites.about'); **(推荐)**\n\n访问 [http://127.0.0.1:8000/about](http://127.0.0.1:8000/about),效果如图所示:\n\n\n\n<hr>\n\n# 向视图传递变量\n\n1、 **with()** 与 原生渲染\n\n```php\npublic function about () {\n $name = 'Lee';\n return view('sites/about')->with('name', $name);\n}\n```\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n</head>\n<body>\n <h1>about me <?= $name; ?></h1>\n</body>\n</html>\n```\n\n2、 使用 **blade** 模板引擎语法来渲染\n\n我们注意到视图文件都是 **.blade.php** 文件,所以理所应当使用更简洁的模板渲染语法。\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n</head>\n<body>\n <h1>about me {{ $name }}</h1>\n</body>\n</html>\n```\n\n**{!! $name !!}** 的语法可以用来解析html。譬如 $name 的值为 `<span style='color: red'> Lee </span>` 时,但要小心xss 攻击!\n\n\n\n\n3、使用数组传递多个变量\n\n```php\npublic function about () {\n return view('sites/about')->with([\n 'first' => 'Lee',\n 'last' => 'Mp'\n ]);\n}\n```\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n</head>\n<body>\n <h1>about me {{$first}} {{$last}}</h1>\n</body>\n</html>\n```\n\n4、类 yii 的传递方式\n```php\npublic function about () {\n $data = [];\n $data['first'] = 'Lee';\n $data['last'] = 'Mp';\n return view('sites/about', $data);\n}\n```\n\n5、 **compact** 方式**(推荐)**\n```php\npublic function about () {\n $first = 'Lee';\n $last = 'Mp';\n return view('sites/about', compact('first', 'last'));\n}\n```\n\n","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"Laravel 入门示例(一):初识控制器与路由","url":"/2019/01/19/laravel-demo1/","content":"\n1、先用 **artisan** 命令新建一个 **Controller**,命名为 **SitesController**\n\n```bash\n$ php artisan make:controller SitesController\n```\n\n生成的文件在目录: **\\app\\Http\\Controllers\\SitesController.php**,添加内容如下:\n\n```php\n<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\n\nclass SitesController extends Controller\n{\n // Route::get('/', 'SitesController@index');\n public function index () {\n \treturn view('welcome');\n }\n}\n\n\n```\n\n这里的 welcome 页面是在:**\\resources\\views\\welcome.blade.php**\n\n<!--more--> \n\n2、打开 **\\routes\\web.php**,修改内容为:\n\n```php\n<?php\n\n// Route::get('/', function () {\n// return view('welcome');\n// });\n\nRoute::get('/', 'SitesController@index');\n```\n\n刷新页面,一切如故。 ","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"laravel 安装与启动","url":"/2019/01/19/laravel安装与启动/","content":"\n## 1、使用 composer 安装 laravel\n\n[https://packagist.org/packages/laravel/laravel](https://packagist.org/packages/laravel/laravel)\n\n```bash\n$ composer create-project laravel/laravel laravel5\n```\n\n  \n\n## 2、启动 laravel\n\n##### 方式一:php 内置服务器\n\n指定 public 文件夹即可\n\n```bash\n$ php -S localhost:8000 -t public\n```\n\n##### 方式二:使用laravel提供的命令行工具artisan\n\n```bash\n$ php artisan serve\n```\n<!--more--> \n\n\n预览 [http://localhost:8000](http://localhost:8000) ,效果如下:\n\n","tags":["php","laravel"],"categories":["php","laravel"]},{"title":"hexo插入本地图片","url":"/2019/01/19/hexo插入本地图片/","content":"\n安装 [hexo-asset-image](https://github.com/CodeFalling/hexo-asset-image)\n\n```bash\n$ cnpm install hexo-asset-image --save\n```\n\n**_config.yml** 开启 **post_asset_folder: true**\n\n```yaml\npost_asset_folder: true\n```\n\n<!--more--> \n\n完成安装后用hexo新建文章的时候会发现_posts目录下面会多出一个和文章名字一样的文件夹。\n\n\n\n只需要使用该语句即可插入图片\n\n```\n\n```","tags":["hexo"],"categories":["hexo"]},{"title":"composer疑难杂症","url":"/2019/01/19/composer疑难杂症/","content":"\n## The openssl extension is required for SSL/TLS protection\n\n\n\n解决方法:在php.ini文件中打开php_openssl扩展 \n\n```\nextension=php_openssl.dll\n```\n\n<!--more--> ","tags":["php","cpmposer"],"categories":["cpmposer"]}]