最近在折腾一个项目,需要根据用户从哪个网站跳转过来显示不同的内容。听起来挺简单的对?但实际操作起来坑还真不少,今天就把这些坑都填平了给你们看看。
先来个最简单的实现方案:
if(isset($_SERVER['HTTP_REFERER'])){
$referer = $_SERVER['HTTP_REFERER'];
if(strpos($referer, 'google.com') !== false){
header('Location: vip-page.php');
exit;
}
}
看起来很美是不是?但现实往往比代码残酷得多。首先HTTP_REFERER这个变量是可以伪造的,其次有些浏览器压根就不发送这个头信息。我测试的时候发现,用Chrome直接输入网址访问时,这个变量就是空的。
更坑爹的是,有些安全软件会主动过滤掉这个头信息。我遇到过最奇葩的情况是,某个杀毒软件会把所有referer都改成它自己的官网,当时排查这个问题差点没把我整崩溃。
为了解决这个问题,我搞了个增强版的判断逻辑:
function getRealReferer(){
$referer = '';
if(isset($_SERVER['HTTP_REFERER'])){
$referer = $_SERVER['HTTP_REFERER'];
// 过滤掉明显伪造的referer
if(strpos($referer, 'antivirus.com') !== false){
$referer = '';
}
}
return $referer;
}
$realReferer = getRealReferer();
if(empty($realReferer)){
// 如果没有referer,尝试其他判断方式
if(isset($_SERVER['HTTP_ORIGIN'])){
$realReferer = $_SERVER['HTTP_ORIGIN'];
}
}
这个方案稍微靠谱点了,但还不够完美。后来我发现有些流量统计工具会在URL后面加参数标记来源,比如utm_source之类的。于是我又加了个判断:
if(isset($_GET['utm_source'])){
$source = $_GET['utm_source'];
switch($source){
case 'weibo':
header('Location: weibo-landing.php');
break;
case 'baidu':
header('Location: baidu-special.php');
default:
// 默认跳转
}
exit;
}
说到跳转,这里有个大坑要提醒你们。很多新手会忘记在header跳转后加exit,结果代码继续执行,跳转根本没生效。我就见过有人调试了半天,最后发现是因为少写了个exit,那场面简直不要太尴尬。
为了防止跳转失效,我习惯性会这么写:
header('Location: target.php');
die('Redirecting...'); // 比exit更有逼格
有时候我们需要记录跳转来源做数据分析。这时候要注意防注入:
$referer = isset($_SERVER['HTTP_REFERER']) ?
htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_QUOTES) : '';
$stmt = $pdo->prepare("INSERT INTO refer_log (referer, ip) VALUES (?, ?)");
$stmt->execute([$referer, $_SERVER['REMOTE_ADDR']]);
说到IP,有些特殊需求要根据IP所在地跳转。这时候可以这样:
$ip = $_SERVER['REMOTE_ADDR'];
$details = json_decode(file_get_contents("http://ipinfo.io/{$ip}/json"));
if($details->country == 'CN'){
header('Location: chinese-version.php');
} else {
}
但要注意,这种API调用是有频率限制的,而且会拖慢页面加载速度。最好在本地维护一个IP库,或者用CDN的边缘计算功能来实现。
有时候我们还需要处理移动端和PC端的跳转。这个判断就更有意思了:
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
$isMobile = false;
$mobileAgents = ['iphone','android','ipod','blackberry','windows phone'];
foreach($mobileAgents as $agent){
if(strpos($userAgent, $agent) !== false){
$isMobile = true;
break;
}
}
if($isMobile){
header('Location: mobile-site.php');
} else {
不过现在响应式设计这么流行,这种跳转其实已经不太推荐了。除非是特别复杂的场景,否则还是用CSS媒体查询更靠谱。
最后说一个高级玩法:用Session来跟踪用户来源。这个适合需要多步跳转的场景:
session_start();
if(empty($_SESSION['origin'])){
$_SESSION['origin'] = $_SERVER['HTTP_REFERER'];
} else {
$_SESSION['origin'] = 'direct';
}
}
// 后续页面都可以通过$_SESSION['origin']来判断来源
这个方案的优点是即使用户在站内跳转多次,我们仍然能知道他最初是从哪来的。缺点是会增加服务器负担,而且对搜索引擎不太友好。
总结一下来路跳转的几个要点:
1. 永远不要完全信任HTTP_REFERER
2. 跳转后一定要记得exit或die
3. 记录来源时要防SQL注入
4. 移动端判断现在已经不太推荐了
5. 复杂场景可以考虑用Session跟踪
写完这些代码,我突然想到一个哲学问题:如果一个用户在隐身模式下访问网站,又没有referer,那他到底是从哪来的?算了,这种问题还是留给产品经理去思考,我们码农只管实现需求就行。