axios-统一用拦截器在header请求头和请求体中添加参数并使用PHP获取
axios封装和拦截器
axios+拦截器+自定义请求头处理+自定义请求头导致的请求预检处理
axios-统一用拦截器在header请求头和请求体中添加参数并使用PHP获取
axios-关于封装一个底层axios请求和拦截器interceptors,catch错误(throw或reject)?
axios-options请求预检和Vue中axios封装以及withCredentialstrue
axios-abortController、signal配合axios实现一个可以中断请求的操作
/* eslint-disable */
import axios from 'axios'
import { ERR_OK, BASE_URL } from "./config";
import { showAlert } from "@/common/js/sweet-alert";
import { getToken } from "./jwt";
axios.defaults.baseURL = BASE_URL;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
// axios向跨域请求携带cookie 必须配合服务端header
axios.defaults.withCredentials = true;
// 请求拦截器:统一添加Token到请求头
axios.interceptors.request.use(
async (config) => { // 加 async
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
const APP_ID = window.setting.app_id;
// 在请求体中添加固定的参数
if (config.method === 'get') {
// GET 请求:添加到 URL 参数
config.params = {
app_id: APP_ID,
// 展开原有的 params,确保不会覆盖
...config.params
};
}
else if (['post', 'put', 'patch'].includes(config.method)) {
// POST 等请求:添加到请求体 区分 FormData 和普通对象
if (config.data instanceof FormData) {
// 如果是 FormData,直接追加 app_id
config.data.append('app_id', APP_ID);
} else {
// 普通对象,合并 app_id
config.data = {
app_id: APP_ID,
...config.data
};
}
}
// 异步函数返回的是 Promise,axios 处理更稳定
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
// 在 Axios 的响应拦截器中,成功拦截器必须返回 response(或修改后的 response),否则后续的 .then 会接收到 undefined。
async (response) => {
// 这里必须返回response,否则后续.then会拿到undefined
return response;
},
async (error) => {
// console.log(error.code)
// 统一处理比如超时等错误
switch (error.code) {
case 'ECONNABORTED':
showAlert({
title: '服务器繁忙',
text: '请稍候刷新重试',
icon: 'error',
});
break;
case 'ERR_NETWORK':
// 网络错误(比如跨域等)
showAlert({
title: '网络错误',
text: '无法连接到服务器,请检查网络设置后,刷新重试',
icon: 'error',
});
break;
// 主动中断请求
case 'ERR_CANCELED':
console.log('主动中断请求', error.code);
break;
default:
// 处理其他网络错误
showAlert({
title: '服务器繁忙',
text: '服务器开小差了,请您稍候再试',
icon: 'error',
});
}
return Promise.reject(error);
}
);
/*
* params 必须是一个对象
* */
export function get(url, params) {
// 如果不是一个对象就包一下
if (!(typeof params == 'object')) {
params = { params }
}
return axios.get(url, {
params: params
}).then((res) => {
const serverData = res.data;
if (serverData.code === ERR_OK) {
return serverData.data
} else {
// 通用的错误处理
errorHandle(serverData);
// console.log(serverData.message);
throw new Error(serverData.msg || '接口请求失败');
}
}).catch((e) => {
console.error(e);
// 重新抛出错误,让上层调用者处理
throw e;
// return null;
})
}
/*
* params 必须是一个对象
* */
export function post(url, params) {
if (!(params instanceof FormData) && typeof params !== 'object') {
params = { params };
}
return axios.post(url, params).then((res) => {
const serverData = res.data;
if (serverData.code === ERR_OK) {
return serverData.data;
} else {
errorHandle(serverData);
throw new Error(serverData.msg || '接口请求失败');
}
}).catch((e) => {
console.error('post请求错误:', e);
throw e;
// return null;
});
}
// upload方法(专门处理文件上传,独立配置)
export function upload(url, formData) {
// 校验参数:确保传递的是FormData(避免传错格式)
if (!(formData instanceof FormData)) {
console.error('upload方法仅支持FormData参数!');
throw new Error('参数格式错误,需传递FormData');
// return Promise.reject(new Error('参数格式错误,需传递FormData'));
}
// 独立请求配置:覆盖默认头,确保文件格式正确
const requestConfig = {
headers: {
// 关键:设置为multipart/form-data(axios也会自动识别FormData并生成,这里显式设置更保险)
'Content-Type': 'multipart/form-data'
}
};
// 发送文件上传请求
return axios.post(url, formData, requestConfig).then((res) => {
// console.log(res)
const serverData = res.data;
// console.log(serverData)
if (serverData.code === ERR_OK) {
return serverData.data; // 返回后端的文件URL等数据
} else {
errorHandle(serverData);
console.log('文件上传失败:', serverData);
throw new Error(serverData.msg || '接口请求失败');
}
}).catch((e) => {
console.log('图片上传失败:', e);
throw new Error('图片上传失败,请稍候重试');
});
}
function errorHandle(serverData) {
switch (serverData.code) {
// 1000是登录方面的问题
case 1000:
window.alert(serverData.msg);
window.location.href = serverData.data.login;
break;
// 400是上传图片问题
case 400:
showAlert({
text: serverData.msg
});
break;
default:
showAlert({
text: serverData.msg
});
}
}以上是我在全局axios下,对axios进行的封装,里面包含了拦截器、基础的post\get、上传请求,并且在拦截器中统一添加了携带token的自定义请求头Header以及在请求体中添加了统一的app_id参数,这样就不需要每次在方法中单独设置app_id这个参数了。
注意,upload传进来的必须是一个formdata对象
<input
type="file"
ref="fileInput"
accept="image/png,image/jpg,image/jpeg"
@change="handleFileChange"
class="file-input"
>
handleFileChange(e) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('image', file);
upload('urlxxx', formData)
},
在拦截器中也是通过检测是不是formdata,如果是,那么就需要使用formData.append来添加参数。当然,以上axios封装是基于全局的(还可以创建单独的axios实例,避免全局污染),而且还可以在get\post等方法中,添加第三个config参数,通过用 AbortController 包装请求,还可以实现主动中断请求的操作。
参考:https://www.wubin.work/blog/articles/613
当然因为发送了自定义请求头,所以后端会触发请求预检,后端也需要添加修改。
参考:https://www.wubin.work/blog/articles/615
PHP处理请求预检
axios中的withCredentials: true不能单独使用,否则会触发跨域错误,需要后端配合设置两个关键响应头:
Access-Control-Allow-Credentials: true告诉浏览器:「允许这个跨域请求携带凭证」。
Access-Control-Allow-Origin: 具体域名(如http://localhost:8081)必须指定明确的前端域名,不能用 *(浏览器规定:带凭证的跨域请求不允许 Origin 为 *)。
所以当有自定义Header的时候PHP需要添加白名单机制:
<?php
header("content-type:application/json;charset=utf-8");
// 定义允许的前端域名白名单(数组形式,末尾无斜杠)
$allowedOrigins = [
'http://192.168.1.101:8080',
'http://localhost:8080',
'https://yourdomain.com' // 可添加更多域名
];
try{
// 动态获取前端请求源(可能为空,如非跨域请求)
/*
* 比如项目地址:https://ai2hinen9t1.thexin.cn/ai/article-chufa/
* $_SERVER['HTTP_ORIGIN']得到的就是https://ai2hinen9t1.thexin.cn
* 所以白名单只需要添加https://ai2hinen9t1.thexin.cn 这个域名即可
*/
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// 判断当前请求源是否在白名单中
$isAllowed = in_array($origin, $allowedOrigins);
// 1. 处理 OPTIONS 预检请求 不要执行业务逻辑
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
if ($isAllowed) {
header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
header("Access-Control-Allow-Headers: Authorization, Content-Type");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Credentials: true");
// Access-Control-Max-Age 让浏览器缓存预检结果,减少 OPTIONS 请求次数,提升性能
header("Access-Control-Max-Age: 86400"); // 缓存预检结果24小时
}
exit;
}
// 2. 处理实际请求(POST/GET)
if ($isAllowed) {
header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
header("Access-Control-Allow-Credentials: true");
} else {
// 直接抛错,进入catch统一处理
throw new Exception('当前请求不在白名单中,请与管理员联系', 1003);
}
// 假设原有业务逻辑
$log = "{$_SERVER['REQUEST_METHOD']} " . date('Y-m-d H:i:s') . "\n";
$filename = __DIR__. '/debuglog/' . uniqid() . '_debug.log';
$filecontent = $log . print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
file_put_contents($filename, $filecontent, true) . "\n\n", FILE_APPEND);
} catch (Exception $e) {
// 直接给错误状态
http_response_code(401);
// 获取错误码和错误信息
$errcode = $e->getCode();
$errmsg = $e->getMessage();
exit(输出json等)
}
?>在白名单检查那里,$_SERVER['HTTP_ORIGIN']当是同源请求(x1/a.html get访问 x1/server/xxx.php)、从浏览器直接访问url地址、服务端之间的CURL请求的时候,都会是空!所以需要额外加一个判断,只有有值的时候,才去检测来源,如果没有那么就默认它是来自同源的请求!然后服务端之间不要依赖origin!要用额外的自定义api_key去核实!
$allowedOrigins = [
'https://ai2hinen9t1.thexin.cn'
];
// 动态获取前端请求源(可能为空,如非跨域请求)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// 如果是空 代表是一个同源的请求 不处理
if(empty($origin)) {
$isAllowed = true;
} else {
// 判断当前请求源是否在白名单中
$isAllowed = in_array($origin, $allowedOrigins);
}PHP获取自定义请求头的内容
在 PHP 中获取 HTTP 请求头(Header)中的Authorization信息(也就是你说的 Token)有两种常用方法:
方法一:使用$_SERVER超全局变量(最常用)
PHP 会将大部分请求头转换为$_SERVER数组中的元素,格式通常是HTTP_前缀加上大写的 header 名称,并用下划线_替换横杠-。
对于Authorization: Bearer 12345567888这个 header,你可以这样获取:
<?php
// 从 $_SERVER 数组中获取 Authorization header
$authorizationHeader = $_SERVER['HTTP_AUTHORIZATION'];
// 检查 header 是否存在
if (!isset($authorizationHeader)) {
// 如果不存在,返回 401 Unauthorized 错误
http_response_code(401);
echo json_encode(['error' => 'Authorization header is missing']);
exit;
}
// 检查 header 是否以 'Bearer ' 开头
$token = null;
$pattern = '/^Bearer\s+(.*)$/i'; // 正则表达式,不区分大小写
if (preg_match($pattern, $authorizationHeader, $matches)) {
$token = trim($matches[1]);
}
if ($token === null) {
// 如果 Token 格式不正确
http_response_code(401);
echo json_encode(['error' => 'Invalid token format']);
exit;
}
// 在这里使用你的 token
echo "成功获取到 Token: " . $token;
// 例如:12345567888
?>方法二:使用getallheaders()函数(推荐在 PHP-FPM 等现代环境中使用)
getallheaders()函数会返回一个包含所有 HTTP 请求头的关联数组。这个方法的代码看起来更直观。
<?php
// 获取所有请求头
$headers = getallheaders();
// 检查 'Authorization' header 是否存在
if (!isset($headers['Authorization'])) {
http_response_code(401);
echo json_encode(['error' => 'Authorization header is missing']);
exit;
}
$authorizationHeader = $headers['Authorization'];
// 同样使用正则表达式提取 Token
$token = null;
$pattern = '/^Bearer\s+(.*)$/i';
if (preg_match($pattern, $authorizationHeader, $matches)) {
$token = trim($matches[1]);
}
if ($token === null) {
http_response_code(401);
echo json_encode(['error' => 'Invalid token format']);
exit;
}
// 在这里使用你的 token
echo "成功获取到 Token: " . $token;
// 例如:12345567888
?>两个方法的对比
| 特性 | $_SERVER['HTTP_AUTHORIZATION'] | getallheaders() |
|---|---|---|
| 兼容性 | 非常好,兼容所有 PHP 版本和服务器环境(如 Apache, Nginx + PHP-FPM)。 | 在 Apache 下表现良好。在 Nginx + PHP-FPM 环境中,需要确保fastcgi_param AUTHORIZATION $http_authorization;已配置,否则可能获取不到。 |
| 代码简洁性 | 稍长,需要手动构造键名。 | 更简洁、直观,直接通过原始 header 名称获取。 |
推荐优先使用方法一($_SERVER['HTTP_AUTHORIZATION'])。
$_SERVER['HTTP_AUTHORIZATION']和getallheaders()返回是Null的问题
如果$_SERVER['HTTP_AUTHORIZATION']和getallheaders()返回的都是Null,那么这是因为服务器(如 Apache)默认不会将Authorization头传递给 PHP,需要通过配置显式开启。
出于安全和协议规范的考虑,Apache 等服务器默认会过滤Authorization头,不会将其暴露给 PHP 的$_SERVER变量。只有显式配置后,才能通过$_SERVER['HTTP_AUTHORIZATION']获取到该头信息。
Apache环境(局部配置):
在项目根目录的.htaccess文件中添加以下配置:
# 方法一:通过 SetEnvIf 传递 Authorization 头
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# 方法二:通过 mod_rewrite 传递(需确保已启用 mod_rewrite)
RewriteEngine On
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]添加后重启 Apache 服务,即可通过$_SERVER['HTTP_AUTHORIZATION']正常获取 Token。
如果修改重启后还未生效,可能是 Apache 未启用mod_rewrite或mod_setenvif模块,在httpd.conf中:
# 启用 mod_setenvif(方法一依赖)
LoadModule setenvif_module modules/mod_setenvif.so
# 启用 mod_rewrite(方法二依赖,若用方法二需开启)
LoadModule rewrite_module modules/mod_rewrite.so还需要允许.htaccess生效:
在httpd.conf中搜索`(或你项目所在的目录配置),确保以下参数为All`:
<Directory "${INSTALL_DIR}/htdocs">
Options Indexes FollowSymLinks Includes ExecCGI
AllowOverride All # 必须设为 All,允许 .htaccess 生效
Require local
</Directory>Apache环境(全局配置):
全局开启后,本地 Apache 所有项目都能直接获取Authorization头,无需每个项目单独配置.htaccess,核心是修改 Apache 主配置文件(我本地是phpstudy,找到http.conf文件):
添加全局传递规则
在httpd.conf文件末尾(推荐方法一):
# 方法一:全局传递 Authorization 头(优先选,无需启用重写模块)
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
# 方法二:若需用重写模块传递(已启用 mod_rewrite 时可用)
# RewriteEngine On
# RewriteCond %{HTTP:Authorization} .
# RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]文件内搜索mod_setenvif.so,必须确保mod_setenvif.so行处于解开注释的状态
# 启用 mod_setenvif(方法一必须开启)
LoadModule setenvif_module modules/mod_setenvif.so
# 若用方法二,需启用 mod_rewrite
# LoadModule rewrite_module modules/mod_rewrite.so修改好之后,记得重启apache服务器
Nginx环境(未测试,仅记录):
如果使用 Nginx + PHP-FPM,需要在 Nginx 配置文件中显式传递Authorization头:
location ~ \.php$ {
# 其他配置...
fastcgi_param AUTHORIZATION $http_authorization;
}如何验证当前服务器设置是否能正常使用getallheaders()$_SERVER['HTTP_AUTHORIZATION']
作为开发者,有时候也不知道服务器当前的设置是否支持getallheaders()和$_SERVER['HTTP_AUTHORIZATION'],我们该如何进行验证?很简单,做一个简单的请求即可:
前端HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>jQuery Header 请求测试</title>
<!-- 引入 jQuery(使用 CDN) -->
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> -->
<script src="https://www.qdxin.cn/js/xinquery.js"></script>
</head>
<body>
<h1>测试 jQuery 发送 Header 请求</h1>
<button id="sendRequest">发送请求</button>
<div id="result"></div>
<script>
$(function() {
$('#sendRequest').click(function() {
// 发起 POST 请求
$.ajax({
url: 'test-1.php', // 目标 PHP 文件
type: 'POST', // 请求方法(GET/POST 均可)
headers: {
// 自定义 Header(重点)
'Authorization': 'Bearer 123456789', // Token 示例
'X-Custom-Header': 'Test-Value' // 自定义其他 Header
},
data: {
username: 'test',
age: 20
},
success: function(response) {
// 请求成功后的处理
$('#result').html(`
<h3>请求成功!</h3>
<pre>${JSON.stringify(response, null, 2)}</pre>
`);
},
error: function(xhr, status, error) {
// 请求失败后的处理
$('#result').html(`
<h3>请求失败!</h3>
<p>状态码:${xhr.status}</p>
<p>错误信息:${error}</p>
</pre>
`);
}
});
});
});
</script>
</body>
</html>后端PHP,test-1.php
<?php
// 输出 $_SERVER['HTTP_AUTHORIZATION'] 的值
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '未获取到';
echo "HTTP_AUTHORIZATION 的值:" . $authHeader . "\n";
// 同时输出 getallheaders() 的结果(用于对比)
if (function_exists('getallheaders')) {
$headers = getallheaders();
$authFromHeaders = isset($headers['Authorization']) ? $headers['Authorization'] : '未获取到';
echo "通过 getallheaders() 获取的 Authorization 头:" . $authFromHeaders . "\n";
} else {
echo "服务器不支持 getallheaders() 函数\n";
}简单测试服务器是否支持getallheaders函数:
<?php
if (function_exists('getallheaders')) {
echo "服务器支持 getallheaders() 函数";
} else {
echo "服务器不支持 getallheaders() 函数";
}
?>更加完善的测试代码(仅做记录,使用第一段代码足够):
<?php
// 设置响应为 JSON 格式
header("Content-Type: application/json; charset=utf-8");
// 允许跨域(如果前端和后端不在同一域名下)
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Authorization, X-Custom-Header, Content-Type");
// 收集所有请求头信息
$headers = [];
if (function_exists('getallheaders')) {
$headers = getallheaders();
} else {
// 兼容不支持 getallheaders() 的环境
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
}
// 构造返回数据
$response = [
'status' => 'success',
'received_headers' => $headers,
'received_data' => $_POST, // 接收 POST 数据
'server_auth_header' => $_SERVER['HTTP_AUTHORIZATION'] ?? '未获取到'
];
// 返回 JSON 数据
echo json_encode($response, JSON_UNESCAPED_UNICODE);
?>PHP完整获取自定义请求头和请求体+域名白名单
注意,这里如果确定$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; 此脚本只是通过浏览器进行访问,那么这么写没问题,一旦有服务器->服务器的curl通信,那么就需要注意,需要对服务器curl发起的请求,添加origin,如下:
$ch = curl_init();
$options = array(
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HEADER => false,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
// 手动添加 Origin 头,值可以是你允许的任何一个白名单域名
'Origin: https://ai2hinen9t1.thexin.cn'
]
);
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if(curl_error($ch)) {
$errMsg = 'Error: ' . curl_error($ch);
curl_close($ch);
return false;
}
curl_close($ch);如果不加,那么服务端->服务端的通信,$_SERVER['HTTP_ORIGIN']就只会返回空值。这一点要特别注意。
<?php
try {
// 定义允许的前端域名白名单(数组形式,末尾无斜杠)
/**
* 比如项目地址:https://ai2hinen9t1.thexin.cn/ai/article-chufa/
* $_SERVER['HTTP_ORIGIN']得到的就是https://ai2hinen9t1.thexin.cn
* 所以白名单只需要添加https://ai2hinen9t1.thexin.cn 这个域名即可
*/
$allowedOrigins = [
'http://192.168.1.101:8080'
];
// 动态获取前端请求源(可能为空,如非跨域请求)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// 判断当前请求源是否在白名单中
$isAllowed = in_array($origin, $allowedOrigins);
// 处理 OPTIONS 预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
if ($isAllowed) {
header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
header("Access-Control-Allow-Headers: Authorization, Content-Type");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 86400"); // 缓存预检结果24小时
}
exit;
}
// 处理实际请求(POST/GET)
if ($isAllowed) {
header("Access-Control-Allow-Origin: {$origin}"); // 返回当前请求的域名
header("Access-Control-Allow-Credentials: true");
} else {
throw new Exception('当前请求不在白名单中,请与管理员联系', 1003);
}
# 验证用户权限
// 从 $_SERVER 数组中获取 Authorization header 这个依赖服务器配置
$authorizationHeader = $_SERVER['HTTP_AUTHORIZATION'];
// 检查 header 是否存在
if (!isset($authorizationHeader)) {
throw new Exception('header不存在', 1003);
}
// 检查 header 是否以 'Bearer ' 开头
$token = null;
// 正则表达式,不区分大小写
$pattern = '/^Bearer\s+(.*)$/i';
if (preg_match($pattern, $authorizationHeader, $matches)) {
$token = trim($matches[1]);
}
if ($token === null) {
// 如果 Token 格式不正确
throw new Exception('Invalid token format', 1003);
}
// 获取前端拦截器统一添加的app_id
$appid = $_REQUEST['app_id'] ?? '';
if(!$appid) {
throw new Exception('appid is wrong', 1003);
}
} catch(Exception $e) {
// 直接给401状态
http_response_code(401);
$errcode = $e->getCode();
$errmsg = $e->getMessage();
$json = json_encode([
'code' => $errcode,
'msg' => $errmsg,
'data' => []
], JSON_UNESCAPED_UNICODE);
exit($json);
}
目录