在 rest api 出现之前,想暴露一个接口需要先使用 wp_ajax_nopriv_wp_ajax_ 这两个钩子定义接口,再通过 admin-ajax.php 调用之(调用形式固定为 http://127.0.0.1/wp-admin/admin-ajax.php?action=),有了 rest api 就不用受此限制了,可玩性也大大提高了。

使用 rest api 只需要定义自己的路由和处理函数,就可以通过自定义链接来访问自己的接口了。

注册路由

在主题文件的 function.php 文件中加入下方的钩子,以分别定义 test 和 test2 两个路由

register_rest_route(
		'/v1', // 命名空间, 注意: wp 这两个字符为系统保留使用, 不建议在这里使用
		'/test', // 路由基础路径
		array(
			'methods'  => 'GET,POST', // HTTP METHOD, 支持逗号分割的字符串, 或字符串数组, 如: 'GET,POST' 或 array('POST', 'PUT');
            'callback' => 'func_demo1', // 处理路由请求的最终处理函数
			'permission_callback' => '__return_true', // 这是一个回调函数, 若对外公开需要返回 true; 否则, 返回 false. 可以通过此回调函数来判断处理用户权限. for security
		),
	);
	register_rest_route(
		'/v1',
		'/test2',
		array(
			'methods'  => 'POST',
            'callback' => 'func_demo2',
			'permission_callback' => '__return_true',
		),
	);

add_action('rest_api_init', 'register_brave_router');

添加处理函数

只有路由还不行,还要有对应的路由处理函数,下面在主题文件的 function.php 文件中分别添加 test 和 test2 的路由处理函数。

// test 的处理函数
function func_demo1 () {
	return array('key' => 'hello');
}

// test2 的处理函数
function func_demo2 ($request) {
	$data = wp_unslash($request->get_json_params()); // 取出来接口传入的参数
	return $data; // 返回接口传入的参数
}

访问自定义的接口

有两种方式访问刚刚添加的接口,一种是默认形式链接,一种是固定连接,分别如下

http://127.0.0.1/?rest_route=/v1/test
http://127.0.0.1/wp-json/v1/test

如果使用后者将得到下图这样的响应结果:

自定义前缀

如果不想要 wp-json 这样的前缀,wp 也提供了自定义的钩子,下方的钩子(同样,需要添加到主题文件的 function.php 文件中)将把 wp-json 改为 api。
注意:更改这里的前缀需要到【设置】-【固定链接】, 点一下保存【更改按钮】, 以刷新路由重定向规则使之生效

function rename_brave_rest_url_prefix() {
	return 'api';
}

add_filter('rest_url_prefix', 'rename_brave_rest_url_prefix');

不小心咬破了口腔,最近几天用了这药我终于理解影视剧里那些任凭敌人严刑拷打拒不屈服,往往敌人抓上一小撮盐往伤口上一撒便失节丧气的人了。
药瓶

今天午饭过后我去血站献了血,献血的原因很直接,老丈人生病住院需要用血。医院告知血液紧张暂时没血,若想尽快用血,就需要家人朋友去血站献血。

去年11月初家人在另一所医院住院,隔壁床位需要用血的老太太也遇到了缺血的问题,同样医生也是催促家属献血。

上述献血行为是定向的,需要献血人在血站告知和登记自己献血的目的是为哪个医院哪个病区几号床的病人供血,并且,不要求献血人跟用血人的血型一致,哪怕病人是O型血,献血人是B型血,也是可以的,亲属献了血病人就有血液可用了。这里有些事情不能明说,总之,就是这样。

献血前一定要吃饭,献血人最近不能感冒发烧,没生过重大疾病,不曾感染过特定的传染性疾病,年龄在18-55岁之间,体重不低于100斤,前一天没有熬夜喝酒,血站工作人员一一询问确认这些情况,核对献血人的身份信息(要带上身份证)。然后,献血人扫二维码填写个人信息阅读确认献血须知,确认献血量(分别有200毫升、300毫升、400毫升三档可选,不过血站的人会尽量推荐你献300毫升或400毫升,其实坚持选200毫升也是可以的);最后,血站人员会采集献血人的血液现场化验,大约1分钟左右便可知道献血人的血液是否符合标准,符合标准便进入采血环节了。

采血的过程还是比较快的,确认完在哪支胳膊上采血,便安排我坐下。采血操作很简单,工作人员拿过来一套类似医院打吊瓶用的输液设备,其实是一样的,只不过工作方式反了一下:针头扎进血管,血液顺着连接导管流进另一端的储血袋,装满就行了。采血时工作人员提醒说采血的针头比较粗,刚扎进去时会有点疼,建议不要看,我说我不看你扎吧。确实是有点疼的,仔细看那个针头比医院输液用的针头粗了很多,胖一圈都不止。

随后这位工作人员坐旁边问了我一堆问题:在哪上班,休息几天,过年什么时候放假,提醒献完血要多喝水,最近不能剧烈运动高空作业。当然我也问了她一些问题,比如一天有多少人过来献血,比较敏感就不发出来了。没等我将注意力转移到储血袋上,血已经采完了,拔了针按了一会扎针的地方,最后缠上一圈胶带,整个采血环节就结束了。

采完血,工作人员询问有没有不舒服的感觉,让我先喝水休息个15分钟再离开。对了,虽然是定向献血,但也有献血纪念品的,是一只杯子。另外,这个血站虽在闹市人流密集的商场边上,工作人员却只有一个人。从我进门到离开,整个血站总共就工作人员还有我这个献血人两个人,碰巧这几天大降温,外面寒风凛冽,献血屋的空调开的是真足,春天般的温暖,毫不夸张地说这次献血是我有生以来第一次享受这种一对一的公共服务。

关于献血后有没有不适的症状,针对这一问题,我刻意把这篇文章拖了几天再发出来,就是为了观察自己有没有什么不适。总体而言并没有感觉到头晕等不适症状,跟正常时候一样,献完血我就去上班了。献血后的第二天和第三天每晚睡前会有一些畏寒的感觉,即便屋里开了空调也会觉得有些冷,之后就跟往日无异了。

我发现这个公司的产品设计有一个偏好或是成规,倾向于在每个基础操作上为用户提供多种选择,大概是坚信这样的设计能为用户使用提供便利。

这个是一种好设计吗?

我认为这种设计在开发、测试、培训、运维等环节上会额外增加难度,浪费资源。多个选择也意味着出 bug 的几率增加,影响产品稳定性。

至于用户最终怎么选择,取决于用户对产品的认知,取决于哪种选择的资源容易获取,取决于哪种选择的操作简单,取决于哪种选择的可靠性强,等等。可以肯定的是,这事不像一夫多妻制社会轮流过夜那种模式,用户兼顾不了每个选择。

粗暴地为用户提供多个选择的产品,看似是以用户为中心,为用户着想,对用户负责。恰恰,这算是典型的不花时间搞清楚用户真实需求、产品设计不明确的不负责任行为。

最近读了 O'Reilly 的技术译文书《SQL经典实例》,原书最后一章有一篇标题为《给经过两次转置的结果集添加列标题》的实例,该实例处理后的数据如下图所示,其结果有点类似电影结尾的职员表,或是以前校园里张贴的成绩排行榜。

SQL 经典实例

作者的想法很棒,同时也觉得作者的实现似乎有些过于复杂和抽象了,不太容易阅读和理解,在此以另一种方法实现,也算是对我自己学习结果的一次检验。(这里的标题仅仅是为匹配原书标题而取的,实际上我并不知道这个场景叫什么比较合适。)

创建数据表:

create table it_app (deptno int NULL,ename VARCHAR(30) NULL);
create table it_sch as select * from it_app where 1<>1;

写入示例数据:

INSERT INTO it_app VALUES (100, 'Clay');
INSERT INTO it_app VALUES (100, 'Mark');
INSERT INTO it_app VALUES (100, 'Jim');
INSERT INTO it_app VALUES (200, 'Lily');
INSERT INTO it_app VALUES (200, 'Lucy');
INSERT INTO it_app VALUES (200, 'Judah');
INSERT INTO it_app VALUES (300, 'Scott');
INSERT INTO it_app VALUES (300, 'Mary');
INSERT INTO it_app VALUES (100, 'Oracle');

INSERT INTO it_sch VALUES (500, 'Kate');
INSERT INTO it_sch VALUES (500, 'Steve');
INSERT INTO it_sch VALUES (500, 'Kettle');
INSERT INTO it_sch VALUES (400, 'Matt');
INSERT INTO it_sch VALUES (400, 'Lary');
INSERT INTO it_sch VALUES (400, 'Danny');

最终的 SQL 实现:

with temp_table as (
-- 整理数据,拼接上部门号, 序号取0(依据 row_number() 从1开始的事实)
select 'app' as mark, deptno, to_char(deptno) as ename, 0 as row_num from it_app
GROUP BY deptno
UNION ALL
select 'app' as mark, deptno,ename,row_number() over(PARTITION by deptno ORDER BY ename) row_num
from it_app
UNION ALL
-- 整理数据, 同上
select 'sch' as mark, deptno, to_char(deptno) as ename, 0 row_num from it_sch
GROUP BY deptno
UNION ALL
select 'sch' as mark, deptno, ename, row_number() over(PARTITION by deptno ORDER BY ename) row_num from it_sch
), tmp_data_src as (
-- 重新进行一次排序编号
select mark, deptno, ename, row_number() over (partition by mark order by deptno,row_num asc) as row_num from temp_table
)
-- 完成转置
select
	max(case mark when 'app' then ename end) as apps,
	max(case mark when 'sch' then ename end) as research
from tmp_data_src
GROUP BY row_num
ORDER BY row_num;

实际上我的这个方案也有它的缺点:较原书的实现方式本实现会各多读一次数据表。

对原书实现方案的改进

原书的实现似乎有 bug: 生成 row_number() 时只用了 id 一个字段,显然还需要加上 ename 字段,否则没法保证多截取的那行跟被替换为 deptno 的那行是同一个员工,下面是我在原作者的基础上做了调整后的实现,简化了层级结构,且实现了按姓名升序排列:

with temp_level as (
	select level as id from dual connect by level < 3 -- 每行需要计算的次数
), temp_table as (
	-- 将最后一行的雇员名称替换为部门号
	select c.mark,c.deptno,c.ename,c.ttl_row,c.row_order,decode(c.row_order, c.ttl_row, TO_CHAR(c.DEPTNO), c.ename) as ename2,
				 row_number() over (partition by c.mark ORDER BY c.mark, c.deptno asc) as last_rownum
	from (
		select a.mark, a.deptno, a.ename, a.ttl_row,b.id,row_number() over (partition by a.DEPTNO order by b.id, a.ename) as row_order 
		from (
			select 'app' as mark, deptno, ename, count(1) over (partition by DEPTNO) as ttl_row --这个字段用于计算行数使用需要再此基础上+1行
			from it_app
			union all
			select 'sch' as mark, deptno, ename, count(1) over (partition by DEPTNO) as ttl_row
			from it_sch
		) a,
		temp_level b
	) c
	where c.row_order > c.ttl_row - 1
)
select
	max(case d.mark when 'app' then ename2 end) as apps,
	max(case d.mark when 'sch' then ename2 end) as research
from temp_table d
GROUP BY d.last_rownum
ORDER BY d.last_rownum