1. 前言
现在网上已经有很多PHP的IP黑白名单编写的教程,但大部分都是基于txt或者json文件来存储规则的。于是萌生一个想法,为啥不可以使用数据库来存储这些规则呢,说干就干。本文就是介绍我如何使用PHP+SQLite3来实现名单编写。
2. 数据库的创建和连接
private function createTable(): void
{
$this->db->exec("
CREATE TABLE IF NOT EXISTS rules (
id INTEGER PRIMARY KEY AUTOINCREMENT COMMENT '自增ID',
ip_rule TEXT NOT NULL UNIQUE COMMENT 'IP规则',
is_active BOOLEAN DEFAULT True COMMENT '启用状态',
remark TEXT DEFAULT '' COMMENT 'IP规则' COMMENT '备注',
create_time REAL NOT NULL COMMENT '创建时间',
type TEXT CHECK(type IN ('white', 'black')) COMMENT '类型,白名单或黑名单'
)");
}
private function connectDB(): void
{
try {
$this->db = new SQLite3($this->config['db_path']);;
} catch (Exception $e) {
error_log("Database connection failed: " . $e->getMessage());
header('HTTP/1.1 500 Internal Server Error');
exit('服务暂时不可用');
}
}
这里的
config['db_path']
就是你的sqlite数据库文件地址。
3. IP地址的获取
public function getIP(): string
{
if (getenv("HTTP_CLIENT_IP"))
return getenv("HTTP_CLIENT_IP");
else if (getenv("HTTP_X_FORWARDED_FOR"))
return explode(',', getenv("HTTP_X_FORWARDED_FOR"))[0];
else if (getenv("REMOTE_ADDR"))
return getenv("REMOTE_ADDR");
else
return "Unknown";
}
4. 核心验证代码的编写
/**
* @param string $ip 要验证的IP
* @param string $rule 规则条目
* @return bool 是否匹配
* IP验证核心逻辑
*/
private function checkIpMatch(string $ip, string $rule): bool
{
if (empty($ip) || empty($rule)) return false;
// CIDR格式验证,例如:192.168.1.0/24
if (str_contains($rule, '/')) {
$parts = explode('/', $rule, 2);
if (count($parts) !== 2 || !ctype_digit($parts[1])) return false;
$mask = (int)$parts[1];
if ($mask < 0 || $mask > 32) return false;
$subnet = @inet_pton($parts[0]);
$ipBin = @inet_pton($ip);
if ($subnet === false || $ipBin === false) return false;
$length = 8 * min(strlen($subnet), strlen($ipBin));
$maskLong = ~((1 << ($length - $mask)) - 1);
return (hexdec(bin2hex($ipBin)) & $maskLong) === (hexdec(bin2hex($subnet)) & $maskLong);
}
// 通配符格式验证,例如127.*.*.1
if (str_contains($rule, '*')) {
$segments = explode('.', $rule);
if (count($segments) !== 4) return false;
$pattern = [];
foreach ($segments as $seg) {
if ($seg === '*') {
$pattern[] = '\d+';
} else {
$pattern[] = preg_quote($seg, '/');
}
}
return (bool)preg_match('/^' . implode('\.', $pattern) . '$/', $ip);
}
// 直接IP验证
return $ip === $rule;
}
CIDR格式:通过二进制运算验证IP是否属于指定子网
通配符格式:转换为正则表达式匹配IP段
5. 查询数据库进行匹配
/**
* @param string $ip 要验证的IP
* @return string
* 检查IP是否在黑或者白名单中(白名单优先策略)
*/
public function checkAccess(string $ip): string
{
$stmt = $this->db->prepare("
SELECT ip_rule, type FROM rules WHERE is_active = 1
ORDER BY
CASE WHEN type = 'white' THEN 0 ELSE 1 END
");
$result = $stmt->execute();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
if (empty($row['ip_rule']) || empty($row['type'])) continue;
if ($this->checkIpMatch($ip, $row['ip_rule'])) {
return $row['type'] === 'white' ? 'allow' : 'deny';
}
}
return 'default';
}
这里我返回的是字符串,'allow'代表在白名单中,'deny'代表在黑名单中,‘default’表示默认
6. 验证使用
$checkIP = getIP();
if(checkAccess($checkIP) == 'allow'){
// 执行白名单访问操作
}elseif (checkAccess($checkIP) == 'deny'){
// 执行黑名单访问操作
}else{
// 执行默认访问操作
}
结束......