[{"categories":["Web Technologies"],"contents":" 微信，是一个生活方式\n超过十亿人使用的手机应用\n支持发送语音短信、视频、图片和文字\n可以群聊，仅耗少量流量，适合大部分智能手机\n作为一个 Web 开发者，可以说现在很难不与微信发生交集了，简单来说，与 Web 开发相关的，大致可以分为微信内置浏览器页面和小程序两大类，本文用于记录一下日常开发中遇到的问题及解决的方法，后续会不断补充\u0026hellip;\n微信内置浏览器 Web 开发 Q: 微信浏览器是什么？ 微信内打开 Url 链接，使用的是其内置的 WebView，可以看做是系统原生浏览器的一个子集，不过微信对其做了一些扩展，比如说加入了WeixinJSBridge，但是 Web 开发时做兼容，主要参考的还是系统版本及其对应的浏览器版本。\nAndroid 有些区别，微信 6.1 版本以上的 android 用户，使用的是 QQ 浏览器的 X5 内核；而 5.4-6.1 之间的版本，若用户安装了 QQ 浏览器就是使用的 X5 内核，否则使用的是系统内核。\nQ: 如何检测是否在微信浏览器中？ Google 一下，会有很多结果，一般而言，目前就 2 种方式：\n1. 通过检测navigator.userAgent是否包含MicroMessenger 1 2 3 4 5 const isWeChat = ((ua) =\u0026gt; ua.length \u0026amp;\u0026amp; /MicroMessenger/gi.test(ua))( window \u0026amp;\u0026amp; window.navigator \u0026amp;\u0026amp; window.navigator.userAgent ? window.navigator.userAgent : \u0026#34;\u0026#34; ); 2. 通过检测WeixinJSBridge是否存在 这里有一个坑，可能开发时会遇到过Uncaught ReferenceError: WeixinJSBridge is not defined这样的报错，有过 WebView 开发的同学可能会清楚些，像WeixinJSBridge这样的扩展对象，不一定在页面正常的加载前就已经准备结束，所以微信提供了一个document的事件来让我们监听WeixinJSBridge是否已经准备完成，在其没有准备好前，调用WeixinJSBridge就会遇到像上面的那个报错\n所以，如果你想利用WeixinJSBridge来检测是否在微信中，请记住，这个过程是异步的。\n1 2 3 4 5 6 7 const isWeChat = (callback) =\u0026gt; { if (window.WeixinJSBridge \u0026amp;\u0026amp; typeof window.WeixinJSBridge !== \u0026#34;undefined\u0026#34;) { callback(); } else { document.addEventListener(\u0026#34;WeixinJSBridgeReady\u0026#34;, callback); } }; 小程序开发 TODO\n参考 微信 JS-SDK 说明文档 微信浏览器到底是什么内核？ - 杨秋实的回答 - 知乎 浅析 \u0026ldquo;WeixinJSBridge is not defined\u0026rdquo; ","date":"2018-05-10T08:08:08+08:00","permalink":"https://itony.net/wechat-battle/","section":"post","tags":["Note","WeChat"],"title":"微信开发杂谈"},{"categories":["Web Technologies"],"contents":"Apple 在发布 macOS High Sierra 后，系统也终于自带了 php v7.1，相比于之前，如果想使用 php7，还得额外想办法( Homebrew 或者 php-osx )而言着实方便了不少。\n但是，系统自带的 PHP 只有基础的配置，如果想做 PHP 开发，Xdebug 还是必须的，以下就总结一下如何在 macOS High Sierra 中为系统自带的 PHP 增加 Xdebug 模块。\n基础环境( macOS 及 PHP 信息) macOS High Sierra: v10.13.3 PHP: v7.1.7 安装 Xdebug Xdebug 官网安装文档中有 MAC 推荐的方式，鉴于系统自带的是 PHP 是v7.1.7，所以在选择的时候，需要选择php71-xdebug这个安装包。\n另外由于 brew 中的php71-xdebug依赖于php71的，所以建议加上--without-homebrew-php这个参数，这样的话 brew 就会忽略安装php71。\n1 brew install php71-xdebug --without-homebrew-php 不过这个时候，或许你会碰到下面这样的报错：\n1 2 3 4 5 6 7 8 phpize grep: /usr/include/php/main/php.h: No such file or directory grep: /usr/include/php/Zend/zend_modules.h: No such file or directory grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory Configuring for: PHP Api Version: Zend Module Api No: Zend Extension Api No: 提示缺失依赖，从而导致phpize无法正常工作，phpize是用来准备 PHP 扩展库的编译环境的，理论上系统自带的 PHP 应该是有phpize的，但是没有在/usr/include/php/*里面找到它需要的模块，并且检索/usr/include时发现这个目录根本不存在。\nGoogle 了一圈，解决问题，就需要在/usr/include中补全相关的内容，在 OSX v10.10 以前系统，需要手动做软链来解决：\n1 sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include /usr/include 但是 v10.11 以后的系统重写了安全策略，所以会遇到权限问题(sudo也不行)：\n1 ln: /usr/include: Operation not permitted 不过好在 Apple 为开发人员准备了 Xcode，这是一个很强大的工具，但是体积也很大(下载安装有点慢)，而一般我们只需要它提供的Command Line Tools就够了，上面的问题，其实只要安装Command Line Tools就可以解决：\n1 xcode-select --install 接下来，跟着提示做，安装、同意协议\u0026hellip; 等待安装结束以后，再用 brew 来安装 php71-xdebug:\n1 brew install php71-xdebug --without-homebrew-php 一切结束以后，brew 会给出提示：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 To finish installing xdebug for PHP 7.1: * /usr/local/etc/php/7.1/conf.d/ext-xdebug.ini was created, do not forget to remove it upon extension removal. * Validate installation via one of the following methods: * * Using PHP from a webserver: * - Restart your webserver. * - Write a PHP page that calls \u0026#34;phpinfo();\u0026#34; * - Load it in a browser and look for the info on the xdebug module. * - If you see it, you have been successful! * * Using PHP from the command line: * - Run `php -i \u0026#34;(command-line \u0026#39;phpinfo()\u0026#39;)\u0026#34;` * - Look for the info on the xdebug module. * - If you see it, you have been successful! 开启 PHP 的 Xdebug 经过上面步骤，系统里面是有 Xdebug 了，但是在php.ini配置文件中不一定有，因此需要手动添加 Xdebug 的配置项：\n1 2 3 4 5 6 7 8 [xdebug] zend_extension=\u0026#34;/usr/local/opt/php71-xdebug/xdebug.so\u0026#34; xdebug.remote_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_connect_back = 1 xdebug.remote_port = 9000 xdebug.scream = 0 xdebug.show_local_vars = 1 然后就是重启php-fpm：\n1 2 3 4 5 # 关闭php-fpm sudo killall php-fpm # 启动php-fpm sudo php-fpm 运行php -i \u0026quot;(command-line 'phpinfo()')\u0026quot; | grep xdebug后，你就可以看到关于 Xdebug 的配置内容了：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 xdebug ... xdebug.remote_autostart =\u0026gt; On =\u0026gt; On xdebug.remote_connect_back =\u0026gt; On =\u0026gt; On xdebug.remote_cookie_expire_time =\u0026gt; 3600 =\u0026gt; 3600 xdebug.remote_enable =\u0026gt; On =\u0026gt; On xdebug.remote_handler =\u0026gt; dbgp =\u0026gt; dbgp xdebug.remote_host =\u0026gt; localhost =\u0026gt; localhost xdebug.remote_log =\u0026gt; no value =\u0026gt; no value xdebug.remote_mode =\u0026gt; req =\u0026gt; req xdebug.remote_port =\u0026gt; 9000 =\u0026gt; 9000 xdebug.remote_timeout =\u0026gt; 200 =\u0026gt; 200 xdebug.scream =\u0026gt; Off =\u0026gt; Off ... Visual Studio Code - PHP Debug VSCode 是目前最流行的开发工具之一，虽然轻量，但是对标各类 IDE 毫不逊色，微软良心之作，通过安装不同的插件可以扩展它的能力，其中有一款 PHP Debug 的插件，可以作为 Xdebug 的桥梁，方便直接通过 Xdebug 调试 PHP，官方的描述十分贴切：\nPHP Debug Adapter for Visual Studio Code\n官网的指导也写的相当不错：\nInstall XDebug \u0026gt; I highly recommend you make a simple test.php file, put a phpinfo(); statement in there, then copy the output and paste it into the XDebug installation wizard. It will analyze it and give you tailored installation instructions for your environment. In short: On Windows: Download the appropiate precompiled DLL for your PHP version, architecture (64/32 Bit), thread safety (TS/NTS) and Visual Studio compiler version and place it in your PHP extension folder. On Linux: Either download the source code as a tarball or clone it with git, then compile it. Configure PHP to use XDebug by adding zend_extension=path/to/xdebug to your php.ini. The path of your php.ini is shown in your phpinfo() output under \u0026ldquo;Loaded Configuration File\u0026rdquo;. Enable remote debugging in your php.ini: 1 2 3 [XDebug] xdebug.remote_enable = 1 xdebug.remote_autostart = 1 There are other ways to tell XDebug to connect to a remote debugger than remote_autostart, like cookies, query parameters or browser extensions. I recommend remote_autostart because it \u0026ldquo;just works\u0026rdquo;. There are also a variety of other options, like the port (by default 9000), please see the XDebug documentation on remote debugging for more information. 4. If you are doing web development, don\u0026rsquo;t forget to restart your webserver to reload the settings 5. Verify your installation by checking your phpinfo() output for an XDebug section.\n这里需要注意的是它推荐开启 Xdebug 配置项中的remote_autostart这一项。\n好了，经过上面的操作，你应该可以跟 Demo 里面一样在 VSCode 中调试 PHP 了。 ","date":"2018-03-19T08:08:08+08:00","permalink":"https://itony.net/macos-php/","section":"post","tags":["PHP","BackEnd","Debug Tool"],"title":"macOS系统PHP7增加Xdebug"},{"categories":["Web Technologies"],"contents":"前言 日常开发中，经常会有与 App 联调的工作，除了 iOS 的 Safari 和 Android 的 adb，用来查看 console 的信息，往往还需要抓取一下 HTTP 的数据包，用来确认网络情况。\n概述 Charles 就是一款非常优秀的跨平台网络代理工具，支持 Windows、Mac、Linux，不过最重要的是它的功能非常强大，当然这么好的工具当然不是免费的，官方需要 $30 / license， 不过通过一些合作的代理商可能会便宜一些。\n功能特点 支持SSL 代理 - 以纯文本格式查看 SSL 请求和响应； 支持限制带宽 - 模拟低速的移动网络或者网络延迟的使用场景； 支持 AJAX 调试 - 以树或文本的形式查看 XML 和 JSON 请求和响应； 支持AMF调试 - 以 Flash 的形式查看 Flash Remoting / Flex Remoting 消息的内容； 支持重复请求以便于后端开发； 支持编辑 Request 参数； 支持拦截和编辑 Request 和 Response 的内容； 支持检查 HTML，CSS 和 RSS 内容是否符合 W3C 标准。 基本介绍 如何安装本文就不累述了，官网介绍的很详细了，也很简单，对照着自己的环境，下载安装包安装即可。\nCharles 的界面很简洁，分为 Structure 和 Sequence 模式，这 2 种模式可以获取的信息是差不多的，不过 Sequence 模式下多了一个便捷的 Filter 输入框，可以快速的过滤出来当前需要的查看的网络请求。\n如何使用 Charles 启动时默认会抓取本机的网络请求，所以一开始，你可能会看到很多网络请求的信息，如果你想停止或者清理，可以用工具条上的便捷键来操作： 下面就说说在 Mac 下如何通过 Charles 抓取移动设备上的网络请求。\n主要步骤分为以下几步：\n设置并开启 Charles 代理； 配置移动设备代理； 如果需要抓取 HTTPS，则需要配置 SSL 证书； 设置并开启 Charles 代理 打开菜单，点击 Proxy 一栏，就可以看到 Proxy Settings\u0026hellip; 的选项： 接着在 **Port: ** 一栏中填入代理的端口号，这里填写的是 8888： 点击 OK 即可生效设置，然后再打开菜单，点击 Help 一栏，找到到 Local IP Address 获取本机当前局域网中的 IP 地址： 配置移动设备代理 以 iOS 为例，操作步骤 设置 -\u0026gt; 无线局域网 -\u0026gt; wifi 设置(叹号图标) -\u0026gt; HTTP 代理 -\u0026gt; **配置代理 -\u0026gt; 手动 -\u0026gt; 服务器(上一步获取的本机局域网 IP) 和 **端口(上一步设置的端口号)**： 如果一切顺利，你的本机 Charles 会有一个提示出现：\n选择 Allow 就可以代理你的 iOS 了： 如果你仅仅是需要 HTTP 的代理，那么上述的应该就可以满足了，不过随着 HTTPS 的推广，就需要额外的设置 SSL 证书来获取 HTTPS 的内容了。\n配置 SSL 证书，抓取 HTTPS 如果我们在自己的服务上已经配置了 SSL，开启了 HTTPS，那么用 HTTP 的方式抓包就只能看到一堆的乱码了。\n这时候，不过我们可以用 Charles 作为中间人来进行 HTTPS 的代理，用它的根证书动态签发一张证书，同时让你的浏览器收不到服务端证书的，然后 Charles 来伪装服务端的证书，你的浏览器接受 Charles 的证书用于 SSL 加密，而 Charles 仍然用目标服务器的 SSL 证书与服务端进行通信，所以 Charles 就可以用它自己的根证书来解码你发出的请求了，如果想了解的更多，可以去找找有关于中间人攻击的资料。\n具体到操作其实就 3 步：\n在本机安装 Charles 根证书； 在客户端安装 Charles 提供的证书； 开启 Charles 的 SSL Proxying； 在本机安装 Charles 根证书 Charles 提供了非常简单的方式来安装，你只需要打开菜单，点击 Help 一栏，选中 SSL Proxying 就可以看到 Install Charles Root Certificate 的选项： 点击安装以后，会打开本地的 Keychain Access 提示是否添加，选择 Add 即可。 由于 Charles 的提供的 SSL 根证书是它自己颁发的，并未经过权威机构的认证，所以，有时候会经常提示证书的安全性问题，这个时候，你可以在 Keychain Access 中找到这个证书，并在 Trust 一项中选择 Always Trust即可： 在客户端安装 Charles 提供的证书 打开菜单，点击 Help 一栏，选中 SSL Proxying 就可以看到 Install Charles Root Certificate on a Mobile Device or Remote Browser 的选项，它会提示你用需要连接的设备去访问 chls.pro/ssl 这个 URL： 客户端在访问 chls.pro/ssl，会得到一个证书文件，不论是 iOS 还是 Android，都会进入证书的添加环节，下面以 iOS 为例： 这里需要注意一点，iOS 10.3 以后的系统，需要在 证书信任设置 中启用才行（Charles-ssl-certificates），操作步骤 设置 -\u0026gt; 通用 -\u0026gt; 关于本机 -\u0026gt; 证书信任设置 -\u0026gt; 开启对应证书： 开启 Charles 的 SSL Proxying 打开 Charles 的菜单，点击 Proxy 一栏，就可以看到 SSL Proxy Settings\u0026hellip; 的选项，然后增加一项规则即可： 好了，本文就先介绍到这，最多也就是操作 6 步，你就可以通过 Charles 截取 HTTPS 的数据包了，当然 Charles 的功能远不止如此，更多的惊喜，你可以慢慢的去挖掘。\n2018.04.23 Q: Android 提示 键入凭据存储的密码 ？\nA: 检查是否设置了锁屏密码，如果没有需要先设置锁屏密码，再次安装chls.pro/ssl下载的证书，输入锁屏密码即可；\n参考 charlesproxy 细说 Charles 配置 HTTPS 代理的乱码问题 Charles 抓包配置流程(Charles4.1.2 iOS) ","date":"2017-12-21T08:08:08+08:00","permalink":"https://itony.net/charles-note/","section":"post","tags":["Note","Debug Tool"],"title":"Charles小书"},{"categories":["Hobbies"],"contents":"在开发中，有时需要用到一些私有库，这些不太能够发布到公有仓库的，在使用npm和yarn安装的时候，往往是从私有 git 仓库来拉取，但是这样的话，就不能使用语义化版本号的方式来控制版本了。\n为了解决这个事情，最好的方式就是用私有 npm 仓库，npmjs 官方倒是有提供这个服务，但是这个价格（如果你是土豪，这个倒也是不错的选择）和速度，却令人望而止步啊！\n没办法，还是自己折腾吧，首先考虑到的就是用 cnpm 来自己搞一个，毕竟有阿里在维护，而且功能齐全，下面是 cnpm 官方的线路图：\n不过想搭建 cnpm 的私有库，需要依赖 Node.js 和 MySQL，不是特别轻便，所以就想到了之前有过一眼之缘的sinopia，可惜这个库有点年代了，最后找到一个 fork 出去的项目叫做verdaccio，它做的风生水起，而且用起来特别简单。\n安装的话就一步：\n1 $ npm install --global verdaccio verdaccio 在文件系统上存储数据，没有额外依赖，而且提供了一套默认配置，我们可以直接启动仓储服务，不过最好事先将需要的文件及文件夹创建好：\n1 2 3 4 5 6 7 8 9 10 11 # 缓存包的存放目录 $ mkdir -p storage # 配置文件 $ touch config.yaml # 储存auth信息的文件 $ touch htpasswd # verdaccio的日志文件 $ touch verdaccio.log 接着来修改一下默认配置文件：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 # 设置托管或缓存包的存放目录 storage: ./storage # 权限控制 auth: # 默认使用htpasswd做权限控制 htpasswd: # 制定 htpasswd 文件路径，htpasswd 中存储者用户名和加密过的秘钥 file: ./htpasswd # 最多允许注册用户数 max_users: 1000 # 备用仓库，如果 verdaccio 找不到，就会用到这里的仓储 uplinks: npmjs: url: https://registry.npmjs.org/ yarnjs: url: https://registry.yarnpkg.com/ cnpmjs: url: https://registry.npm.taobao.org/ # 包访问或发布的规则 packages: \u0026#39;@*/*\u0026#39;: access: $all publish: $authenticated proxy: npmjs \u0026#39;**\u0026#39;: access: $all proxy: npmjs # 日志配置 logs: - { type: file, path: verdaccio.log, level: info } # 开启web模式 web: enable: true 好了，一切就绪，使用nohup在后台启动一下吧：\n1 $ nohup verdaccio \u0026amp; 打开 http://localhost:4873 ，你就可以看到下面这个界面了（4873 是默认端口）：\nverdaccio 默认 web 界面有最基础的提示，如果要注册这个私有仓库需要运行：\n1 $ npm adduser --register http://localhost:4873 如果需要发布，则需要运行：\n1 $ npm publish --register http://localhost:4873 最后，如果你需要指定域名或者使用 https，verdaccio 就有配置项可以使用，当然你也可以像我一样用 Nginx 做代理，另外 verdaccio 可以使用不同的 plugin 来扩展功能，不亚于 cnpm，不过开箱即用的体验做的确实不错。\n补充说明 上面只不过是简单的说明了一下，实际在运用的时候，尤其是写一个依赖于现有 npm 仓库中的包，比如说你用到了lodash，在安装私有库的时候会遇到403的问题，提示unregistered users are not allowed to access package lodash。\n此时，需要修改packages中的权限，具体的可以参考verdaccio - Package Access，参考如下：\n1 2 3 4 5 6 7 8 9 10 packages: \u0026#39;@*/*\u0026#39;: access: $all publish: $authenticated proxy: npmjs # 这里的\u0026#39;**\u0026#39;表示所有npm资源 \u0026#39;**\u0026#39;: # access 表示可访问的权限，如果没有设置all或者@all，就会报403错误 access: $all proxy: npmjs 参考 verdaccio - A lightweight private npm proxy registry (sinopia fork) Sinopia | 从零开始搭建 npm 仓库 使用 verdaccio 搭建 npm 私有仓储 ","date":"2017-12-20T08:08:08+08:00","permalink":"https://itony.net/npm-repository-registry/","section":"post","tags":["Node.js","NPM"],"title":"搭建私有npm仓库"},{"categories":["Web Technologies"],"contents":"最近逛 Github 的 Trending，发现一个 Node.js 项目的辅助工具，作者是鼎鼎大名的TJ，用于帮助清理 Node.js 项目中日渐臃肿的node_modules，下图是**node-prune**项目中的一张打趣图，虽然个人觉得这也不是 npm 一家独有的现象，不过还是很形象。\n那么，先来看一段**node-prune**的简易描述：\nnode-prune is a small tool to prune unnecessary files from ./node_modules, such as markdown, typescript source files, and so on.\n接着，简单介绍一下如何安装及使用。\n首先，node-prune 这个工具是基于 go 的，所以你的系统上得安装 go，如果你也是使用 MAC 的话，那就比较简单了，直接使用brew安装就好了：\n1 $ brew install go 接着就是在本地安装 node-prune：\n1 $ go get github.com/tj/node-prune/cmd/node-prune 这里有点需要注意一下，安装完以后，你会发现无法直接运行node-prune这个命令，因为node-prune目前的位置是在~/go/bin里面，而这个路径可能并不存在于你的PATH中，所以你需要手动输入node-prune的路径才行。\n最后就是使用node-prune来清理node_modules中的冗余文件了。\n1 $ ~/go/bin/node-prune /PATH/TO/PROJECT/node_modules /PATH/TO/PROJECT/node_modules就是你项目中的node_modules路径。\n来个效果图 一个小项目就可以轻松瘦身25MB，使用又是如此简单，对于 SSD 硬盘才 250G 的我而言，这个工具确实绝对值得拥有。\n最后如果你需要遍历清理一个目录下所有项目的node_modules，可以参考一下这个shell脚本：\n1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/env bash find * -prune -type d | while read d; do if [[ -e \u0026#34;${d}/package.json\u0026#34; \u0026amp;\u0026amp; -d \u0026#34;${d}/node_modules\u0026#34; ]] then echo \u0026#34;-------- Node.js Repository: { $d } ---------\u0026#34; echo \u0026#34;start node-prune process...\u0026#34; /PATH/TO/go/bin/node-prune \u0026#34;${d}/node_modules\u0026#34; echo \u0026#34;prune done!\u0026#34; echo \u0026#34;\u0026#34; fi done ","date":"2017-11-28T08:08:08+08:00","permalink":"https://itony.net/node-prune/","section":"post","tags":["Node.js","NPM","Linux","Go"],"title":"node-prune助你瘦身node_modules"},{"categories":["Hobbies"],"contents":"实现“按任意键继续/Press any key to continue”效果 关键词：stty\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 get_char() { SAVEDSTTY=`stty -g` # 隐藏终端输入显示 stty -echo stty cbreak # dd等待用户按键 # bs(block size) 块大小 = 1 count总数 = 1， 作用只取一个字符 # 2 \u0026gt; /dev/null， 不显示任何信息 dd if=/dev/tty bs=1 count=1 2\u0026gt; /dev/null # 恢复终端显示 stty -raw stty echo stty $SAVEDSTTY } echo \u0026#34;\u0026#34; echo \u0026#34;Press any key to start...or Press Ctrl+c to cancel\u0026#34; char=`get_char` ","date":"2017-11-08T08:08:08+08:00","permalink":"https://itony.net/shell-note/","section":"post","tags":["Note","Linux","Shell"],"title":"常用Shell整理"},{"categories":["Hobbies"],"contents":"有时候需要在 ECS 上访问 Github 的服务，那个速度，真是惨不忍睹啊，正好前几天通过Vultr 搞了一个私人的 SS 服务，上个 Google、看个油管什么的再也没问题了，于是动了在 ECS 上部署 SS Client 的念头。\n说动手就动手，SS Client 安装最方便的就是通过pip了，简简单单的使用：\n1 pip install shadowsocks 就搞定了，不过这边有个坑，如果你使用的加密协议是aes-256-gcm的话，你就需要用下面这种方式来升级一下才能支持aes-256-gcm（方法来源）：\n1 pip install --upgrade git+https://github.com/shadowsocks/shadowsocks.git@master 然后创建 shadowsocks 的配置文件/etc/sslocal.json，下面是配置模板：\n1 2 3 4 5 6 7 8 9 10 { \u0026#34;server\u0026#34;: \u0026#34;YOUR_SS_SERVER_IP\u0026#34;, \u0026#34;server_port\u0026#34;: \u0026#34;YOUR_SS_SERVER_PORT\u0026#34;, \u0026#34;local_address\u0026#34;: \u0026#34;127.0.0.1\u0026#34;, \u0026#34;local_port\u0026#34;: YOUR_LOCAL_PORT, \u0026#34;password\u0026#34;: \u0026#34;YOUR_SS_SERVER_PASSWORD\u0026#34;, \u0026#34;timeout\u0026#34;: 100, \u0026#34;method\u0026#34;: \u0026#34;aes-256-gcm\u0026#34;, \u0026#34;fast_open\u0026#34;: false } 接着就让sslocal依据配置文件/etc/sslocal.json在后台运行就可以了，这里用到了nohup，同时将输出清理掉：\n1 nohup sslocal -c /etc/sslocal.json /dev/null 2\u0026gt;\u0026amp;1 \u0026amp; 为了方便起见，还可以在开机启动文件/etc/rc.local中加入以上内容，方便以后重启以后自动开启sslocal。\n好了，开启sslocal后，运行一下：\n1 curl --socks5 127.0.0.1:YOUR_LOCAL_PORT ipinfo.io 如果一切正常，你可以看到包含你 SS Server IP 的运行结果：\n1 2 3 4 5 { \u0026#34;ip\u0026#34;: \u0026#34;YOUR_SS_SERVER_IP\u0026#34;, \u0026#34;hostname\u0026#34;: \u0026#34;YOUR_SS_SERVER_HOSTNAME\u0026#34;, ... }# 不过 SS 走的是 Socket5，平时我们用到最多的还是 HTTP(s)，这时候需要再安装一个Privoxy用于将 Socket5 转到 HTTP(s)上。\n一般yum中会自带 Privoxy，如果没有的话，你可以先安装一下EPEL 扩展：\n1 yum install epel-release -y 然后再安装 Privoxy:\n1 yum install privoxy -y 修改 Privoxy 配置文件/etc/privoxy/config中的相关内容，增加转发规则：\n1 2 # 注意别忘了结尾那个. forward-socks5t / 127.0.0.1:YOUR_LOCAL_PORT . 运行 Privoxy，并将它列入开机启动项：\n1 2 systemctl start privoxy systemctl enable privoxy 最后别忘了，在.bashrc或者.zshrc中加入http_proxy与https_proxy的设置：\n1 2 export http_proxy=http://127.0.0.1:8118 export https_proxy=http://127.0.0.1:8118 8118是 Privoxy 默认的监听端口，你也可以自行设定。\n参考资料 https://github.com/shadowsocks/shadowsocks/issues/986 https://brickyang.github.io/2017/01/14/CentOS-7-%E5%AE%89%E8%A3%85-Shadowsocks-%E5%AE%A2%E6%88%B7%E7%AB%AF/ https://www.loyalsoldier.me/fuck-the-gfw-with-my-own-shadowsocks-server/ ","date":"2017-11-05T08:08:08+08:00","permalink":"https://itony.net/shadowsocks-ecs/","section":"post","tags":["Linux","Shadowsocks"],"title":"SS助力ECS"},{"categories":["Hobbies"],"contents":" 世界那么大，我想去看看！ There is such a lot of world to see.\n自从 V*P*N 被禁后，就折腾起了 SS(R)+BBR 的方案，先后入手了 bandwagonHOST、Digitalocean、UFOHost、Vultr\u0026hellip;\n真是验证了那句话，生命不止，折腾不惜。\n总的来说，还是 Vultr 的 Tokyo 的机房最合适，别看某些自称香港 CN2 线路，但是带宽太小，价格也不便宜，虽然 ping 的结果看着不错，但是上个 Google 都卡，油管的 HD 就更加别指望了，所以，交完学费，还是给大家推荐平民路线的**Vultr**。\n先来一张效果图： 下面来说一下如何在Vultr上搭建自己的 SS(R)+BBR 服务吧。\n搞个账号 首先你得有个Vultr账户，在以前Vultr没有支持支付宝的时候，Paypal 是最方便的支付方式，但是现在Vultr已经支持支付宝了，难道是\u0026hellip;（此处省略 N 字）\n开台服务器 接着直接新开一个服务器，这里不得不说 Vultr 的用户体验做的真是不错，简洁、使用。 Server Location 选择 Tokyo Server Type 选择 Centos7 当然，你可以选着 Ubuntu16，这 2 个系统对于后面搭建 BBR 服务最不容易出问题。 至于其它的请随意，不过个人建议还是开启 IPv6，以后便于扩展点其它的用途。\n搭建 SS(R)+BBR 感谢秋水逸冰提供了一键式的脚本方案\n搭建 SS 服务 这里使用秋水逸冰提供了一键式的脚本，具体的可以参考这边，下面就简单的说一下如何操作：\n使用 root 用户登录，运行以下命令： 1 2 3 4 5 wget --no-check-certificate -O shadowsocks-all.sh https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-all.sh chmod +x shadowsocks-all.sh ./shadowsocks-all.sh 2\u0026gt;\u0026amp;1 | tee shadowsocks-all.log 安装完成后，脚本提示如下 1 2 3 4 5 6 7 8 Congratulations, your_shadowsocks_version install completed! Your Server IP :your_server_ip Your Server Port :your_server_port Your Password :your_password Your Encryption Method:your_encryption_method Welcome to visit:https://teddysun.com/486.html Enjoy it! 安装 BBR 这里还是用秋水逸冰提供了一键式的脚本方案，具体参考这里\n以前还有速锐，不过现在除了破解版都不能用了，不过 Google 的 BBR 也是不错的选择。\n使用 root 用户登录，运行以下命令： 1 2 3 4 5 wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh chmod +x bbr.sh ./bbr.sh 安装完成后，脚本会提示需要重启 VPS，输入 y 并回车后重启。\n检查 BBR 是否安装成功 1 2 3 sysctl net.ipv4.tcp_available_congestion_control # \u0026gt;\u0026gt;\u0026gt; net.ipv4.tcp_available_congestion_control = bbr cubic reno 好了，服务端的东西差不多结束了，至于如何在客户端配置我就不多说了，网上一搜一大堆，推荐配合 Chrome 的 SwitchyOmega 会更加舒服。\n其它 这里得说一下 Vultr，如果你跟我一样，用的是 Centos7 的系统，你可能会碰到端口被禁用的问题，其实这是因为系统默认启用了firewall导致的，firewall.service默认只开启了22端口，所以你需要新增刚才配置的 SS 服务端口才行：\n1 2 3 firewall-cmd --zone=public --add-port=YOUR_SERVER_PORT/tcp --permanent # \u0026gt;\u0026gt;\u0026gt; success 记得要重启firewalld：\n1 systemctl restart firewalld 如果你对firewalld感兴趣，可以看看这篇文章。\n","date":"2017-11-03T08:08:08+08:00","permalink":"https://itony.net/the-big-world/","section":"post","tags":["Linux","Shadowsocks","VPS"],"title":"THE BIG WORLD"},{"categories":["Web Technologies"],"contents":"Next.js是目前用于实现 React 服务端渲染框架中的比较流行的一个，得力于ZEIT的维护，使得相对于其它的一些框架，不论是在文档还是配套上面，它都比较齐全。\n官方就提供了learnnextjs这样的交互式入门教程，这里就不多说了（结合文档效果更好）。\n既然是实战，这里就说点自己遇到的问题及解决方案，下面将问题总的划分为几个分类：\nJS 方面的坑 使用jsx作为文件后缀？ 一开始笔者习惯性的使用jsx作为文件后缀，发现控制台中老是报错，说是模块没有找到，这种一看就是配置问题，需要修改next.config.js中 webpack 的配置，增加extensions的jsx还有对应的babel-loader的配置，结果发现完全不起作用，看来是 next.js 中对于相关文件做了特殊的处理，目前来看只能使用*.js作为文件后缀了。\n官方目前的回复也是这样，暂时就支持*.js作为后缀，具体可以看一下这个issue\nJS 中读取process.env.*返回undefined？ 如果直接用node执行*.js文件，那么cross-env就比较合适，这个包帮我们处理的跨平台时env定义的问题，但是next.js是依赖于构建工具webpack的，换句话说，如果直接在package.json的scripts中使用cross-env是无法直接影响webpack的处理结果的，还需要用到webpack.DefinePlugin这个插件来做一次定义，next.js提供了一个简单的例子，核心代码如下：\n1 2 3 4 5 6 7 8 9 10 11 webpack: (config) =\u0026gt; { config.plugins.forEach((plugin) =\u0026gt; { if (plugin.constructor.name === \u0026#34;DefinePlugin\u0026#34;) { plugin.definitions[\u0026#34;process.env.SECRET\u0026#34;] = JSON.stringify( process.env.SECRET ); } }); return config; }; 另外，这只是其中的一个解决方法，next.js还提供了一个基于Babel和dotenv的例子，原理就是在 Babel 处理阶段就将代码中的process.env.*替换处理。\n使用babel处理node_modules中部分 JS 的方法？ 为了兼容 Android4.3，就必须要用 babel 结合相关的工具对 ES6 做兼容性处理，但是，默认的next.config.js中关于jsx的处理一般都会设置exclude过滤掉node_modules，就像这样的配置：\n1 2 3 4 5 { test: /\\.(js|jsx)$/, use: [\u0026#39;babel-loader\u0026#39;], exclude: /node_modules/, } next.js的 issues 中有不少关于这个的讨论，目前实践下来，最简单的就是直接修改exclude属性即可：\n1 2 3 4 5 6 7 { test: /\\.(js|jsx)$/, use: [\u0026#39;babel-loader\u0026#39;], // 这里对react及react-dom 2个做了过滤处理 // 也就是说这2个会被babel做处理 exclude: /node_modules\\/(?!(react|react-dom)\\/).*/ } 当然，babel 还需要结合.babelrc和.browserslistrc这些配置项及babel-plugin-transform-runtime这样的工具，如果需要了解更多，可以参考 Babel 笔记 这篇文章。\nStatic 静态资源方面的坑 官网教程中并没有全面的介绍如何控制好静态资源的加载及维护，下面就说说一些常见的问题。\n如何引入图片、字体等静态资源？ webpack 及配套的*-loader，使得我们可以很方便的在 JS 文件中通过import载入各种静态资源，不过 next.js 基础中并未对图片、字体等文件做特殊配置，所以需要我们手动在next.config.js中增加 webpack 的相关配置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { test: /\\.(png|jpe?g|gif)(\\?.*)?$/, use: [{ loader: \u0026#39;emit-file-loader\u0026#39;, options: { name: \u0026#39;dist/[path][name].[ext]\u0026#39;, }, }, { loader: \u0026#39;url-loader\u0026#39;, options: { limit: 2000, outputPath: \u0026#39;static/images/\u0026#39;, publicPath: \u0026#39;/_next/\u0026#39;, }, }, ], }, { test: /\\.(woff2?|eot|ttf|otf|svg)(\\?.*)?$/, use: [{ loader: \u0026#39;emit-file-loader\u0026#39;, options: { name: \u0026#39;dist/[path][name].[ext]\u0026#39;, }, }, { loader: \u0026#39;url-loader\u0026#39;, options: { limit: 2000, outputPath: \u0026#39;static/fonts/\u0026#39;, publicPath: \u0026#39;/_next/\u0026#39;, }, }, ], } 如何引用全局静态资源？ 官网的推荐是在根目录的static文件夹中放入即可，后续不论是在 JS 还是 CSS 文件中，只需要写绝对路径/static/{path_to_file}即可，next.js 帮助做了一些类似于路由映射的处理，很遗憾，官方并未给出 cache 的方案，目前来看有 2 中思路\n1. 基于 URL 中param的 cache 方案 1 2 3 4 5 \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34; href={`/static/styles/app.css?${this.props.__NEXT_DATA__.buildStats[\u0026#34;app.js\u0026#34;].hash}`} /\u0026gt; 2. 基于 URL 中path结合脚本迁移文件路*(暂时是个思路，未具体实现)* 1 2 3 4 5 \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34; href={`/_next/${this.props.__NEXT_DATA__.buildStats[\u0026#34;app.js\u0026#34;].hash}/app.css`} /\u0026gt; 文件迁移脚本\njq是一个轻量级的命令行 JSON 处理工具\n1 2 3 #!/usr/bin/env bash NEXT_BUILD_ID=`cat .next/build-stats.json | jq -r \u0026#39;.[][]\u0026#39;` cp -Rf ./static/* ./_next/${NEXT_BUILD_ID}/ CSS 方面的坑 官方提供了很多 CSS 的示例，但是个人觉得这个是踩坑最多的地方\n实现CSS Modules？ 得益于css-loader，如果单单是做客户端渲染，只需要在 webpack 的配置中启用modules配置即可，从而简简单单的实现BEM，但是 Next.js 官方并不支持该方案，具体可以参考这个issue，虽然大家也都集思广益，造出了各种方案，但是具体实施起来效果都不怎么理想，反倒是官方推荐的styled-jsx方案，结合postcss之后，成了不错的选择。\nwith-styled-jsx-plugins这个例子已经实现了基础的配置，你只需要增加postcss.config.js扩展一下即可：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 /** * postcss.config.js * * @see https://github.com/zeit/next.js/tree/master/examples/with-global-stylesheet/ */ const postcssEasyImport = require(\u0026#34;postcss-easy-import\u0026#34;); const postcssCssnext = require(\u0026#34;postcss-cssnext\u0026#34;); const cssnano = require(\u0026#34;cssnano\u0026#34;); const lost = require(\u0026#34;lost\u0026#34;); const postCSSPluginCombinations = [ // keep this first // https://github.com/TrySound/postcss-easy-import#options postcssEasyImport({ // prefix: \u0026#39;_\u0026#39;, }), lost(), postcssCssnext({ // so imports are auto-prefixed too // @see http://robin-front.github.io/2016/04/09/postCSS-loader%E9%85%8D%E7%BD%AE/ browsers: [\u0026#34;\u0026gt; 1% in CN\u0026#34;, \u0026#34;last 2 versions\u0026#34;], features: { rem: false, }, }), ]; module.exports = { plugins: process.env.NODE_ENV === \u0026#34;production\u0026#34; ? [ ...postCSSPluginCombinations, cssnano({ autoprefixer: false, discardUnused: { fontFace: false }, }), ] : postCSSPluginCombinations, }; styled-jsx与 babel 的冲突？ 之前在babel的生产环境发布前，会利用一些plugin优化 react 的产出代码，其中最常用的就是transform-react-remove-prop-types、transform-react-constant-elements、transform-react-inline-elements，这个时候 styled-jsx对于\u0026lt;style jsx\u0026gt;{***}\u0026lt;/style\u0026gt;中的处理就会与**transform-react-constant-elements、transform-react-inline-elements**冲突了，导致无法正常处理scope及optimized的功能，这里建议直接将transform-react-constant-elements和transform-react-inline-elements去除即可。\ncssnano处理后@font-face内容缺失？ 一般现在都会在上生产环境前使用 cssnano 对 CSS 文件做一次压缩优化，不过现在碰到一个问题，在处理后，@font-face的内容缺失了，cssnano 的 issue 中有人提供了解决方式：禁用其对@font-face的优化即可。\n1 2 3 4 5 6 7 cssnano({ autoprefixer: false, // 禁用对`@font-face`的优化 discardUnused: { fontFace: false, }, }); 首次渲染pages/_document.js，全局未能生效？ next.js 推荐使用styled-jsx处理 css，所以在_document.js中想当然的使用了下面的写法：\n1 2 3 \u0026lt;style jsx global\u0026gt; {mainStyles} \u0026lt;/style\u0026gt; 这样就直接导致了一个问题，在第一次访问服务的时候，发现mainStyles中的内容并未被加载并执行，next.js 的issues 中有人也已经反映过这个问题并且给出了解决方案，简单来说就是直接使用\u0026lt;style\u0026gt;加载即可，不要使用\u0026lt;style jsx global\u0026gt;。\n","date":"2017-11-01T08:08:08+08:00","permalink":"https://itony.net/nextjs-battle/","section":"post","tags":["Note","FrontEnd","React","SSR"],"title":"Next.js实战"},{"categories":["Web Technologies"],"contents":"众所周知，JS 生成伪随机数无非就是采用Math.random()，但是，如何生成一个有范围的伪随机数（整数），一般都会这么写：\n1 2 3 4 5 6 7 8 9 10 11 12 13 /** * 生成一个介于 min ~ max 的随机数 */ function getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } /** * 生成一个介于 min ~ max 的随机整数 */ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } 没错，以前我也是这么解决的（Google 一下，so easy！)。\n但是没有仔细考虑过，为何要这么做，今天无意翻到 stackoverflow 上一个解答，十分详细的说明背后的逻辑，总结一下可以归纳为 3 条：\nMath.random()返回一个从 0(包含) 到 1(不包含) 的随机数，具体如下：\n1 [0 .................................... 1) 现在我们需要有一个取值范围，也就是min(包含) 到 max(不包含)，结合原始的示意图，就有下面这个图了：\n1 2 [0 .................................... 1) [min .................................. max) 好吧，但是明显我们需要将min转换为0，那么很显然，max那个部分得变为max - min，示意图如下：\n1 2 [0 .................................... 1) [min - min ............................ max - min) 简化一下就是：\n1 2 [0 .................................... 1) [0 ............................ max - min) 很明显，再结合Math.random()，进行乘法计算，我们就可以获取到一个介于 min 到 max 的随机整数了：\n1 2 3 4 5 6 Math.random() | [0 .................................... 1) [0 .................................... max - min) | x (what we need) 但是，这个结果是不包含 min 的，别忘了，它的范围是从 0 开始的，所以我们需要在此基础上加上min的值，也就是：\n1 Math.random() * (max - min) + min; 这样就实现了一个min(包含) 到 max*(不包含)*的伪随机数了。\n但是，有时候我们还需要是一个整数的 min(包含) 到一个整数的 max(不包含) 的伪随机数，这个时候，我们就想到了 JS 里面的几个原生的方法：Math.round,Math.ceil,Math.floor，他们都能返回一个整数值给我们.\n但是仔细的理解，你会发现，如果使用Math.round(Math.random() * (max - min)) + min，它返回的随机数并不是均匀分布的，下面这个图可以帮你理解一下:\n1 2 3 min...min+0.5...min+1...min+1.5 ... max-0.5......max └───┬───┘└────────┬───────┘└───── ... ─────────┘└───┬──┘ ← round() min min+1 max 看图，也许你就发现了，明显随机到 max 的概率要小于随机到 min 的概率。\n接下来，我们尝试一下Math.floor(Math.random() * (max - min + 1)) + min，再画个示意图：\n1 2 3 4 min.... min+1... min+2 ... max-1... max.... max+1 (is excluded from interval) | | | | | | └───┬────┘└───┬───┘└─── ... ┘└───┬───┘└───┬────┘ ← floor() min min+1 max-1 max 很明显，此时，随机到 max 的概率要基本上与随机到 min 的概率一致了，也就是达到了一种概率均匀分布的状态。\n另外，你也可以尝试一下Math.ceil(Math.random() * (max - min - 1)) + min，你会发现随机到 min 的概率要小于随机到 max 的概率，也就是随机概率又不均匀了。\n参考资料 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random http://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range?answertab=active#tab-top ","date":"2017-09-12T08:08:08+08:00","permalink":"https://itony.net/javascript-random-number/","section":"post","tags":["FrontEnd","Javascript","Math"],"title":"JS生成有范围伪随机数"},{"categories":["Web Technologies"],"contents":"Brendan Eich用了 10 天就创造了 JavaScript，因为当时的需求定位，导致了在设计之初，在语言层就不包含很多高级语言的特性，其中就包括模块这个特性，但是经过了这么多年的发展，如今对 JavaScript 的需求已经远远超出了 Brendan Eich 的预期，其中模块化开发更是其中最大的需求之一。\n尤其是 2009 年 Node.js 出现以后，CommonJS 规范的落地极大的推动了整个社区的模块化开发氛围，并且随之出现了 AMD、CMD、UMD 等等一系列可以在浏览器等终端实现的异步加载的模块化方案。\n此前，虽然自己也一直在推进模块化开发，但是没有深入了解过模块化演进的历史，直到最近看到了一篇文章《精读 JS 模块化发展》，文章总结了History of JavaScript这个开源项目中关于 JavaScript 模块化演进的部分，细读几次之后，对于一些以前模棱两可的东西，顿时清晰了不少，下面就以时间线总结一下自己的理解：\n在1999 年的时候，绝大部分工程师做 JS 开发的时候就直接将变量定义在全局，做的好一些的或许会做一些文件目录规划，将资源归类整理，这种方式被称为直接定义依赖，举个例子：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 // greeting.js var helloInLang = { en: \u0026#34;Hello world!\u0026#34;, es: \u0026#34;¡Hola mundo!\u0026#34;, ru: \u0026#34;Привет мир!\u0026#34;, }; function writeHello(lang) { document.write(helloInLang[lang]); } // third_party_script.js function writeHello() { document.write(\u0026#34;The script is broken\u0026#34;); } 1 2 3 4 5 6 7 8 9 10 11 // index.html \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34; /\u0026gt; \u0026lt;title\u0026gt;Basic example\u0026lt;/title\u0026gt; \u0026lt;script src=\u0026#34;./greeting.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;./third_party_script.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body onLoad=\u0026#34;writeHello(\u0026#39;ru\u0026#39;)\u0026#34;\u0026gt;\u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 但是，即使有规范的目录结构，也不能避免由此而产生的大量全局变量，这就导致了一不小心就会有变量冲突的问题，就好比上面这个例子中的writeHello。\n于是在2002 年左右，有人提出了命名空间模式的思路，用于解决遍地的全局变量，将需要定义的部分归属到一个对象的属性上，简单修改上面的例子，就能实现这种模式：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // greeting.js var app = {}; app.helloInLang = { en: \u0026#34;Hello world!\u0026#34;, es: \u0026#34;¡Hola mundo!\u0026#34;, ru: \u0026#34;Привет мир!\u0026#34;, }; app.writeHello = function (lang) { document.write(helloInLang[lang]); }; // third_party_script.js function writeHello() { document.write(\u0026#34;The script is broken\u0026#34;); } 不过这种方式，毫无隐私可言，本质上就是全局对象，谁都可以来访问并且操作，一点都不安全。\n所以在2003 年左右就有人提出利用IIFE结合Closures特性，以此解决私有变量的问题，这种模式被称为闭包模块化模式：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // greeting.js var greeting = (function () { var module = {}; var helloInLang = { en: \u0026#34;Hello world!\u0026#34;, es: \u0026#34;¡Hola mundo!\u0026#34;, ru: \u0026#34;Привет мир!\u0026#34;, }; module.getHello = function (lang) { return helloInLang[lang]; }; module.writeHello = function (lang) { document.write(module.getHello(lang)); }; return module; })(); IIFE 可以形成一个独立的作用域，其中声明的变量，仅在该作用域下，从而达到实现私有变量的目的，就如上面例子中的helloInLang，在该 IIFE 外是不能直接访问和操作的，可以通过暴露一些方法来访问和操作，比如说上面例子里面的getHello和writeHello2 个方法，这就是所谓的 Closures。\n同时，不同模块之间的引用也可以通过参数的形式来传递：\n1 2 3 4 5 6 7 8 9 10 11 // x.js // @require greeting.js var x = (function (greeting) { var module = {}; module.writeHello = function (lang) { document.write(greeting.getHello(lang)); }; return module; })(greeting); 此外使用 IIFE，还有 2 个好处：\n提高性能：通过 IIFE 的参数传递常用全局对象 window、document，在作用域内引用这些全局对象。JavaScript 解释器首先在作用域内查找属性，然后一直沿着链向上查找，直到全局范围，因此将全局对象放在 IIFE 作用域内可以提升 js 解释器的查找速度和性能；\n压缩空间：通过参数传递全局对象，压缩时可以将这些全局对象匿名为一个更加精简的变量名；\n在那个年代，除了这种解决思路以外，还有通过其它语言的协助来完成模块化的解决思路，比如说模版依赖定义、注释依赖定义、外部依赖定义等等，不过不常见，所以就不细说了，究其本源，它们想最终实现的方式都差不多。\n不过，这些方案，虽然解决了依赖关系的问题，但是没有解决如何管理这些模块，或者说在使用时清晰描述出依赖关系，这点还是没有被解决，可以说是少了一个管理者。\n没有管理者的时候，在实际项目中，得手动管理第三方的库和项目封装的模块，就像下面这样把所有需要的 JS 文件一个个按照依赖的顺序加载进来：\n1 2 3 4 5 6 7 8 9 10 11 12 \u0026lt;script src=\u0026#34;zepto.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;jhash.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;fastClick.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;iScroll.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;underscore.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;handlebar.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;datacenter.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;deferred.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;util/wxbridge.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;util/login.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;util/base.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026#34;util/city.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 如果页面中使用的模块数量越来越多，恐怕再有经验的工程师也很难维护好它们之间的依赖关系了。\n于是如 LABjs 之类的加载工具就横空出世了，通过使用它的 API，动态创建\u0026lt;script\u0026gt;，从而达到控制 JS 文件加载以及执行顺序的目的，在一定的程度上解决了依赖关系，例如：\n1 2 3 4 5 6 7 $LAB .script(\u0026#34;greeting.js\u0026#34;) .wait() .script(\u0026#34;x.js\u0026#34;) .script(\u0026#34;y.js\u0026#34;) .wait() .script(\u0026#34;run.js\u0026#34;); 不过 LABjs 之类的加载工具是建立在以文件为单位的基础之上的，但是 JS 中的模块又不一定必须是文件，同一个文件中可以声明多个模块，YUI 作为昔日前端领域的佼佼者，很好的糅合了命名空间模式及沙箱模式，下面来一睹它的风采：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // YUI - 编写模块 YUI.add(\u0026#39;dom\u0026#39;, function(Y) { Y.DOM = { ... } }) // YUI - 使用模块 YUI().use(\u0026#39;dom\u0026#39;, function(Y) { Y.DOM.doSomeThing(); // use some methods DOM attach to Y }) // hello.js YUI.add(\u0026#39;hello\u0026#39;, function(Y){ Y.sayHello = function(msg){ Y.DOM.set(el, \u0026#39;innerHTML\u0026#39;, \u0026#39;Hello!\u0026#39;); } },\u0026#39;3.0.0\u0026#39;,{ requires:[\u0026#39;dom\u0026#39;] }) // main.js YUI().use(\u0026#39;hello\u0026#39;, function(Y){ Y.sayHello(\u0026#34;hey yui loader\u0026#34;); }) 此外，YUI 团队还提供的一系列用于 JS 压缩、混淆、请求合并（合并资源需要 server 端配合）等性能优化的工具，说其是现有 JS 模块化的鼻祖一点都不过分。\n不过，随着 Node.js 的到来，CommonJS 规范的落地以及各种前端工具、解决方案的出现，很快，YUI3 就被湮没在了历史的长流里面，这样成为了 JS 模块化开发的一个分水岭，引用一段描述：\n从 1999 年开始，模块化探索都是基于语言层面的优化，真正的革命从 2009 年 CommonJS 的引入开始，前端开始大量使用预编译。\nCommonJS 是一套同步的方案，它考虑的是在服务端运行的 Node.js，主要是通过require来加载依赖项，通过exports或者module.exports来暴露接口或者数据的方式，想了解更多，可以看一下《CommonJS 规范》，下面举个简单的例子：\n1 2 var math = require(\u0026#34;math\u0026#34;); esports.result = math.add(2, 3); // 5 由于服务器上通过require加载资源是直接读取文件的，因此中间所需的时间可以忽略不计，但是在浏览器这种需要依赖 HTTP 获取资源的就不行了，资源的获取所需的时间不确定，这就导致必须使用异步机制，代表主要有 2 个：\n基于 AMD 的 RequireJS\n基于 CMD 的 SeaJS\n它们分别在浏览器实现了define、require及module的核心功能，虽然两者的目标是一致的，但是实现的方式或者说是思路，还是有些区别的，AMD 偏向于依赖前置，CMD 偏向于用到时才运行的思路，从而导致了依赖项的加载和运行时间点会不同，关于这 2 者的比较，网上有很多了，这里推荐几篇仅供参考：\n《SeaJS 和 RequireJS 的异同》 《再谈 SeaJS 与 RequireJS 的差异》 本人就先接触了 SeaJS 后转到 RequireJS，虽然感觉 AMD 的模式写确实没有 CMD 这么符合一惯的语义逻辑，但是写了几个模块以后就习惯了，而且社区资源比较丰富的 AMD 阵营更加符合当时的项目需求（扯多了），下面分别写个例子做下直观的对比：\n1 2 3 4 5 6 // CMD define(function (require) { var a = require(\u0026#34;./a\u0026#34;); // \u0026lt;- 运行到此处才开始加载并运行模块a var b = require(\u0026#34;./b\u0026#34;); // \u0026lt;- 运行到此处才开始加载并运行模块b // more code .. }); 1 2 3 4 5 // AMD define([\u0026#34;./a\u0026#34;, \u0026#34;./b\u0026#34;], function (a, b) { // \u0026lt;- 前置声明，也就是在主体运行前就已经加载并运行了模块a和模块b // more code .. }); 通过例子，你可以看到除了语法上面的区别，这 2 者主要的差异还是在于：\n何时加载和运行依赖项？\n这也是 CommonJS 社区中质疑 AMD 最主要原因之一，不少人认为它破坏了规范，反观 CMD 模式，简单的去除define的外包装，这就是标准的 CommonJS 实现，所以说 CMD 是最贴近 CommonJS 的异步模块化方案，不过孰优孰劣，这里就不扯了，需求决定一切。\n此外同一时期还出现了一个 UMD 的方案，其实它就是 AMD 与 CommonJS 的集合体，通过 IIFE 的前置条件判断，使一个模块既可以在浏览器运行，也可以在 Node.JS 中运行，举个例子：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // UMD (function (define) { define(function () { var helloInLang = { en: \u0026#34;Hello world!\u0026#34;, es: \u0026#34;¡Hola mundo!\u0026#34;, ru: \u0026#34;Привет мир!\u0026#34;, }; return { sayHello: function (lang) { return helloInLang[lang]; }, }; }); })( typeof module === \u0026#34;object\u0026#34; \u0026amp;\u0026amp; module.exports \u0026amp;\u0026amp; typeof define !== \u0026#34;function\u0026#34; ? function (factory) { module.exports = factory(); } : define ); 个人觉得最少用到的就是这个 UMD 模式了。\n2015 年 6 月，ECMAScript2015也就是ES6发布了，JavaScript 终于在语言标准的层面上，实现了模块功能，使得在编译时就能确定模块的依赖关系，以及其输入和输出的变量，不像 CommonJS、AMD 之类的需要在运行时才能确定（例如 FIS 这样的工具只能预处理依赖关系，本质上还是运行时解析），成为浏览器和服务器通用的模块解决方案。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // lib/greeting.js const helloInLang = { en: \u0026#39;Hello world!\u0026#39;, es: \u0026#39;¡Hola mundo!\u0026#39;, ru: \u0026#39;Привет мир!\u0026#39; }; export const getHello = (lang) =\u0026gt; ( helloInLang[lang]; ); export const sayHello = (lang) =\u0026gt; { console.log(getHello(lang)); }; // hello.js import { sayHello } from \u0026#39;./lib/greeting\u0026#39;; sayHello(\u0026#39;ru\u0026#39;); 与 CommonJS 用require()方法加载模块不同，在 ES6 中，import命令可以具体指定加载模块中用export命令暴露的接口（不指定具体的接口，默认加载export default），没有指定的是不会加载的，因此会在编译时就完成模块的加载，这种加载方式称为编译时加载或者静态加载。\n而 CommonJS 的require()方法是在运行时才加载的：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // lib/greeting.js const helloInLang = { en: \u0026#34;Hello world!\u0026#34;, es: \u0026#34;¡Hola mundo!\u0026#34;, ru: \u0026#34;Привет мир!\u0026#34;, }; const getHello = function (lang) { return helloInLang[lang]; }; exports.getHello = getHello; exports.sayHello = function (lang) { console.log(getHello(lang)); }; // hello.js const sayHello = require(\u0026#34;./lib/greeting\u0026#34;).sayHello; sayHello(\u0026#34;ru\u0026#34;); 可以看出，CommonJS 中是将整个模块作为一个对象引入，然后再获取这个对象上的某个属性。\n因此 ES6 的编译时加载，在效率上面会提高不少，此外，还会带来一些其它的好处，比如引入宏（macro）和类型检验（type system）这些只能靠静态分析实现的功能。\n可惜的是，目前浏览器和 Node.js 的支持程度都并不理想，截止发稿，也就只有 Chrome61+ 与 Safari10.1+ 才做到了部分支持。\n不过可以通过 Babel 这类工具配合相关的 plugin（可以参考《Babel 笔记》），转换为 ES5 的语法，这样就可以在 Node.js 运行起来了，如果想在浏览器上运行，可以添加 Babel 配置，为模块文件添上 AMD 的define函数作为外层，再并配合 RequireJS 之类的加载器即可。\n更多关于 ES6 Modules 的资料，可以看一下《ECMAScript 6 入门 - Module 的语法》。\n参考 精读 js 模块化发展 History of JavaScript JavaScript 模块化七日谈 JavaScript Module Pattern: In-Depth 前端模块化开发那点历史 JavaScript Modules: A Beginner’s Guide ","date":"2017-09-07T08:08:08+08:00","permalink":"https://itony.net/javascript-module-history/","section":"post","tags":["FrontEnd","Javascript"],"title":"JavaScript模块化开发的演进历程"},{"categories":["Web Technologies"],"contents":"正如封面图所述，Babel作为一个 JavaScript 的语法编译器，可以将ES6/7/8代码转为ES5代码，从而在现有环境执行。\n但是初次配置.babelrc的时候，各种presets、plugins看的眼花缭乱，不知道如何下手，下面就自己学习 Babel 时遇到的问题做一下总结：\n如果你是初次接触 babel，推荐阅读阮一峰的《Babel 入门教程》\nPlugin、Preset、Stage-X 的关系 按照 Babel 官网的介绍，其实Preset和Stage-X都是归属到Plugin里面的，只不过所覆盖的范围不同而已。\n举个例子，如果需要转换 ES2015(ES6)的语法，那么你可以在.babelrc的plugins中按需引入check-es2015-constants、es2015-arrow-functions、es2015-block-scoped-functions等等几十个不同作用的 plugin：\n1 2 3 4 5 6 7 8 9 // .babelrc { \u0026#34;plugins\u0026#34;: [ \u0026#34;check-es2015-constants\u0026#34;, \u0026#34;es2015-arrow-functions\u0026#34;, \u0026#34;es2015-block-scoped-functions\u0026#34;, // ... ] } 但是 Babel 团队为了方便，将同属 ES2015 的几十个 Transform Plugins集合到babel-preset-es2015一个 Preset 中，这样你只需要在.babelrc的presets加入es2015一个配置就可以完成全部 ES2015 语法的支持了：\n1 2 3 4 5 6 // .babelrc { \u0026#34;presets\u0026#34;: [ \u0026#34;es2015\u0026#34; ] } 另外，不论是 Plugin 还是 Preset，有不少都有单独属于自己的配置项，具体如何操作的可以看一下官网的说明。\n上面介绍了 Plugin 与 Preset，那么 Stage-X 就很好理解了，stage-0、stage-1、stage-2、stage-3、~~stage-4~~分别对应的就是进入标准之前的 5 个阶段，不同stage-x之间存在依赖关系，数字越小，阶段越靠后，靠后阶段包含前面阶段所有的功能，简单理解就是stage-0包含stage-1/2/3的内容，所以如果你不知道需要哪个stage-x的话，直接引入stage-0就好了。\nPS: babel-preset-stage-4已经整合入 Presets 不单独发布了。\n以上就是一些基础概念，目前，官方推荐使用babel-preset-env，它可以根据你的配置结合compat-table来帮你自动引入你需要的 plugins，它有很多配置项，下面介绍几个常用的：\ntargets： { [string]: number | string }，默认 {}；\n需要支持的环境，可选例如：chrome, edge, firefox, safari, ie, ios, node，甚至可以指定版本，如node: \u0026quot;6.10\u0026quot;或者node: \u0026quot;current\u0026quot;代表使用当前的版本；\ntargets.node： number | string | \u0026quot;current\u0026quot; | true；\n指定node的版本，例如：6.10；\ntargets.browsers： Array\u0026lt;string\u0026gt; | string；\n指定需要兼容的浏览器清单，具体参考browserslist，例如：[\u0026quot;last 2 versions\u0026quot;, \u0026quot;safari \u0026gt;= 7\u0026quot;]；\n例如需要配置兼容[\u0026quot;last 2 versions\u0026quot;, \u0026quot;safari \u0026gt;= 7\u0026quot;]的babel-preset-env：\n1 2 3 4 5 6 7 8 9 10 // .babelrc { \u0026#34;presets\u0026#34;: [ [\u0026#34;env\u0026#34;, { \u0026#34;targets\u0026#34;: { \u0026#34;browsers\u0026#34;: [\u0026#34;last 2 versions\u0026#34;, \u0026#34;safari \u0026gt;= 7\u0026#34;] } }] ] } 此外，不同的 plugins 和 presets 或许有些功能是重复的，有些存在依赖关系，在配置的时候还有前后顺序的不同，那么 Babel 在运行的时候是怎么处理的呢？总结一下，规律大概有以下几点：\nplugins 优先于 presets 进行编译； plugins 按照数组的 index 增序(从数组第一个到最后一个)进行编译； presets 按照数组的 index 倒序(从数组最后一个到第一个)进行编译，因为作者认为大部分会把 presets 写成[\u0026quot;es2015\u0026quot;, \u0026quot;stage-0\u0026quot;]，具体细节可以看这个。 摘自《如何写好.babelrc？Babel 的 presets 和 plugins 配置解析》\nbabel-polyfill与babel-runtime的选择 Babel 默认只转换新的 JavaScript 语法，而不转换新的 API，比如Iterator、Generator、Set、Maps、Promise等等全局对象，以及一些定义在全局对象上的方法（比如Object.assign）都不会转码，具体的可以参考babel-plugin-transform-runtime模块的definitions.js文件。\nbabel-polyfill与babel-runtime就是为了解决这种全局对象或者全局对象方法不足的问题，而诞生的 2 种解决方式。\n当然，你还可以用promise-polyfill此类 Polyfill 解决全局对象的问题；\n或者用lodash此类 Utils 解决Object.assign这种方法扩展的问题。\n先说说babel-polyfill，它的做法比较暴力，就是将全局对象通通污染一遍，这样做的坏处有几点：\n可能会增加很多根本没有用到的 polyfill； 可能会污染子模块的局部作用域，严重的或许会导致冲突； 但是，这样做也有好处，如果你的运行环境比较 low，比如说 Android 一些老机子，而你有需要大量使用Promise、Object.assign、Array.find之类的全局对象或者其所属方法，那么使用babel-polyfill，绝对是一劳永逸。\n接着，再来说说babel-runtime，相对而言，它的处理方式比较温柔，套用步步高的广告词就是哪里需要加哪里，比如说你需要Promise，你只需要import Promise from 'babel-runtime/core-js/promise'即可，这样不仅避免污染全局对象，而且可以减少不必要的代码。\n不过，如果 N 个文件都需要Promise，难道得一个个文件的加import Promise from 'babel-runtime/core-js/promise'么，显然不是，Babel 已经为这样情况考虑过了，只需要使用babel-plugin-transform-runtime就可以轻松的帮你省去手动import的痛苦，而且，它还做了公用方法的抽离，哪怕你有 100 个模块使用了Promise，但是 promise 的 polyfill 仅仅存在 1 份，所有要的地方都是引用一地方，具体的配置参考如下：\n1 2 3 4 5 6 7 8 9 10 11 // .babelrc { \u0026#34;presets\u0026#34;: [ \u0026#34;env\u0026#34;, \u0026#34;stage-0\u0026#34; ], \u0026#34;plugins\u0026#34;: [ \u0026#34;transform-runtime\u0026#34; ], \u0026#34;comments\u0026#34;: false } 此外，需要注意的是，如果你直接写[1,2,3].find((i) =\u0026gt; (i === 1))这样的语法，babel-runtime处理以后的结果并不会引入Array.find相关的 polyfill：\n1 2 3 4 5 6 7 // 源码 [1, 2, 3].find((i) =\u0026gt; i === 1); // 转码后 [1, 2, 3].find(function (i) { return i === 1; }); 你需要使用Array.find才可以：\n1 2 3 4 5 6 7 8 9 10 11 12 13 // 源码 Array.find([1, 2, 3], (i) =\u0026gt; i === 1); // 转码后 var _find = require(\u0026#34;babel-runtime/core-js/array/find\u0026#34;); var _find2 = _interopRequireDefault(_find); function _interopRequireDefault(obj) { return obj \u0026amp;\u0026amp; obj.__esModule ? obj : { default: obj }; } (0, _find2.default)([1, 2, 3], function (i) { return i === 1; }); 当然，你也可以在需要的地方引入core-js/fn/array/find或者lodash/find作为一个 function 来使用：\n1 2 3 import ArrayFind from \u0026#34;core-js/fn/array/find\u0026#34;; ArrayFind([1, 2, 3], (i) =\u0026gt; i === 1); 不过，如果你执意要使用[1,2,3].find((i) =\u0026gt; (i === 1))这样的语法，那么只能对Array.prototype进行扩展，增加一个find的方法了，babel-polyfill就可以实现，当然你也可以自己做扩展。\n写在最后，我在Github 上开了一个项目，做了几个测试，有兴趣的可以一起来试试看。\n2017.7.30 补充\n关于babel与webpack结合使用的教程网上已经有很多了，有不少却还在用v1.*的版本（不推荐），从而在过渡到v2.*或者v3.*(推荐)的版本时，碰到一个关于babel的配置问题，示例如下：\n1 2 3 4 // .babelrc - webpack v1.* { \u0026#34;presets\u0026#34;: [\u0026#34;env\u0026#34;, \u0026#34;stage-0\u0026#34;] } 1 2 3 4 5 6 7 8 9 10 11 12 // .babelrc - webpack v2.* - v3.* { \u0026#34;presets\u0026#34;: [ [ \u0026#34;env\u0026#34;, { \u0026#34;modules\u0026#34;: false } ], \u0026#34;stage-0\u0026#34; ] } 很明显，一眼就能看出相对于v1.*的版本，v2.*或者v3.*版本多了\u0026quot;modules\u0026quot;: false这项配置，如果仔细看官网的迁移指南，你就能明白了，以前你可能需要用babel来将ES6的模块语法转换为AMD、CommonJS、UMD之类的模块化标准语法，但是现在 webpack 已经把这个事情做了，所以就不需要babel来做了，但是babel配置项中的modules默认值是commonjs，所以你需要将modules设置为false才行，不然就冲突了。\n参考资料 http://www.ruanyifeng.com/blog/2016/01/babel.html https://excaliburhan.com/post/babel-preset-and-plugins.html https://segmentfault.com/q/1010000005596587?from=singlemessage\u0026amp;isappinstalled=1 https://github.com/brunoyang/blog/issues/20 https://github.com/lmk123/blog/issues/45 http://www.cnblogs.com/flyingzl/p/5501247.html ","date":"2017-07-31T08:08:08+08:00","permalink":"https://itony.net/babel-note/","section":"post","tags":["Note","FrontEnd"],"title":"Babel笔记"},{"categories":["Hobbies"],"contents":" Life is short, you need Python \u0026ndash; Bruce Eckel\n日常开发中难免会与 Python 打交道，本文记录一些平时遇到的问题及解决方案：\n升级pip所有安装包 这个也算是stackoverflow上一个经典问题了，下面就贴一下具体代码：\n1 pip freeze --local | grep -v \u0026#39;^\\-e\u0026#39; | cut -d = -f 1 | xargs -n1 pip install -U 另外，Github 上 \u001c 有个issue专门做了需不需要为pip添加upgrade和upgrade-all的讨论，有兴趣的可以去参与一下。\n升级pyxattr遇到无法找到attr/xattr.h的问题 具体报错信息如下：\n1 2 3 4 5 6 7 8 # centos7 / Python 2.7.5 / pip 9.0.1 gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -D_XATTR_VERSION=\u0026#34;0.6.0\u0026#34; -D_XATTR_AUTHOR=\u0026#34;Iustin Pop\u0026#34; -D_XATTR_EMAIL=\u0026#34;iustin@k1024.org\u0026#34; -I/usr/include/python2.7 -c xattr.c -o build/temp.linux-x86_64-2.7/xattr.o -Wall -Werror -Wsign-compare xattr.c:29:24: fatal error: attr/xattr.h: No such file or directory #include \u0026lt;attr/xattr.h\u0026gt; ^ compilation terminated. error: command \u0026#39;gcc\u0026#39; failed with exit status 1 如果你也碰到了上面的问题，解决方案很简单，安装一下对应的系统包就行了：\n1 2 3 4 5 # Install RPM: sudo yum install -y libattr-devel # Install Deb: sudo apt-get install -y libattr1-dev 升级pygpgme遇到无法找到gpgme.h的问题 具体报错信息如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # centos6 / Python 2.6.6 / pip 9.0.1 Running setup.py install for pygpgme ... error Complete output from command /usr/bin/python -u -c \u0026#34;import setuptools, tokenize;__file__=\u0026#39;/tmp/pip-build-iYzzHD/pygpgme/setup.py\u0026#39;;f=getattr(tokenize, \u0026#39;open\u0026#39;, open)(__file__);code=f.read().replace(\u0026#39;\\r\\n\u0026#39;, \u0026#39;\\n\u0026#39;);f.close();exec(compile(code, __file__, \u0026#39;exec\u0026#39;))\u0026#34; install --record /tmp/pip-6Ep2BO-record/install-record.txt --single-version-externally-managed --compile: running install running build running build_py creating build creating build/lib.linux-x86_64-2.6 creating build/lib.linux-x86_64-2.6/gpgme copying gpgme/editutil.py -\u0026gt; build/lib.linux-x86_64-2.6/gpgme copying gpgme/__init__.py -\u0026gt; build/lib.linux-x86_64-2.6/gpgme running build_ext building \u0026#39;gpgme._gpgme\u0026#39; extension creating build/temp.linux-x86_64-2.6 creating build/temp.linux-x86_64-2.6/src gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/python2.6 -c src/gpgme.c -o build/temp.linux-x86_64-2.6/src/gpgme.o In file included from src/gpgme.c:22: src/pygpgme.h:24:19: error: gpgme.h: No such file or directory In file included from src/gpgme.c:22: src/pygpgme.h:32: error: expected specifier-qualifier-list before ‘gpgme_ctx_t’ src/pygpgme.h:37: error: expected specifier-qualifier-list before ‘gpgme_key_t’ src/pygpgme.h:42: error: expected specifier-qualifier-list before ‘gpgme_subkey_t’ src/pygpgme.h:48: error: expected specifier-qualifier-list before ‘gpgme_user_id_t’ src/pygpgme.h:54: error: expected specifier-qualifier-list before ‘gpgme_key_sig_t’ src/pygpgme.h:124: error: expected ‘)’ before ‘err’ src/pygpgme.h:125: error: expected ‘)’ before ‘err’ src/pygpgme.h:126: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘pygpgme_check_pyerror’ src/pygpgme.h:130: error: expected ‘)’ before ‘*’ token src/pygpgme.h:131: error: expected ‘)’ before ‘key’ src/pygpgme.h:132: error: expected ‘)’ before ‘siglist’ src/pygpgme.h:133: error: expected ‘)’ before ‘siglist’ src/pygpgme.h:134: error: expected ‘)’ before ‘ctx’ src/pygpgme.h:135: error: expected ‘)’ before ‘ctx’ src/gpgme.c: In function ‘create_module’: src/gpgme.c:92: warning: implicit declaration of function ‘gpgme_check_version’ src/gpgme.c:92: warning: assignment makes pointer from integer without a cast error: command \u0026#39;gcc\u0026#39; failed with exit status 1 简单推测这个应该和系统的gpgme有关，Google 了一下，发现有人已经提出过这个问题Can\u0026rsquo;t upgrade to 0.3 on Centos 6.4，同时，William Grant 也给出了分析，需要 centos 系统安装gpgme-devel即可。\n1 2 # Install RPM: sudo yum install -y gpgme-devel 升级pycurl遇到无法找到gpgme.h的问题 具体报错信息如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # centos6 / Python 2.6.6 / pip 9.0.1 Complete output from command python setup.py egg_info: Traceback (most recent call last): File \u0026#34;\u0026lt;string\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; File \u0026#34;/tmp/pip-build-vOVaGA/pycurl/setup.py\u0026#34;, line 823, in \u0026lt;module\u0026gt; ext = get_extension(sys.argv, split_extension_source=split_extension_source) File \u0026#34;/tmp/pip-build-vOVaGA/pycurl/setup.py\u0026#34;, line 497, in get_extension ext_config = ExtensionConfiguration(argv) File \u0026#34;/tmp/pip-build-vOVaGA/pycurl/setup.py\u0026#34;, line 71, in __init__ self.configure() File \u0026#34;/tmp/pip-build-vOVaGA/pycurl/setup.py\u0026#34;, line 107, in configure_unix raise ConfigurationError(msg) __main__.ConfigurationError: Could not run curl-config: [Errno 2] No such file or directory curl-config无法找到，十有八九跟系统的curl有关，查了一下，确实又是缺少了libcurl-devel，可以看一下这个gist：\n1 2 # Install RPM: sudo yum install -y libcurl-devel 升级pip至v9.*版本以后，遇到权限问题 1 OSError: [Errno 13] Permission denied: \u0026#39;/usr/lib/python2.7/dist-packages/attr/_compat.py\u0026#39; 解决方案有 2 种，第一种比较直接，用sudo权限，第二种就是启用pip的--user配置项，例如，本文前面所说的升级全部的 pip package 时，命令修改如下：\n1 pip freeze --local | grep -v \u0026#39;^\\-e\u0026#39; | cut -d = -f 1 | xargs -n1 pip install --user yum无法正常使用，提示pycurl有问题 1 2 3 4 5 6 7 There was a problem importing one of the Python modules required to run yum. The error leading to this problem was: pycurl: libcurl link-time ssl backend (nss) is different from compile-time ssl backend (openssl) Please install a package which provides this module, or verify that the module is installed correctly. 看提示，应该是pycurl所依赖的libcurl用的是nss的 SSL 方案，但是pycurl使用的却是openssl的 SSL 方案，所以导致了冲突，果然，在stackoverflow上看到了相关的东西，解决方案也比较简单，如下操作即可：\n1 2 3 pip uninstall pycurl export PYCURL_SSL_LIBRARY=nss pip install pycurl --no-cache-dir 排查了一下原因，发现可能是之前想玩 shadowsocks 的时候，安装了python-devel一系列之后又做了一次全局的依赖升级，导致了这个问题，有空了再去深究\n","date":"2017-07-27T08:08:08+08:00","permalink":"https://itony.net/python-note/","section":"post","tags":["Note","Python"],"title":"Python的笔记"},{"categories":["Hobbies"],"contents":" 封面图来自于qr-code-generator\n或许在 10 年多你都没有听说过二维码，不过现在，在这个信息化的社会中，仿佛它已经无处不在了，下面就简单的介绍一下：\n什么是二维码？ QR 图码（全称为快速响应矩阵图码；英语：Quick Response Code）是二维条码的一种，于 1994 年由日本 DENSO WAVE 公司发明。\n一般呈正方形，常见的是黑白两色。在 3 个角落，印有较小，像“回”字的正方图案。这 3 个是帮助解码软件定位的图案，用户不需要对准，无论以任何角度扫描，数据仍然可以正确被读取。\n存储容量 QR 码设有 1 到 40 的不同版本（种类)，每个版本都具备固有的码元结构(码元数)。（码元是指构成 QR 码的方形黑白点。)\n“码元结构”是指二维码中的码元数。从版本 1(21 码元 ×21 码元)开始，在纵向和横向各自以 4 码元为单位递增，一直到版本 40(177 码元 ×177 码元)，目前版本 40 可以存储中文汉字最多 984 字符（采用 UTF-8）。\n容错能力 QR 码有容错能力，QR 码图形如果有破损，仍然可以被机器读取内容，最高可以到 7%~30%面积破损仍可被读取，理所当然，容错率愈高，QR 码图形面积愈大，所以一般折衷使用 15%容错能力。\nL 档次，约 7%的字码可被修正 M 档次，约 15%的字码可被修正 Q 档次，约 25%的字码可被修正 H 档次，约 30%的字码可被修正 结合存储容量，两者有对应的关系大致如下： 如何生成二维码？ 关于具体的编码原理，本文就不细说了，如果想了解的，可以去看一下这篇文章《二维码的生成细节和原理》，写的非常详细，况且如今，已经有很多的在线服务就能方便的帮你生成二维码，例如:\nqr-code-generator 草料 也有很多可以运行在本地的服务，比如说:\n基于 Python 的QR-Code 基于 Nodejs 的node-qrcode 基于 C 的libqrencode 下面就简单说一下libqrencode：\n如果你用 MAC，那么安装就很简单了直接运行brew install qrencode即可，完了以后，就可以调用qrencode的命令模式了，具体的文档，你可以通过man qrencode查阅，下面就简单句两个例子：\n1 2 3 4 5 6 7 8 9 10 11 # 生成指定字符串的二维码 qrencode -o ./qrcode.png \u0026#39;https://itony.net/qrcode-note\u0026#39; # 生成无边框指定字符串的二维码 qrencode -o ./qrcode.png \u0026#39;https://itony.net/qrcode-note\u0026#39; -m 0 # 生成透明背景指定字符串的二维码 qrencode -o ./qrcode.png \u0026#39;https://itony.net/qrcode-note\u0026#39; --background ffffff00 # 自定义图片尺寸的 qrencode -o ./qrcode.png \u0026#39;https://itony.net/qrcode-note\u0026#39; -s 6 以上就是最简单的例子了，仅供参考。\n参考链接 https://zh.wikipedia.org/wiki/QR%E7%A2%BC http://www.qrcode.com/zh/ ","date":"2017-07-26T08:08:08+08:00","permalink":"https://itony.net/qrcode-note/","section":"post","tags":["QR Code"],"title":"QR Code的随笔"},{"categories":["Web Technologies"],"contents":" 本文记录一些自己在使用 Codeigniter 开发时遇到的坑及解决方法，仅供参考。\n1、 升级到V3之后，如果有多个域名同时指向到$config['base_url']的解决方案 很多时候，当使用 Nginx 做 VHOST 的时候，我们可能会定义server xxx.com www.xxx.com，本意就是不论打开xxx.com还是www.xxx.com都可以正常的访问站点，但是 Codeigniter 从V2升级到V3之后，官方公布了升级指南明确指出：\nMake sure your ‘base_url’ config value is not empty\n如果你没有配置$config['base_url']，那么根据system/core/Config.php的源码，可以知道，这个时候base_url会被设置为$_SERVER['SERVER_ADDR']，也就是IP，如果你一个 IP(IPV6)就一个站点，那或许这么处理没啥问题，但是，如果用 VHOST 处理就麻烦了，好在，官方文档比较尽责，提供了一个不错的解决方案：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $allowed_domains = array(\u0026#39;xxx.com\u0026#39;, \u0026#39;www.xxx.com\u0026#39;); $default_domain = \u0026#39;xxx.com\u0026#39;; if (in_array($_SERVER[\u0026#39;HTTP_HOST\u0026#39;], $allowed_domains, TRUE)) { $domain = $_SERVER[\u0026#39;HTTP_HOST\u0026#39;]; } else { $domain = $default_domain; } if ( ! empty($_SERVER[\u0026#39;HTTPS\u0026#39;])) { $config[\u0026#39;base_url\u0026#39;] = \u0026#39;https://\u0026#39;.$domain; } else { $config[\u0026#39;base_url\u0026#39;] = \u0026#39;http://\u0026#39;.$domain; } 这样就可以根据$allowed_domains来动态过滤相应的域名了\n2、 为 Codeigniter 增加 smarty 模板引擎 很多 Codeigniter 的使用者都是极简主义者，因为 Codeigniter 够简单，所以对于view的处理，就连官网的文档《模板解析类》也有这样的描述：\nCodeIgniter 并没有 让你必须使用这个类，因为直接在视图中使用纯 PHP 可能速度会更快点。 尽管如此，一些开发者还是喜欢使用模板引擎，他们可能是和一些其他的不熟悉 PHP 的设计师共同工作。\n就如上述所说，往往团队中会有一些人喜欢用模板引擎，比如说smarty，很可惜官网的文档中只有一个模板类的描述，也不甚详细，Google 了一圈，找到一篇 coolphptools 上的文章《CodeIgniter and Smarty》，写的比较详细(中文版请访问参考链接[4])。\n好了，接下来就动手吧，因为我本地安装了composer，所以我就直接运行composer require smarty/smarty安装smarty到项目根目录的vendor/文件夹中，你也可手动下载然后复制到该文件夹中。\n接着，在application/libraries中创建Smartie.php，写入以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 \u0026lt;?php if (!defined(\u0026#39;BASEPATH\u0026#39;)) { exit(\u0026#39;No direct script access allowed\u0026#39;); } /** * Smartie Class * * @package CodeIgniter * @subpackage Libraries * @category Smarty * @author Kepler Gelotte * @link http://www.coolphptools.com/codeigniter-smarty */ require_once BASEPATH . \u0026#39;../vendor/smarty/smarty/libs/Smarty.class.php\u0026#39;; class Smartie extends Smarty { public $debug = false; public function __construct() { parent::__construct(); $this-\u0026gt;template_dir = APPPATH . \u0026#34;views\u0026#34;; $this-\u0026gt;compile_dir = APPPATH . \u0026#34;_views\u0026#34;; if (!is_writable($this-\u0026gt;compile_dir)) { // make sure the compile directory can be written to @chmod($this-\u0026gt;compile_dir, 0777); } // Uncomment these 2 lines to change Smarty\u0026#39;s delimiters // $this-\u0026gt;left_delimiter = \u0026#39;{{\u0026#39;; // $this-\u0026gt;right_delimiter = \u0026#39;}}\u0026#39;; $this-\u0026gt;assign(\u0026#39;FCPATH\u0026#39;, FCPATH); // path to website $this-\u0026gt;assign(\u0026#39;APPPATH\u0026#39;, APPPATH); // path to application directory $this-\u0026gt;assign(\u0026#39;BASEPATH\u0026#39;, BASEPATH); // path to system directory log_message(\u0026#39;debug\u0026#39;, \u0026#34;Smarty Class Initialized\u0026#34;); } public function setDebug($debug = true) { $this-\u0026gt;debug = $debug; } /** * Parse a template using the Smarty engine * * This is a convenience method that combines assign() and * display() into one step. * * Values to assign are passed in an associative array of * name =\u0026gt; value pairs. * * If the output is to be returned as a string to the caller * instead of being output, pass true as the third parameter. * * @access public * @param string * @param array * @param bool * @return string */ public function view($template, $data = array(), $return = false) { if (!$this-\u0026gt;debug) { $this-\u0026gt;error_reporting = false; } $this-\u0026gt;error_unassigned = false; foreach ($data as $key =\u0026gt; $val) { $this-\u0026gt;assign($key, $val); } if ($return == false) { $CI = \u0026amp;get_instance(); if (method_exists($CI-\u0026gt;output, \u0026#39;set_output\u0026#39;)) { $CI-\u0026gt;output-\u0026gt;set_output($this-\u0026gt;fetch($template)); } else { $CI-\u0026gt;output-\u0026gt;final_output = $this-\u0026gt;fetch($template); } return; } else { return $this-\u0026gt;fetch($template); } } } // END Smartie Class 引用Smarty.class.php的时候，记得检查一下引用的路径，或者你也可以将application/config/config.php中的$config['composer_autoload']设置为BASEPATH . '../vendor/autoload.php'，这样就可以自动加载 composer 的资源了，而不需要在这里手动使用require_once引用 Smarty。\n另外template_dir与compile_dir也可以自己去设置，这里仅作参考。\n然后，设置application/config/autoload.php，在$autoload['libraries']中添加'smartie' =\u0026gt; 'smarty'，这样就可以在controllers/*.php中使用$this-\u0026gt;smarty-\u0026gt;view()的方法了，对应的 view 文件中已经可以使用smarty的语法了。\n如果，你下载了 coolphptools 中的codeigniter-smarty_3.zip，你会发现测试example的时候会报错，这是因为 Kepler Gelotte 对 Smarty 做了结合 Codeigniter 部分扩展，你可以将 codeigniter-smarty*3.zip 中application/third_party/smarty/libs/plugins文件夹下的几个function.ci*\\*\\*.php文件全部拷贝至你的vendor/smarty/smarty/libs/plugins目录下，并且需要在function.ci_form_validation.php手动加载form_validation：\n1 2 3 4 5 6 // get a Code Igniter instance $CI = \u0026amp;get_instance(); // 在此加载form_validation类库 // 或者，在autoload中全局加载 $CI-\u0026gt;load-\u0026gt;library(\u0026#39;form_validation\u0026#39;); $_validation = $CI-\u0026gt;form_validation; 结果示意图： 参考链接 https://www.codeigniter.com/user_guide/installation/upgrade_300.html#step-19-make-sure-your-base-url-config-value-is-not-empty https://github.com/bcit-ci/CodeIgniter/issues/4576 http://www.coolphptools.com/codeigniter-smarty https://jasonhzy.github.io/2016/02/27/ci-smarty/ ","date":"2017-07-20T08:08:08+08:00","permalink":"https://itony.net/codeigniter-note/","section":"post","tags":["Note","PHP","BackEnd"],"title":"Codeigniter折腾笔记"},{"categories":["Hobbies"],"contents":"关于如何为 Ghost 做备份，Google 一下，你会找到有很多种方式，不过由于本博客使用了 Mysql 作为 Ghost 的数据库，平时自己又习惯于使用 Git 做版本管理，所以就衍生了一个用 Git 来备份博客数据的想法。\n大致的思路就是，先用mysqldump备份 Ghost 的 database，将其压缩存储至 Ghost 根目录的content/data文件夹中，通过git commit将其添加入 Git 仓库，然后push至[Bitbucket]。\nPS: 市面上有很多可以提供私有仓库的 Git 服务，孰优孰劣各有不同，这里就选用了自己常用的Bitbucket。\n那么首先就来建立一个名为cron_backup.sh的文件，具体代码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #!/bin/sh # Set variables DB_NAME=\u0026#34;xxx\u0026#34; MYSQL_USER=\u0026#34;xxx\u0026#34; MYSQL_PASSWORD=\u0026#34;xxx\u0026#34; BACKUP_TIME=$(date +\u0026#34;%Y%m%d%H%M\u0026#34;) BACKUP_DIR=\u0026#34;path/to/ghost/content/data\u0026#34; FULLDATE=$(date +\u0026#34;%Y-%d-%m %H:%M\u0026#34;) # Check current Git status and update git status git pull origin HEAD # 判断MYSQL是否启动,mysql没有启动则备份退出 MYSQL_PS=`ps -ef |grep mysql |wc -l` MYSQL_LISTEN=`netstat -an |grep LISTEN |grep 3306|wc -l` if [ [$MYSQL_PS == 0] -o [$MYSQL_LISTEN == 0] ]; then echo \u0026#34;ERROR:MySQL is not running! backup stop!\u0026#34; exit else echo \u0026#34;Welcome to use MySQL backup tools!\u0026#34; fi # 连接到mysql数据库，无法连接则备份退出 mysql -u$MYSQL_USER -p$MYSQL_PASSWORD \u0026lt;\u0026lt;end SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \u0026#34;$DB_NAME\u0026#34;; exit end FLAG=`echo $?` if [ $FLAG != \u0026#34;0\u0026#34; ]; then echo \u0026#34;ERROR:Can\u0026#39;t connect mysql server! backup stop!\u0026#34; exit else echo \u0026#34;MySQL connect ok! Please wait......\u0026#34; echo \u0026#34;The database $DB_NAME backup start...\u0026#34; # 判断有没有定义备份的数据库，如果定义则开始备份，否则退出备份 `mysqldump -hlocalhost -P3306 -u$MYSQL_USER -p$MYSQL_PASSWORD $DB_NAME --default-character-set=\u0026#34;utf8\u0026#34; | gzip \u0026gt; $BACKUP_DIR/$BACKUP_TIME.sql.gz` FLAG=`echo $?` if [ $FLAG == \u0026#34;0\u0026#34; ];then echo \u0026#34;database $DB_NAME success backup to $BACKUP_DIR/$BACKUP_TIME.sql.gz\u0026#34; else echo \u0026#34;database $DB_NAME backup fail!\u0026#34; fi fi # 删除过期备份，则进行删除操作，暂定为7天以前的备份数据 find $BACKUP_DIR -mtime +7 -exec rm -f {} \\; # Add to Git and commit git add -A git commit -m \u0026#34;Automatic backup - $FULLDATE\u0026#34; git push origin HEAD 以上脚本中的DB_NAME、MYSQL_USER、MYSQL_PASSWORD及path/to/ghost/content/data请自行替换。\n接着你先为 Ghost 的根目录下初始化 Git，并连接 Bitbucket 远程仓库，如有疑问，可以 Google，这点在这里就不描述了。\n现在，来测试一下这个脚本，如果遇到权限问题，请赋予当前用户chmod +x的权限，如果没有问题，你的备份脚本就已经 OK 了。\n不过，如果仅仅是这样感觉还是麻烦了一些，做好能做个定时脚本，这时候我们就用到了crontab，为当前的用户添加一个定时任务吧，让系统自动每天凌晨帮你备份一次：\n1 2 # crontab command 00 00 * * * cd path/to/ghost \u0026amp;\u0026amp; ./cron_backup.sh \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 这里有一点小提示，为何不是直接运行path/to/ghost/cron_backup.sh，因为有可能你执行crontab并非 Ghost 目录的拥有者，会导致 Git 操作失败。\n以上想法及示例代码仅供参考。\n参考链接 https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/12.4.md https://jabran.me/articles/automatic-database-backup-using-git-hosting https://gist.github.com/oodavid/2206527 https://gist.github.com/pakcheong/6570839 https://zh.wikipedia.org/wiki/Cron ","date":"2017-07-17T18:08:08+08:00","permalink":"https://itony.net/ghost-backup-to-bitbucket/","section":"post","tags":["Linux","VPS","Ghost"],"title":"备份Ghost至Bitbucket"},{"categories":["Hobbies"],"contents":"1、系统警告Could not open a connection to your authentication agent. 一般这个问题会出现在你使用ssh-add命令的时候，究其原因其实很简单就是你还没有启动ssh-agent，网上有很多解决方式，最便捷的就是直接开启ssh-agent：\n1 2 eval `ssh-agent -s` ssh-add 不过，如果每次都这样开启ssh-agent也太麻烦了一些，Joseph M. Reagle 就提供了一段shell脚本，方便自启动ssh-agent：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SSH_ENV=\u0026#34;$HOME/.ssh/environment\u0026#34; function start_agent { echo \u0026#34;Initialising new SSH agent...\u0026#34; /usr/bin/ssh-agent | sed \u0026#39;s/^echo/#echo/\u0026#39; \u0026gt; \u0026#34;${SSH_ENV}\u0026#34; echo succeeded chmod 600 \u0026#34;${SSH_ENV}\u0026#34; . \u0026#34;${SSH_ENV}\u0026#34; \u0026gt; /dev/null /usr/bin/ssh-add; } # Source SSH settings, if applicable if [ -f \u0026#34;${SSH_ENV}\u0026#34; ]; then . \u0026#34;${SSH_ENV}\u0026#34; \u0026gt; /dev/null #ps ${SSH_AGENT_PID} doesn\u0026#39;t work under cywgin ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ \u0026gt; /dev/null || { start_agent; } else start_agent; fi 如果你是使用bash那么直接将这段代码放入你的.bash_profile即可，然后source .bash_profile或者重新登了一下，你可以看到以下提示：\n1 2 Initialising new SSH agent... succeeded 接下来你就可以使用ssh-add进行操作了。\n参考链接 could-not-open-a-connection-to-your-authentication-agent start-ssh-agent-on-login http://mah.everybody.org/docs/ssh http://www.cygwin.com/ml/cygwin/2001-06/msg00537.html ","date":"2017-07-17T08:08:08+08:00","permalink":"https://itony.net/ssh-note/","section":"post","tags":["Note","Linux","SSH"],"title":"SSH一些常见问题"},{"categories":["Hobbies"],"contents":"本文属于一个入门级的教程，简单记录如何基于阿里云 ECS的 CentOS7 系统，用Ghost搭建一个博客系统。\n首先，来了解一下，整个环节下来，需要用到哪些东西：\n一个完成 A 记录解析设置的域名（建议做好 ICP 备案），例如本站的域名itony.net； 使用 MySQL 做数据库服务； 使用 Nginx 做网页服务器（Ghost 的代理服务）； 使用 NodeJS 做后端服务； Ghost一个开源的博客系统； 一切准备就绪，开始使用root用户的初始化的密码远程登录入系统：\n1 2 # xxx.xxx.xxx.xxx 为你的公网IP，可以在ECS控制台中找到 ssh root@xxx.xxx.xxx.xxx 查看一下系统信息：\n1 2 cat /etc/redhat-release # \u0026gt;\u0026gt;\u0026gt; CentOS Linux release 7.3.1611 (Core) 更新一下系统，安装一些基础工具：\n1 2 3 4 5 6 7 8 9 # 阿里云的centos已经优化了yum的源，你也可以自行修改 yum update -y \u0026amp;\u0026amp; yum upgrage -y \u0026amp;\u0026amp; yum clean all # curl, wget都是方便后边的下载 # vim作为一个老牌Linux文本编辑工具，你值得拥有 yum install curl wget vim -y # 更新全系统的vim配置 curl https://raw.githubusercontent.com/tonyc726/vim-for-server/master/vimrc \u0026gt; /etc/vimrc 接下来，就直接动手来安装 Ghost 需要用到的系统环境吧，由于 CentOS7 已经不带 Mysql 数据库了（默认的数据库是 MariaDB，Mysql 的一个分支），所以需要先安裝 MySQL Repository（具体的版本号，可以参考 https://dev.mysql.com/downloads/repo/yum/）:\n1 2 3 4 5 6 7 8 9 10 11 # 安裝 MySQL Repository rpm -Uvh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm # 安装MySQL数据库的服务器社区稳定版 yum install mysql-community-server -y # 启动MySQL服务 systemctl start mysqld.service # 设置随系统自启动 systemctl enable mysqld.service 安装完成以后，运行mysql_secure_installation，初始化 MySQL 的一些配置，不过由于 MySQL5.7 默认会初始化 Root 用户的密码，所以在执行之前需要获取这个初始密码：\n1 grep \u0026#34;password\u0026#34; /var/log/mysqld.log 拿到密码以后，就可以用 root 账户进入 MySQL 了，然后来创建 Ghost 需要的用户及数据库：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 运行以后输入刚才拿到的密码 mysql -uroot -p # 添加新数据库blog，后面Ghost会用到 CREATE DATABASE blog; # 添加新用户ghost # 请将password替换为你需要的密码 CREATE USER ghost IDENTIFIED BY \u0026#39;password\u0026#39;; # 授予ghost用户在blog数据库上的所有权限 # password就是你刚才创建ghost账户时设置的密码 GRANT ALL PRIVILEGES ON blog.* TO \u0026#39;ghost\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;password\u0026#39;; # 刷新权限 # 当执行过GRANT，CREATE USER，REVOKE命令之后，必须要执行刷新权限才能生效 FLUSH PRIVILEGES; # Ctrl+D 退出 好了，以上就是 MySQL 部分，接下来安装 NodeJS：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 推荐使用以下方式安装NodeJS # 本系统使用V6稳定版，如需其它版本，你可以自行调整 curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash - # 安装NodeJS及其依赖包 yum install gcc-c++ make nodejs -y # NodeJS安装以后，默认已经自带的NPM # 不过npm之前一直被吐槽，建议升级最新版或者用yarn代替 # 升级npm至最新版本(^v5.0.4) npm install npm@latest -g # 安装yarn代替（推荐） curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - yum install yarn -y # 测试一下 node -v # \u0026gt;\u0026gt;\u0026gt; v6.11.0 npm -v # \u0026gt;\u0026gt;\u0026gt; v5.1.0 yarn -v # \u0026gt;\u0026gt;\u0026gt; v0.27.5 这里有个坑，如果直接使用yum install nodejs -y也是可以安装NodeJS的，不过nodesource维护的更加好，而且可以升级npm。\n如果你已经看到了最后输出的信息，那么久代表NodeJS已经就绪了，接下来安装Nginx并做一些简单的配置：\n1 2 3 4 5 6 7 8 # 直接安装即可 yum install nginx -y # 立即启动 systemctl start nginx # 设置开机自启动 systemctl enable nginx 现在，打开你的浏览器，输入你的公网 IP，不出意外，你就已经可以看到一个Nginx的欢迎页面了。\n不过，出于后期的扩展或者安全性考虑，我们需要为系统创建一个名为www的用户，专门用于管理网站：\n1 2 3 4 5 6 7 8 9 10 11 # 添加新用户，默认会创建一个名为www的用户组 adduser www # 设置密码(请记录下你的密码，以后需要用到) passwd www # 分配Root权限 - 让我们新创建的www用户拥有Root用户的权限 gpasswd -a www wheel # 将nginx用户添加入www的用户组，方便后期的权限控制 usermod -a -G nginx,www nginx 以上就是 Ghost 需要用到的基础系统了，下面就来安装 Ghost，新开一个 Terminal 窗口，用 www 账户登录：\n1 ssh www@xxx.xxx.xxx.xxx 然后下载 Ghost 最新的代码，这里我选用的最新的稳定版v0.11.9:\n1 2 3 4 5 6 7 8 9 10 11 # 下载Ghost最新的稳定版 curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip # 解压 unzip -uo ghost.zip -d ghost # 进入Ghost的目录并安装生产环境的依赖 cd ghost \u0026amp;\u0026amp; npm install --production # 修改Ghost的config，修改数据库配置 vim config.js 完整的config.js配置如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var path = require(\u0026#39;path\u0026#39;), config; config = { production: { url: \u0026#39;https://www.itony.net\u0026#39;, mail: {}, database: { client: \u0026#39;mysql\u0026#39;, connection: { host: \u0026#39;127.0.0.1\u0026#39;, user: \u0026#39;ghost\u0026#39;, //用户名 password: \u0026#39;password\u0026#39;, //ghost的密码 database: \u0026#39;blog\u0026#39;, //数据库名 charset: \u0026#39;utf8\u0026#39;, }, debug: false, }, server: { host: \u0026#39;127.0.0.1\u0026#39;, port: \u0026#39;2368\u0026#39;, }, }, }; module.exports = config; 运行一下npm start --production，如果看到下面的提示，证明你已经完成 80%了：\n1 2 3 Ghost is running in production... Your blog is now available on https://www.itony.net Ctrl+C to shut down 目前你是开着远程连接到你的服务器，一旦你把窗口关闭了，那么你刚才起的npm start --production可就悲剧了，如何避免呢，这时候我们就需要一个能帮我们在后台稳定运行 node 服务的工具了，目前比较火热的要数PM2了：\n1 2 3 4 5 6 7 8 9 # 全局安装PM2 yarn global add pm2 --prefix /usr/local # 用PM2来启动Ghost的node服务 # 先切到ghost的目录，然后再运行 NODE_ENV=production pm2 start index.js --name \u0026#34;blog\u0026#34; # 检查运行结果 pm2 show blog OK，这时候已经是 90%的进度了，不过你去浏览器里面访问可还不行，因为你服务起的是2368的端口，而你直接访问https://www.itony.net就相当于访问https://www.itony.net:80也就是80端口，而80端口现在已经被 Nginx 使用了，所以我们还得做最后一步，使用 Nginx 代理你的2368：\n1 2 3 4 5 # 进入Nginx的目录，如果你是按照之前的流程走的，那么应该是在/etc/nginx cd /etc/nginx # 用sudo权限编辑nginx.conf sudo vim nginx.conf 在http那一层中加入 Ghost 的 server 配置：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 由于这里是入门级的教程，所以不做vhost的处理了，想了解Nginx的可以自行去学习 http { ... server { listen 80; listen [::]:80; server_name itony.net www.itony.net; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:2368; } } } 检查 Nginx 的配置，并重启：\n1 2 3 4 5 # 检查Nginx语法是否正确 sudo nginx -t # 如果正确的话，重启Nginx服务 systemctl restart nginx.service 好了，如果以上都没有问题，你应该已经可以在浏览器里面访问你的域名了。\n","date":"2017-07-05T08:08:08+08:00","permalink":"https://itony.net/aliyun-ecs-ghost/","section":"post","tags":["Linux","VPS","Ghost"],"title":"基于阿里云ECS的Centos7搭建Ghost"},{"categories":["Hobbies"],"contents":"很早之前买了一个Raspberry Pi 3，之前在上面玩过爬虫，搭过简单的 web 服务，不过一直用的是 Nginx，在以前用 Seajs 的时候，用过基于 Tengine 的 Combo 服务，所以就想着在 Pi 上试试看，下面就简单的记录一下，如何在 Raspbian 系统上安装 Tengine：\n安装系统依赖包 1 2 3 4 5 6 7 8 # 解决PERL，需要安装以下依赖包 sudo apt-get install libpcre3 libpcre3-dev -y # 解决OpenSSL，需要安装以下依赖包 sudo apt-get install libssl-dev -y # 其它可能需要的，看具体的提示 sudo apt-get install gcc libpcre3 libpcre3-dev zlib1g zlib1g-dev openssl libcurl4-openssl-dev -y 下载、解压、编译、安装 1 2 3 4 5 6 7 8 9 10 11 # 下载 wget http://tengine.taobao.org/download/tengine-2.2.0.tar.gz # 解压 tar zxvf tengine-2.2.0.tar.gz # 编译、安装 cd tengine-2.2.0 \u0026amp;\u0026amp; \\ sudo ./configure \u0026amp;\u0026amp; \\ sudo make \u0026amp;\u0026amp; \\ sudo make install 安装结果 1 2 3 4 5 6 7 8 9 10 11 12 13 nginx path prefix: \u0026#34;/usr/local/nginx\u0026#34; nginx binary file: \u0026#34;/usr/local/nginx/sbin/nginx\u0026#34; nginx configuration prefix: \u0026#34;/usr/local/nginx/conf\u0026#34; nginx configuration file: \u0026#34;/usr/local/nginx/conf/nginx.conf\u0026#34; nginx pid file: \u0026#34;/usr/local/nginx/logs/nginx.pid\u0026#34; nginx error log file: \u0026#34;/usr/local/nginx/logs/error.log\u0026#34; nginx http access log file: \u0026#34;/usr/local/nginx/logs/access.log\u0026#34; nginx http client request body temporary files: \u0026#34;client_body_temp\u0026#34; nginx dso module path: \u0026#34;/usr/local/nginx/modules/\u0026#34; nginx http proxy temporary files: \u0026#34;proxy_temp\u0026#34; nginx http fastcgi temporary files: \u0026#34;fastcgi_temp\u0026#34; nginx http uwsgi temporary files: \u0026#34;uwsgi_temp\u0026#34; nginx http scgi temporary files: \u0026#34;scgi_temp\u0026#34; ","date":"2017-07-04T08:08:08+08:00","permalink":"https://itony.net/raspbian-install-tengine/","section":"post","tags":["Raspberry Pi","Raspbian","Linux"],"title":"Raspbian安装Tengine"},{"categories":["Hobbies"],"contents":" 在数学中，辗转相除法，又称欧几里得算法（英语：Euclidean algorithm），是求最大公约数的算法。辗转相除法首次出现于欧几里得的《几何原本》（第 VII 卷，命题 i 和 ii）中，而在中国则可以追溯至东汉出现的《九章算术》。\n\u0026ndash; 《辗转相除法 - 维基百科》\n以上是 wikipedia 中的一段摘要，理论上欧几里得的辗转相除法实际可以计算任意多整数的最大公约数。\n不过，今天主要是和大家分享一下其中一个最简单的案例，求两个整数的最大公约数。\n根据 wikipedia 的解释，两个整数的最大公约数等于其中较小的数和两数的差的最大公约数，公式如下：\n1 g = GCD(a, b) = GCD(b, r0) = GCD(r0, r1) = … = GCD(rN−2, rN−1) = rN−1 可能上面的描述不是很直观，那就用个例子来解释一下：\n1 2 3 4 5 6 7 8 9 10 11 12 a = 1071; b = 462; // 从1071中不断减去462直到小于462（可以减2次，即商q0 = 2），余数是147 1071 = 2 × 462 + 147 // 然后从462中不断减去147直到小于147（可以减3次，即q1 = 3），余数是21 462 = 3 × 147 + 21 // 再从147中不断减去21直到小于21（可以减7次，即q2 = 7），没有余数 147 = 7 × 21 + 0 // 此时，余数是0，所以1071和462的最大公约数是21 其实说白了就是不断的取余，直到余数为 0，就可以得出最大公约数了。\n那么，能想到最直接的方式就是使用递归了，下面用Javascript实现一遍：\n1 2 3 4 5 6 7 8 9 10 11 12 13 const GCD = (a, b) =\u0026gt; { // a 必须 大于 b if (b === 0) { return a; } let max = a; let min = b; if (max \u0026lt; min) { min = a; max = b; } return GCD(min, max % min); }; ","date":"2017-07-03T08:08:08+08:00","permalink":"https://itony.net/euclidean-algorithm/","section":"post","tags":["Math","Algorithm","Javascript"],"title":"欧几里得算法"},{"categories":["Web Technologies"],"contents":" 转自小李刀刀 \u0026ndash; 原文地址，对排版内容作了一些修改。\n读到《重新认识 CSS 的权重》这篇*(链接已删除)*，鬼哥在文章最后给出了便于记忆的顺序：\n!important \u0026gt; 内联 \u0026gt; ID \u0026gt; 类 \u0026gt; 标签 | 伪类 | 属性选择 \u0026gt; 伪对象 \u0026gt; 继承 \u0026gt; 通配符\n那么这个顺序是怎么得出来的呢？实际上在CSS2 规范关于具体性(specificity)的定义中，描述是非常明确的，但是很多中文版本的 css 图书中采用了 10 进制的简单相加计算方式（包括第一版《CSS 权威指南》，第二版中已经纠正）。\n因此把规范中对 CSS 层叠优先级的相关定义意译一下，希望给初入门或对权重计算尚有疑惑的朋友提供一些参考。\n选择器权重值的计算 A：如果规则是写在标签的 style 属性中（内联样式），则 A=1，否则，A=0.\n对于内联样式，由于没有选择器，所以 B、C、D 的值都为 0，即 A=1, B=0, C=0, D=0（简写为 1,0,0,0，下同）。\nB：计算该选择器中 ID 的数量。\n例如，#header 这样的选择器，计算为 0, 1, 0, 0。\nC：计算该选择器中伪类及其它属性的数量（包括 class、属性选择器等，不包括伪元素）。\n例如， .logo[id=\u0026lsquo;site-logo\u0026rsquo;] 这样的选择器，计算为 0, 0, 2, 0。\nD：计算该选择器中伪元素及标签的数量。\n例如，p:first-letter 这样的选择器，计算为 0, 0, 0, 2。\nCSS2 规范中给出的一些例子：\nCode A B C D Specificity li {...} 0 0 0 1 0,0,0,1 li:first-line {...} 0 0 0 2 0,0,0,2 ul li {...} 0 0 0 2 0,0,0,2 ul ol+li {...} 0 0 0 3 0,0,0,3 h1 + *[rel=up] {...} 0 0 1 1 0,0,1,1 ul ol li.red {...} 0 0 1 3 0,0,1,3 li.red.level {...} 0 0 2 1 0,0,2,1 #x34y {...} 0 1 0 0 0,1,0,0 style=\u0026quot;...\u0026quot; {...} 1 0 0 0 1,0,0,0 根据这样的定义，所以很多文章简单地把规则归纳为：\n内联样式 的权重值是 1000 \u0026gt; ID 选择器 的权重值是 100 \u0026gt; class 选择器 的权重值是 10 \u0026gt; 标签选择器 的权重值是 1\n整条规则中的所有选择器权重值相加得到整个样式规则的权重值，数字越大权重值越高。\n大多数情况下，按照这样的理解得出的结论没有问题，但是遇到下面这样的情况就出现问题了：\n1 2 3 4 5 6 7 8 9 /* 样式一 */ body header div nav ul li div p a span em { color: red; } /* 样式二 */ .count { color: blue; } 按照错误的计算方法，样式一的权重值是 11，样式二的权重值是 10 如果这两条规则用于同一个元素，则该元素应该是红色。实际结果却是蓝色。\n权重值的比较 按照四组计算的正确方法，上面例子中的样式一权重值应该是 0, 0, 0, 11，样式二的权重值是 0, 0, 1, 0。\n根据规范，计算权重值时，A,B,C,D 四组值，从左到右，分组比较，如果 A 相同，比较 B，如果 B 相同，比较 C，如果 C 相同，比较 D，如果 D 相同，后定义的优先。\n样式二和样式一的 A、B 相同，而样式二的 C 大于样式一，所以，不管 D 的值如何，样式二权重值都大于样式一。这就是正确的答案。\n特殊的 !important 在按照 ABCD 四组计算比较之外，在定义样式的时候，还可以对某一个属性应用 !important。对于一直从事编程而没做过重构的人，需要特别注意的是这里的“!”与其在编程语言中的意义刚好相反，不是代表“不重要”而是代表“很重要”。\nCSS2 规范中规定：!important 用于单独指定某条样式中的单个属性。对于被指定的属性，有 !important 指定的权重值大于所有未用 !important 指定的规则。\n例如：\n1 2 3 4 5 6 7 8 9 10 11 /* 样式一 */ #header nav ul li.current { color: red; font-weight: bold; } /* 样式二 */ li:hover { color: blue !important; font-weight: normal; } 就整条规则而言，样式一的权重值为 0, 1, 1, 3，而样式二的权重值仅为 0, 0, 0, 2。所以应用于相同元素时，应该样式一生效。但是对于 color 这个属性，由于在样式二中用 !important 做了指定，因此 color 将应用样式二的规则。而 font-weight 则按照规定用样式一的规则。\n如果多条规则中都对同一个属性指定了 !important 呢？这时候 !important 的作用相互抵销，依然按照 ABCD 四组计算比较。\n因此 !important 的作用只有在具有唯一性时才能提现，但是我们永远无法预料自己什么时候又需要覆盖一个已经指定了 !important 的属性，所以最好的办法就是：不要使用 !important。\n关于 inherit 除了直接指定到元素上的样式规则以外，每个属性值还有一个可能为 inherit (继承) 的值。表示元素的该样式属性继承自父级元素，与父级元素的定义一致。比如：\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;style\u0026gt; .list .item { color: red; } \u0026lt;/style\u0026gt; \u0026lt;ul class=\u0026#34;list\u0026#34;\u0026gt; \u0026lt;li class=\u0026#34;item\u0026#34;\u0026gt; \u0026lt;span\u0026gt;某些文字\u0026lt;/span\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 上例中，样式规则并未针对 span 标签指定 color 属性， 但是 span 中的文字会显示为红色， 这就是因为 span 的 color 属性默认值为 inherit.\n对于 inherit 的属性，只要记住一点：\n继承而来的属性值，权重永远低于明确指定到元素的定义。\n只有当一个元素的某个属性没有被直接指定时，才会继承父级元素的值。例如：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 \u0026lt;style\u0026gt; span { color: blue; } .list .item { color: red; } \u0026lt;/style\u0026gt; \u0026lt;ul class=\u0026#34;list\u0026#34;\u0026gt; \u0026lt;li class=\u0026#34;item\u0026#34;\u0026gt; \u0026lt;span\u0026gt;某些文字\u0026lt;/span\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; 同样的例子， 第一条规则的权重是 0,0,0,1, 第二条规则的权重是 0,0,2,0. 虽然第二条规则的权重更高，但是它是针对 li 元素的直接指定，并不是针对 span 元素定义的，所以计算 span 的 color 属性权重值时，实际上就是 inherit 的红色与直接指定的蓝色的对比。 按照规则，只要有直接指定的值（蓝色），就不会再取继承的值（红色），所以 span 中的文字显示为蓝色。\n这条规则最典型的场景就是链接文字的颜色，由于一般浏览器自带的样式表都有针对 a 标签的颜色及下划线的直接指定，所以网页样式表中对 a 标签的父级元素指定 color 属性及 text-decoration 属性，通常不会起作用。 但是我们可以通过下面的 reset 来改变这一点：\n1 2 3 4 5 6 7 8 9 10 11 12 13 \u0026lt;style\u0026gt; a { color: inherit; text-decoration: inherit; } .item { color: red; } \u0026lt;/style\u0026gt; \u0026lt;p class=\u0026#34;item\u0026#34;\u0026gt; \u0026lt;a href=\u0026#34;#\u0026#34;\u0026gt;链接文字\u0026lt;/a\u0026gt; \u0026lt;/p\u0026gt; 在上例中，由于我们的样式表对 a 标签直接指定了 color 和 text-decoration 属性值， 覆盖了浏览器的默认样式，所以在没有特别指定 a 标签的颜色和下划线定义的前提下， 会从父级元素 p 继承，因此链接会显示为红色。\n特别补充：inherit 在 CSS1 规范中并未定义，所以 IE6, IE7 以及 IE8 的 QuirksMode 不支持。\n总结 一条样式规则的整体权重值包含四个独立的部分：[A, B, C, D]; A 表示内联样式，只有 1 或者 0 两个值； B 表示规则中 ID 的数量； C 表示规则中除了 ID、标签和伪元素以外的其它选择器数量； D 表示规则中标签和伪元素的数量； 比较时从高位到低位（从 A 到 D）分别比较，高位相同才需要比较低位； 有 !important 标记的属性权重值无视没用 !important 指定的一切情况； 多次指定 !important 时，相互抵销。 参考 CSS 权重值（MDN 称之为 CSS 优先级）\n重新认识 CSS 的权重\ncss 权重\n","date":"2016-08-30T09:19:20+08:00","permalink":"https://itony.net/css-style-cascading-weight-value/","section":"post","tags":["Note","FrontEnd","CSS"],"title":"深入解析CSS样式层叠权重值"},{"categories":["Web Technologies"],"contents":"最近在做项目时，使用 table，div，css 布局，最顶部加入 img（图片）后，底部总是有空白，经过查找了大量资料，问题总算是解决了。\n网上朋友说是在进行页面的 DIV+CSS 排版时，遇到 IE6（当然有时 Firefox 下也会偶遇）浏览器中的图片元素 img 下出现多余空白的问题绝对是常见的对于该问题的解决方法也是“见机行事”，根据原因的不同要用不同的解决方法，这里把解决直接把解决 img 图片布局下边的多余空隙的 BUG 的常用方法归纳，供大家参考。\n1、将图片转换为块级对象 即设置 img 的 style 为：style=\u0026quot;display:block\u0026quot;，或者设置其 CSS 为：\n1 2 3 img { display: block; } 经试验，此方法可以解决。\n2、设置图片的垂直对齐方式 即设置图片的vertical-align属性为“top，text-top，bottom，text-bottom”也可以解决，或者增加一组 CSS 代码：\n1 2 3 img { display: block; } 3、设置父对象的文字大小为 0px 即，在#table 中 css 中添加一行：\n1 2 3 4 .parent { // img parent DOM font-size: 0; } 可以解决问题。但这也引发了新的问题，在父对象中的文字都无法显示。就算文字部分被子对象括起来，设置子对象文字大小依然可以显示，但在 CSS 效验的时候会提示文字过小的错误。\n4、改变父对象的属性 如果父对象的宽、高固定，图片大小随父对象而定，那么可以设置overflow:hidden来解决。\n1 2 3 4 .parent { // img parent DOM overflow: hidden; } 5、设置图片的浮动属性 即在本例中增加一行 CSS 代码：\n1 2 3 4 img { float: left; clear: left; } 如果要实现图文混排，这种方法是很好的选择。\n6、取消图片标签和其父对象的最后一个结束标签之间的空格。 这个方法要强调下，在实际开发中该方法可能会出乱子，因为在写代码的时候为了让代码更体现语义和层次清晰，难免要通过 IDE 提供代码缩进显示，这必然会让标签和其他标签换行显示，比如说 DW 的“套用源格式”命令。\n所以说这个方法可以供我们了解出现 BUG 的一种情况，具体解决方案的还得各位见招拆招了。\n","date":"2016-08-29T08:08:08+08:00","permalink":"https://itony.net/remove-img-element-blank/","section":"post","tags":["FrontEnd","CSS"],"title":"去除`img`底部空白"}]