2024-01-08 01:30:00
洒家在开着空调睡觉时,老是会冻醒或者热醒,所以想在室内实现恒温。之前基于米家 APP 已经成功搞了一套恒温系统(具体方案以后再写),后来看到小米新出的小米中枢网关有一个米家自动化极客版的功能,可以基于图形化编程实现更丰富的自动化。洒家好奇图形化编程的表现力和效果如何,于是就买了一台玩了一下。这篇文章主要介绍洒家搞出来的这套实现室内恒温的方案,基于小米中枢网关的米家自动化极客版功能,编程读取米家智能温湿度计 3 的温度数据,负反馈控制空调、暖风机的开关与设置,从而自动化控制室内温度。同时讨论一下米家自动化极客版存在的产品设计问题。
按照洒家博客的惯例,本文不详细介绍基本操作、图形化编程概念等别人写过的最基础的东西。如果不熟悉这些基础知识,请先查阅小米官方和其他用户写的教程:
可能由于功耗、续航、可移动性、减少发热对温度测量结果的影响等原因,目前小米所有的智能温湿度计,包括小米电子温湿度计、小米米家电子温湿度计 Pro、小米米家蓝牙温湿度计 2、米家智能温湿度计 3,都是纽扣电池供电的,并且只支持通过蓝牙网关联网,不支持通过 Wi-Fi 联网。如果想要联网实现自动化,就需要一台能提供蓝牙网关的设备。本文介绍的方案中,小米中枢网关已经自带了蓝牙网关,因此米家空调伴侣 Pro 万能遥控版自带的蓝牙网关是重复的,可以替换为不带蓝牙网关的普通版空调伴侣智能插座。
如果空调是智能空调,可以直接接入米家,则不需要空调伴侣智能插座。如果空调是冷暖型的,则不需要额外购买制热的设备。洒家住的公寓只有一台单冷型空调,因此又额外购买了一台米家石墨烯暖风机用于制热。
洒家的程序针对的是上文所述的单冷空调和暖风机两台设备,如果只有一台冷暖型的空调,对子函数部分稍加修改就可以正常工作。
编程和配置时需要注意:
创建 4 个手动控制
,用于手动产生虚拟事件,从而控制自动化。
操作步骤:点击智能
- 右上角加号 - 手动控制
,输入手动控制名称,点击添加执行动作
- 设备
- Xiaomi 中枢网关
- 产生虚拟事件
,输入虚拟事件代码,点击右上角对勾确认,点击创建
保存。点击刚刚创建的手动控制
- 更多设置
,根据需要设置显示在房间页
、通过小爱语音控制
以及添加到桌面快捷操作
。
4 个手动控制
的名称和虚拟事件代码如下:
开启自动制热
:event_enable_auto_heating
开启自动制冷
:event_enable_auto_cooling
关闭自动控温
:event_disable_auto_temperature
查询自动控温状态
:event_query_auto_temperature_status
创建 4 个自动化
,在点击查询自动控温状态
后,响应小米中枢网关产生的虚拟事件,产生通知和日志,从而接收查询结果。
操作步骤:点击智能
- 右上角加号 - 自动化
,开始创建自动化
。点击添加触发条件
- 设备
- Xiaomi 中枢网关
- 虚拟事件发生
,输入虚拟事件代码,点击右上角对勾确认;点击添加执行动作
- 通知
,输入通知内容。点击创建
,保存时输入自动化
的名称。
4 个自动化
的虚拟事件代码,通知内容和名称如下:
status_auto_cooling_enabled
,自动制冷已开启
,查询自动制冷状态为开启
status_auto_cooling_disabled
,自动制冷已关闭
,查询自动制冷状态为关闭
status_auto_heating_enabled
,自动制热已开启
,查询自动制热状态为开启
status_auto_heating_disabled
,自动制热已关闭
,查询自动制热状态为关闭
设置完毕后,米家 APP 的首页和智能页看起来是这样的:
平时只需要在手机上操作,不需要在电脑上打开米家自动化极客版的网页。在米家 APP 的首页房间页点击之前创建的 4 个手动控制,在夏天点击开启自动制冷
,冬天点击开启自动制热
,需要关闭时点击关闭自动控温
即可。喜欢语音控制的读者也可以通过小爱语音控制。
开启自动控温后,可以成功实现室内恒温,温湿度计的温度统计曲线基本上是一条横线。洒家实验了几个晚上,配置好参数后,实际体验效果不错,开着空调睡觉时再也不会冻醒或者热醒了。
一些实践经验:
点击查询自动控温状态
时,可以在手机通知或者米家 APP 右上角的日志中看到查询结果:
目前1米家自动化极客版这一套图形化编程方案有很多问题,不知道小米是出于什么原因选择了这一套方案。洒家认为,小米首先应该搞一个易用的社区自动化程序分享平台;其次应该把图形化编程改成表现力更强的代码编程,并提供在代码中声明参数、设备等配置项的 API,自动生成配套的图形化设置界面。具体分析如下:
编程本身就是有门槛,且令普通人望而却步的事。虽然图形化编程看起来简单一点,但是也是有学习成本的,个人估算图形化编程并不能显著增加用户量。假设总用户量为 10000 人,可能只有 100 人会用图形编程,10 人会用代码编程,100 人相对总数 10000 人仍然是一个很小的比例。洒家认为,与其用图形化编程降低编程门槛,不如搞一个类似 Greasy Fork、Steam 创意工坊的社区程序分享平台。再简单的编程也比不上不用编程,如果“小白”用户可以在社区里搜索自己的使用场景,一键下载“大神”(包括小米员工)发布的自动化程序,简单改几个参数,自动化就能运行起来了,这样才足够简单,才能把用户量搞上去。
在“大神”带动“小白”的局面下,为了让“大神”(包括小米员工)编程编得舒服,应该把图形化编程改成表现力更强的代码编程。对比代码编程,图形化编程有很多问题:
事件发生或状态更新
卡片获取米家智能温湿度计 3 的温度有 Bug,经常产生不了事件,导致自动化失效。似乎可以用循环
加上查询当前状态
替代。25.200001
的结果,洒家没有仔细研究,猜测可能会导致一些逻辑判断的 bug。查询当前状态
查询米家空调伴侣 Pro 万能遥控版的空调 - 设定温度
,条件设置为介于
,会出现满足条件
和否则
2 个分支同时执行的情况。总之,只有用开放的思维,帮助社区用户充分发挥无穷的创造力,让用户之间充分交流,形成“大神”带动“小白”的局面,才更有希望把用户生态搞活,提高商业价值,实现用户和厂商的双赢。如果小米或者其他公司看到了这篇文章获得了启发,真的把洒家的设想实现了,简单致谢一下就行了。
目前:2024 年 1 月,小米中枢网关的固件版本为 3.1.0_0051
,米家自动化极客版的版本为 v1.4.0
。↩
2023-12-31 17:30:00
沿着海滨栈道徒步前行,从小梅沙到大梅沙,2023 年从这里开始。
最后一路走到码头门口,但是这里似乎不让随意参观。
深圳的绿化做得不错,有许多绿地、公园、绿道零星遍布整个城市。洒家很宅,不常运动,但是每次出门徒步、爬山、骑行,呼吸时嗅着一阵阵自然的气味,都能感到压力和焦虑在慢慢离去。
这些硅化木形成于几千万年甚至上亿年前,历经沧海桑田,又被人类重新立起来,做成了化石森林景观。洒家摸着这些冰冷的石头,不禁感叹人类之渺小,人生之短暂。
详情请看之前这篇:大芬油画村半日游。
江面上的雾是湿空气遇到温度较低的江水形成的。东江湖上还有专门在渔船上表演撒网的人。
去之前没仔细做攻略,到了才知道,原来仰天湖大草原在山上。山路十八弯,车很难开,开车的兄弟的驾驶水平很了不起。当天下着小雨,山上全是雾,没有多少人,跟网上查到的平常的景像完全不一样,有一种沉静的世外之美。
郴州不仅自然风光很美,回来之后才知道,当年造原子弹用的铀原料也来自这里的 711 矿。
听说在岛的东边可以看到日出。一大早趁天还没亮时起床,想去租一辆电动车,还怕岛上租车的小店都没有开门,结果一出酒店,租车的老板已经把车停在门口等着做生意了。一路飞驰,早晨的凉风飕飕,到达米岩观日出景点时,岸边的礁石上已经挤满了人。望着东边的海平线,慢慢等到了日出的时间,天上却全是云,把太阳层层遮住了。这时,我们在西边意外地见到了彩虹。
坐着大巴车沿着海岸线前进,在路边见到了一些烂尾楼。印象最深的是,有一家自己用塑料布封住了窗户,还装了空调,不知道水电是怎么解决的。
个人感觉这部由韦伯作曲的经典音乐剧整体很好听。开场吊灯升起,Overture(序曲)奏响全场时非常震憾。很多曲子很优美,例如上半场的 Think of Me(想念我)(这首歌也出现在了武林外传第三十回中);有的曲子的旋律又似曾相识,好像在哪听过。推荐叨叨冯聊音乐对这部音乐剧的讲解,看完表演再看讲解又有了更深的理解。
演出后发现这部音乐剧还有个续集 Love Never Dies(真爱永恒 / 爱无止尽),洒家看了一下剧情简介,感觉非常扯淡。
洒家 10 多年前在一位喜欢日本文化的同学那里第一次听到了矶村由纪子的音乐,没想到这次还能来听现场的演奏会。表演者共 3 个人:矶村由纪子(钢琴),甘建民(二胡),丸田美纪(日本筝)。甘建民是旅日多年的华人二胡演奏家,除了演奏,还负责翻译和报幕。感觉他在介绍时,不太熟悉曲子和搭档的中文名;另外他也有点忙不过来了,有一首曲子演奏完,不小心顺口说起了日语。
这次演出的曲子很好听。除了最经典的《风居住的街道》,洒家最喜欢的是《樱花盛开》。可惜这次没有演《绯红华尔兹》。由于现场三人用的乐器是固定的,因此有些曲子和录音版本不同。
表演结束后,主办方又搞了个交流会环节,观众可以向表演者们提问。现场有几位资深粉丝用流利的日语提问,甘建民表示,自己已经去日本几十年了,还没有他们的日语流利。矶村由纪子现场透露《风居住的街道》的创作背景:多年前她第一次来中国的时候,在广州看到了一条河,曲子的旋律就自然出现在她的脑海中了。个人猜测这条河有可能是珠江。
演出前洒家只是简单看了一下莫扎特的生平和剧中人物关系,歌曲则是在现场第一次听。听完之后感觉有些歌没有什么记忆点,但是回去之后仔细研究了几天,又感觉越听越好听。以后看演出之前一定要充分预习几遍,把曲子听熟,否则就浪费了。
个人最喜欢的 3 首歌是:
资源推荐:
这场演出的定位是公益、入门熏陶,大部分都是影视通俗曲目,并不深奥,对观众的门槛很低,甚至连票价也是免费的。演出的形式也不严肃,在曲目之间,表演者母子两人用简短的“尬剧”衔接,非常滑稽,在表演前有时还会有对曲子和乐器的讲解和科普。可惜,可能因为这是免费的演出,很多人并不珍惜,迟到了好一会才进来。
曲目单里本来有一首 Charles-Marie Widor 的 C.M. 维多尔:管风琴第五交响曲,Op.42,No.1,第一乐章,去之前专门预习了好几遍,现场却没有表演。《摇摆巴赫》这首曲子在演出前搜不到具体指的是什么,实际是巴赫著名的 Toccata and Fugue in D minor, BWV 565 的改编,果然一提到管风琴,这首曲子永远都逃不掉。除了器乐演奏之外,赵小玲还在管风琴的伴奏下用英文唱了一首《我心永恒》,最后还邀请观众一起合唱了《我和我的祖国》,可惜演奏大厅的大门一关,手机一点信号都没有,记不清歌词也没办法现场搜。
洒家上大学时,执行力比现在还差,最大的遗憾之一就是没有去当地的大教堂听一场管风琴演奏。这次听完之后洒家感觉管风琴的声音确实非常震憾,听的时候起了一身鸡皮疙瘩。可以想象,古代没有大音箱,人们在教堂里听到这样震憾的音乐,一定想当场跪拜在神的面前。
个人感觉演出曲目中最好的一首是《过雨荷花》,现场出演的人和视频里一样,也是方嘉莹(琵琶)和雷焕然(古筝,作曲)。视频下面有一条评论写得不错,摘录过来:
现场听更好听。两个乐器的声音好像在交谈,又好像是雨声,时而是淅沥的雨滴,时而是狂风骤雨。曲子很有新意,没有局限于传统的五声音阶,E4 G4 A4 B4 D5 E5 C5# A4 B4 那个乐句反复出现也很有记忆点。
还有一首《春江花月夜》印象也比较深,在每一段结尾,萧在演奏时做了渐慢处理,和之前找的录音版本的诠释不一样。但是这次演出大部分曲子的音阶和节奏型的套路都很相似,风格也都差不多,旋律比较缺乏记忆点,也许作为背景音乐还行,连着仔细欣赏的话,有些审美疲劳。去之前已经提前熟悉了几遍,但是有些曲子是新作品,录音、视频和谱子在网上都找不到。听完之后感觉听现场要比听录音好很多,声音更清晰和立体,还可以看到表演者的神态和动作,这也是音乐表演的一部分。
很多研究网络安全与隐私保护的人都有个毛病,有时候会把自己的系统配置得过于安全,以至于有时候会影响自己正常使用。洒家就遇到了这种情况。洒家之前修改了手机的相机设置,不在拍摄的照片文件里嵌入精确位置信息。但是 20 岁之后,洒家感觉自己的记忆力开始慢慢减退,有一次翻相册里以前的照片,竟然想不起来是在哪里拍的。看来李彦宏的“用隐私换取便利”在一定程度上也是成立的。为了在未来更方便考证相册里的照片是在哪里拍的,洒家把在照片中嵌入位置信息的功能又打开了。拍照是一种记录生活中美好记忆的好方法,只需用手机随便拍几张,不用担心拍得不好看,也不用担心浪费胶卷,就可以记住在某一天去了哪里。至于隐私,只需要在分享照片文件的时候,记得根据需要清除 Exif 信息就行了。
2023-04-03 23:20:00
洒家有几个硬盘盒/硬盘底座/硬盘扩展坞。最近看了一些科普视频1,然后实际查看原装电源的铭牌,查阅硬盘详细参数表,发现这些硬盘底座产品的原装电源确实无法满足硬盘的供电需要。且不提纹波与噪声指标是否合格,就连最基础的输出电流都无法满足硬盘峰值需求,长期使用必然有损坏硬盘的风险,现实中也已经有大量硬盘损坏的案例。于是洒家决定把原装电源替换成明纬的更大功率的电源来降低损坏的风险2。洒家最终选择的电源的价格已经接近甚至超过了硬盘底座本身的价格,但是洒家认为这笔钱应该花,因为百十块钱的电源可以保护价值几百甚至一两千的硬盘,况且硬盘有价,数据无价。洒家这么多年被便宜货坑过不少,如果硬盘突然损坏,购买新硬盘的金钱成本、恢复备份和重建 RAID 的时间成本、数据丢失的损失都超过了电源的价格。
在购买过程中,洒家被明纬电源的型号搞得眼花缭乱3,也没搜到原装电源插头的具体尺寸,自己量又量错了,一同买了 P1J4 转 P1M5 转接头,最后却没用上。因此,这篇博客不是给电源打广告,也无意煽动电源焦虑,主要是把洒家的经验和网上找不到的资料发出来,供读者参考。
产品概况:
根据铭牌和实际测量及测试,硬盘扩展坞原装电源适配器概况如下:
产品概况:
根据铭牌和实际测量及测试,硬盘底座原装电源适配器概况如下:
根据物理知识,我们平常接触的电源适配器一般都属于恒压源。电源的输出电压要和原装电源保持一致,硬盘底座这类产品一般是 12V。而电源的输出电流则不能低于用电器的峰值输入电流,需要综合考虑硬盘的峰值电流/峰值功率、设备是否支持顺序上电等因素,然后再加上一些余量。硬盘的峰值电流/峰值功率可以通过查阅厂商提供的参数表确定,可以把硬盘型号和 manual
、specification
、data sheet
等关键词一起搜索,一般都可以搜到 PDF 格式的详细参数表。对于单盘位产品,12V 2A 可能不能满足一些硬盘的供电需要,至少需要 12V 3A 输出的电源。
洒家买的是明纬电源,下面是几种类似的型号:
型号 | 接地 | 电压 | 电流 | 纹波与噪声(mVp-p) 规格书 / 测试报告 |
链接 |
---|---|---|---|---|---|
GSM60A12-P1J | 有 | 12V | 5A | 100 / 72 | 京东自营 规格书 测试报告 |
GST60A12-P1J | 有 | 12V | 5A | 120 / 59.6 | 京东自营 规格书 测试报告 |
GSM60B12-P1J | 无 | 12V | 5A | 100 / 55.6 | 京东自营 规格书 测试报告 |
GSM40A12-P1J | 有 | 12V | 3.34A | 100 / 52.5 | 京东自营 规格书 测试报告 |
GST40A12-P1J | 有 | 12V | 3.34A | 120 / 50.8 | 京东自营 规格书 测试报告 |
GSM40B12-P1J | 无 | 12V | 3.34A | 100 / 58 | 京东自营 规格书 测试报告 |
GSM36B12-P1J | 无 | 12V | 3A | 120 / 80 | 京东自营 规格书 测试报告 |
GST36B12-P1J | 无 | 12V | 3A | 100 / 22.8 | 京东自营 规格书 测试报告 |
其中 GSM 系列属于医疗用途7,贵一些;GST 系列则属于工业用途,便宜一些。对比产品规格书和价格,洒家原本以为两个系列的质量应该是 的关系。后来看了官方提供的数年前的测试报告,发现 GSM 系列在纹波与噪声指标上并不一定就比 GST 系列强。可惜洒家既不会分析电源里面的电路结构和元件,又没有检测能力,只能纸上谈兵地猜测一下,大概 GSM 系列比 GST 系列价格贵在了满足医疗安全等标准方面。给硬盘底座供电,GST 系列应该就够用了。
明纬电源不带输入线,需要单独购买。输出插头都是 P1J4 规格的,如果需要其他规格的插头要另外购买转接头。洒家上面提到的 2 款硬盘底座的电源接口都是 P1J 规格的,不用买转接头,供读者参考。
听君一席话,消费两百多。↩
注意:替换原装电源并不能代替数据备份。↩
更乱的是,目前一共有 3 个明纬京东自营店铺,不知道互相是什么关系:明纬(MEANWELL)京东自营专区,明纬(MEANWELL)电源京东自营专区,明纬(MEANWELL)京东自营专卖店↩
P1M:外径 5.5mm,内径 2.5mm,长 11 mm↩
USB 3.0:即 USB 3.1 Gen 1 或 USB 3.2 Gen 1×1(USB 标准化组织 USB-IF 改名部的“杰作”),带宽 5 Gbps↩↩
然而无论是 GSM 还是 GST 系列,商品评价里大部分都是用于硬盘盒、硬盘柜,还有一些人用来烧 Hi-Fi。↩
2023-03-19 02:15:00
最近我们去大芬油画村逛了半天。
我们这次主要想参观“世界艺术大师乔治·莫兰迪华南首展”,这次展览有乔治·莫兰迪的几十件作品真迹。洒家在绘画领域是个外行,只是以前在高中上过美术课,看过一些科普,认识几张名画。来之前洒家并不了解乔治·莫兰迪,通过看这次展览才逐渐了解了他的生平和作品。
下地铁进入大芬油画村,抬头就能看见路灯杆的广告牌上都是这次展览的广告。穿过一条街,街边都是和油画有关的店。走到尽头,我们看到了大芬美术馆,然后上二楼买票进入展厅。展厅入口堵着很多精心打扮的姑娘,她们也不进去看作品展览,只是一直在摆姿势,不停地自拍、她拍。万万没想到这里也沦为网红打卡拍照的地方了。
挤进展厅,里面人还是很多。乔治·莫兰迪的作品都挂在墙上,和人们隔着一条警戒绳。有的作品前围满了人,只能先看别的,等他们走了再挤过去看。洒家对着画拍了几张照片,回来再看,总感觉和现场看到的不一样。照片不仅颜色不准确,还看不到画的笔触、厚度、反光和质感。静物和风景都显得平平无奇,再也让人体验不到现场看的那种震撼,好像画里面的“灵魂”没了。
作为一个外行,洒家也说不上什么门道,只觉得他的画有一种吸引力,能吸引我盯着它们细细观察。看似简单的瓶瓶罐罐、花朵、郊外风景,好像是精心伪装的模样,实际上并不简单,不知道为什么能从里面感受到一种沉稳、宁静和永恒。
也许洒家在写文章时和乔治·莫兰迪有一些共通的追求。几年前有一篇高考满分作文《生活在树上》,洒家读了以后感觉很惭愧,就像高中时辞藻华丽的范文,自己这辈子学不了,也学不像。洒家写文章常常反复修改,只求用尽可能通俗凝炼的文字把思想精确地传递给读者,能做到“波泽春涨”,“润物无声”,偶有妙笔就可以了。
出来后,我们又下到一楼参观了“发生·发现——深圳·中国写意油画巡礼”展览。这个展览大部分都是写意抽象朦胧的当代作品,放眼望去,色彩饱和度普遍比乔治·莫兰迪的作品高,风格有明显不同。下面是几张有点意思的作品:
从美术馆出来,太阳还没落山,我们又在村子里逛了一会。
洒家之前看过一些岳敏君的作品,“笑脸人”表现出来的“玩世现实主义”引起了我的共鸣。这次在一家店发现了他的临摹作品,对此很惊喜,这也是意料之外情理之中。
整个大芬油画村围绕着油画形成了产业。道路和胡同两旁主要都是自产自销的油画商店和工作室,也有画框装裱店,绘画材料店,还有指导零基础游客临摹照片体验画油画的店。这里有各种主题的油画,名画临摹比较多,梵高、莫奈的名画几乎家家都有。还有风景画(甚至有 iPhone 自带壁纸)、吉祥寓意的装饰画、领袖标准画像、当代名人画像等。除了油画,洒家还见到了国画、刺绣和书法作品。很多老板正坐在挂满油画的店里临摹名画或者摄影作品,有的把照片贴在旁边,有的在旁边放着显示器,临摹也不用去野外或者展览馆了。有的老板还在旁边架着手机直播。有些店门口贴着画家的履历,我们正看着介绍,画家就看着我们疑惑地从门里走了出来,搞不好我们遇到了业内的名人。这些油画的价格并不贵,小幅的百十块钱就能买到,大点的也不过几百块钱。洒家看了之后很疑惑,不知道他们的产量和销量如何,毕竟人都是要恰饭的,他们靠画画能不能养活自己?另外,洒家在这里很少见到原创作品,对于追求艺术还是商业,原创突破还是批量生产,做大师还是工匠,他们有没有选择的机会?他们怎样面对理想和现实的矛盾?
回来之后洒家才知道,原来这里是全国乃至全球最重要的商品油画(行画)生产和交易基地。凭借低成本优势,在巅峰时期这里的近万名画工每年可以批量复制几百万张商品画。大部分产业工人是野路子出身,他们不能被称为画家或者画师,只能叫做画工,经过简短的培训之后就可以进入流水线干活。每个流水线上的画工甚至只需要负责一幅名画的一个局部,通过这种方式,大芬油画村甚至创造过 400 多名画工用一个半月生产 36 万张画的奇迹。因此,有人把这里略带批评地称作“艺术界的华强北”。
我们又看了纪录片《中国梵高》。片中的赵小勇临摹梵高的名画 20 多年,做梦都想见到梵高。终于,他到达欧洲,见到了梵高的真迹,却感受到了震撼、失落和迷茫。洒家对赵小勇的经历感同身受,感慨对艺术的追求确实是一种奢侈品。但是一旦萌生了创作原创作品的想法,改变了自己的定位,底层的画工也就从此走上了追求艺术的漫漫长路。
扩展阅读:
2022-11-25 00:10:00
最近洒家的 GitHub Pro 教育优惠到期了。洒家担心无法再从私有仓库发布 GitHub Pages 网站,于是提前研究了其他几款主流的静态网站托管平台,发现它们的 Web 服务配置和 GitHub Pages 有一些差别,服务器的行为特性各不相同。于是洒家设计了一套测试样例进行了更深入的实验,简单分析了一下实验结果,供读者在选择托管平台、配置静态网站生成器时参考。
2.4.52-1ubuntu4.2
)/
|-- 404.html
|-- index.html
|-- test1
| `-- index.html
|-- test1.html
|-- test2.html
|-- test3
| `-- test3.html
`-- test4
`-- index.html
file://
协议、Apache 服务器等最基本的场景正常使用的访问方式测试结果中,200 /index.html
表示 HTTP 状态码为 200
,返回了 /index.html
的文件内容;301 --> /test1/
表示 HTTP 重定向到 /test1/
;404 (default)
表示状态码为 404
,返回了服务器默认错误页面,以此类推。
下列测试结果中,每个表格都会挑选一个测试对象作为标准测试对象,其他测试对象和标准测试对象对比,行为完全相同的测试点用绿色标记,行为不同但基本上可以等价替换的测试点用黄色标记(包括不同的 3xx
状态码跳转到了相同的 URL、都是 404
状态码但是响应消息正文不同等情况),行为完全不同的测试点用红色标记。红色的格子越多,和标准行为的差别越大。标准测试对象本身不做任何彩色标记。
下表为基本测试的测试结果,以 Apache 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
下列表格展示了完整测试的测试结果。下表以 Apache 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index | 404 (default) | 200 /index.html | 200 /index.html | 308 --> / | 404 /404.html | 301 --> / |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1 | 301 --> /test1/ | 200 /test1.html | 302 --> /test1/ | 200 /test1.html | 200 /test1/index.html | 200 /test1.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index | 404 (default) | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 404 /404.html | 301 --> /test1/ |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2 | 404 (default) | 200 /test2.html | 200 /test2.html | 200 /test2.html | 404 /404.html | 200 /test2.html |
/test2/ | 404 (default) | 404 /404.html | 200 /test2.html | 308 --> /test2 | 404 /404.html | 301 --> /test2 |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test2.html/ | 404 (default) | 404 /404.html | 200 /test2.html | 404 /404.html | 200 /test2.html | 301 --> /test2 |
/test3 | 301 --> /test3/ | 301 --> /test3/ | 302 --> /test3/ | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/ | 200 (Index of /test3) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/test3 | 404 (default) | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 404 /404.html | 200 /test3/test3.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4 | 301 --> /test4/ | 301 --> /test4/ | 302 --> /test4/ | 308 --> /test4/ | 200 /test4/index.html | 301 --> /test4/ |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index | 404 (default) | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 404 /404.html | 301 --> /test4/ |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
/404 | 404 (default) | 200 /404.html | 200 /404.html | 200 /404.html | 404 /404.html | 200 /404.html |
/404.html | 200 /404.html | 200 /404.html | 200 /404.html | 308 --> /404 | 200 /404.html | 200 /404.html |
/xx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/ | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test1/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
下表以 GitHub Pages 为标准测试对象:
Apache | GitHub Pages | GitLab Pages | Cloudflare Pages | Vercel | Netlify | |
---|---|---|---|---|---|---|
/ | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html | 200 /index.html |
/index | 404 (default) | 200 /index.html | 200 /index.html | 308 --> / | 404 /404.html | 301 --> / |
/index.html | 200 /index.html | 200 /index.html | 200 /index.html | 308 --> / | 200 /index.html | 200 /index.html |
/test1 | 301 --> /test1/ | 200 /test1.html | 302 --> /test1/ | 200 /test1.html | 200 /test1/index.html | 200 /test1.html |
/test1.html | 200 /test1.html | 200 /test1.html | 200 /test1.html | 308 --> /test1 | 200 /test1.html | 200 /test1.html |
/test1/ | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 301 --> /test1 |
/test1/index | 404 (default) | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 404 /404.html | 301 --> /test1/ |
/test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 200 /test1/index.html | 308 --> /test1/ | 200 /test1/index.html | 200 /test1/index.html |
/test2 | 404 (default) | 200 /test2.html | 200 /test2.html | 200 /test2.html | 404 /404.html | 200 /test2.html |
/test2/ | 404 (default) | 404 /404.html | 200 /test2.html | 308 --> /test2 | 404 /404.html | 301 --> /test2 |
/test2.html | 200 /test2.html | 200 /test2.html | 200 /test2.html | 308 --> /test2 | 200 /test2.html | 200 /test2.html |
/test2.html/ | 404 (default) | 404 /404.html | 200 /test2.html | 404 /404.html | 200 /test2.html | 301 --> /test2 |
/test3 | 301 --> /test3/ | 301 --> /test3/ | 302 --> /test3/ | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/ | 200 (Index of /test3) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test3/test3 | 404 (default) | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 404 /404.html | 200 /test3/test3.html |
/test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 200 /test3/test3.html | 308 --> /test3/test3 | 200 /test3/test3.html | 200 /test3/test3.html |
/test4 | 301 --> /test4/ | 301 --> /test4/ | 302 --> /test4/ | 308 --> /test4/ | 200 /test4/index.html | 301 --> /test4/ |
/test4/ | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html |
/test4/index | 404 (default) | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 404 /404.html | 301 --> /test4/ |
/test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 200 /test4/index.html | 308 --> /test4/ | 200 /test4/index.html | 200 /test4/index.html |
/404 | 404 (default) | 200 /404.html | 200 /404.html | 200 /404.html | 404 /404.html | 200 /404.html |
/404.html | 200 /404.html | 200 /404.html | 200 /404.html | 308 --> /404 | 200 /404.html | 200 /404.html |
/xx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/ | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/xx/xxx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
/test1/xx.html | 404 (default) | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html | 404 /404.html |
在最普通的 Web 服务的基础上,各个平台都加上了一些新的访问和跳转规则。测试结果反映出各个平台实现特性的逻辑和规则的优先级有一些区别(例如 /test1
既可以返回 /test1.html
,又可以跳转到 /test1/
,还可以直接返回 /test1/index.html
)。洒家横竖看了半天,发现想做个简单易懂且全面的总结并不简单,无论用文字还是伪代码都只会越搞越乱,因此只能粗略总结一下,建议有兴趣的读者直接看原始结果。
各个静态网站托管平台的特性支持情况如下:
/test3/
都会返回 404
/404.html
:全部都支持.html
访问 .html
文件:除 Vercel 外,全部都支持/test4
直接访问 /test4/index.html
:仅 Vercel 支持,其他都是先跳转到 /test4/
在 Cloudflare Pages 平台,访问所有 /path/to/index.html
都会 308
跳转到 /path/to/
,访问其他 .html
文件也会通过 308
跳转去掉 .html
扩展名。根据洒家的理念,静态网站应该保持最大的兼容性,而不依赖任何平台的特性。无论把代码上传到任何平台、任何服务器上都应该可以正常访问,甚至直接在本地通过 file://
协议打开也应该能正常访问。由于这种强制跳转特性,洒家不会选择 Cloudflare Pages 平台。
而 Netlify 平台在基本测试中也存在瑕疵。同时存在 /test1/index.html
和 /test1.html
的情况下,访问 /test1/
会先 301
跳转到 /test1
,然后 200
返回 /test1.html
的内容,这和一般的预期不同。访问 /test1
的结果可以存在争议,而访问 /test1/
则应该是想访问 /test1/index.html
。再观察 /test2/
的访问结果,在存在 /test2.html
的情况下,访问 /test2/
会 301
跳转到 /test2
,猜测访问 /test1/
出现的现象可能是由于这个特性的优先级过高。
在 Vercel 平台,存在 /test4/index.html
的情况下,访问 /test4
没有跳转到 /test4/
,而是直接 200
返回了 /test4/index.html
。同时,我们又可以直接访问 /test4/index.html
。同时存在两种路径层级的访问方式,这可能导致页面资源相对引用混乱。
从基本测试可以看出,只有 GitHub Pages、GitLab Pages 和 Vercel 符合洒家的基本标准。从完整测试可以看出,最接近 Apache 的是 Vercel,最接近 GitHub Pages 的则是 GitLab Pages。因此,洒家可能会用 GitLab Pages 或者 Vercel 替代 GitHub Pages。然而到最后洒家忙活的这一圈也没用上,因为洒家另外搞出了一套方案,改用 GitHub Actions 从公开仓库私密发布 GitHub Pages 网站,不用再换平台了。感兴趣的读者可以前往仓库 Phuker/phuker.github.io 查看这套方案。