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{
   // 执行默认访问操作
}

结束......