微信校验文件不用文件

H5 站刚迁完,HTTPS 正常,接口正常,页面也正常。接下来要接微信,第一道门槛是「业务域名」。

业务域名,是微信允许你在微信内置浏览器里直接打开的域名白名单。不在白名单里的域名,用户在微信里点开就会被微信接管,跳到它自己的一张提示页。要让微信相信这个域名归你管,它给的办法是给你一个随机文件名和一段随机内容,让你放到「网站根目录」,然后微信的服务器自己来请求一次,比对返回的内容。

这次微信给的是新版校验形式。

文件名是一串随机的 32 位十六进制字符串,加 .txt,旧版是 MP_verify_xxxx.txt。文件内容是一段随机的 40 位十六进制字符串。部署位置,微信的说法是「网站根目录」。校验方式是微信服务器去请求 https://<你的域名>/<校验文件名>.txt,然后比对响应体。

麻烦就在这个「网站根目录」上。我这台的根目录,根本不在我能随手 touch 的地方。

text 复制代码
微信服务器
  └─> https://h5.example.com/<校验文件名>.txt
        └─> DNS → 宿主 443
              └─> 1Panel 的 OpenResty 容器(listen 443,server_name h5.example.com)  ← 请求先到这儿
                    └─> location ^~ /  →  proxy_pass http://127.0.0.1:<端口>
                          └─> H5 容器(nginx:80,静态根 /usr/share/nginx/html/)

H5 是跑在容器里的静态站点,前面还挡着一层 1Panel 的 OpenResty。同机又跑着 6 个别人的站点,面板配置动错了是要出事故的。

微信在问的其实是:谁能控制这个域名的 HTTP 响应。

在这一层拓扑里,答案是反代配置,不是文件系统。只要我能让 OpenResty 对 GET /<校验文件名>.txt 返回那 40 个字节,微信就满意了。它不管这 40 个字节来自磁盘上的一个文件,还是来自配置里的一行字符串。

落地方式我数过,有四种,而且差别不小。

直接让 nginx return 文本最短,不用建文件,零路径,跟容器完全解耦,三个站点通用,换文件名时改一次配置就行。指向文件那条要建文件,配置里必须写容器内路径,同样跟容器解耦、三个站点通用,只是换文件名得同时改配置和换文件,多一步。

容器直投就是 docker cp 进容器,见效最快,可容器一重建文件就没了,而且只有那个有静态服务的站能这么做,多个站点得逐个 cp。

我选了第一种。理由不是它最短,而是它把「域名归属」这件事用配置直接答完了:不碰文件系统、不碰容器、也不依赖镜像构建。

配置加在该站点的 server 块里。

nginx 复制代码
    # 微信业务域名校验(2026-09-22)
    location = /<校验文件名>.txt {
        default_type text/plain;
        return 200 '<校验内容>';
    }

三个站点各贴同样一段。微信的业务域名校验是按域名逐个发起的,哪个域名在微信里被拦了,就给哪个加,内容一模一样。

写这段的时候有几个点,是我从实测和文档里抠出来的。

必须精确匹配。漏掉 =,写成 location /<校验文件名>.txt,会被 location ^~ / 先命中,请求反代给后端,回 404。= 后面留个空格,官方语法是 location [ = | ~ | ~* | ^~ ] uri,=/路径 解析器也认,但没必要冒这个险。

末尾不带换行。return 输出的是字符串本体,不含末尾换行,正好 40 字节,跟微信期望的严格对上。

不需要 root。return 在 rewrite 阶段就返回了,后面的 root、try_files 根本不执行。

default_type 让 Content-Type 是 text/plain,微信只看 body,写不写都能过,显式写下来更明确。位置放哪儿都行,精确匹配 = 优先级最高,server 块内放哪都不会被 location ^~ / 吃掉。

分号别漏,return 200 '...';。

改完之后,面板保存会自动 reload,手工改的话先 nginx -t 再 nginx -s reload。

验证的时候别省后三条。

bash 复制代码
# ① 内容与微信的请求完全一致
curl -s https://h5.example.com/<校验文件名>.txt; echo
# 期望:<校验内容>

# ② 字节数与 Content-Type
curl -s https://h5.example.com/<校验文件名>.txt | wc -c          # 期望 40
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" \
  https://h5.example.com/<校验文件名>.txt                          # 期望 200 text/plain

# ③ 确认主站没被改坏(同机有别人的站点,这条必须做)
curl -sI https://h5.example.com/ | head -1                        # 期望 200
curl -s -H 'Host: api.example.com' http://127.0.0.1/api/v1/products | head -c 60; echo

# ④ 确认配置真的加载了(这条最有价值)
docker exec <面板容器> nginx -T 2>&1 | grep -A3 <校验文件名>

写完这段配置我心里其实是有点虚的。改的是整台机器上所有站点共用的反代。所以动配置之前先做了一次备份,而且把备份验了一遍,列得出全部域名才算成立。

bash 复制代码
BK=<备份目录>; TS=$(date +%Y%m%d_%H%M%S); mkdir -p "$BK"
find /opt/1panel -name "*.conf" -type f 2>/dev/null | tar -czf "$BK/openresty_all_$TS.tar.gz" -T -
tar -tzf "$BK/openresty_all_$TS.tar.gz" | grep -E "example|<他人域名A>|<他人域名B>"   # 列得出域名才算备份成立

这里我踩过一次。早先那条备份命令写成 find ... | head -1,抓到的是项目自带的 conf.d。打出来一个 1.5K 的包,里面不含任何站点配置,等于没备份。

备份这件事,不验证就等于没做。

这 4 行配置能成立,靠的是 nginx 的匹配顺序。nginx 处理请求时,location 的匹配顺序是:精确匹配 = 先,然后是前缀匹配 ^~,再是正则,最后是普通前缀。1Panel 生成的站点配置,主入口是 location ^~ /,它把所有请求都反代给后端容器。

而 location = /xxx.txt 是精确匹配,优先级最高。命中之后,return 在 rewrite 阶段就把请求结束了,proxy_pass 属于后面的 content 阶段,压根没机会执行。

这就是零文件的物理基础。

text 复制代码
微信服务器 ---> OpenResty(精确匹配命中 ---> return 200)
                    │
                    ✗ 不往下走:不查磁盘、不反代、不进容器
                    ↓
              (后端容器完全无感)

不需要文件,不需要 root,不需要 docker cp,也不需要重新构建镜像。H5 容器以后换镜像、换版本、重建,全部无感。

要是漏掉 = 会怎样?

写成 location /<校验文件名>.txt 就是前缀匹配,按优先级排在 location ^~ / 后面。^~ 先命中,请求被反代给 H5 容器,容器里没这个文件,于是 404。

我记得这个 404 特别迷惑。语法检查 nginx -t 是通过的,curl 站点首页也是正常的,只有校验这一个路径失败。这是这条路上最常见的翻车点。

那内容为什么必须严格 40 字节?

微信比对的是响应体。return 输出字符串本体,不带末尾换行;echo 会带。所以走文件路线的话,得这么写:

bash 复制代码
printf '%s' '<校验内容>' > <目录>/<校验文件名>.txt
wc -c <目录>/<校验文件名>.txt      # 期望 40

printf '%s' 和 echo 差的那一个字节,就是校验通过与校验失败的距离。

指向文件那条我没选,还有一个原因:配置里的 root 必须写容器内路径。OpenResty 本身是个容器,挂载关系是宿主 /opt/1panel/www 对应容器 /www。写成宿主路径 /opt/1panel/www/sites/...,nginx 在容器里找不到,请求变成 404。而 nginx -t 照样通过,非常隐蔽。

容器直投那条用 docker cp 把文件塞进 H5 容器的静态根,大约 30 秒就能见效,但它只对有静态文件服务的那个站有效,而且容器一重建就没了。要想持久,就得把文件放进前端的 public/ 再 commit,等于把运营期的临时校验写进了代码仓库。

微信重新校验时会换一套文件名和内容,这时候几条路的代价就不一样了。

直接 return 文本,改一次配置,换掉 location 的文件名和 return 的内容,大约 30 秒。

指向文件加精确匹配,改配置换文件名,再换一个新文件。

指向文件加正则匹配,最省事,什么都不用改,把新文件丢进那个目录就行------前提是主 location 不是 ^~。

容器直投最麻烦,重新 cp 一遍,要持久还得改代码、重建镜像。

正则写法是 location ~ ^/[0-9a-f]{32}\.txt$ 这种,只在主 location 是 location / 的时候才可用。面板生成的是 ^~ /,^~ 优先级高于正则,会先把请求吃掉。动手前先确认一下:

docker exec <面板容器> nginx -T 2>&1 | grep -E "^\s*location" | sort -u

检查项我挨个过一遍。

备份做了没做而且验没验过,用 tar -tzf 去 grep 域名,列得出域名才算。语法通没通过,docker exec <面板容器> nginx -t,要回 syntax is ok。

配置真加载了没,nginx -T 2>&1 | grep -A3 <校验文件名>,要回显出那段 location。

路径返回的内容对不对,curl -s https://h5.example.com/<校验文件名>.txt,要和微信给的一致。接上 wc -c 要是 40。状态码和类型对不对,curl -s -o /dev/null -w "%{http_code} %{content_type}",要是 200 text/plain。

主站有没有被改坏,我用 curl -sI https://h5.example.com/ | head -1 看,要 200,接口要回 JSON。同机有别人的站点,这条必须做。站点基线变没变,用多域名 Host 探测,要和改动前一致。其余站点加没加,把上一条换个域名重跑,同样要返回内容。

平台侧提交校验,回公众平台点提交。微信里访问还拦不拦,用微信打开 H5,要能正常显示。若微信那个页面已经超时,回公众平台重新发起一次就行,已经部署好的内容不会失效。

微信问的是「谁控制这个域名的响应」,我能答这个问题的那一层是反代配置,跟文件系统没关系。精确匹配 = 是这套写法成立的前提,漏掉它就会静默 404;return 不带换行,所以是严格 40 字节。备份不验证等于没备份,tar -tzf 列得出域名才算成立。

写完之后我又想了一遍那个 1.5K 的假备份。要是当时没顺手 tar -tzf 验一下,我可能就带着它去改整台机器的反代了。

相关推荐
福大大架构师每日一题1 小时前
2026-10-20:统计二叉树中支配节点的数量。用go语言,给定一棵完全二叉树的根节点 root,树中每个节点都有一个值。 对于一个节点 x,考察由 x 本身
后端
SensorFlow1 小时前
测试埋点别混进正式报表:环境隔离、身份标记与查询口径三层验收
后端
从零到一未来可期1 小时前
第6集:RAG检索不准?3个参数调优路线图
后端
柠檬味拥抱1 小时前
空中目标检测数据集 | 8900张YOLO低空安防数据集
后端
2601_961963381 小时前
电子合同检索与全文搜索怎么测、怎么选:六个指标 + 五家平台对比 + 八个自测问题
数据库·后端·python·django·智能合约
Code_Artist2 小时前
论反射编程的魅力——程序如何在运行时认识自己?
后端·源码·编程语言
UaoN2 小时前
作为全栈开发者,为什么看到 Shopify 回归原生,我依然选择 React Native
前端·后端
打工仔折腾 AI2 小时前
从零开始大模型微调:基于 PyTorch 与 ChatGLM 的 LoRA 实践指南
人工智能·pytorch·后端·python·深度学习·性能优化·ai agent 实战
wing983 小时前
人生第一个私活:不到 2 小时,到手 350 元
前端·后端·程序员