MBanner 介绍

最近做了一个安卓项目,因为有个部分是需要实现一个可以展示标题和图片的 AD Banner。所以就同性交友网上(github)上找了一些案例。 作为一个安卓初学者,所以一开始就选用了 star 最多的那个。效果也很好,但是对方提出了一个需求,就是希望在图片上显示标题。做一个类似的蜻蜓上面的 Banner 效果。所以,就搜索到了 QingTingBannerView 这个项目。 这个项目的 star 数非常高。所以一开始认为是挺靠谱的一个。但是使用中发现了如下几个问题。 在执行 BannerView.setEntities 的时候,他并不是直接的将数组替换,而仅仅是做一个 append 操作。也就是说,当刷新的时候,3个 Banner 会变成 6个 Banner,如果没有在外面做去重的话。 下部的 Title 和是在图片下方,会多占用20dp 左右的空间,在小屏手机上展示效果不佳。 对于业务来说,我们需要更多的可定制化。但是在这个设计中,PageAdapter 只有一个 class,而且是已经实现好的。所以不存在继承接口重新设计的可能性。 基于以上的几点。我们产生了自己开发 Banner 库的想法。 所以,基于上述项目,开发了 MBanner 类库。 主要的特性有: 支持图片的缩放。 添加 Banner 上长按响应事件。 可以通过一定的修改,变成图片阅览器。 添加自动滚动。 添加代码自定义图片背景色和进度条颜色 API。 使用 Glide 库作为图片加载,支持图片缓存。 添加默认加载动画,也可自定义动画。 具体的代码在:GITHUB-MBANNER 实机效果如下: 图片 Banner: 注意,上面是不添加点击放大功能的 banner,下面添加放大功能(不能触发点击和长按事件)。 图片浏览器: 此处图片可以双击放大/恢复原状。左右滑动,但是图片的点击和长按事件无法触发。 安装方式 通过 gradle 安装: 添加 JitPack 仓库到你的 build 文件,即项目根目录的 build.gradle: allprojects { repositories { … maven { url “https://jitpack.io” } } } 添加依赖: dependencies { compile ‘com.github.MikeCoder:MBanner:1.0.6’ // 表示指定的版本 compile ‘com.github.MikeCoder:MBanner:+’ // 表示最新的版本(推荐使用) } 然后在 Android Studio 中点击 sync now 按钮即可 通过下载导入安装(不推荐) 使用方法: 基本使用: 将其作为简单的 Banner,添加点击事件和长按事件: MBannerView mBannerView1 = (MBannerView) this.findViewById(R.id.banner_view1); Banner banner = JSON.parseObject(readAssets(), Banner.class); // This just get resources from the file final ArrayList<MBannerEntity> entities = new ArrayList<>(); for (int i = 0; i < banner.getRecommends().size(); i++) { MBannerEntity entity = new MBannerEntity(); entity.setImgURL(banner.getRecommends().get(i).getThumb()); entity.setTitle(banner.getRecommends().get(i).getTitle()); entities.add(entity); } assert mBannerView1 != null; mBannerView1.setEntities(entities); mBannerView1.setOnBannerClickListener(new OnBannerClickListener() { @Override public void onClick(int idx) { Toast.makeText(MainActivity.this, “CLICK:” + idx + “=> ” + entities.get(idx).getTitle(), Toast.LENGTH_SHORT).show(); } }); mBannerView1.setOnLongClickListener(new OnBannerLongClickListener() { @Override public boolean onLongClick(View v, int idx) { Toast.makeText(MainActivity.this, “LONG CLICK:” + idx + “=> ” + entities.get(idx).getTitle(), Toast.LENGTH_SHORT).show(); return true; } }); 如果希望添加自动滑动,使用如下方法,例子是3秒滑动一次。 // set…

如何实现 PHP 中的 Router

因为最近在写自己的 MFramework, 算是一个 PHP 的 web 框架吧。所以这个 Router 就是首先的一步。 首先看下 Kohana 框架的路由编写方式: Route::set(‘blogs’, ‘blogs/((/)(/))’)->defaults( array ( ‘controller’ => ‘blog’, ‘action’ => ‘list’, ‘limit’ => 10 , ‘page’ => 0 ) ); 然后是 Zend 的路由设置, 建议阅读他的官方文档:Zend Router: $route = Method::factory(array( ‘verb’ => ‘post,put’, ‘defaults’ => array( ‘controller’ => ‘Application\Controller\IndexController’, ‘action’ => ‘form-submit’, ), )); 还有 thinkphp 的路由,不过,TP 为了简化开发,已经把路由固定死,常规的开发,只能根据他的规则来进行一些开发。 最后就是 Laravel 的路由设置。 Route::get(‘user/{id}/{name}’, function($id, $name) { dd(1); }) ->where(array(‘id’ => ‘[0-9]+’, ‘name’ => ‘[a-z]+’)) 可以看到的一点,就是,路由的基本规范就是能将不同的 Http 请求方法区别开来,最好还是能对参数进行一些预处理。 最后,以 Laravel 为例,写一下基本的 Router 思路。 对于一个 Router, 首先就是要有六个基本的方法,就是用来设置 GET,POST,PATCH,DEL,PUT,ANY 这些访问的方法。通常而言,都会写成六个 function。不过这边看到一个,利用魔术方法的优雅的实现思路,而且可以极大的扩展自定义协议。代码如下: /** * @method static get(string $route, Callable $callback) * @method static post(string $route, Callable $callback) * @method static put(string $route, Callable $callback) * @method static delete(string $route, Callable $callback) * @method static options(string $route, Callable $callback) * @method static head(string $route, Callable $callback) */ public static function __callstatic($method, $params) { $uri = dirname($_SERVER[‘PHP_SELF’]).’/’.$params[0]; $callback = $params[1]; array_push(self::$routes, $uri); array_push(self::$methods, strtoupper($method)); array_push(self::$callbacks, $callback); } 基本上,当调用静态方法的时候,都会访问这个方法。所以,我们只需要对此进行一些处理就好,将结果保存到内部的一个数组中,为了加快搜索速度,可以用 HTTP-METHOD => URL 的思路进行处理。 所以当,执行如下代码时: Route::get(‘/fuck’, function() { echo ‘fuck world!’; }); Route::dispatch(); 会生成一个路由表,即 [GET] => [‘/fuck’] => [function] 的一个 map,在最后 dispatch 的时候进行查找。 然后就是参数的校验。这边就是纯粹的自定义了。不过从上述的几个框架上看,基本都是对纯数字和纯英文做一个校验,其实也就是一个正则的表达式。这边也只需要自定义几个参数,以 num 为例。 public static $patterns = array( ‘:any’ => ‘[^/]+’, ‘:num’ => ‘[0-9]+’, ‘:all’ => ‘.*’ ); // $route = ‘/fuck/(:num)’; $searches = array_keys(static::$patterns); $replaces =…

Hexo 标签云插件

现已升级至2.0.*版本,请移步 Hexo-Tag-Cloud 查看具体安装方法。 按照常理,首先要说明为什么要写这个东西。最主要的原因是,我在看同学博客的时候,被大部分的标签云恶心到了。 要么就是一个静态的列表,要么就是一个不知道怎么停下来的标签球。为表愤怒,我就写了这个插件。 求 STAR Hexo-Tag-Cloud 使用方法 在 hexo 博客的根目录找到 package.json 这个文件夹,添加如下的依赖: { “name”: “hexo-site”, “version”: “0.0.0”, “private”: true, “hexo”: { “version”: “3.2.0” }, “dependencies”: { “hexo”: “^3.2.0”, “hexo-deployer-git”: “^0.1.0”, “hexo-generator-archive”: “^0.1.4”, “hexo-generator-category”: “^0.1.3”, “hexo-generator-index”: “^0.2.0”, “hexo-generator-tag”: “^0.2.0”, “hexo-renderer-ejs”: “^0.2.0”, “hexo-renderer-marked”: “^0.2.10”, “hexo-renderer-stylus”: “^0.3.1”, “hexo-server”: “^0.2.0”, “hexo-tag-cloud”: “1.0.*” // 就是这句 } } 执行 npm install 然后可以试着执行 hexo g 重新生成静态文件,这时候可以看一下 public 目录下是否有 tagcloud.xml 和 tagcloud.swf 这两个文件。如果有,则表示插件运行正常。 如果上一步发生问题,请在 github 上提交 issue 并且附上 error log。 提交地址:GITHUB ISSUE 然后运行 hexo s, 查看 http://localhost:4000/tagcloud.swf 是否可以看到标签云。如果不可以,请查看 tagcloud.xml 是否有内容。 最后,就是将系统原有的 tagcloud 换成新版的 tagcloud.这边以官方自带的 landscape 主题为例。 找到 hexo/themes/landscape/layout/_widget/tagcloud.ejs 这个文件,将里面的内容修改为: <% if (site.tags.length){ %> <div class=”widget-wrap”> <h3 class=”widget-title”><%= __(‘tagcloud’) %></h3> <div class=”widget tagcloud”> <embed tplayername=”SWF” splayername=”SWF” type=”application/x-shockwave-flash” src=”tagcloud.swf” mediawrapchecked=”true” pluginspage=”http://www.macromedia.com/go/getflashplayer” id=”tagcloudflash” name=”tagcloudflash” bgcolor=”#f3f3f3″ quality=”high” wmode=”transparent” allowscriptaccess=”always” flashvars=”tcolor=0xbd1016&amp;tcolor2=0x808080&amp;hicolor=0x0065ff&amp;tspeed=100&amp;distr=true” height=”100%” width=”100%”> </embed> </div> </div> <% } %> 最后再次执行 hexo g && hexo s, 查看首页是否已经替换成功。 好好享受新版的 tagcloud 还有 hexo 吧。 最重要的,请不要使用中文作为 tag,会存在编码问题 效果展示 这边首先上一个图片效果图: 然后就是一个 Live Demo: 请点击这个链接:mikecoder.github.io 原理 这边其实很简单,就是在 exit 事件结束前,遍历所有的 tag,然后记录在 tagcloud.xml 里面,通过将 tagcloud.swf 读取这个 xml,然后前端生成标签云。 接下来吐槽的 不推荐 Hexo 作为自己的博客系统,虽然这是免费的。不过,官方提供的插件挂载点,真的少。这边是官方文档中,插件的挂载点: deployBefore deployAfter exit generateBefore generateAfter new 这边和专业的博客系统比起来,真的是少的太多了。能做的事情也确实很少。这边推荐一下 emlog , 虽然不如 wordpress 功能强大,但是作为一个个人博客而言,功能已经可以满足的很好。 Hexo 主要把自己作为一个静态页面生成器,所以在设计上走的是简化的路线。如果重新设计的话,其实可以将前端的页面进行充分模块化,方便用户进行自定义修改。 还有就是 npm 的设计,也是挺好的,不过踩了一个坑,就是 npm 的包上传的时候,ReadMe 不识别 README.md,所以一开始上传的时候,提示没有找到 ReadMe 相关的文档。后来找到需要在 package.json 里面配置,也是挺蛋疼的。 last but not least, hexo 虽然支持 markdown 语法,但是,博客的本身并不是 markdown, 如果之后想要迁移,得花很大的精力将博客前的那段描述去除掉。这也是我不用 hexo 最主要的原因。

在 Dingo/Api 中实现自定义错误回复

目前在用 Laravel 做一个 APP 后台的开发。因为都是 API,所以需要定义个通用的通信协议。这个比较好解决,而且我之前的思路也都有,具体可以看这篇:设计自认为优雅的接口。这里就不再废话了。 考虑到之后的通用性和可维护性,我们决定使用 Dingo/Api 这个插件进行 API 的管理,开发。所以,理所当然的遇到了这么一个应用场景。 这是使用 Dingo/Api 之后,标准的返回值: url: http://www.ehs.com/api/test/index { “status_code”: 100, “message”: “成功”, “timestamp”: 1457267879, “data”: “eyJmZmYiOiJmZmYifQ==”, “sign”: “d550edf4061cd60a404cd18350a1dbf299e4e69502c5be64de58861565c8e9ea” } 但是,如果代码中出现了错误,或者说异常吧。常见的就是使用了未定义的方法,或者什么乱七八糟的错误。那么,Dingo/Api 会接管所有的异常,统一返回一个通用 json,以下是开了 API_DEBUG 之后遇到的返回情况: url: http://www.ehs.com/api/test/index { “message”: “Undefined variable: code2”, “status_code”: 500, “debug”: { “line”: 38, “file”: “/Users/Mike/Workspace/project/ehs/ehs-web/bootstrap/function.php”, “class”: “ErrorException”, “trace”: [ “#0 /Users/Mike/Workspace/project/ehs/ehs-web/bootstrap/function.php(38): …” “#1 /Users/Mike/Workspace/project/ehs/ehs-web/app/Http/…” “#2 [internal function]: App\\Http\\Controllers\\Api\\V1\\Test\\TestController->index()”, “#3 /Users/Mike/Workspace/project/ehs/ehs-web/vendor/laravel…”, … “#57 /Users/Mike/Workspace/project/ehs/ehs-web/public/index.php(58) …”, “#58 {main}” ] } } 当然,如果线上的话,关闭 API_DEBUG , 就是这样的返回值: url: http://www.ehs.com/api/test/index { “message”: “Undefined variable: code2”, “status_code”: 500 } 这样看上去没什么问题。因为这边 status_code 是比较规范的。对于 PHP 来说,直接 json_decode 之后,并没有什么难办的地方。但是对面安卓和 IOS 则是使用的强类型语言。尤其是 Java,需要对每一个 Json 对象进行新建,然后序列化。所以,这种格式不统一的返回结果,是无法接受的。 那么问题来了,如何修改 Dingo/Api 的错误返回值。首先,依旧是上stackoverflow,并没有什么实质性的进展,然后就是世界上最大的同性交友网站:GITHUB,果不其然,找到了同样的业务场景的提问,然后回答却是一盆冷水。作者对该业务场景的回答, 还有这个给我启发的 issue。也是蛋疼。 所以就是需要自己 Hack 了。 当然,通过跟踪代码,可以很快的找到 Dingo/Api 中的错误处理代码,但是,并不建议直接修改这边的代码,因为,之后可能还是会通过 composer 来更新项目的依赖关系,如果直接修改,那么在之后的维护中,就会有不确定因素。增加了系统的风险。所以放弃。 解决方法也很简单。首先就是需要把所有的异常信息归总到一个地方。这个可以通过很贱的配置就可以达到。我的做法就是在routes.php里面加上这么一段代码,接管所有的 Exception: // 将所有的 Exception 全部交给 App\Exceptions\Handler 来处理 app(‘api.exception’)->register(function (Exception $exception) { $request = Illuminate\Http\Request::capture(); return app(‘App\Exceptions\Handler’)->render($request, $exception); }); 然后就是 Handler 中进行判断了。首先就是找到 API 接口的 request,和正常访问的 request 的区别,这个很简单,dd 一下,就知道了。这有坑,建议不要直接 dd($request),如果电脑好,那也无所谓。 上结论就是可以通过判断如下代码进行判断: public function render($request, Exception $e) { if ($request->server->get(‘API_PREFIX’)) { return APIReturn(ERROR, ‘ ‘.$e->getMessage(), [‘code’ => $e->getCode()]); } return parent::render($request, $e); } 这样,当 API 访问出现异常的时候,就是如下的返回值: url: http://www.ehs.com/api/test/index { “status_code”: 200, “message”: “Fatal error: Call to undefined function App\\Http\\Controllers\\Api\\V1\\Test\\ddd()”, “timestamp”: 1457270046, “data”: “eyJjb2RlIjowfQ==”, “sign”: “8aef6cbdbc84a570f48ccb02d89c363dbc0263468682313fa1cd873fa338b161” } 至此,暂时解决了这个蛋疼的小问题。接下来就是 API 中最麻烦的状态维持,就是 APP 的用户登录。这个,虽然有 oath2.0这样的标准在这边,但是,可能还是会再调研几个其他的解决办法。方便开发和维护吧。尽可能的简答,是我一直想做的。越简单越好。

Coding.net 作弊混码币

该方法已失效,就放出来了。哈哈,算是记录下。 扯淡 无意之间发现了 Coding.net 这个代码托管网站,然后又发现了码币这个东西,居然是和实体货币可以交换的。于是就有了想法。 主要看看哪些选项是可以增加码币的,官方的说明如下: 有一点要注意下,就是邀请好友是0.02码币,不是说明中的0.01码币,并且确实存在这个100个上限。 当然这个码币也是挺诱人的,和人民币1:50的汇率。所以,接下来教大家如何刷这个东西 使用方法 点击 求 star 的 cheat-coding.net 项目, 目前只支持 *uix 系统, 如 Mac, Ubuntu 等等。 在一个空旷的目录下执行: git clone https://github.com/MikeCoder/cheat-coding.net.git 执行 cp config.py.example config.py 然后按照 config.py 中的提示,完成项目的配置 最后执行 sh guard.py, 如果是希望一直在后台运行就执行: nohup sh guard.py > logs/all.log 顺利的话,这样就可以了。不过因为考虑到这个刷码币需要24小时不间断,所以,最好是能在服务器上进行部署。这样可以保证不会因为网络的问题,时常异常。虽然有处理,不过最好还是保持网络畅通。 注意地方 目前只支持163企业邮箱。 暂时不支持 Windows,不过应该可以用 MinGW 进行模拟,不过,没有进行测试 具体的思路 代码的提交和任务的提交,都是很容易的。主要就是一个登陆的 post 和一个 git commit and git push 的定时任务。 唯一比较麻烦的就是那个邀请好友加入。因为其他的几个除了一次性的加成,也只有这个能保证每天 0.1 码币的稳定收入。所以这个是重中之重。 好在我有163企业邮箱,这个的好处就是可以免费申请很多很多的邮箱,而且不需要进行手机号码校验和验证码 有了邮箱。我们就通过邀请链接输入用户的信息 然后登陆新建的邮箱,找到那封验证邮件,然后访问那个验证链接 这样,一个伪造的用户就这么诞生了,为了保证用户名的不同,我选了时间戳作为用户名 当然,在新建邮箱的时候,163还会有一个防刷的保护,不过绕过也挺简单的 通过上述的方法,就可以达到每天邀请5个用户加入 coding.net, 获得弥足珍贵的 0.1 码币 最后就是重复的保证。那就用到了最简单暴力的解决办法。如下: #! /bin/sh while true do nohup python main.py > ./logs/all.log done 最后效果 最后 祝玩得开心

我是如何给 vim 添加 markdown 实时预览功能

一切的一切都是源于同性交友网站:GITHUB,之前,我给自己写了一个简单的 markdown 预览插件,所以就没有去更新了。直到后来,有人找到我,希望能够添加实时预览功能,包括同步的滚动,做成类似于 Mac 下的 Mou 的效果。详细 issue 所以,我这边就闲里抽空把这个功能做了下。 首先就是吐槽下 Python 居然不能管理线程。这个确实挺蛋疼的。不能强行关闭线程。其次,是 VIM 插件开发,资料真的少的可怜。基本上 bing 和 google 到的资料大多都是如何使用别人的插件,而不是如何创造插件。 作为一个在兴趣驱动编码时喜欢造轮子的逗比,我就决定自己写了。 首先就是如何实时的拿到数据,并且不影响 VIM 的正常使用。这很自然的想到了线程和 Ajax。这就意味着,我需要实现一个最简单的 web 服务器,包含静态文件的读取和编辑内容的发送。这个的方案决定确实挺麻烦的。主要有以下几个思路: 直接在 web 服务器实现该功能,静态文件获取和最新编辑内容 生成 tmp.html,然后使用浏览器打开这个tmp.html 通过本地文件加载 js 和 css 然后访问服务器,获取最新数据 很明显,最为一个最优雅的实现方案,第一个绝对是首选。不过,考虑到之前插件的实现原理,通过后者可以最大限度的利用当前代码,同时,服务器端的代码量大幅度降低。所以,思考再三,决定采用第二种方式。虽然丑陋,但是可以最快速度完成该功能。 然后就是服务器的选型。因为是后台进行脚本启动,所以这个的可选余地变得非常大。考虑到我的绝活不是 python, 不是 php。所以一开始是准备使用 java 进行编写的。而且 java 中对线程的支持非常好。同时之前也有过写服务器的经验,但是,作为一个兴趣驱动的项目,我选择了之前编写第一版的 python。 最后就是如何做到同步滚动。这个一开始的思路就是锚点。但是,如何准确的插入锚点,就是一个比较大的问题。因为这个是要进行 markdown 解析的。解析之后的行数不一定对应解析前的行数。主要有如下几个思路: 直接在 markdown 解析库中加入锚点解析 通过特殊字符标定锚点,然后再返回的解析数据中进行替换 记录光标动作,发送状态信息,完全通过前端 js 实现 第一个方案,考虑到之后的解析库更新,为了减少后期的维护成本,这个方案就被否了。然后就是第三个,讲道理的话,这个方案是最佳的。但是,无法确定鼠标的初始位置。因为解析之后的行数不能对应到光标所在的行数,为了实现确定初始位置,就需要标定。然后就和第二个方案一致了。所以最后,考虑到代码实现和效果,采用了第二种方案。 不过这个并不简单,比如在如下的位置: This is Title ¶— 当光标处在这个位置时,通过简单的添加就出现了问题,我是以”{ANCHOR}” 作为特殊标定字符串,所以上述的 markdown 就变成了: This is Title {ANCHOR}— 就背离了原始的 markdown 语法。所以这边就是要考虑所有的边际情况。这个,我也不能说完全的避免了。不过,在我测试的几天里,并没有发现这个 bug。 遗憾 由于之前的设计和编码实现中,并没有仔细阅读他人的代码,后来完成第一版之后,我发现了这个版本的实时预览插件:iamcco/markdown-preview.vim,他的设计就很巧妙了,通过 websocket 进行通信,这样,无论实时性还是代码的优雅程度就已经碾压我了。这个也是我要在下一个版本中进行改进的地方。 自找麻烦 为什么说这个呢,因为作为一个码农,会对版本控制有着狂热的追求,所以这个就给自己带来了一些不方便的地方。比如这个插件,我习惯性的加上了版本的属性。因为不同版本中,css 和 js 的文件会不同。同时考虑到提高其他用户的自定义程度,我将资源文件放到了.vim 目录下。这样,我需要更新插件之后,自动判断当前版本,然后确定是否进行资源文件的复制。 这个方案想了很久,最后确定了一种,就是在生成的资源文件夹中添加上当前版本的编号。比如现在的版本是2.0.0-alpha,所以我会在 .vim/MarkDownRes/ 中生成一个 2.0.0-alpha 名字的空文件,在运行的时候,我就会判断,当前版本是否是符合要求。不符合要求我就覆盖资源文件,然后新增一个版本文件。 还有就是针对不同版本系统的支持,直接看代码: def init(): if vim.eval(“exists(‘g:MarkDownResDir’)”) == ‘1’: DisResDir = vim.eval(‘g:MarkDownResDir’) else: if platform.system() == ‘Windows’: DisResDir = os.path.join(vim.eval(‘$HOME’), ‘vimfiles’, ‘MarkDownRes’) elif vim.eval(“has(‘nvim’)”) == ‘1’: DisResDir = os.path.join(vim.eval(‘$HOME’),’.nvim’, ‘MarkDownRes’) else: DisResDir = os.path.join(vim.eval(‘$HOME’), ‘.vim’, ‘MarkDownRes’) if vim.eval(“exists(‘g:SourceMarkDownResDir’)”) == ‘1’: SourceResDir = vim.eval(‘g:SourceMarkDownResDir’) else: if platform.system() == ‘Windows’: SourceResDir = os.path.join(vim.eval(‘$HOME’), ‘vimfiles’, ‘bundle/markdown-preview.vim/resources’) elif vim.eval(“has(‘nvim’)”) == ‘1’: SourceResDir = os.path.join(vim.eval(‘$HOME’),’.nvim’, ‘bundle/markdown-preview.vim/resources’) else: SourceResDir = os.path.join(vim.eval(‘$HOME’), ‘.vim’, ‘bundle/markdown-preview.vim/resources’) if not os.path.isdir(DisResDir) or not os.path.isfile(os.path.join(DisResDir, markdown_version.__PLUGIN_VERSION__)): if os.path.isdir(DisResDir): commands.getoutput(‘rm -rf ‘ + DisResDir) print ‘updating markdown-preview plugin…’ if platform.system() == ‘Windows’: # not test on windows print commands.getoutput(‘xcopy /E ‘ + SourceResDir + ‘ ‘ + DisResDir) else: print commands.getoutput(‘cp -R ‘ + SourceResDir + ‘…

PHP 调用 exec 执行中文命令的坑

写在之前 首先,我们的项目中有这么一个需求,就是需要在发送请求时,需要调用 java 写的一个加密库。所以不可避免的会使用到 php 的 exec 方法执行 shell 命令。 一切都很正常,直到,出现了中文。哎。具体的 case 如下: 样例代码: <?php $cmd = ‘java -jar sign-maker.jar mike messi’; exec($cmd, $ret, $out); var_dump($ret); $cmd = ‘java -jar sign-maker.jar 麦克 梅西’; exec($cmd, $ret, $out); var_dump($ret); 其中,sign-maker.jar 就是我们按照第三方的加密协议的一个签名包(虽然也是我写的),执行这段代码,我们可以得到如下的结果: php index.php array(1) { [0]=> string(80) “495cc9e9269265cc0e7d58940367976571a1c4fdb90bf842ee4ba703fb1a554abf0772218e29d3d8” } array(2) { [0]=> string(80) “495cc9e9269265cc0e7d58940367976571a1c4fdb90bf842ee4ba703fb1a554abf0772218e29d3d8” [1]=> string(80) “e442a87d369a1a3c610bb2d18bd38fdad3b52644ab0ef86a21b57a5d0d75cb8dbf0772218e29d3d8” } 可以看到,我们已经生成了两个 sign 值。但是,在传输过程中,对面居然报了 sign 无效的错误。觉得很奇怪,第一个 sign 是正确的,但是第二个就是败了。于是我们在终端中手动输入了这个命令: └─[$]> java -jar sign-maker.jar 麦克 梅西 92144a18e9f75ec2e257c9bb15a05825a706064445b9492efd065dfd6c8b38d0bf0772218e29d3d8 发现 sign 果然不同。所以第一反应是字符编码的问题,尝试了 utf8encode,iconv 等等方法转换字符,但是都是无功而返。最后定为到环境变量的问题上。 解决办法 解决办法先上,在 exec 的命令前首先指定字符集: <?php $set_charset = ‘export LANG=en_US.UTF-8;’; $cmd = ‘java -jar sign-maker.jar mike messi’; exec($set_charset.$cmd, $ret, $out); var_dump($ret); $cmd = ‘java -jar sign-maker.jar 麦克 梅西’; exec($set_charset.$cmd, $ret, $out); var_dump($ret); 这样即可。 如何定位 在尝试了 php 的字符转化的方法之后,确实可以断定不是 PHP 的问题,所以最后转移到,php 中 exec 的环境上。我们直接使用如下代码确定 exec 时的环境变量: <?php system(‘env | grep LANG’); 结果在服务器上发现并没有返回值。而在终端中,执行 env | grep LANG 却有如下输出: ➜ ~ env | grep LANG LANG=en_US.UTF-8 所以即可断定,是环境中语言设置的问题,遂使用上述解决办法解决。 顺便说一下 exec 中的那个 $ret 的结果居然是 append 的。。。一开始还不知道,结果在一个循环中调用的时候,一直使用 $ret[0] 作为结果,哎。都是泪,这个 bug 调了好久才知道。 PS:感谢今天不请自来协助加班的兄弟们。

设计优雅的 API 接口

最近有个设计上的需求,我们之前基于 opencart 的一个电商项目,需要进行对应的 APP 开发。这个就牵涉到了很多的问题。 opencart 之前的登陆表示是基于 session 的,但是如果使用 APP,APP 可能会使用 H5 和 Native 两种形态,这边有可能会产生一个 session 的不统一。 之前使用的基于 url 的方式进行访问的页面,如何比较优雅的实现对应的移动 API。 API 部分会涉及到一个升级问题,如何优雅的实现升级,而且不会影响到先前版本的使用。 其次是我们 APP 上会实现支付部分,而这个部分有可能基于 H5 来做,但是 APP 的开发的说法,支付的 SDK 是 Native 的,如何做到在 H5 上优雅的支付,又是个问题。 API 使用 json 做为协议,那么如何尽可能的减少结构体或者类的使用(照顾强类型的 json 解析)。 这次主要讲下第一点和第二点的我的解决办法。顺带说下第三点。 第一个问题: 这边就是一个如何将 session 显式的从 cookie 转移到每次请求的 request body 中。这个可以通过模仿 oauth2 的协议进行。具体的流程图如下: 这边主要注意的是 token 的有效时间。和数据包的签名。由于 token 是明文,恐怖分子可以使用中间人攻击进行数据的窃取,轻易的获得用户 token,而掌握了 token 之后,如果数据包没有对应的签名,恐怖分子很容易进行数据包的篡改,然后发回服务器。所以这边我们会对数据包的数据进行签名。 举几个例子,request body 的协议如下: # 以登陆之后获取用户信息为例,使用 GET 方法访问 https://url/userinfo/{uid} head: token : 用户登陆凭证 sign : 用户对 body 中的内容签名值 body: infoscope : 用户获取信息的范围 timestamp : 发送请求时的时间戳 # 以登陆之后修改用户密码为例,使用 POST 方法访问 https://url/userinfo/{edit} head: token : 用户登陆凭证 sign : 用户对 body 中的内容签名值 body: timestamp : 发送请求时的时间戳 infoscope : password password : 用户原有密码 chpassword : 用户修改之后的密码 这边之所以使用 timestamp,主要是进行一个 token 的有效期判定,同时为了防篡改,timestamp 也在 sign 的范围里。 至于服务器端如何进行用户的判定。也是相对来说比较简单。opencart 原有架构是基于 session 的,登陆之后,他会将用户的 customer_id 存放在 session 里面,然后在每次请求的同时,会根据 customer_id 初始化用户对象。然后这边我们重新设计了一个 API,APP 用户访问之后,我们会通过用户的 token 查询缓存。判断用户是否有权进行该项操作。 验证通过之后,我们会进行用户的初始化。达到了一个用户的登录状态保持。 如果登陆超时,或者无权,我们会相应返回对应的 json 值。 第二个问题: 首先我们的指导设计思想就是 RestFul。 所有的API部署在专用域名之下。 充分利用 HTTP 协议,如 Response Code 和 Request Method 安全起见,使用 https 代替 http 第一点 我们的所有 API 部署在 https://url/mobile/api/ 下,如 GET https://url/api/userinfo/{uid} GET https://url/api/products/ 这边顺带说下我们如何解决版本问题。一般来说有两种方式: 将版本号写到 url 中 将版本号写到 Request Body 中或者 Header 中 这边我们采用直接写到 url 中,简单粗暴 第二点 我们所知道 Http Response Code 如下: 200 OK – [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent) 201 CREATED – [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted – [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT – [DELETE]:用户删除数据成功。…

VIM 的 MarkDown 预览插件

首先,我习惯在 VIM 下进行代码的编写和文档的编写,但是有一个比较蛋疼的地方,就是没有比较好的预览方式,之前用过 sublime,觉得它的 markdown preview 的插件功能就刚刚好,使用简单不需要多余的配置。 在之前,我都是在 Mou 下进行文档的编写,然后导出 PDF 还有预览效果。不过始终不是很方便,因为,有时候写文档和代码都在 VIM 里面。如果需要查看效果,就要去打开其他的软件就觉得比较麻烦。尤其是,我希望能有代码高亮的功能。 找过几个 Vim preview 的插件: vim-preview 没有代码高亮 vim-instant-markdown 太复杂,不需要,也没有代码高亮 vim-markdown-preview 也是没有代码高亮,同时,也太重 … 以上, 所以就自己写了一个插件。markdown-preview.vim。主要是满足自己的一个需求。 不用实时预览 代码自动识别高亮 主题可自定义 同时,代码的主题自定义也在开发中。 安装和使用的方法也很简单。 推荐使用 Bundle 和 Vundle 进行安装 添加 Bundle ‘MikeCoder/markdown-preview.vim’ 到 vimrc 或者 vimrc.bundles 里面 执行 BundleInstall 进行安装就可以了 BTW:推荐使用 k-vim 做为 VIM 的配置 插件使用方法 正常编辑你的.md 或者.markdown 文件。 如果需要进行预览的时候就可以执行:MarkdownPreview default 命令,在浏览器中进行预览 default是主题,插件自带有default 和 GitHub 两个主题。 你也可以添加快捷键的方式简化操作,如:map m :MarkdownPreview GitHub 自定义 如果你想添加自己的主题。可以按照如下步骤: 打开你的.vim 文件夹,一般位于$HOME/.vim 然后找到 MarkDownRes 文件夹 进入就可以看到 default.css 和 GitHub.css 两个文件 将你的 css 文件添加到这,如 example.css 然后回到 vim,执行:MarkDownPreview example 就可以用 example.css 的效果渲染你的 markdown 文档了 代码高亮 插件自带有代码高亮功能。如下的 python 代码: import re import inspect __version__ = ‘0.7.1’ __author__ = ‘Hsiaoming Yang lepture.com>’ __all__ = [ ‘BlockGrammar’, ‘BlockLexer’, ‘InlineGrammar’, ‘InlineLexer’, ‘Renderer’, ‘Markdown’, ‘markdown’, ‘escape’, ] _key_pattern = re.compile(r’\s+’) _escape_pattern = re.compile(r’&(?!#?\w+;)’) _newline_pattern = re.compile(r’\r|\r’) _block_quote_leading_pattern = re.compile(r’^ *> ?’, flags=re.M) _block_code_leadning_pattern = re.compile(r’^ {4}’, re.M) _inline_tags = [ ‘a’, ’em’, ‘strong’, ‘small’, ‘s’, ‘cite’, ‘q’, ‘dfn’, ‘abbr’, ‘data’, ‘time’, ‘code’, ‘var’, ‘samp’, ‘kbd’, ‘sub’, ‘sup’, ‘i’, ‘b’, ‘u’, ‘mark’, ‘ruby’, ‘rt’, ‘rp’, ‘bdi’, ‘bdo’, ‘span’, ‘br’, ‘wbr’, ‘ins’, ‘del’, ‘img’, ‘font’, ] _pre_tags = [‘pre’, ‘script’, ‘style’] _valid_end = r'(?!:/|[^\w\s@]*@)\b’ _valid_attr = r”'”[^”]*”|'[^’]*’|[^'”>]”’ _block_tag = r'(?!(?:%s)\b)\w+%s’ % (‘|’.join(_inline_tags), _valid_end) 在预览效果中,就是如下的效果: 希望大家喜欢。