文章添加页面, 截图后,粘贴图片到编辑框,有时候会出现如图的提示,这是什么原因?如果超时或者oss 服务器故障,会返回什么?
Unexpected token'<', "<html>
<he"... is not valid JSON
OSS 服务层的故障都会兜成 JSON,不会产生 HTML。截图里 <html> 换行后 <he… 的格式正是 nginx 默认错误页(<html>\r\n<head><title>…)
一、那段英文是怎么冒出来的
前端粘贴上传的代码(article-editor.js)是无条件按 JSON 解析的:
fetch(...).then(r => r.json())
fetch 只有在连不上(断网/DNS/CORS)时才会走 catch;只要服务器有任何 HTTP 响应回来——哪怕是 502、504、413 的 HTML 错误页——fetch 都算成功,然后 .json() 去解析 <!DOCTYPE html><html><head>…,于是抛出:
Unexpected token '<', "<he"… is not valid JSON
这个异常被 catch 接住,alert 原样显示出来。所以它的含义不是"图片有问题",而是"服务器这次根本没回 JSON"。
二、哪些环节会返回 HTML(以及各自长什么样)
你们上传要经过四跳,每一跳的失败形态不同:
失败环节 状态码 返回内容 典型原因
① nginx 请求体超限 413 nginx HTML 错误页 client_max_body_size 默认仅 1MB,截图 PNG 很容易超
② nginx → PHP-FPM 502 / 504 nginx HTML 错误页 FPM 挂了/连接数满(502);PHP 执行超过 fastcgi_read_timeout(默认60秒,504)
③ PHP → 远程 OSS curl 30秒超时 JSON(已兜底) OssService 返回 {"code":0,"msg":"请求 OSS 失败:…"},不会出英文报错
④ 后台登录过期 302→200 登录页 HTML 见下,这是"有时候"的高频原因
注意 ③:你们代码其实已经设计得不错,OSS 超时/宕机/返回异常都在 PHP 里兜成 JSON。所以只要看到 HTML,一定是 PHP 之外的环节(nginx、FPM,或重定向)
三、有办法处理,分两层
方案 A:前端"防御性解析"(最治本,改动小)
思路是不要假设响应一定是 JSON,解析前先验身:
先看 response.ok 和 Content-Type:不是 application/json 就不解析,按状态码翻译成人话——413="图片太大"、504="服务器超时,请重试"、200但HTML="登录已过期,请刷新重登";
给 fetch 套 AbortController 设前端超时(fetch 本身没有超时机制);
catch 里再兜一层:即使解析报错,也提示"上传失败,请重试",而不是把 Unexpected token 原文甩给用户;
502/504 多为瞬时故障,可自动重试 1 次。
这样不管后端吐什么,用户看到的永远是明确的中文提示。
方案 B:nginx 把错误也吐成 JSON(运维侧,不动业务代码)
在上传接口的 location(或后台 API 统一前缀)上用 error_page 兜底:
nginx
error_page 413 502 503 504 = @upload_err;
location @upload_err {
default_type application/json;
return 500 '{"code":0,"msg":"服务器繁忙或超时,请稍后重试"}';
}
要点:只对接口路径做,别全局改,否则网页出错时用户也会下载一坨 JSON。配合把这条链路上的参数对齐,让"谁先超时"有明确分工:
client_max_body_size 调到略大于业务限制(代码里正文图是 5MB,nginx/PHP 都应 ≥5MB 留点余量);
超时梯度建议 nginx fastcgi_read_timeout > PHP max_execution_time > OSS 的 CURLOPT_TIMEOUT(30s)。现在 OSS 在 SSL 证书失败时会重试一次,最坏 30×2=60 秒,恰好顶着 nginx 默认的 60 秒,边界上可能互相打架,值得留意;
PHP 的 upload_max_filesize、post_max_size 也要和 nginx 一致,否则 PHP 超限返回的又是另一种形态。
方案 C:架构层(了解即可)
截图通常只有几百 KB,频繁 504 说明瓶颈在服务器↔OSS 那一跳或 FPM 承载力。长期可以考虑前端拿签名直传 OSS,少一跳转发,从根上消除"我们的服务器等远程 OSS"的超时;大文件再加分片和进度条。但这改动大,不是现在必须做的。
四、下次复现时怎么 10 秒定位
F12 → Network → 点那条 uploadImage 请求:
413 → 体积超限,调 nginx/PHP 大小;
504/502 → 看 /var/log/nginx/error.log 同时段是否有 upstream timed out,再看 PHP-FPM slow log;
200 + HTML → 看响应体标题,是登录页就是 session 过期;
状态 (failed) 没状态码 → 连接被直接掐断(网络/防火墙)。
小结:能处理,而且不用动业务逻辑——核心就两件事:前端解析前先判断响应类型,nginx 给接口路径加 JSON 版 error_page;另外建议优先排查 413(截图超 1MB)和登录过期这两个"有时候"最常见的真凶。