[{"title":"django 与 mysql 勾结指南","url":"/2019/04/14/djangoandmysql/","content":"\n
\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
\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 \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 \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
\n\n操作预览:\n1. 准备一条数据线,并连接电脑和手;\n2. 使用 `flutter devices` 查看设备能否找到;\n3. 在 `Android studio` 中选择你的真机,然后点击 `[debug]`;\n4. 真机自动安装App。\n\n \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
\n\n看上去比windows客户端多了很多选项,但实际上只需要设置这五个:\n\n\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
\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 \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
\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 \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
\n问题:重复请求的问题在开发中很常见,浪费网络资源倒是其次,最怕的是前一次的点击逻辑,把后面的逻辑覆盖了。\n\n> 譬如,你在列表中点击了“吴彦祖”,然后再快速点击了“吴孟达”。假设“吴彦祖”的数据请求需要3秒,而“吴孟达”只需要2秒,那么结果就是,页面会先渲染出“吴彦祖”,而后又马上“吴孟达”的数据覆盖。\n>\n> **用户本来是想查看“吴彦祖”的照片,而界面展示的却是“吴孟达”……**\n\n \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\n\n\n \n SingleAjax\n \n \n \n \n\n\n\n\n\n\n```\n\n代码中我们模拟了十次重复的请求,发现前9次都被abort了。只留下最后一条。\n\n\n\n\n下面是axios的示例 singeAxios:\n```html\n\n\n\n \n Document\n \n \n\n\n\n\n\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
\n\nPub(https://pub.dartlang.org/ )是Google官方的Dart Packages仓库,类似于node中的npm仓库,android中的jcenter,我们可以在上面查找我们需要的包和插件,也可以向pub发布我们的包和插件。我们将在后面的章节中介绍如何向pub发布我们的包和插件。\n\n \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
\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 \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
\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 \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
\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 \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<\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 \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
\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
\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 \n\n
\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
\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
\n\nDockerfile 是一个文本文件,记录了镜像构建的所有步骤。\n\n```bash\n$ vim Dockerfile\n```\nDockerfile 内容如下:\n\n> FROM centos\n> RUN yum install -y vim\n\n开始构建\n\n \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
\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 \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
\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
\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
\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 构建镜像,
请注意最后一个`.` 指明在当前路径寻找 Dockerfile |\n| $ docker history <镜像> | 查看镜像的构建层级 |\n\n\n\n> Dockerfile 支持以 ‘#’ 开头注释\n\n \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 | 设置环境变量,环境变量可被后面的指令使用如:
`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
\n\nhello-world 是 Docker 官方提供的一个镜像。通常用来验证 Docker 是否安装成功。 我们先通过 `$ docker pull` 从 Docker Hub 下载它。\n```bash\n$ docker pull hello-world\n```\n\n \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
\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
\n\n切换国内 [DaoCloud](https://dashboard.daocloud.io/settings/profile) 镜像服务。 [免费注册](https://account.daocloud.io/signup)后进入控制台,找到右上角的[加速器图标](https://www.daocloud.io/mirror)。\n\n \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
\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 \n\n> 如果外网ip访问不了。请确保你服务器的安全组,以及本机的防火墙配置。\n\n```bash\n$ curl localhost:8080\n\nIt works!
\n```\n\n\n\n\n","tags":["Docker"],"categories":["Docker"]},{"title":"Nginx与php结合","url":"/2019/01/25/Nginx与php结合/","content":"\n
\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 \n\n
\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 \"\" >> /usr/local/nginx/html/index.php\n```\n\n\n","tags":["nginx","php"],"categories":["nginx"]},{"title":"hexo的认知","url":"/2019/01/25/hexo的认知/","content":"\n
\n\n### 预览:\n0. 如何使用摘要?加入 ` `即可\n1. 自定义文章模板\n2. 加入 `\n \n \n\n\n @yield('content')\n\n