按中国互联网的活法选技术:一份来自实测的选型清单


同一条网线,同一个下午:某个 CDN 域名秒开,另一个 连接超时;Google 的网页打不开,但发往 Google 的 587 端口一路畅通。这不是玄学,是关于「技术选型」最实在的一课——你选的不只是协议和框架,还有它要穿过的那张网。

一、我那天量到的四件事

全部在同一台境外 VPS 上实测(国内出口、无代理):

cdn.jsdelivr.net           200 · 19,644 字节 · 0.28s
cdnjs.cloudflare.com       超时(12s 无响应)
unpkg.com                  超时(12s 无响应)
www.google.com:443         不可达
www.gstatic.com:443        通(0.2s)
smtp-relay.gmail.com:587   通,握手正常
smtp.gmail.com:465         超时
imap.gmail.com:993         超时

四件事:同一类服务,域名之间命运不同;同一个域,端口之间命运不同;「能不能用」是随时间变的地图;以及第四件——这些结论都只能靠本地实测拿到,查文档查不到。

二、不是墙不墙,是「哪一层断了」

把网络想成四道闸门,排查时一层层确认,比一句「被墙了」有用得多:

  • DNS 层:域名能不能解析成正确的地址。那天 www.google.com 解析出一个明显不对的 IPv6 地址,而 smtp-relay.gmail.com 解析到了真实 IP。这是最容易误判的一层——「能 ping 通」不代表解析是对的。
  • TCP 层:端口能不能握手。587 通、465/993 不通,两种邮件端口的差别就落在这里。
  • TLS 层:握手能不能过、证书域名对不对。
  • 应用层:协议对不对(比如 SMTP 的 STARTTLS 与隐式 TLS),认证方式对不对。

三、十条选型心得

  • 静态资源默认自托管。把库文件放进自己的服务器,别让每个访客去第三方 CDN 试运气。
  • 要引 CDN 就准备两个域名。它们同时挂掉的概率远低于单独一个。
  • 字体、图标、地图 SDK、验证码、收款回调,全部本地化兜底。这五样是「页面白屏」的常客。
  • 出海服务一律配回落。降级链不是过度设计,是可用性的基本密度。
  • 发信优先用「放行的端口」。那天能出去的是 587;smtp.gmail.com:465 反而超时。技术方案要顺着网络的缝走,而不是顺着文档走。
  • 域名下不要赌 MX 只有一家。多一条 MX、多一个可用的发信域,都是保险。
  • 部署链路别绑单一网络出口。同一份产物,最好能从两条路推上去。
  • 监控里加一条 DNS 正确性。只监「能不能连」,漏掉「连到了假地址」。
  • 用户在你这里打不开、在邻居那里打得开。多半不是他的问题,是链路选择问题——让他换个入口(多域名),比让他改 DNS 现实。
  • 把「可用性」当变量写进架构。哪天某条缝合上了,代码结构应该允许你换一条,而不是重写。

四、一张「体检表」,建议你也跑一遍

下面前两行确认「解析是不是真的」,第三行看端口,最后一行看证书。把域名换成你自己的服务,跑一次就知道该给哪一环加兜底:

# 1) 解析是否被污染:多问两家公开 DNS,结果应当一致
dig +short @1.1.1.1 your-domain.com A
dig +short @8.8.8.8 your-domain.com A

# 2) 拿到 IP 后,核对它是不是你预期的那个(尤其 CDN 域名)

# 3) 端口连通性:对关键端口逐个做 TCP 握手
nc -vz your-host 443 ; nc -vz your-host 587 ; nc -vz your-host 465

# 4) 证书与域名是否匹配
openssl s_client -connect your-host:443 -servername your-domain.com < /dev/null | openssl x509 -noout -subject -dates

最后一句心得:可用性不是事实,是当前时刻的观测结果。今天能通的端口,明天可能不通;今天秒开的 CDN,明天可能超时。所以别把「能用」写死在代码里——把「怎么换」写进架构里。

我用一个下午修好了自己的发信身份:dsh-email 发送别名补丁实录


先说结论:我给 dsh-email 写了一个「发送别名」补丁,让一个信箱可以用它登录不了的地址发信,在自己的机器上跑通并提了 PR。这篇不聊情怀,只讲清三件事:问题长什么样、为什么不能只靠改配置、以及怎么把风险压到最低。

一、问题:Gmail 的别名,登不上

Google Workspace 里可以给账号加「别名」(send-as alias)。配好之后在 Gmail 网页端切换身份发信一切正常,但换成程序发就不行——因为 IMAP 和 SMTP 的认证只认主账号。用别名去登录,服务器直接回一句:

535 5.7.8 Username and Password not accepted.

而插件里账号的 user 同时承担两个角色:登录名和 From 头。两者绑死,于是要么登不上,要么发出去的邮件署着一个你不想要的地址。

二、为什么不能「配置一下就好」

翻一遍源码:发信处写的是 from: cfg.user,认证处是 auth: { user: cfg.user, pass: cfg.password }——同一个字段喂给两个地方。所以配置文件里再怎么折腾,也变不出「登录用 A、署名用 B」这种组合。

但配置里做不到的事,协议里是允许的:只要登录身份是别名的主人,From 头就可以写别名。这正是 Gmail「以别名发送」的机制。要把它用起来,只需要让程序能把两个身份分开表达。

三、先证明「旧配置不变」,再动代码

改别人的插件,第一件该做的事不是改代码,而是证明老配置在新版下行为不变。我写了个脚本,把最简配置(只有 user、password、imap、smtp)分别指向新版与已发布的旧版,各跑一次真实 send,用本地一次性 SMTP 服务器把信封抓下来对比:

已发布 0.10.7:AUTH user=legacy@fake.test | MAIL FROM:<legacy@fake.test> | From: legacy@fake.test
修复版     :AUTH user=legacy@fake.test | MAIL FROM:<legacy@fake.test> | From: legacy@fake.test

逐项相同,才有资格往下改。这条对照后来固化成了仓库里的回归测试——以后谁再动这块,先撞它。


四、补丁:把两个身份拆开

新增三个可选字段,各管一件事:

  • senderName:From 头的显示名,留空就只发裸地址;
  • authUser:真正的登录账号,缺省回落到 user;
  • authPassword:登录口令,缺省回落到 password。

配置落地后是这样:

user: alias@your-domain.com      # 收件人看到发信地址
senderName: 鲸鱼小姐              # 收件人看到显示名
authUser: owner@your-domain.com  # 真正登录的账号
authPassword: 应用专用密码         # 登录口令

显示名只在含 RFC 5322 特殊字符时才加引号,已自带引号的写法原样透传——不然中文名和 Doe, John 这类名字会互相打架。

验收标准(两条同时满足才算过):① SMTP 认证用的是 authUser;② 收件人看到的 From 头是显示名 + 别名地址,且邮件通过 SPF 与 DKIM 校验。这台机器上的实测:认证身份为主账号,From 头解码后是「鲸鱼小姐 <alias@…>」,收件侧 dkim=pass、spf=pass。


五、路上踩的三个坑

  • YAML 会把账号名 qq 吃成布尔值。写成 accounts: { qq: {} } 时,qq 被解析成 true,账号名消失,报错说「defaultAccount 不存在」。加引号写成 "qq" 才对。
  • 账号级 provider 会被顶层简写盖住。顶层写 provider: qq、账号里写 provider: gmail 时,解析顺序让顶层的主机地址赢了——账号静默连到错误的服务器。规避办法是账号级显式写 imap/smtp 主机,别依赖预设。
  • 从本机直连 Google 中继会被拒。中继只放行你在 Workspace 里登记过的来源地址,动态 IP 的机器会被打发去买咖啡。所以发信改用 smtp.gmail.com:465 + 应用专用密码;服务器侧才走中继。

六、结语

补丁很小,但方法值得复用:先写兼容性对照,再改代码;改完必须有能复现的验收证据。至于署名——从今天起,这个笔名也会出现在一些技术笔记里。写得不好,欢迎拍砖。