如何用 Markdown 写 PPT

reveal-js 是通过网页来制作 PPT 的 JavaScript 框架,通过它可以轻松制作精致的网页 PPT。它自身也支持 Markdown。
但使用过几次之后,感觉对于日常使用还是稍重了些。每次新建 PPT 都需要复制粘贴 boilerplate code。

reveal-md 让你从这些繁琐的步骤中解放出来。它使用 reveal-js 自带的 Markdown 功能,让你只需写 Markdown,其他的事它帮你搞定。

安装

简单地说下如何使用。

1
2
3
4
mkdir slides && cd slides
yarn init -y
yarn add reveal-md
yarn exec reveal-md demo

此时炫酷的 PPT 网页就在你眼前呈现了。

语法

标准的 Markdown 语法,默认 --- 作为 PPT 页面的分割线。

1
2
3
4
5
6
7
8
9
10
11
12
# Title
* Point 1
* Point 2
---
## Second slide
> Best quote ever.
Note: speaker notes FTW!

演示命令

演示命令 reveal-md,启动本地 server 并使用 reveal.js 渲染 Markdown 文件。

1
yarn exec reveal-md demo.md

更详细的配置请查看 reveal-md 文档reveal-js 文档

Rails 通过 path 实现 subdomain —— default_url_options 的妙用

最近在开发微信开放平台,需要通过 url 来区别不同的第三方。最简单直接的方法,就是通过 subdomain 来实现。
比如有两个第三方 A、B,那么 A 的域名为 A.example.com,B 的域名为 B.example.com。但这次由于 https 证书的问题只能通过 path 来实现,也就是 A 的域名为 www.example.com/A,B 的域名为 www.example.com/B。

但通过 path 实现会有一个严重的问题,就是如何保证生成的 url 带有第三方的信息。

1
2
3
scope path: '/:third_party' do
resources :orders
end

上面的配置使用 orders_path 时会报错 missing required keys: [:third_path]
最笨的办法莫过于给所有的 url 手动加上,但这方法随着项目增大会变得不可行。

我们希望 url 的生成可以更加智能,当访问 url 为 www.example.com/A 时,使用 orders_path 可以自动生成 /A/orders。从逻辑上,这是行得通的。

首先想到 route 可以添加 default 配置,于是做了以下尝试,可惜报错了。在 route 时只能配置静态的默认值。

1
2
3
4
# undefined local variable or method `params'
scope path: '/:third_party', default: {third_party: params[:third_party]} do
resources :orders
end

那么,有没有类似的方法,提供 url default 配置的功能呢?这时就轮到 default_url_options 出场了。
default_url_options 的用处不仅仅是在 config 时设置默认的 host,每个 Controller 都有该方法,调用 url_helper 时会结合该方法生成最终的 url。

在对应的 Controller 里添加以下代码,这时调用 orders_path 就会根据域名中的 third_parth 生成对应的域名了。

1
2
3
def default_url_options
{third_party: params[:third_party]}
end

解决了生成 url 的问题,我们还需要解决测试时 url 的问题。这里我对 ActionController::TestCase 进行了 monkey patch,供大家参考。

1
2
3
4
5
6
7
8
9
10
11
class ActionController::TestCase
alias_method :orig_process, :process
def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
if @controller.is_a? WechatBaseController
params.merge!({app_alias: 'test'})
end
orig_process(action, method: method, params: params, session: session, body: body, flash: flash, format: format, xhr: xhr, as: as)
end
end

微信开发总结

最近开发了基于微信的相关项目,主要是微信的网页开发、微信支付以及发送消息这块的内容。项目本身没什么难度,但由于微信封闭的体系、混乱的配置以及分散的文档,爬了不少的坑,这里总结下经验。

文档

先说说微信的文档。微信的文档不是统一的,比如公众平台技术文档在一个网站,而支付文档在另一个网站,但支付文档所使用的 js 的相关文档又在前一个网站。除此之外,还有小程序文档开放平台文档。从这混乱的文档相信你也能或多或少体会到开发的难处。

配置

出于安全性(更多是封闭性)的考虑,微信的配置繁琐且复杂,这里我把整个流程都记录下来以供参考。
首先登录公众平台,在开发-基本配置里获得 AppSecret 并保存下来,之后网站就不会再显示开发者密码,只能重置。
同样在该页面,配置 IP 白名单。如果你需要接收用户公众号的消息以及事件推送,同一页面添加服务器配置。
然后,在设置-公众号设置-功能设置,按照说明对业务域名、JS 接口安全域名、网页授权域名进行设置。
如果有微信支付的需求,去微信商户平台,商户平台-产品中心-开发配置中设置公众号支付的授权目录。注意,授权目录必须是发起支付网址的上一级目录。举例来说,
发起支付的网址为 www.xxx.com/orders/22/pay,那么支付目录就必须为 www.xxx.com/orders/22/,填 www.xxx.com/ 或者 www.xxx.com/orders/ 都是不行的。再加上支付的授权目录只能填 5 个,发起支付的域名得注意设计。

PS: 没有公众号的朋友可以通过微信的测试号来试验除微信支付以外大部分的功能。

互联网创业核心技术:构建可伸缩的 Web 应用

《互联网创业核心技术:构建可伸缩的 Web 应用》整本书围绕“可伸缩”三个字,对 Web 应用的每一层展开了全面细致的解说。
如果你和我一样,对负载均衡、水平伸缩之类的概念不是很了解,或者不清楚它在整个架构中的使用,那么你应该来读下这本书。
读完之后你会发现架构这东西是有迹可循的,是围绕一系列基本的原则建立起来的。

一图胜千言,这本书不仅有着易懂的语言,而且有大量简洁的示意图。

FactoryGirl 源码浅析

FactoryGirl 是我个人十分喜欢的 Gem,它能很方便地模拟测试数据。使用技巧可见 FactoryGirl 技巧。出于兴趣我研究了下它的源代码,说实话比想象得要复杂。由于 FactoryGirl 配置的灵活性以及 Ruby 本身的语言特点,使得它代码整体上比较飘逸。
这里我会对它最基础的方法进行分析,版本为 4.8.0。

FactoryGirl.define

先从定义 Factory 开始。下面的代码定义了名为 user 的 Factory,它有一个名为 name 的属性,值为 Kay。

1
2
3
4
5
FactoryGirl.define do
factory :user do
name 'Kay'
end
end

正则表达式必知必会

匹配单个字符

匹配纯文本

正则:Ben
>
Hello, my name is Ben. Please visit my website at http://www.forta.com/.

匹配任意字符

. 匹配任意单个字符。在绝大多数的正则表达式实现里,. 只能匹配除换行符以外的任何单个字符。
正则:sales.
>
sales1.xls
orders3.xls
sales2.xls
sales3.xls
apac1.xls
europe2.xls
na1.xls
na2.xls
sa1.xls

Rails 多台服务器上 js 不一致的问题

发现问题

周二发布正式的时候,发生了匪夷所思的事情。
打开发布的网页,有一定的几率操作会没有反应。但在测试的时候却从没有这个问题发生。通过调试工具查看网页,发现一半的网页会加载 application-b1a 开头的文件,而另一半会加载 application-745 开头的文件。加载 applicaton-745 时会报 404,使得操作没有反应。

这里简单地说下发布的情况。该服务会发布到 A、B 两台服务器上,nginx 接收请求并转发到 A、B 上的实例。
查看正式服务器上的文件发现,A 上存在 application-b1a 文件,B 上存在 application-745 文件。看来 nginx 会查找 服务器 A 上的文件,由于找不到 application-745 而返回 404。先把 B 上的 applicaiton-b1a 复制到 A,临时修复这个问题。

排查原因

为什么两个服务器上生成的 js 文件名会不一致?我们先简单地回顾下 Asset Pipeline 生成 js 的过程。正式环境上, Asset Pipeline 会预编译文件,生成类似于 application-908e25f4bf641868d8683022a5b62f54.js 的文件。其中 908e 这串表示摘要,是根据文件的内容生成的 MD5,当 js 文件发生改变时生成的摘要也会发生变化。

1
2
3
4
5
// 引用 js
<%= javascript_include_tag "application" %>
// 生成的 html
<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>

Rails 如何在开发模式下重新加载源代码

在 Rails development 环境下,若更改了部分代码,只需要重新发起请求,就能看到最新代码的结果,不需要重启服务器。
下文简述 Rails 是如何做到这点的。

config/development.rb,也就是 development 的环境配置文件,我们可以看到不同于其他环境的一行:

1
2
3
4
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false

正如注释所说的,它会使得 Rails 在每次接收请求时都重新加载源代码。

初始化

我们先从 Rails 的初始化说起,以 Rails 4.2.6 为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
# rails/application/default_middleware_stack.rb
def build_stack
...
unless config.cache_classes
middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? }
end
...
private
def reload_dependencies?
config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
end
end

当 config.cache_classses 为 true 时,middleware 会增加 ActionDispatch::Reloader,而这正是重新加载源代码的关键。
ps: middleware 可以通过 rake middleware 来查看。

处理请求

当服务器接收到请求时,中间件 ActionDispatch::Reloader 使用回调 prepare、cleanup 来实现重载源代码。其中 prepare 在处理请求前被调用,cleanup 在处理请求后被调用。

MySQL EXPLAIN 解读

EXPLAIN 解释了 MySQL 是如何执行 SQL 语句的。使用的方法很简单,在 SQL 语句前加上 EXPLAIN 关键字就可以。
下面是一个简单的例子,测试数据在文章末尾。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 例 1
mysql> EXPLAIN SELECT name FROM users WHERE id = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: users
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra: NULL
1 row in set (0.00 sec)

EXPLAIN 列的解释:

  • id:SELECT 标识符,下面具体分析
  • select_type: SELECT 类型,下面会具体分析
  • table: 查询所使用的表
  • type: JOIN 的类型,下面会具体分析
  • possible_keys: 可能使用的索引,但不一定会真正使用
  • key: 真正使用的索引
  • key_len: 所使用的索引长度
  • ref: 与索引比较的列
  • rows: 预估需要扫描的行数
  • Extra: 额外信息

Rails Concern 源码研究

ActiveSupport::Concern 是为了更方便地 include 模块而推出的工具类。

使用方法

首先来看下它的使用方法。

1
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
# 传统的 Module 引入
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
module ClassMethods
...
end
end
# 使用 Concern
module M
extend ActiveSupport::Concern
included do
scope :disabled, -> { where(disabled: true) }
end
class_methods do
...
end
end

可见,通过 included 和 class_method 两个类方法使得 Module 的写法更加清晰。
你可能有些疑问,对比传统的引入,使用 Concern 虽然更加清晰了,但没什么巨大的优点。而传统的引入也可以通过将方法分成两个 module,如 InstanceMethods、ClassMethods,来达到同样的效果。
在这个简单的例子上,确实如此。但在一些嵌套的 include 上 Concern 的优势就体现出来了。