我的 2016 年终总结

又到了写年终总结的时候了,时间过得真鸡儿快啊。虽然没有 Maxine Caulfield 那样操控时间轴的能力,但是回顾一下我在 2016 的大事件的能力还是有的:

概览

过去的一年里,本博客的基本访问情况如下图:

Google Analytics Overview

这期间,本博客一共迎来了 36,637 位用户,他们一共产生了 56,667 次会话以及 96,728 次浏览。平均每天 100 位用户、155 次会话以及 265 次浏览。

比起去年好了一些,但还是要加把劲让更多的人知道我啊 (`ε´ )

GA Traffic Source

流量大部分是来自 Google 和直接访问,还有其他的一些搜索引擎(让我惊讶的是百度的流量排在第三,要知道我可是完全放弃了对度狗的 SEO 的)和第三方引荐。

因为 Ghost 博客系统并没有 WordPress 那么强大的文章管理功能,所以要做统计只能自己去数据库里 SQL 查询啦 |ー` )

过去的一年中,我写了 57 篇博文:

SELECT COUNT(*) FROM `posts` WHERE `published_at` > '2016-01-01'

SELECT title, published_at, post_views.pv  
FROM `posts` INNER JOIN `post_views` ON posts.
阅读全文→

博客启用新域名 Blessing.Studio

原来的域名 prinzeugen.net 一直被人吐槽说不好记,但苦于没有什么中意的其他的好记的域名,所以换域名的计划就一直搁置着。

其实我以前就听说过 .studio 这个顶级域名了,但是当时(15 年中旬)还没有开放注册,所以后来也就忘却了它的存在。然而几天前在机缘巧合下我得知了它早在 2015.10 就已经开放注册了,并且现在一年只要百来块就能搞到,于是。。

QQ20161224205054.png

这也是没有办法的事情嘛~ QQ20161225134536.jpg

そういうわけで、博客要迁移至新域名 blessing.studio 啦~

目前 prinzeugen.net 已经 301 至新域名,并且提交了 Google Search Console 和 Disqus 评论的转移请求,估计过个一天可以迁移完毕。

嗯?你说 .studio 不能备案,对百度 SEO 不好?

Who cares. QQ20161225135139.jpg

阅读全文→

Laravel 使用 whoops 处理错误最优雅的姿势

filp/whoops 这个错误处理类库有什么好处我这里就不赘述了,谁用谁知道。

Laravel 在 4.x 时代是有集成了 whoops 的,但是在 5.x 去掉了。不过作为一个 out-of-the-box 的错误处理类库,我们依然可以很方便地将 whoops 带回 Laravel 中。

网上有很多文章都讲述了 Laravel 使用 whoops 的方法,但总有些小问题(像是代码太丑了啊,代码太丑了啊之类的)。其中我认为最优雅的实现是这篇文章所描述的:Bringing Whoops Back to Laravel 5,我下面的也是基于他给出的代码修改的。


安装 whoops 之类的步骤我这里就不说了,这些在它的 README 上都有。安装完后打开 app/Exceptions/Handler.php 这个文件,进行如下修改:

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $e
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $e)  
{
    if ($e instanceof
阅读全文→

手动修改 Laravel url() 函数生成的 URL 中的根地址

大家都晓得 Larevel 的一票帮助函数中有个 url(),可以通过给予的目录生成完整的 URL,是非常方便的一个函数:

// return: https://skin.dev/user/profile
url('user/profile')  

但是这玩意生成的 URL 中要补完的部分是框架内部根据 Request 自动判断的,而自动判断出的东西有时候会出错(譬如在套了一层反向代理之类的情况下)。

文档上并没有提到我们要如何才能自定义它生成的 URL 中的根地址和协议头部分(http(s)),这就非常吃瘪了。那我们要咋办呢?

首先我们来看看 url() 被定义的位置:

# File: src/Illuminate/Foundation/helpers.php

/**
 * Generate a url for the application.
 *
 * @param  string  $path
 * @param  mixed   $parameters
 * @param  bool    $secure
 * @return Illuminate\Contracts\Routing\UrlGenerator|string
 */
function url($path = null, $parameters = [], $secure = null)  
{
    if (is_null($path)) {
        return app(UrlGenerator::class);
    }

    return app(UrlGenerator::class)-&
阅读全文→

Artisan::call('migrate') 在 APP_ENV 为 production 时不工作的解决方法

最近把版本库里 .env.exampleAPP_ENV 字段值从原来的 local 改为了 production,原意是为了更好的区分开发和生产环境。

然而今天在主机壳的虚拟主机上测试我的程序的时候(准备把演示站搬过去),却出现了奇怪的问题——数据库 Migration 不管用了。

我是在控制器里通过调用 Artisan::call('migrate') 来执行数据库迁移操作的(毕竟虚拟主机哪来的 shell 访问),但是这次这条命令竟然没有执行任何事务。

通过 Artisan 执行 Command 的简化流程大概是像这样的:

Illuminate\Foundation\Console\Kernel  
↓
Illuminate\Console\Application  
↓
Illuminate\Console\Command  
↓
Illuminate\Container\Container  
↓
// 这个就是执行 Artisan::call('migrate') 时调用的类
Illuminate\Database\Console\Migrations\MigrateCommand  
↓
……

经过一段时间的排查,最后锁定了是在 MigrateCommand 这个类停止继续往下执行的,即接下来的脚本都没有被执行到,也就是说问题就出在 MigrateCommand 这里。

在这个类的 fire 方法里,我们可以看到:

if (! $this->confirmToProceed()) {  
    return;
}

就是因为这个 $this->confirmToProceed() 返回了一个 bool(false),才导致脚本在这里停止执行了。

这个

阅读全文→

使用 Certbot 更新 Let's Encrypt 证书

Let's Encrypt 证书即将过期时会给你发送邮件,这个还是比较贴心的。这样也就不会陷入证书过期却没发现的尴尬境地(Let's Encrypt 的证书只有 90 天的有效期)。

2826714bc9645e4b9828433b8e674800.png

以前我写过使用 Certbot 这个工具申请证书的文章,而同样使用这个工具更新证书只需要一行命令:

certbot renew --post-hook "service nginx reload"  

certbot 这个脚本的位置呀名称啥的自己看着改,对于我来说是 ./certbot-auto。加了个钩子可以让它在证书更新更新完毕后重载 Nginx 配置来更新证书。

b2134999636066c29c9e93a40bd88a57.png

不想每次都登上去更新的也可以把上面那行脚本加入 crontab(crontab -e),让它每个月执行一次:

# 这里用绝对路径,保险一点
0 0 1 * * /home/xxx/certbot-auto renew --post-hook "service nginx reload" >/dev/null 2>&1  

P.S. Xshell 管理远程机子比起单纯的终端来还是很方便的,而且最近也对 Home/School 发放免费许可了。

参考链接:Renewing certificates - Certbot User Guide

阅读全文→

PHP 远程文件下载的进度条实现

PHP 实现远程下载文件到服务端并不是什么新鲜玩意,用 cURLfile_get_contentsfopen 等都能够轻易实现。

但是这几种常规的方法都是在一个线程内下载文件,等文件下载完毕以后才能返回 HTTP 响应。所造成的结果就是用户在页面上点击「下载到服务器」按钮后,会看到空白页和加载的小菊花转啊转,转好久之后才出现「下载成功」的页面。

当然,我上面所举例的情况是只使用纯粹的表单 POST 发送请求的情况。现在的话就算再不济也一般会使用 ajax 发送请求,然后在前台放个加载动画,等收到下载成功的回应之后再进行下一步操作。

但是!即使是去掉了恶心的且需要等待的空白页,这样做还是对用户体验有不好的影响。没有具体的下载进度,只有一个一直转呀转的菊花图,估计挺多用户都无法坐和放宽吧(至少对于我来说是这样的)

而我一个 PHP 项目的一键更新系统正好需要重构,遂研究了如何在 PHP 作为后端时显示远程文件下载进度条,并捣鼓出了个像样的解决方案,在这里分享一下。


0x01 原理

也许你在搜索「PHP 下载 进度条」的时候会看到有些文章使用 PHP 的输出控制函数(flush 之类的)控制缓冲区来实现进度条。但是——

那都是狗屁!

没有人可以保证用户的 PHP 关闭了默认开启的 output buffering,也无法保证 浏览器 / Web Server 不对脚本输出进行缓存。如果上述两者其中之一处于开启状态的话,你就会喜闻乐见的发现本应该慢慢增长的进度条会在等待完漫长的 xx 秒后一下子蹦到 100%(因为控制前端进度条长度的语句被缓存起来,在脚本执行结束后一并发送了,而不是一块一块地传给浏览器)

关于上面缓冲区控制的进度条就是辣鸡的更多讨论可以查看文章底部的参考链接。

闲话休提。那么我们该如何实现下载进度条的更新呢?

首先通过后端一点点输出控制进度条语句的方案已经

阅读全文→

耳机插头修理技巧

群里大佬 pan爷低价出了我一个耳机插头附近断线的 ATH-ES55,这已经是挺久以前的事了。当时我去某宝上买了个耳机插头,刮掉耳机线的漆包草草焊上去就完事了(好歹是挺不错的耳机你就这样对待啊喂)。然而最近耳机插头接触不良了,我就寻思着重新焊下,在这里记录一下需要注意的地方。

虽然修耳机这种事我在小学的时候就鼓捣过了,但总归当时还是不专业,也没啥像样的工具。而且最近听的好歹也算是 HiFi 耳机,不能再用之前那种乡巴佬方法了(笑)。在网上搜了一些,学到了不少,在此记录一下。

下面几乎都是盗图,侵删w

3.5mm 耳机插头接线图

82f3a4e9e890e1d4ab91352def12f33a.png

以上是三节的插头,至于四节的手机耳机接头。。论外吧,要用自己查去(美标国标啥的),反正我又不修这玩意。

万用表

接线前请注意用万用表分清耳机线上的左右声道,一般来说金色的是地线。没有万用表也可以把线头贴在插头上,然后听那种左右声道分明的音乐来区分(没错就是以前的我www)

顺带一提我当时听的是鹿乃翻唱的「ハロ/ハワユ」(笑

去掉漆包线的漆

关于漆包线上面那一层薄薄的漆,我以前都是用小刀刮或者打火机烧。但是,这种做法是不对的。

正确做法应该是要给耳机上锡:

  1. 把耳机线浸到熔化的松香里
  2. 把沾有松香的耳机线浸到熔化的焊锡里大概 5s
  3. 上好焊锡后焊接到插头上时也会方便很多(不会老是散开)

注意材料放置的顺序!

---[插头外壳]---[热缩管]--[焊接点]--[插头本体]>

你体会过那种焊接完发现热缩管没套的绝望吗(死目)?

一定要用热缩管哟

一般来说卖耳机插头的店家都会随包附送几个热缩管。请务必使用这些热缩管套在焊接点上,加热使其收缩之后再套上外壳,不然你会拥有一段时间后发现接触不良旋开插头外壳一看发现所有的线被扭在一起的美好经验。

ddcf02a89e632cb5365f2184498353c7.png

另外也要注意在测试完耳机功能正常后再用热风机吹,不然也会体验到绝望的滋味ww

83ef065ad7a59aebd1ea017d3cd9d02d.png

关于焊接

我焊的时候插头上死活上不好锡,最后是直接滴上去的,拜此所赐焊接部分也是奇丑无比(扶额)。焊接这玩意还是得要经验,得多练习啊。

啊对,

阅读全文→

Laravel 框架下的插件机制实现(一)—— 构建插件系统

插件,即 Plug-in(又有外挂、Extension、Addon 等叫法),是一类特定的功能模块,通过和应用程序的互动,用来替应用程序增加一些所需要的特定的功能。插件的特点是:

  • 当你需要它的时候激活它,不需要它的时候禁用/删除它;
  • 无论是激活还是禁用都不影响系统核心模块的运行。

也就是说插件是一种非侵入式的模块化设计,实现了核心程序与插件程序的松散耦合。上面的介绍部分摘自 中文 WikiPedia

虽然现在网上有很多 PHP 的插件机制的实现(当然我指的是英文内容,中文关于 PHP 插件机制的搜出来现在还是那么点破文章),譬如 FoolCode/Plugin,或者 WordPress 的插件实现。

不过我今天想介绍的是如何使用 Laravel 的服务容器、事件机制等功能来实现一个比较优雅的插件机制。

Greatly inspired by Flarum,在此致谢 ヾ(´ω゚`)

这是这个系列的第一篇,主要描述了该如何构建一个插件系统。第二篇则会讲述该如何利用这个插件系统开发一个插件。

该开始了


0x01 目录结构

首先我们要想好每个插件的目录结构 应该是啥样的,譬如哪里存储 插件元信息,在哪里给插件存放 初始化代码,在哪里给插件存放自己的 业务逻辑 等等。

举些栗子:WordPress 的插件信息是直接存储在 PHP 文件的注释里的,Flarum 甚至是直接用 composer 来管理插件(每个插件都是一个 composer 包,插件信息就在 composer.json)里,也是猛的不行 ( ;゚д゚)

然而我们还得照顾虚拟主机用户,所以直接用 composer

阅读全文→

实现点击按钮加载 Gist 代码块

GitHub Gist 应该也是很多人都在用的功能了,我也喜欢把一些大段的代码块贴上去而不是直接写在博文里。

但是 Gist 的 embed js 是在页面加载的时候就直接开始加载了的,所以或多或少还是对网页的性能有所影响。受上次搞的「点击加载 Disqus」启发,我也准备写一个点击加载 Gist。

最开始我尝试了传统方法,即动态创建一个 <script> 标签并添加到文档流中。然而却得到了一个这个错误:

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.  

看来动态加载的 js 文件中是不允许写文档流的。而不巧的是 Gist 提供的 embed js 正是使用 document.write 来实现的。

在网上找了一下,虽然也有些利用 iframe 的解决方法,譬如:Dynamic Gist Embedding,但都感觉不咋好(嫌麻烦)

后来,我在 SegmentFault 的动态预览 Gist 的功能上发现了,Gist 竟然能够提供 JSONP 式的回调!

阅读全文→