無標題文檔

emojify.php, 让 Typecho 用上 Emoji 表情

Emoji 能让单调的沟通多些色彩,团队这边近期也开始使用 Emoji 作为 git 提交的提示符使用

其中,有很多相关的命令行工具可以使用。但翻遍了一圈,竟然没有 PHP 相关的类库,于是就自己写了一个( 很久没写 PHP 了,感觉生疏了好多 😑 )。

表情符号的数据源自好几个项目,所以从数据量上面来说应该是目前 emojify 类工具的超集了,超过了两千多条。例如,可以完全放心使用 emoji-cheat-sheet 下的定义。

具体的对应数据可以在这里查看(文件很大),顺便还可以看看自己系统对于 Emoji 字符的支持情况。

因为是 PHP 相关,所以在完成这个简单的类库以后顺便做成了 Typecho 插件。这个插件安装很简单,只要在 Typecho 的 Plugin 目录下无脑 git clone :

git clone git@github.com:mingcheng/emojify.php.git  Emojify

然后使用管理员登录,开启插件就可以使用了 😘。Github 的地址在 https://github.com/mingcheng/emojify.php 这里,欢迎测试以及提交 Issues(当然,你也可以留言测试下 Emoji 😄 )。

- eof -

rx-filewatcher.java,基于 NIO WatchService 的 Reactive 封装

嫌我啰嗦的话,直接看代码: https://github.com/mingcheng/rx-filewatcher.java

在写个小东西需要监控文件改变状态的时候,用到了 NIO 东西很方便也好用。但是,在使用 WatchService 的时候碰到了些问题。

首先,使用原生的接口必须开启个线程,然后使用个持续循环去读取 WatchEvent (这也是大部分教程描述这样子做的):

WatchKey key;
while ((key = watchService.take()) != null) {
    for (WatchEvent<?> event : key.pollEvents()) {
        //process
    }
    key.reset();
}

这样子做有几个不好的地方:一是开了个匿名线程,如果后期代码也有类似的情况,线程就不好处理,而且很容易无法监控到这个线程的状态;第二就是破坏了代码原有的业务逻辑,读到这里如果阅读代码的小伙伴不熟悉 WatchService 就很难理解。

其次,WatchService 的注册是一次性的,也就是说无法监控到子目录以下的情况,必须随时监控是否创建了子目录然后再对应 register 子目录 watch(这样子一个简单的功能就会搞得非常的复杂)。

我考虑到使用 Rx 重写这块的代码,然后翻阅 Github 的时候发现已经有人造了这个轮子。代码量不大,但核心功能是我想要的,只是最新更新已经在三年前了。

集成了 Helmbold 这个哥们的库以后,又发现了几个问题:

  1. 这个库是写在三年前的,还在用 Rx1 的库,和我目前的 Rx2 有部分的不兼容;
  2. dispose 部分有问题,dispose 以后线程没有终止还在运行;
  3. 获取到 WatchEvent 的 Context 只有文件名没有路径。

因为代码量本身就不是很多,同时考虑到这块还是需要以后自己维护,因此 fork 了他的代码自己做了些改造

对比以上的原生代码,使用 Rx 封装的代码就会干净和直观很多:

PathObservables
  .subscribeOn(Schedulers.io())
  .watchRecursive(Paths.get("some/directory/"))
  .subscribe(event -> System.out.println(event));

然后,基本上已经满足了我的需求。

顺便说下,WatchService 里面还有个坑,就是使用网络磁盘映射的时候,可能会造成 WatchEvent 会有丢失,在这里有详细的讨论,这是目前 NIO 已知的 Bug。如果你对文件状态监控要求实时性比较高,建议不要使用 WatchService 使用其他的方案。

相关的资源:

  1. https://github.com/mingcheng/rx-filewatcher.java
  2. https://docs.oracle.com/javase/tutorial/essential/io/notification.html

后记,原作者已经 merge 了我的 pull requests

Arch Linux 下安装 EM7345 4G 网卡小记

💊 后记(10/18):人生苦短,后来发现安装完 Manjaro Linux 以后,这个 4G 网卡已经都配置好可以直接使用。

-

最近买了台二手的 ThinkPad X1 Carbon 用来当做开发用机,系统方面选择安装了 Arch Linux 。

由于硬件方面并不是算很新,所以系统安装硬件的过程很顺利。还注意到 X1 Carbon 支持 4G 网卡,手头上刚好有一张联通的流量卡,考虑能否用起来。但是打开了盖板以后发现只是预留了插槽和天线,并没有 4G 网卡模块。

https://friable.rocks/_/2018_09_20/1537424625.jpg

根据 ArchLiux 的文档,淘宝上找了家靠谱的卖家,名为  Sierra EM7345 的模块很便宜,原装拆机的只要一百多,而且对 Linux 的支持很好,于是下单。

还是要夸下 ThinkPad 的,硬件安装非常方便,拧开螺丝就可以安装上去。硬件安装好了以后,启动使用 # lsusb 就能看到硬件了。

然后安装对应的软件包:

# pacman -S usbutils usb_modeswitch modemmanager mobile-broadband-provider-info

但是启动 ModemManage 的时候发现了问题,有报错信息:

ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:19.0': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:1c.1/0000:04:00.0': not supported by any plugin

好在 ArchLinux 的 Wiki 上有对应的信息说明,对应的应该是 udev 的权限问题。于是增加相应的配置文件,发现并不成功。然后搜索了一圈,并没有对应的解决方案。

后来,在 rules.d 目录中发现了另外个文件 /lib/udev/rules.d/77-mm-sierra.rules 里面虽然没有对应的 Vendor, 但是从注释可以看出来这是 Sierra 相对应的 udev 权限配置文件,于是将配置加入到 /etc/udev/rules.d 其中。

注意,直接修改 /lib/udev/rules.d/ 目录的 rule 文件可能在下次更新的时候被覆盖掉。

ACTION!="add|change|move|bind", GOTO="mm_sierra_end"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", GOTO="mm_sierra"
GOTO="mm_sierra_end"

LABEL="mm_sierra"

# @see https://wiki.archlinux.org/index.php/USB_3G_Modem
# @see https://support.lenovo.com/us/en/solutions/pd031021
# @see https://www.freedesktop.org/software/ModemManager/api/1.8.0/ref-overview-modem-filter.html

ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{FILTER_RULE_TTY_ACM_INTERFACE}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_CDC_WDM}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_EXPLICIT_WHITELIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_BLACKLIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DEFAULT_ALLOWED}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_PLATFORM_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_WITH_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_VIRTUAL}="1"

LABEL="mm_sierra_end"

然后,刷新 udev 的配置,并通知 kernel 启用新的权限:

# udevadm control -R && udevadm trigger

再看下对应设备文件文件的 rule 是否已经启用成功:

# udevadm info /dev/cdc-wdm0

对应的输出

P: /devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
N: cdc-wdm0
E: DEVNAME=/dev/cdc-wdm0
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
E: FILTER_RULE_TTY_ACM_INTERFACE=1
E: ID_MM_CANDIDATE=1
E: ID_MM_DEVICE_PROCESS=1
E: MAJOR=180
E: MINOR=0
E: MM_FILTER_RULE_CDC_WDM=1
E: MM_FILTER_RULE_EXPLICIT_WHITELIST=1
E: MM_FILTER_RULE_NET=1
E: MM_FILTER_RULE_TTY=1
E: MM_FILTER_RULE_TTY_BLACKLIST=1
E: MM_FILTER_RULE_TTY_DEFAULT_ALLOWED=1
E: MM_FILTER_RULE_TTY_DRIVER=1
E: MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY=1
E: MM_FILTER_RULE_TTY_PLATFORM_DRIVER=1
E: MM_FILTER_RULE_TTY_WITH_NET=1
E: MM_FILTER_RULE_VIRTUAL=1
E: SUBSYSTEM=usbmisc
E: USEC_INITIALIZED=10423134

配置生效,kernel 的配置看起来一切正常。

这时候启动 ModemManage 还是发现有错误信息,但是过了一段时间以后对应的 Modem 已经是 enable 可用状态(这点百思不得解,可能是 4G 网络连接需要初始化比较慢)。

Sep 20 14:25:44 x1-carbon ModemManager[2047]: opening device...
Sep 20 14:25:44 x1-carbon ModemManager[2047]: [/dev/cdc-wdm0] Read max control message size from descriptors file: 512
Sep 20 14:25:44 x1-carbon ModemManager[2047]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem: state changed (unknown -> disabled)
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (disabled -> enabling)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabling -> enabled)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (unknown -> registering)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (registering -> home)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabled -> registered)

然后在控制台下使用 mmcli 获取相关的信息,具体的 mmcli 可以参考这里的文档。发现了一个不大不小的问题,就是 sim 卡的日期还是 20040101 ,很明显没有和网络同步(后来发现这块并不重要)。

$ mmcli -m 0 --simple-status

/org/freedesktop/ModemManager1/Modem/0
  -------------------------
  Status |          state: 'connected'
         | signal quality: '67' (recent)
         |          bands: 'unknown'
         |    access tech: 'lte'
  -------------------------
  3GPP   |   registration: 'home'
         |  operator code: '46001'
         |  operator name: 'CHN-UNICOM'
         |   subscription: 'unknown'

再尝试启动 modem-manage-gui 看其信息,过一会发现已经注册了网络并能获取对应的信息。

https://friable.rocks/_/2018_09_20/1537427550.png

硬件方面的配置没有问题,那么软件方面的设置就简单多了。然后使用 Gnome 的网络配置,配置 APN 已经相关的网络参数,过一会就能看到久违的信号了。

https://friable.rocks/_/2018_09_20/1537427570.png

总结下,这个问题困扰了我一个晚上的时间,然后也走了很多的弯路。

主要还是在硬件资源的权限方面,这块还是要多看 Arch 的 Wiki 以及对应软件的 Manual(开源社区的锅都扔给用户了)。

相关的参考链接:

PS,有什么靠谱的联通流量卡推荐下?

- eof -

集成 Dubbo Spring Boot 时的 ZooKeeper 版本问题

微服务节点使用 Spring Boot 会方便很多,在搭建 Spring Boot 的时候碰到了个不大不小的问题,在这里记录下。

主要情况是配置好了 Dubbo Spring Boot 启动 Provider 节点的时候发现异常,抛出了两个错误:

Caused by: org.apache.zookeeper.KeeperException$UnimplementedException: KeeperErrorCode = Unimplemented for ...
Caused by: java.lang.IllegalStateException: KeeperErrorCode = Unimplemented for ...

检查堆栈发现是 Dubbo 建立 ZooKeeper 链接的时候,就直接抛出了异常:

// from com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(ZookeeperRegistry.java:116) ~[dubbo-2.6.2.jar:2.6.2]

protected void doRegister(URL url) {
    try {
        this.zkClient.create(this.toUrlPath(url), url.getParameter("dynamic", true));
    } catch (Throwable var3) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + this.getUrl() + ", cause: " + var3.getMessage(), var3);
    }
}

但是 ZooKeeper 的服务器配置是正常的,百思不得:

$ echo stat | nc localhost 2181
Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT

然后继续查看 KeeperException$UnimplementedException 异常的定义,

    public static class UnimplementedException extends KeeperException {
        public UnimplementedException() {
            super(Code.UNIMPLEMENTED);
        }
    }

对应的调用:

// @from org.apache.zookeeper.ZooKeeper.create
public String create(final String path, byte data[], List<ACL> acl,
        CreateMode createMode, Stat stat, long ttl)
        throws KeeperException, InterruptedException {
    final String clientPath = path;
    PathUtils.validatePath(clientPath, createMode.isSequential());
    EphemeralType.validateTTL(createMode, ttl);

    final String serverPath = prependChroot(clientPath);

    RequestHeader h = new RequestHeader();
    setCreateHeader(createMode, h);
    Create2Response response = new Create2Response();
    if (acl != null && acl.size() == 0) {
        throw new KeeperException.InvalidACLException();
    }
    Record record = makeCreateRecord(createMode, serverPath, data, acl, ttl);
    ReplyHeader r = cnxn.submitRequest(h, record, response, null);
    if (r.getErr() != 0) { // 这里抛出的异常
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                clientPath);
    }
    if (stat != null) {
        DataTree.copyStat(response.getStat(), stat);
    }
    if (cnxn.chrootPath == null) {
        return response.getPath();
    } else {
        return response.getPath().substring(cnxn.chrootPath.length());
    }
}

那么基本上可以判定 1、是 ZooKeeper 本身链接的问题,和 Dubbo 没有关系;2、实际上 ZooKeeper 本身的服务已经连接上,但 makeCreateRecord 方法调用出现了异常。

那么,可以得出结论是 Java 端和 ZooKeeper 服务端出现了通讯问题。然后发现日志上 jar 包的版本是 zookeeper-3.5.3-beta.jar ,同时查看了下 Manifest 内容如下:

Implementation-Title: org.apache.zookeeper
Implementation-Version: 3.5.3-beta
Implementation-Vendor: The Apache Software Foundation

然后再运行客户端查看了下服务器的版本,两者版本不一致,突然觉得应该是版本的问题。

$ echo stat | nc localhost 2181
Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT

很明显 Java 端的版本比服务端的版本要新,那么考虑使用和服务端同个版本的 jar 包试试。

修改对应 build.gradle 的 dependencies 如下,不要纳入 dubbo-spring-boot-starter 提供的 ZooKeeper 的 jar 包

dependencies {
    // ...
    compile('com.alibaba.boot:dubbo-spring-boot-starter:0.2.0') {
        exclude(module: 'org.apache.zookeeper')
    }
    compile 'org.apache.zookeeper:zookeeper:3.4.13'
}

然后再运行 gradle clean bootRun -x test 发现 Dubbo 正常启动,问题解决。

这个问题有点坑,同时很难发现,我又查了下对应的资料。官方其实已经有对应说明,简单的说就是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 有不兼容的情况。

而回过头来看 dubbo-spring-boot-starter 包的 pom.xml 定义,对应的 ZooKeeper 这块的引用是这样子的:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
</dependency>

没有指定版本,也就是默认是取 Maven 库的最新版本,目前是 3.5.4-beta 自然对应本地版本 3.4.13 就有冲突了(竟然不向下兼容,坑)。

相关讨论,看来被坑的不止我: https://stackoverflow.com/questions/35734590/apache-curator-unimplemented-errors-when-trying-to-create-znodes

最后顺便说一句,如果有用到 Spring Cloud 相关的 Zookeeper 组件,也要留个心眼:

dependencies {
    // ...
    compile('org.springframework.cloud:spring-cloud-starter-zookeeper-config') {
        exclude group: 'org.apache.zookeeper', module: 'zookeeper'
    }
    compile('org.springframework.cloud:spring-cloud-starter-zookeeper-discovery') {
        exclude group: 'org.apache.zookeeper', module: 'zookeeper'
    }
    compile 'org.apache.zookeeper:zookeeper:3.4.13'
}

这样子就能保证统一引用的是指定版本的 ZooKeeper 的 jar 包了。

总结下,dubbo-spring-boot-starter 项目目前相对来说还是比较新,相关的文档还是没跟上,但是已经能够日常和生产环境使用了,还是推荐使用简化配置提高些开发效率。

- eof -

Mermaid,一个生成结构图的工具

以前在设计软件架构的时候免不了使用 Visio 、OmniGraffle 等这样的工具来生成结构图。

它们普遍有个缺点,「成也 GUI、败也 GUI」。就是无法将自己想法比较直观的直接使用文本来输出,有时候看似鼠标点点拖拖所见即所得,然而思路就在这时候被一闪而过。

说到这里,有可能你会想到使用 Graphviz 等文本图形描述语言来生成结构图。它的确很好用,但是无法直接嵌入到 Web 中。通常做法就是使用 Graphviz 生成图片以后,上传到 Web 然后再插入到页面。

文档和图其实都是内容,我们不应该因为排版的问题耗费太多的时间,精力应该更加专注到内容本身。

说了那么多,这就是我推荐 Mermaid 初衷

Generation of diagram and flowchart from text in a similar manner as markdown.

多种类型的图表

目前 Mermaid 成熟的结构图模块可以生成 流程图、序列图以及甘特图。它们生成的语法很简单,例如以下的例子:

流程图

使用源代码

graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;

就可以生成以下的流程图

flowchart

序列图

使用以下的序列图定义

sequenceDiagram
    participant Alice
    participant Bob
    Alice->John: Hello John, how are you?
    loop Healthcheck
        John->John: Fight against hypochondria
    end
    Note right of John: Rational thoughts <br/>prevail...
    John-->Alice: Great!
    John->Bob: How about you?
    Bob-->John: Jolly good!

就可以生成以下的序列图

sequence

甘特图

使用以下的甘特图定义:

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section A section
        Completed task            :done,    des1, 2014-01-06,2014-01-08
        Active task               :active,  des2, 2014-01-09, 3d
        Future task               :         des3, after des2, 5d
        Future task2               :         des4, after des3, 5d
        section Critical tasks
        Completed task in the critical line :crit, done, 2014-01-06,24h
        Implement parser and jison          :crit, done, after des1, 2d
        Create tests for parser             :crit, active, 3d
        Future task in critical line        :crit, 5d
        Create tests for renderer           :2d
        Add to mermaid                      :1d

就可以生成如下的甘特图。

gantt

如果你还是感觉压力,可以尝试使用在线编辑器去感受下

Markdown 集成

Mermaid 很方便和 Markdown 集成使用,甚至类似的代码段以后的嵌入即可以使用:

graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;

Markdown 加载了 Mermaid 插件以后,解析器会自动将代码块中的代码渲染为 SVG 格式的结构图。

gitbook 集成

如果您习惯使用 gitbook 编写技术文档,那么 Mermaid 会让你事半功倍。只需要在 package.json 中加入对应的 devDependencies 就可以直接使用。

  "devDependencies": {
    "gitbook-cli": "^2.3.2",
    "mermaid": "^8.0.0-rc.6",
    "..."
  }

使用 CSS 自定义样式

Mermaid 使用 JavaScript 编写并使用 SVG 输出,因此天生自带了 Web 相关的属性。可以很方便的使用 CSS 定义对应的样式类(Style CSS)来自定义样式。

架构简单便于扩展

Mermaid 只是个结构图的生成引擎,如果你想让它生成其他的结构图,那么可以花点时间来扩展它。简单的说,扩展 Mermaid 生成结构图只需要两个因素:语法定义、以及图形绘制逻辑。

在这里有个比较简单的介绍如何扩展 Mermaid,当然你也可以参考现成的结构图源代码来对比输出。

新项目

Mermaid 是个比较新的工具,功能和文档都在不断的完善中。我发起了个 Mermaid 中文文档翻译计划,目前基本的文档都已经翻译完成,可以在这里阅读。

如果您有任何的意见或者建议,也欢迎您不吝提出。

- eof -

我的照片

嗨!我叫「明城」,八零后、孩她爸、码农、真谷粉、伪果粉、微软无脑黑、宁波佬,现居杭州。 除了这里,同时也欢迎您关注我的 GitHub (2) 、 TwitterInstagram 等,谢谢。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 其实无所谓叫什么名字,作为码农知道取名是件很难的事情。最后想到的这个名字,其实都没啥特别的含义,系统默认的文件名而已。

作为八零后,自认为还仅存点傲娇式的幽默感,以及对平淡生活的追求和向往。 为了免得对号入座和不必要的麻烦,声明本站点所持观点仅代表个人意见,不代表自己所服务公司的立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

文章

项目

微信公众号