- 字数6483
- 阅读时长34 分钟
前言
本站首页以及文章封面使用了随机展示的方式,从图床的精选图库中随机抽取一张展示,让网站每次刷新都呈现出不一样的颜色。
本次笔记包含以下内容
- 1.在图床中上传图片
- 2.使用PHP搭建基础随机图API
- 3.在CDN中配置防盗链措施
注:本站使用的OSS来自赞助商 又拍云 - 加速在线业务 - CDN加速 - 云存储 (upyun.com);PHP版本为8.1;三重防盗链措施(php一重,Referer 防盗链二重,Token三重)。
1.上传图片
准备好自己的图床,在图床中上传图片
i.选择图床
如果您有已经备案的域名,推荐使用 又拍云储存 (使用CDN/OSS需考虑防盗链)
没有域名可以使用 路过图床 (使用此类公共图床则无需考虑防盗链)
ii.上传图片
上传并记录图片的链接(URL),例如https://s11.ax1x.com/2024/01/23/pFZ7Qzt.jpg
如果您的图片链接不是规则的(如下图中的(1).webp,(2).webp那样),请使用a.文件记录的方式,如果是规则链接,请进入2.编写php程序
a.文件记录
将文件链接记录到img.txt的文件里(可以自己改名)
2.编写PHP程序
这个随机图的php需要有以下功能:
- i.在图片链接中随机选择一个
- ii.基础限制:限制访问次数,以及白名单
- iii.进阶限制:与CDN Token 防盗链通信
您也可以根据需求选择需要的代码。
i.基础选择
//如果链接是不规则的,使用下面的代码
<?php
$arr=file('img.txt');//img.txt需要换成前文中存储不规则链接的文本文档
$n=count($arr)-1;
for ($i=1;$i<=1;$i++){
$x=rand(0,$n);header("Location:".$arr[$x],"\n");}
?>
//规则链接
<?php
$randomimg = mt_rand(1, 870);//870改成图床中最后一张图片的编号
$path = "/images/($randomimg).webp";//如果图床中图片编号没有括号,删除($randomimg)的括号
$fpath = "https://img.cmxz.top{$path}";//换成自己的链接
header("Location: $fpath");//重定向
exit();
?>
ii.限制访问
如果使用计费cdn,需要限制api调用次数,阻止盗刷流量。本文使用的是较为简易的限制,只能在一定程度上阻止盗刷。
限制原理:访问一次→ 记录标示符→ 访问次数+1 →如果达到上限→ 拒绝服务
a.php限制访问
以下代码均以 i.基础选择 的规则链接为前提
使用此代码务必新建allow.txt并将自己的域名记录到其中。
不在白名单中则限10张/5min ,白名单内的域名500张/5min (可自行修改)
//version 1.0
<?php
// 尝试获取客户端真实 IP 地址
$ip = $_SERVER['REMOTE_ADDR'];
// 如果 $_SERVER['REMOTE_ADDR'] 返回 0.0.0.0,则尝试使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 或 $_SERVER['HTTP_X_REAL_IP']
if ($ip === '0.0.0.0') {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] !== '') {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_X_REAL_IP']) && $_SERVER['HTTP_X_REAL_IP'] !== '') {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
}
// 获取发起请求的域名
$callingDomain = '';
if (isset($_SERVER['HTTP_REFERER'])) {
$refererParts = parse_url($_SERVER['HTTP_REFERER']);
$callingDomain = isset($refererParts['host']) ? $refererParts['host'] : '';
}
// 生成唯一标识符,可以是 IP 和域名的组合
$identifier = $ip . '_' . $callingDomain;
// 设置每分钟允许的最大调用次数
$limit = 10; // 默认限制,可以根据实际需要调整
// 检查域名是否在 allow.txt 中
$allowedDomains = file('allow.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$isAllowed = in_array($callingDomain, $allowedDomains);
$limit = $isAllowed ? 500 : $limit;
$currentTimestamp = time();
// 读取记录文件,如果不存在则创建
$recordFile = 'records.txt';
if (!file_exists($recordFile)) {
file_put_contents($recordFile, '');
}
$records = file_get_contents($recordFile);
$recordsArray = json_decode($records, true);
if (isset($recordsArray[$identifier])) {
// 如果存在,检查时间戳是否在5分钟内
if ($currentTimestamp - $recordsArray[$identifier]['timestamp'] < 300) {
// 如果在5分钟内,检查调用次数是否达到限制
if ($recordsArray[$identifier]['count'] >= $limit) {
// 如果达到限制,返回 HTTP 403 Forbidden 错误
header('HTTP/1.1 403 Forbidden');
echo "访问太频繁,服务器要炸";
exit();
} else {
// 如果在5分钟内,调用次数+1
$recordsArray[$identifier]['count']++;
}
} else {
// 如果不在5分钟内,重置时间戳和调用次数
$recordsArray[$identifier] = array('timestamp' => $currentTimestamp, 'count' => 1);
}
} else {
// 如果标识符不存在,添加新的标识符
$recordsArray[$identifier] = array('timestamp' => $currentTimestamp, 'count' => 1);
}
// 将更新后的记录写回文件
file_put_contents($recordFile, json_encode($recordsArray));
$time = date('Y-m-d H:i:s');
$counter = file_get_contents('counter.txt');
$counter = (int)$counter + 1;
// 获取访问的客户端类型
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
// 获取当前域名
$domain = $callingDomain;
// 记录访问序号、IP地址、客户端类型、域名和访问时间到 recode.txt
file_put_contents('recode.txt', "Visit #$counter: IP: $ip, Calling Domain: $callingDomain, User Agent: $userAgent, Domain: $domain, Time: $time\n", FILE_APPEND);
// 更新计数器的值并写回 counter.txt
file_put_contents('counter.txt', $counter);
$randomimg = mt_rand(1, 870);//870改成图床中最后一张图片的编号
$path = "/images/($randomimg).webp";//如果图床中图片编号没有括号,删除($randomimg)的括号
$fpath = "https://img.cmxz.top{$path}";//换成自己的链接
header("Location: $fpath");//重定向
exit();
?>
此版本还存在的问题:
- 不会清理过期的记录,需要手动清理,等待后续更新
- ip限制能力有限,还需在cdn中进一步限制
- 重定向后可以得到真实的图片链接
b.cdn限制
在cdn中进一步设置限制规则
比如 Referer 防盗链 和 User-Agent 防盗链, Token防盗链在 c.Token防盗链 中另讲。
Referer 防盗链可以只允许白名单内的域名访问,允许为空便于自己调试
User-Agent 防盗链可以以防止Wp-scan之类的扫站
c.Token防盗链
Token防盗链:通过设置 Token 密钥,配合签名过期时间来控制资源内容的访问时限,也即时间戳防盗链。(可以使资源链接定时过期),通过Token解决上一版本中“重定向后可以得到真实的图片链接”的问题,即便得到了真实链接也无法访问,只能通过api获得 15秒(可自行修改)内有效的链接
需要在CDN中配置Token密钥
本文提供包含 又拍云Token算法 的完整代码,即本站目前使用的api源码。
不过代码还存在些许不足,还请谅解
<?php
// 尝试获取客户端真实 IP 地址
$ip = $_SERVER['REMOTE_ADDR'];
// 如果 $_SERVER['REMOTE_ADDR'] 返回 0.0.0.0,则尝试使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 或 $_SERVER['HTTP_X_REAL_IP']
if ($ip === '0.0.0.0') {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] !== '') {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_X_REAL_IP']) && $_SERVER['HTTP_X_REAL_IP'] !== '') {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
}
// 获取发起请求的域名
$callingDomain = '';
if (isset($_SERVER['HTTP_REFERER'])) {
$refererParts = parse_url($_SERVER['HTTP_REFERER']);
$callingDomain = isset($refererParts['host']) ? $refererParts['host'] : '';
}
// 生成唯一标识符,可以是 IP 和域名的组合
$identifier = $ip . '_' . $callingDomain;
// 设置每分钟允许的最大调用次数
$limit = 10; // 默认限制,可以根据实际需要调整
// 检查域名是否在 allow.txt 中
$allowedDomains = file('allow.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$isAllowed = in_array($callingDomain, $allowedDomains);
// 根据是否允许设置不同的限制
$limit = $isAllowed ? 500 : $limit;
// 获取当前时间戳
$currentTimestamp = time();
// 读取记录文件,如果不存在则创建
$recordFile = 'records.txt';
if (!file_exists($recordFile)) {
file_put_contents($recordFile, '');
}
// 读取记录文件中的内容
$records = file_get_contents($recordFile);
// 将记录文件内容转换成数组
$recordsArray = json_decode($records, true);
// 检查当前标识符是否已经存在
if (isset($recordsArray[$identifier])) {
// 如果存在,检查时间戳是否在5分钟内
if ($currentTimestamp - $recordsArray[$identifier]['timestamp'] < 300) {
// 如果在5分钟内,检查调用次数是否达到限制
if ($recordsArray[$identifier]['count'] >= $limit) {
// 如果达到限制,返回 HTTP 403 Forbidden 错误
header('HTTP/1.1 403 Forbidden');
echo "访问太频繁,服务器要炸";
exit();
} else {
// 如果在5分钟内,调用次数+1
$recordsArray[$identifier]['count']++;
}
} else {
// 如果不在5分钟内,重置时间戳和调用次数
$recordsArray[$identifier] = array('timestamp' => $currentTimestamp, 'count' => 1);
}
} else {
// 如果标识符不存在,添加新的标识符
$recordsArray[$identifier] = array('timestamp' => $currentTimestamp, 'count' => 1);
}
// 将更新后的记录写回文件
file_put_contents($recordFile, json_encode($recordsArray));
// 获取当前时间
$time = date('Y-m-d H:i:s');
// 读取当前计数器的值
$counter = file_get_contents('counter.txt');
$counter = (int)$counter + 1;
// 获取访问的客户端类型
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
// 获取当前域名
$domain = $callingDomain;
// 记录访问序号、IP地址、客户端类型、域名和访问时间到 recode.txt
file_put_contents('recode.txt', "Visit #$counter: IP: $ip, Calling Domain: $callingDomain, User Agent: $userAgent, Domain: $domain, Time: $time\n", FILE_APPEND);
// 更新计数器的值并写回 counter.txt
file_put_contents('counter.txt', $counter);
$randomimg = mt_rand(1, 870);
// 构建路径
$path = "/($randomimg).webp";
// 生成带有签名的链接
$randomSignedLink = generateSignedLink($path);
// 重定向到随机链接
header("Location: $randomSignedLink");
exit();
// 生成带有签名的链接的函数
function generateSignedLink($path) {
// 防盗链参数配置
$key = '换成自己的Token密钥';
$expireTime = time() + 15; //过期时间为15秒
// 构造签名
$sign = substr( md5($key.'&'.$expireTime.'&'.$path), 12, 8 ) . $expireTime;
//取中间8位
// 构造签名后的链接
return "https://img.cmxz.top{$path}?_upt={$sign}";
}
?>
Comments NOTHING