post携带application/json必触发浏览器options预检,因不符合简单请求标准;tp6默认allowcrossdomain中间件不处理options且未放行content-type头,需手动配置access-control-allow-headers、methods并确保options请求被正确拦截或改用表单格式/nginx代理绕过。

POST 携带 application/json 触发预检请求
TP6.0 中前端用 fetch 或 axios 发送 POST 请求时,只要设置了 Content-Type: application/json,浏览器就会自动发起 OPTIONS 预检请求——这不是你写的代码触发的,是浏览器强制行为。
常见错误现象:Failed to load http://localhost:8000/api/login: Response to preflight request doesn't pass access control check,说明后端没正确响应这个 OPTIONS 请求。
- TP6 默认中间件
\think\middleware\AllowCrossDomain::class仅处理简单请求(如GET),对OPTIONS不做任何拦截或返回,导致预检失败 - 即使你在控制器里加了
header('Access-Control-Allow-Origin: *'),它只作用于实际请求(POST),不覆盖预检请求(OPTIONS) - TP6 的路由和中间件机制中,
OPTIONS请求可能根本没走到你的控制器,而是被框架直接拒绝或返回 404/405
TP6.0 中 Content-Type: application/json 必须显式放行
预检请求会检查 Access-Control-Allow-Headers 是否包含 Content-Type。如果后端没在响应头里声明允许该头,浏览器就拒绝后续 POST。
TP6 官方中间件默认只放行基础头,不包含 Content-Type,也不处理 OPTIONS 方法。
- 手动在中间件或控制器开头加:
header('Access-Control-Allow-Headers: Content-Type, Authorization'); - 必须同时设置:
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');(OPTIONS方法不能漏) - 若使用
AllowCrossDomain中间件,需确认其源码是否支持自定义Allow-Headers;实测 TP6.0.13 及之前版本该中间件硬编码为['X-Requested-With'],不包含Content-Type
绕过预检的临时方案:改用 application/x-www-form-urlencoded
如果你只是传 JSON 数据但不想改后端,可以前端降级为表单格式发送——这属于“简单请求”,不触发预检。
示例(axios):
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
axios.post('/api/login', Qs.stringify({ username: 'admin', password: '123' }), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
注意:Qs.stringify 是必需的,不能直接传对象;否则 axios 仍会设成 application/json。
- 后端接收方式要同步改为
$this->request->param()(TP6),而非$this->request->post()或input('post.') - 此法仅适用于纯数据提交;若接口依赖
application/json的语义(如嵌套结构、空值处理),会出错 - 开发环境可快速验证问题是否由预检引起,但生产环境建议优先修复 CORS 配置
Nginx 代理是最干净的解法(推荐)
TP6 跨域问题本质是前后端域名/端口不一致。Nginx 反向代理让前端请求看起来是同源的,彻底避开浏览器 CORS 机制。
配置示例(nginx.conf):
location /api/ {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
前端请求从 http://localhost:8080/api/login 发起,Nginx 转发到 http://127.0.0.1:8000/api/login,浏览器认为这是同源请求。
- 无需修改 TP6 任何代码,也不依赖中间件或
header注入 - 避免了
OPTIONS处理、凭证携带(withCredentials)等兼容性坑 - 唯一要注意的是路径重写:proxy_pass 末尾加不加
/会影响 URL 映射,务必测试location和proxy_pass的斜杠匹配
真正麻烦的不是加几行 header,而是预检请求在 TP6 生命周期里“没地方拦”。要么接管 OPTIONS,要么绕开它——后者更稳。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










