最近在折腾博客图床和随机背景图时,遇到了一个非常绕的问题:
电脑端随机背景正常,手机端却始终加载不出来。
一开始怀疑过 Cloudflare WAF、防盗链、缓存、CORS,甚至一度怀疑是 WebP 格式兼容问题。
结果一路排查下来,真正的原因却藏在一个不起眼的参数里:
type=mobile
这里记录一下完整的排查过程。
我的环境
博客大概是这样的结构:
博客
https://www.example.com
↓
CloudFlare-ImgBed
https://img.example.com
↓
Cloudflare R2
图片实际存储在 R2 中,对外统一通过 CloudFlare-ImgBed 提供。
博客背景图目录类似:
blog/site/backgrounds/
桌面端随机背景使用:
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=landscape
移动端使用:
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=portrait
理论上:
orientation=landscape
负责随机横图,而:
orientation=portrait
负责随机竖图。
问题现象
电脑访问博客:
随机背景图:正常
手机访问博客:
随机背景图:加载失败
但奇怪的是,如果直接在手机浏览器里打开随机图 API:
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=portrait
接口本身又可以访问。
所以第一反应自然是:
是不是 Cloudflare 把手机端的图片请求拦掉了?
第一轮排查:Cloudflare WAF
当时我的图床配置了两条防盗链规则。
一条用于保护正常图片:
/file/*
另一条用于保护随机图 API:
/random
随机图规则最初大概是按照下面的逻辑配置:
Host = img.example.com
Path = /random
Sec-Fetch-Site = cross-site
并且
Sec-Fetch-Mode != navigate
→ Block
目的很简单:
自己博客调用
✅
直接打开随机图片
✅
其他网站直接盗用随机 API
❌
Security Events 里确实出现过 Block
在 Cloudflare 的安全事件中,根据 Ray ID 可以查到类似请求:
Service: Custom Rules
Action: Block
Host: img.example.com
Path: /random
所以当时确实存在 WAF 规则误伤的情况。
随后对规则做了几次调整,主要围绕:
Referer
Sec-Fetch-Site
Sec-Fetch-Mode
判断正常访问和跨站资源请求。
调整后,直接打开随机图 URL 已经可以正常访问。
但问题依旧:
手机打开博客,随机背景还是不显示。
直接关闭 WAF 测试
为了彻底排除防盗链的影响,后来干脆临时关闭了:
图床防盗链
随机图 API 防盗链
结果手机端依然无法加载随机背景图。
到这里基本可以确认:
Cloudflare WAF 并不是最终原因
虽然排查过程中它确实曾经拦截过部分请求,但它只是另外一个问题,并不是导致移动端随机背景始终失败的根因。
第二个怀疑:WebP 兼容问题
我的博客图片基本都使用 WebP。
于是下一个怀疑目标变成了:
/random?type=img
会不会在返回 WebP 时存在 MIME 或手机浏览器兼容问题。
为此我把随机背景图换成了 JPEG。
测试过程中 JPEG 一度可以正常显示,于是很容易得出一个结论:
是不是 WebP 导致手机端无法加载?
但后来发现,这也是一个误判。
真正修复问题之后,我重新把图片换回:
.webp
移动端依然可以正常加载。
也就是说:
JPEG
✅
WebP
✅
WebP 并不是根因。
一个非常关键的测试
后来做了一个特别简单的排除测试。
不用随机 API,直接在博客主题里填一张固定图片:
https://img.example.com/file/blog/site/backgrounds/example.webp
结果:
手机正常显示
于是问题范围一下缩小了。
固定 /file/ 图片
✅
/random 随机接口
❌
这说明:
图片本身没问题
R2 没问题
图床读取文件没问题
WebP 也没问题
问题一定出现在:
/random
这条随机图片链路里。
真正的原因:手机端把 type=img 改成了 type=mobile
后来从服务器侧进一步检查手机端实际发出的请求,终于发现了真正的异常。
后台配置明明是:
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=portrait
但是博客主题在识别到手机端之后,会对 URL 参数进行处理。
原本的:
type=img
被自动改成了:
type=mobile
于是手机端实际请求变成了:
https://img.example.com/random?type=mobile&dir=blog/site/backgrounds&orientation=portrait
问题就出在这里。
type=img 和 type=mobile 完全不是一回事
CloudFlare-ImgBed 的随机图接口中:
type=img
表示:
直接返回图片内容。
请求过程可以简单理解成:
/random?type=img
↓
随机选择图片
↓
直接返回图片
↓
浏览器显示
所以电脑端一直正常。
但是:
type=mobile
对于这个 API 来说,并不是:
返回手机图片。
实际请求后返回的是类似这样的 JSON:
{
"url": "/file/blog/site/backgrounds/example.webp"
}
于是手机端的整个过程变成:
/random?type=mobile
↓
返回 JSON
↓
博客前端仍然把这个响应当成图片
↓
加载失败
这样一来,之前所有奇怪的现象就全部能解释通了。
为什么直接打开随机 API 又正常?
因为手动测试时,我打开的是:
/random?type=img
这个接口当然正常。
但博客手机端真正请求的却是:
/random?type=mobile
也就是说:
后台配置的 URL
≠
浏览器最后实际请求的 URL
这也是整个排查过程中最容易被忽略的地方。
一直手动测试:
/random?type=img
并不能复现博客移动端真实的请求。
最终解决办法
找到移动端修改 URL 参数的逻辑后,取消:
type=img
↓
type=mobile
这一步改写。
让手机端继续使用:
type=img
然后利用:
orientation
来控制横图和竖图。
最终配置如下。
桌面端
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=landscape
手机端
https://img.example.com/random?type=img&dir=blog/site/backgrounds&orientation=portrait
这样参数职责就非常清楚了。
type=img
负责:
告诉随机 API:我要直接获取图片
而:
orientation=landscape
orientation=portrait
负责:
告诉随机 API:我要横图还是竖图
设备类型根本没有必要去修改:
type
这个参数。
修复结果
修改完成后重新测试:
电脑 + WebP
✅
手机 + WebP
✅
电脑 + JPEG
✅
手机 + JPEG
✅
直接访问 /random
✅
随机横图
✅
随机竖图
✅
因此也最终证明:
WebP 没有问题
所以背景图库继续使用 WebP 即可。
没有必要因为这次问题重新换回 JPEG。
WAF 规则也不是白折腾
虽然 Cloudflare WAF 最后不是这次问题的根因,但排查过程中也顺便把图床防盗链理清楚了。
例如普通图片:
/file/*
可以使用 Referer 做一层简单的防盗链。
大致逻辑:
Host = img.example.com
AND
Path starts with /file/
AND
Referer != ""
AND
Referer 不是:
https://example.com/*
https://*.example.com/*
→ Block
效果大概是:
自己博客引用
✅
自己的其他子域引用
✅
直接打开图片 URL
✅
普通第三方网站盗链
❌
随机图:
/random
也可以单独保护。
不过这次排查让我觉得:
防盗链规则不要写得过于激进。
尤其是:
Referer
Sec-Fetch-Site
Sec-Fetch-Mode
在不同浏览器、WebView 和移动端环境中的行为可能并不完全一样。
如果只是个人博客图床,并不是严格的私有文件访问系统,那么做基础防盗链通常就已经足够。
不然很容易出现:
别人还没被拦
自己先被拦了
这次排查踩到的几个坑
1. Cloudflare 出现 Block,不代表它就是唯一问题
Security Events 中确实出现过:
/random
→ Block
说明 WAF 当时确实存在误伤。
但是关闭 WAF 以后问题依旧存在。
所以:
发现一个问题
≠
找到最终根因
这两个概念一定要分开。
2. 换成 JPEG 后能显示,不代表 WebP 就有问题
排查过程中一度更换过 JPEG。
当时的结果很容易让人怀疑:
WebP 兼容性
但最终真正修复:
type=mobile
的问题之后,再换回 WebP 一样可以正常加载。
所以调整配置时最好一次只改变一个变量。
否则:
改 WAF
改缓存
改图片格式
改 API
改主题
全部一起改,最后即使恢复正常,也不知道到底是哪一步起作用。
3. 后台配置的 URL,不一定就是浏览器真正请求的 URL
这是这次最重要的经验。
后台明明写着:
type=img
但浏览器最终实际发出的请求却可能已经变成:
type=mobile
所以遇到前端资源加载异常时,不能只看:
我后台填写了什么
还要看:
浏览器最终请求了什么
可以通过浏览器开发者工具、服务器日志或者 Cloudflare 流量分析查看真实请求。
最后的请求链路
修复以后,逻辑终于变得很简单。
桌面端:
博客主题
↓
/random?type=img
&orientation=landscape
↓
CloudFlare-ImgBed
↓
随机选择横图
↓
R2
↓
返回 WebP
移动端:
博客主题
↓
/random?type=img
&orientation=portrait
↓
CloudFlare-ImgBed
↓
随机选择竖图
↓
R2
↓
返回 WebP
设备差异只交给:
orientation
处理。
而:
type
始终保持:
img
这样两个系统之间的参数逻辑就不会互相冲突。
总结
这次移动端随机背景图片加载失败,前后怀疑过:
Cloudflare WAF
Cloudflare R2
防盗链
缓存
CORS
JPEG
WebP
手机浏览器兼容性
但最终真正的问题只有一个:
博客主题在手机端把:
type=img
自动改成了:
type=mobile
导致 CloudFlare-ImgBed 的:
/random
不再直接返回图片,而是返回类似:
{
"url": "/file/xxx.webp"
}
前端又把这个 JSON 当成图片使用,因此加载失败。
最终只需要保证手机端仍然请求:
type=img
然后使用:
orientation=portrait
控制移动端竖图即可。
这次排查最大的经验大概就是一句话:
不要只看“我配置了什么”,一定要看客户端最后“实际请求了什么”。
因为后台写着:
type=img
并不意味着浏览器最后请求的,也一定还是:
type=img
真正的 Bug,往往就藏在这中间。


Comments NOTHING