#1212 MJML 邮件格式

2026-07-27

MJML(Mailjet Markup Language)是一种专门用于编写响应式 HTML 邮件的标记语言。相当于是 HTML 在电子邮件场景下的更高层的封装,由编译器生成兼容各种邮箱客户端的 HTML。主要解决邮件 HTML 的痛点:

  • Gmail / Outlook / Apple Mail 支持差异巨大
  • 邮件需要大量 table 布局
  • CSS inline 化复杂
  • 移动端适配困难

注意:MJML 只负责 UI 不含模板功能。如果需要使用变量或者其他模板语法,可以另外选择任何一种语言进行渲染,比如 Node Handlebars,Golang 模板,Python Jinja2 等。

MJML 最初由法国邮件服务公司 Mailjet 在 2015 年开源。目前项目已经独立维护,属于开源生态:

  • 语法:MJML
  • 编译器:Node.js 实现
  • 输出:标准 HTML Email

GitHub Repository: mjmlio/mjmlio

MJML 文件格式

扩展名:.mjml

<mjml>
  <mj-head>
    <mj-title>Welcome</mj-title>
  </mj-head>

  <mj-body>

    <mj-section>
      <mj-column>

        <mj-text font-size="20px">
          Hello Woody
        </mj-text>

        <mj-button href="https://example.com">
          Click
        </mj-button>

      </mj-column>
    </mj-section>

  </mj-body>
</mjml>

通过一下方法可以直接预览效果:

  1. VSCode 拓展 [MJML Official]
  2. 官方 MJML Live Editor
  3. 自己实现 Preview 服务,AI 很容易生成一个

渲染 HTML

安装:

npm install -g mjml

转换:

mjml template.mjml -o template.html

生成的 HTML 会包含:

  • table 布局
  • inline style
  • Outlook VML兼容代码
  • media query

#1210 转载:布尔变量取名规则

2026-07-24

传说中,编程有两大难题,一个是缓存失效,另一个是变量起名。

其中,布尔变量尤其难起名。怎样才能贴切地表达,这个变量是表示"真"(true)和"伪"(false)的布尔值呢?

我最近读到一篇文章《布尔变量起名的艺术》,提出使用四个前缀就能给布尔变量正确起名,我觉得很有启发。

  1. is-:描述事物的状态,后面跟形容词,比如 isActive,isDeleted,isEmpty。
  2. has-:描述事物的所有权或包含关系,后面跟名词,比如 hasAccess,hasChildren,hasValidationErrors。
  3. can-:描述事物的能力或权限,比如 canEdit,canDelete,canRetry。
  4. should-:描述事物的意图或逻辑,比如 shouldRetry,shouldCacheResponse。

除了这四个前缀,起名还有另一条规则:永远不在布尔变量名中使用否定词。
比如,不使用 isDisabled,而要用 isEnabled = false。

#1209 一种基于经纬度的地理位置编码方案

2026-07-24

阮一峰的新闻周刊介绍了高德的位置口令,高德本月推出了一个新功能,可以位需要提供准确位置的用户生成一个 6 位编码,其他人直接搜索这个编码就能找到那个位置,而不需要知道这个位置的名称。
文章后面说:要是国家出台统一算法就好了,为每个地点生成一个固定不变的位置码,用处就大了。

我想,这倒没有必要,为所有位置提供编码的方案不就是经纬度么?
经纬度在日常表达方面确实会有一些障碍,读起来不大方便,而且我不确定有多少人知道经纬度,虽然这是初中就学了的。
我设计了一个 8 位编码方案(BASE32,只包含数字和大写英文字母):

经纬度 1 度大约偏差 111 公里,精确到小数点后 3 位小数,可以控制偏差在 100 米范围内,对于日常使用是可以接受的。

总共 360,000 x 180,000 = 648 亿个点。
二进制 36 位可以表达(687 亿),也就是 5 Bytes。
BASE32 8 位足够可以表达了(10995 亿)(7 位只有 343 亿,差一点)。

百度地图开放平台上用坐标拾取器获取两个坐标用来测试,结果如下:

  • 华中科技大学的毛泽东像位置是 (114.420025,30.514723),精度控制到小数点后三位,换算过来就是 BRLMVBKC
  • 美国自由女神像位置是 (-74.044500, 40.689225),精度控制到小数点后三位,换算过来就是 ARYMQF7B

Base32 字母出现的频率较多,为了减少英文字母的使用,我改成十六进制的表达方式,只多了一位而已,可以接受。测试结果如下:

  • 华中科技大学的毛泽东像位置 (114.420025,30.514723) 换算过来就是 0C56CA8542
  • 美国自由女神像位置 (-74.044500, 40.689225) 换算过来就是 0470C817E1

Python 代码示例:

# woody geo codec

import base64

# encode = base64.b32encode
# decode = base64.b32decode
encode = base64.b16encode
decode = base64.b16decode

def wgc_encode(lat:float, lon:float) -> str:
    lat_num = int((lat + 180) * 1000)
    lon_num = int((lon + 90) * 1000)
    num = lat_num * 180000 + lon_num
    return encode(num.to_bytes(5, byteorder="big")).decode()

def wgc_decode(code:str) -> tuple[float, float]:
    num = int.from_bytes(decode(code))
    lat_num = num // 180000
    lon_num = num % 180000
    print(lat_num, lon_num)
    lat = (lat_num - 180000) / 1000
    lon = (lon_num - 90000) / 1000
    return lat, lon

for lat, lon in ((114.420025, 30.514723), (-74.044500, 40.689225)):
    print((lat, lon))
    code = wgc_encode(lat, lon)
    print(code)
    print(wgc_decode(code))

#1208 本地运行 LLM 的限制

2026-07-24

看了《你需要知道的 AI 内存知识》,我才知道:

  1. 本地运行大模型的核心资源主要有三个:内存容量、内存带宽和计算算力,三者共同决定模型规模和运行速度。
  2. 统一内存架构(Unified Memory) 可以让 CPU 和 GPU 共享大容量内存,从而突破传统独立显卡显存容量限制,更容易加载大模型。
  3. 模型名称中的 MoE(Mixture of Experts,混合专家模型) 表示模型只会激活部分参数进行计算,可以显著降低推理所需的内存和算力。
  4. 当前消费级设备,即使价格达到数万元,也仍然存在明显限制:要么模型规模受限,要么推理速度不足,实际更适合运行中小型模型或经过优化的 MoE 模型。

#1207 MD5 与 SHA256 的哈希性能对比

2026-07-24

JavaScript hashing speed comparison: MD5 versus SHA-256(Daniel Lemire, 2025-01-11)通过 JavaScript 基准测试对比了 MD5SHA-256 在 Node.js 23 和 Bun 运行时下的哈希性能,核心结论颠覆了“MD5 更快”的传统认知:

  • 测试背景与方法:使用 1GB 随机 Uint8Array 数据,在 ARM(Apple M2、Graviton 4)和 x86(Intel Ice Lake)系统上,分别对 md5Hashsha256Hash 进行基准测试,底层实际调用 OpenSSL 实现。
  • 关键性能数据:在现代 CPU 上 SHA-256 显著快于 MD5。例如在 Apple M2(Node.js)上,SHA-256 约 2.6 GB/s,MD5 仅约 0.6–0.7 GB/s;在 Intel Ice Lake 上 SHA-256 也达到 1.2 GB/s,优于或持平 MD5。
  • 原因分析:SHA-256 虽然在算法设计上更复杂,但现代处理器(ARMv8、x86 等)普遍带有 硬件密码学扩展指令(如 Intel SHA-NI),对其进行了加速;而老旧且已被破解的 MD5 并未获得类似优化。
  • 最终建议不应再使用 MD5。它既在安全性上已被彻底攻破(存在碰撞攻击),在实际速度上也失去了优势,开发者应默认选择 SHA-256 或更先进的算法(如 BLAKE3,若可用)。

这是采用的 JavaScript 做的基准测试,但是结论说明的是通用硬件现象:现代 CPU(ARM/x86)有 SHA-256 硬件指令加速,而 MD5 无优化,任何语言调底层实现都如此。
因此,在现代处理器上,SHA256 性能大幅优于 MD5 的结论在所有语言上都是通用的。

#1206 波斯湾和霍尔木兹海峡

2026-07-17

波斯湾是一个几乎封闭的内海,三面被陆地包围——北岸伊朗、南岸阿拉伯半岛,只有东南角留了一个约150公里长的狭长缺口,这就是霍尔木兹海峡。

波斯湾夹在伊朗高原与阿拉伯半岛之间,面积约24万平方公里,平均水深仅40米,却蕴藏着全球近一半的原油和约三分之一的天然气。

这片海域唯一的出海通道是霍尔木兹海峡——长约150公里,最窄处仅约33公里,全球近20%的石油日运输量(约2000万桶)必须经此通过,因此它被称作"世界油阀"。

一、海峡两岸是谁的家门口

霍尔木兹海峡北岸全线属伊朗,控制着格什姆岛、霍尔木兹岛等扼守航道的关键岛屿;南岸则分属阿曼和阿联酋——阿曼的穆桑达姆半岛是一块飞地,恰好嵌在海峡最窄处,主航道反而贴近阿曼一侧,这使得阿曼虽非主要产油国,却成了航道安全的关键方。

图片

波斯湾沿岸八国则共同围成这圈"世界油库":伊朗(东北岸)、伊拉克、科威特、沙特阿拉伯、巴林、卡塔尔、阿联酋、阿曼(南岸)。其中除伊朗外,其余六国均为海合会成员。

二、12海里领海叠加出的"无公海"海峡

根据1982年《联合国海洋法公约》,沿海国可主张不超过12海里的领海。伊朗与阿曼均采用12海里基线。

关键几何事实是:海峡最窄处仅约21海里,而伊朗从北岸向南延伸12海里、阿曼从穆桑达姆半岛向北延伸12海里,两国领海在最窄处重叠约3海里。

💡 这意味着海峡最狭窄地带及主要航道上,没有一滴水属于公海,甚至不属于专属经济区——任何过往船只、潜艇都处在伊朗或阿曼的主权水域之内。

三、划界现状:双边协定为主,三处争端悬而未决

波斯湾各国自1950年代起陆续通过双边协定解决大陆架划界,如沙特—巴林、伊朗—科威特、伊朗—沙特等。2001年国际法院还对卡塔尔诉巴林案作出判决,明确了两国的领海与大陆架边界。

但迄今仍有三处主要海洋争端未解:

  • 伊朗 vs 阿联酋:阿布穆萨岛、大通布岛、小通布岛主权争议,三岛扼守海峡西南口,素有"海峡三闸"之称
  • 伊拉克 vs 伊朗:阿拉伯河入海口划界
  • 伊拉克 vs 科威特:布比延岛与瓦尔巴岛归属

四、通行规则的法理拉锯

海峡水域虽属沿岸国主权范围,但依据国际法院1949年《科孚海峡案》确立的习惯法,和平时期连接两片公海的国际海峡,外国船舶享有无害通过权,沿岸国不得单方面阻断。

真正有争议的是《海洋法公约》创设的过境通行权——比无害通过更宽松,不允许沿岸国事前审批。《公约》缔约国(如阿曼)原则上受其约束;但伊朗从未批准《海洋法公约》,且自1974年起就持续反对过境通行制度,因此在习惯国际法层面,伊朗并不当然受该条款约束。这也是美伊每次对峙时法理争执的根源——美方主张"过境通行",伊方坚持"无害通过+沿岸国监管权"。

按12海里领海规则,伊朗对本国岸侧及近岸岛屿向外12海里内享有领海主权,可管航安、环保、无害通过。但霍尔木兹是伊朗+阿曼两岸领海咬合、无公海,且属“用于国际航行的海峡”,和平时期须保障外国船无害通过(伊朗不认过境通行但认此习惯法)。故伊朗有法理管自家领海,无法理独控全海峡、禁航或单方收费。

#1205 百度云 ICP 备案记录

2026-07-16
  • 原备案信息(阿里云 · 2019):备案名称「码厩Markjour」,备案号 鄂ICP备15002462号
  • 迁移原因:不再使用虚拟主机,业务迁至百度云低成本主机,需在百度云重新提交接入/备案,备案名称变更为「码厩开发者笔记」。

初审被拒,原因:

  1. 应急联系电话校验:应急手机号不能与负责人手机号码相同,需提供另一位真实可联系人的号码(当年没有这个要求)。
  2. 前台菜单合规审核:初审要求网站不得出现“留言”和“给我写信”菜单,这算页面交互入口(推测当年备案时尚未增加这两个菜单)。
    • 这两个功能从没用过,暂时屏蔽前端入口以通过核验,后面再看如何处理。

初审通过之后,备案进入 “管局审核中-短信校验中” 状态。
几分钟之后就会收到工信部短信验证码,需要进入指定链接输入验证码、手机号、身份证后六位进行核验。
短信核验之后过一会儿,备案进入 “管局审核中-人工审核中” 状态。

#1204 阅读代码之前执行的 git 命令

2026-07-11

AI 总结了 The Git Commands I Run Before Reading Any Code,可以作为参考。
PS:其中部分命令的有效性是建立在提交规范执行良好的基础之上。我过去经手的项目全部没有提交规范。😂

Five git log commands that diagnose a new codebase before you open a single file: code churn hotspots, bus factor, bug clusters, and crisis patterns.

1. 变更热点 列出去年改动最频繁的 20 个文件,榜首常是团队"无人敢碰"的那个。高变更本身不糟,但若叠加"无人愿拥有",便是代码库拖累的最强信号——每次修改都是补丁叠补丁,小改动的影响范围不可预测。微软 2005 年研究证实,变更率指标比复杂度更能预测缺陷。

git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20

2. 总线因子 按提交数排名贡献者。一人占 60%+ 即为风险;若其已离职半年便是危机。长尾同样关键:30 位贡献者中仅 3 位近一年活跃,说明建造者已非维护者。注意 squash-merge 会压缩作者信息,下结论前需确认合并策略。

git shortlog -sn --no-merges

3. Bug 集群 形态同变更热点,但过滤含 bug 关键词的提交。同时出现在两张列表的文件风险最高:反复出错、反复修补却从未根治。此法依赖提交信息规范。

git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20

4. 提交节奏 按月统计提交数。稳定节奏健康;单月减半常因有人离职;6–12 个月下滑曲线揭示团队失速;周期性尖峰后沉寂则是批量发布模式。这是团队数据,而非代码数据。

git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c

5. 回滚与热修频率 一年中偶发回滚正常;若每几周一次,说明团队不信任部署流程,背后是测试不可靠、缺预发环境或回滚困难。零结果同样值得玩味。

git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'

#1203 Ubuntu 上钉钉不能启动的问题

2026-07-10

从官网下载的钉钉 deb 文件,安装之后竟然无法启动。

-> % ll ~/Downloads/com.alibabainc.dingtalk_8.1.0.6021101_amd64.deb
-rw-rw-r-- 1 catroll catroll 387M 2026-07-10 21:39:31 /home/catroll/Downloads/com.alibabainc.dingtalk_8.1.0.6021101_amd64.deb

-> % apt install ~/Downloads/com.alibabainc.dingtalk_8.1.0.6021101_amd64.deb

一边查看日志(journalctl --user -f),一边点击钉钉图标,可以看到以下错误日志:

7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361857]: ubuntu
7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361857]: ubuntu branch
7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361857]: preload_libs=./libgbm.so ./plugins/dtwebview/libcef.so
7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361873]: ERROR: ld.so: object './libgbm.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361873]: Run Main is_gpu=0 is_zygote=0 is_render=0 is_crashpad_handler=0 cmd : ./com.alibabainc.dingtalk
7月 10 21:49:27 eqr com.alibabainc.dingtalk.desktop[361873]: Load /opt/apps/com.alibabainc.dingtalk/files/8.1.0-Release.6021101//dingtalk_dll.so failed! Err=/opt/apps/com.alibabainc.dingtalk/files/8.1.0-Release.6021101//dingtalk_dll.so: cannot enable executable stack as shared object requires: Invalid argument
7月 10 21:49:27 eqr systemd[4607]: Started app-gnome-com.alibabainc.dingtalk-361857.scope - Application launched by gnome-shell.

经过 AI 的一番分析,结论是:

钉钉 deb 包依赖的 ELF 动态链接配置与当前系统环境不匹配。patchelf 修正了二进制的解释器、RPATH 或依赖库路径,使程序能正确加载运行时库之后就可以启动了。

sudo apt install -f patchelf
sudo patchelf --clear-execstack /opt/apps/com.alibabainc.dingtalk/files/8.1.0-Release.6021101/dingtalk_dll.so