本文目录导读:

我将为您提供一个PHP项目中的IP归属查询与风控系统的实现方案,这个系统包含IP地理位置查询、风险评分、访问频率限制等功能。
IP归属查询模块
1 IP地理位置查询类
<?php
namespace App\Utils;
class IpLocation
{
private $ipdbPath;
private $ipdb = null;
public function __construct($ipdbPath = null)
{
$this->ipdbPath = $ipdbPath ?? __DIR__ . '/../../data/ip2region.xdb';
}
/**
* 获取IP地理位置信息
*/
public function getLocation($ip)
{
// 支持多种数据源,按优先级获取
$location = $this->getFromIp2Region($ip);
if (!$location) {
$location = $this->getFromApi($ip);
}
return $location;
}
/**
* 使用ip2region离线数据库
*/
private function getFromIp2Region($ip)
{
try {
if (!file_exists($this->ipdbPath)) {
return null;
}
// 使用XDB搜索引擎
$searcher = new \Ip2Region();
$result = $searcher->btreeSearch($ip);
if ($result && isset($result['region'])) {
$regions = explode('|', $result['region']);
return [
'country' => $regions[0] !== '0' ? $regions[0] : '',
'region' => $regions[1] !== '0' ? $regions[1] : '',
'province' => $regions[2] !== '0' ? $regions[2] : '',
'city' => $regions[3] !== '0' ? $regions[3] : '',
'isp' => $regions[4] !== '0' ? $regions[4] : '',
'full_location' => $result['region']
];
}
} catch (\Exception $e) {
// 记录日志
error_log("IP查询失败: " . $e->getMessage());
}
return null;
}
/**
* 使用在线API查询
*/
private function getFromApi($ip)
{
// 防止内网IP查询
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$url = "http://ip-api.com/json/{$ip}?lang=zh-CN";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$data = json_decode($response, true);
if ($data && $data['status'] === 'success') {
return [
'country' => $data['country'] ?? '',
'region' => $data['regionName'] ?? '',
'province' => $data['regionName'] ?? '',
'city' => $data['city'] ?? '',
'isp' => $data['isp'] ?? '',
'lat' => $data['lat'] ?? 0,
'lon' => $data['lon'] ?? 0,
'timezone' => $data['timezone'] ?? '',
'full_location' => "{$data['country']} {$data['regionName']} {$data['city']}"
];
}
}
}
return null;
}
/**
* 获取客户端真实IP
*/
public function getClientIp()
{
$ip = '';
// 检查HTTP代理头
$headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
'HTTP_X_CLUSTER_CLIENT_IP'
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ipList = explode(',', $_SERVER[$header]);
$ip = trim($ipList[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
break;
}
}
}
// 如果获取不到,使用REMOTE_ADDR
if (empty($ip) && isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
}
2 IP数据库更新脚本
<?php
namespace App\Console;
class UpdateIpDatabase
{
private $downloadUrl = 'https://github.com/lionsoul2014/ip2region/raw/master/data/ip2region.xdb';
private $localPath;
public function __construct($localPath = null)
{
$this->localPath = $localPath ?? __DIR__ . '/../../data/ip2region.xdb';
}
public function update()
{
$backupPath = $this->localPath . '.backup';
try {
// 备份旧数据库
if (file_exists($this->localPath)) {
copy($this->localPath, $backupPath);
}
// 下载新数据库
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $this->downloadUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true
]);
$data = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && strlen($data) > 1000) {
file_put_contents($this->localPath, $data);
echo "IP数据库更新成功\n";
return true;
} else {
throw new \Exception("下载失败: HTTP {$httpCode}");
}
} catch (\Exception $e) {
// 恢复备份
if (file_exists($backupPath)) {
copy($backupPath, $this->localPath);
unlink($backupPath);
}
echo "更新失败: " . $e->getMessage() . "\n";
return false;
}
// 删除备份
if (file_exists($backupPath)) {
unlink($backupPath);
}
}
}
风控系统模块
1 风控引擎类
<?php
namespace App\RiskEngine;
class RiskEngine
{
private $redis;
private $rules = [];
private $score = 0;
private $riskDetails = [];
public function __construct($redis = null)
{
$this->redis = $redis ?? new \Redis();
$this->loadDefaultRules();
}
/**
* 加载默认规则
*/
private function loadDefaultRules()
{
$this->rules = [
'frequency' => [
'enabled' => true,
'limit' => 100, // 每分钟最大请求数
'weight' => 30, // 违规权重
'time_window' => 60 // 时间窗口(秒)
],
'geography' => [
'enabled' => true,
'black_countries' => ['KP', 'IR', 'SY'], // 高风险国家
'high_risk_countries' => ['RU', 'CN', 'NG'],
'weight' => 40
],
'proxy' => [
'enabled' => true,
'weight' => 25
],
'device_fingerprint' => [
'enabled' => true,
'max_devices_per_ip' => 5,
'weight' => 20
],
'behavior' => [
'enabled' => true,
'speed_limit' => 5, // 每秒操作数
'weight' => 35
]
];
}
/**
* 执行风控检查
*/
public function evaluate($context)
{
$this->score = 0;
$this->riskDetails = [];
// 执行各规则检查
$results = [
$this->checkFrequency($context),
$this->checkGeography($context),
$this->checkProxy($context),
$this->checkDeviceFingerprint($context),
$this->checkBehavior($context)
];
// 汇总结果
foreach ($results as $result) {
if ($result['risk']) {
$this->score += $result['points'];
$this->riskDetails[] = $result['detail'];
}
}
return [
'score' => $this->score,
'level' => $this->getRiskLevel(),
'details' => $this->riskDetails,
'action' => $this->getAction()
];
}
/**
* 检查访问频率
*/
private function checkFrequency($context)
{
$rule = $this->rules['frequency'];
if (!$rule['enabled']) {
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
$ip = $context['ip'];
$key = "risk:frequency:{$ip}";
$current = $this->redis->incr($key);
if ($current === 1) {
$this->redis->expire($key, $rule['time_window']);
}
if ($current > $rule['limit']) {
return [
'risk' => true,
'points' => $rule['weight'],
'detail' => "访问频率过高: {$current}/{$rule['limit']}"
];
}
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
/**
* 检查地理位置风险
*/
private function checkGeography($context)
{
$rule = $this->rules['geography'];
if (!$rule['enabled'] || empty($context['country_code'])) {
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
$countryCode = strtoupper($context['country_code']);
if (in_array($countryCode, $rule['black_countries'])) {
return [
'risk' => true,
'points' => 100,
'detail' => "高风险国家: {$countryCode}"
];
}
if (in_array($countryCode, $rule['high_risk_countries'])) {
return [
'risk' => true,
'points' => $rule['weight'],
'detail' => "中风险国家: {$countryCode}"
];
}
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
/**
* 检查代理/VPN
*/
private function checkProxy($context)
{
$rule = $this->rules['proxy'];
if (!$rule['enabled']) {
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
$isProxy = false;
$proxyType = '';
// 检查常见的代理标识
if (!empty($context['headers'])) {
$headers = $context['headers'];
// 检查常见的代理头部
$proxyHeaders = [
'HTTP_VIA',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_FORWARDED',
'HTTP_X_FORWARDED_HOST',
'HTTP_X_PROXY_ID',
'HTTP_PROXY_CONNECTION'
];
foreach ($proxyHeaders as $header) {
if (!empty($headers[$header])) {
$isProxy = true;
$proxyType = $header;
break;
}
}
}
// 检查代理IP数据库
if (!$isProxy && !empty($context['ip'])) {
$proxyIpKey = "proxy_ips:{$context['ip']}";
$isProxy = $this->redis->sIsMember('known_proxy_ips', $context['ip']);
}
if ($isProxy) {
return [
'risk' => true,
'points' => $rule['weight'],
'detail' => "检测到代理/VPN: {$proxyType}"
];
}
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
/**
* 检查设备指纹
*/
private function checkDeviceFingerprint($context)
{
$rule = $this->rules['device_fingerprint'];
if (!$rule['enabled'] || empty($context['device_id'])) {
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
$ip = $context['ip'];
$deviceId = $context['device_id'];
$key = "risk:devices:{$ip}";
// 记录设备关联
$this->redis->sAdd($key, $deviceId);
$this->redis->expire($key, 86400); // 24小时
$deviceCount = $this->redis->sCard($key);
if ($deviceCount > $rule['max_devices_per_ip']) {
return [
'risk' => true,
'points' => $rule['weight'],
'detail' => "设备数量异常: {$deviceCount}/{$rule['max_devices_per_ip']}"
];
}
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
/**
* 检查行为模式
*/
private function checkBehavior($context)
{
$rule = $this->rules['behavior'];
if (!$rule['enabled']) {
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
$userId = $context['user_id'] ?? 'anonymous';
$action = $context['action'] ?? 'unknown';
$key = "risk:behavior:{$userId}:{$action}";
// 记录操作时间戳
$now = microtime(true);
$this->redis->zAdd($key, $now, $now . ':' . uniqid());
$this->redis->expire($key, 10); // 10秒窗口
// 清除过期数据
$this->redis->zRemRangeByScore($key, 0, $now - 10);
// 计算操作速度
$count = $this->redis->zCard($key);
if ($count > $rule['speed_limit']) {
return [
'risk' => true,
'points' => $rule['weight'],
'detail' => "操作速度异常: {$count}/{$rule['speed_limit']}"
];
}
return ['risk' => false, 'points' => 0, 'detail' => ''];
}
/**
* 获取风险等级
*/
private function getRiskLevel()
{
if ($this->score >= 100) return 'critical';
if ($this->score >= 70) return 'high';
if ($this->score >= 40) return 'medium';
if ($this->score >= 20) return 'low';
return 'safe';
}
/**
* 获取建议操作
*/
private function getAction()
{
switch ($this->getRiskLevel()) {
case 'critical':
return 'block';
case 'high':
return 'challenge_captcha';
case 'medium':
return 'require_verification';
case 'low':
return 'monitor';
default:
return 'allow';
}
}
/**
* 封禁IP
*/
public function blockIp($ip, $minutes = 60)
{
$key = "blocked_ips:{$ip}";
$this->redis->setEx($key, $minutes * 60, '1');
}
/**
* 检查IP是否被封禁
*/
public function isIpBlocked($ip)
{
return $this->redis->exists("blocked_ips:{$ip}");
}
/**
* 获取风控详情
*/
public function getRiskDetails()
{
return $this->riskDetails;
}
}
2 风控中间件
<?php
namespace App\Http\Middleware;
use App\RiskEngine\RiskEngine;
use App\Utils\IpLocation;
use Closure;
class RiskControlMiddleware
{
private $riskEngine;
private $ipLocation;
public function __construct(RiskEngine $riskEngine, IpLocation $ipLocation)
{
$this->riskEngine = $riskEngine;
$this->ipLocation = $ipLocation;
}
public function handle($request, Closure $next)
{
$ip = $this->ipLocation->getClientIp();
// 检查IP是否被封禁
if ($this->riskEngine->isIpBlocked($ip)) {
return response()->json([
'code' => 403,
'message' => '访问被限制'
], 403);
}
// 获取地理位置信息
$location = $this->ipLocation->getLocation($ip);
// 构建风控上下文
$context = [
'ip' => $ip,
'user_id' => auth()->id(),
'device_id' => $request->header('X-Device-ID'),
'user_agent' => $request->userAgent(),
'headers' => $request->headers->all(),
'action' => $request->route()->getName() ?? 'unknown',
'country_code' => $location['country_code'] ?? '',
'request_params' => $request->all()
];
// 执行风控检查
$result = $this->riskEngine->evaluate($context);
// 根据风控结果执行操作
switch ($result['action']) {
case 'block':
$this->riskEngine->blockIp($ip, 30);
return response()->json([
'code' => 403,
'message' => '访问被限制',
'risk_score' => $result['score']
], 403);
case 'challenge_captcha':
return response()->json([
'code' => 401,
'message' => '需要验证',
'captcha_required' => true,
'risk_score' => $result['score']
], 401);
case 'require_verification':
// 记录风险日志
$this->logRiskEvent($ip, $result);
break;
case 'monitor':
// 仅记录日志
$this->logRiskEvent($ip, $result, 'info');
break;
}
return $next($request);
}
private function logRiskEvent($ip, $result, $level = 'warning')
{
// 记录风控日志到数据库或日志文件
\Log::channel('risk')->log($level, "Risk event detected", [
'ip' => $ip,
'score' => $result['score'],
'level' => $result['level'],
'details' => $result['details'],
'timestamp' => now()
]);
}
}
3 风控记录模型
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RiskRecord extends Model
{
protected $table = 'risk_records';
protected $fillable = [
'ip',
'user_id',
'risk_score',
'risk_level',
'action_taken',
'details',
'user_agent',
'request_url',
'request_method'
];
protected $casts = [
'details' => 'array',
'created_at' => 'datetime'
];
/**
* 获取某个IP的风险统计
*/
public static function getIpRiskStats($ip, $days = 7)
{
return self::where('ip', $ip)
->where('created_at', '>=', now()->subDays($days))
->selectRaw('
COUNT(*) as total_events,
AVG(risk_score) as avg_score,
MAX(risk_score) as max_score,
COUNT(CASE WHEN risk_level = "critical" THEN 1 END) as critical_count,
COUNT(CASE WHEN risk_level = "high" THEN 1 END) as high_count
')
->first();
}
/**
* 获取高风险IP列表
*/
public static function getHighRiskIps($threshold = 70, $limit = 100)
{
return self::where('risk_score', '>=', $threshold)
->where('created_at', '>=', now()->subHours(24))
->groupBy('ip')
->selectRaw('ip, COUNT(*) as event_count, AVG(risk_score) as avg_score')
->orderByDesc('avg_score')
->limit($limit)
->get();
}
}
4 风控统计服务
<?php
namespace App\Services;
use App\Models\RiskRecord;
use Illuminate\Support\Facades\Redis;
class RiskStatisticsService
{
/**
* 获取实时风控仪表盘数据
*/
public function getDashboardData()
{
return [
'total_requests_today' => $this->getTodayTotalRequests(),
'blocked_ips_count' => $this->getBlockedIpsCount(),
'high_risk_events' => $this->getHighRiskEventsCount(),
'current_risk_level' => $this->getCurrentRiskLevel(),
'top_risk_ips' => $this->getTopRiskIps(),
'risk_trend' => $this->getRiskTrend()
];
}
/**
* 获取今天的总请求数
*/
private function getTodayTotalRequests()
{
$key = 'stats:requests:' . date('Y-m-d');
return Redis::get($key) ?? 0;
}
/**
* 获取被封禁的IP数量
*/
private function getBlockedIpsCount()
{
// 使用Redis记录被封禁IP
$keys = Redis::keys('blocked_ips:*');
return count($keys);
}
/**
* 获取高风险事件数量
*/
private function getHighRiskEventsCount()
{
return RiskRecord::where('risk_level', 'high')
->where('created_at', '>=', now()->subHours(24))
->count();
}
/**
* 获取当前风险等级
*/
private function getCurrentRiskLevel()
{
$highRiskCount = $this->getHighRiskEventsCount();
if ($highRiskCount > 1000) return 'critical';
if ($highRiskCount > 500) return 'high';
if ($highRiskCount > 100) return 'medium';
return 'low';
}
/**
* 获取风险最高的IP
*/
private function getTopRiskIps($limit = 10)
{
return RiskRecord::selectRaw('ip, COUNT(*) as count, AVG(risk_score) as avg_score')
->where('created_at', '>=', now()->subHours(24))
->groupBy('ip')
->orderByDesc('avg_score')
->limit($limit)
->get();
}
/**
* 获取风险趋势
*/
private function getRiskTrend()
{
$trend = [];
for ($i = 6; $i >= 0; $i--) {
$date = now()->subHours($i * 4);
$count = RiskRecord::where('created_at', '>=', $date)
->where('created_at', '<', $date->copy()->addHours(4))
->count();
$trend[] = [
'time' => $date->format('H:i'),
'count' => $count
];
}
return $trend;
}
}
数据库迁移文件
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateRiskTables extends Migration
{
public function up()
{
// 风险记录表
Schema::create('risk_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('ip', 45)->index();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->integer('risk_score')->default(0);
$table->string('risk_level', 20)->default('safe');
$table->string('action_taken', 50)->nullable();
$table->json('details')->nullable();
$table->string('user_agent', 500)->nullable();
$table->string('request_url', 500)->nullable();
$table->string('request_method', 10)->nullable();
$table->timestamps();
$table->index(['ip', 'created_at']);
$table->index(['risk_level', 'created_at']);
});
// IP黑名单表
Schema::create('ip_blacklist', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('ip', 45)->unique();
$table->string('reason', 500)->nullable();
$table->timestamp('blocked_until')->nullable();
$table->unsignedBigInteger('blocked_by')->nullable();
$table->timestamps();
$table->index('blocked_until');
});
// IP白名单表
Schema::create('ip_whitelist', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('ip', 45)->unique();
$table->string('description', 500)->nullable();
$table->timestamps();
});
// 风险规则配置表
Schema::create('risk_rules', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name', 100)->unique();
$table->string('description', 500)->nullable();
$table->json('config')->nullable();
$table->boolean('enabled')->default(true);
$table->integer('priority')->default(0);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('risk_records');
Schema::dropIfExists('ip_blacklist');
Schema::dropIfExists('ip_whitelist');
Schema::dropIfExists('risk_rules');
}
}
使用示例
1 在控制器中使用
<?php
namespace App\Http\Controllers;
use App\RiskEngine\RiskEngine;
use App\Utils\IpLocation;
use Illuminate\Http\Request;
class ExampleController extends Controller
{
private $riskEngine;
private $ipLocation;
public function __construct(RiskEngine $riskEngine, IpLocation $ipLocation)
{
$this->riskEngine = $riskEngine;
$this->ipLocation = $ipLocation;
}
public function login(Request $request)
{
$ip = $this->ipLocation->getClientIp();
$location = $this->ipLocation->getLocation($ip);
// 创建风控上下文
$context = [
'ip' => $ip,
'user_id' => $request->input('user_id'),
'device_id' => $request->header('X-Device-ID'),
'user_agent' => $request->userAgent(),
'action' => 'login',
'country_code' => $location['country_code'] ?? '',
];
// 执行风控检查
$result = $this->riskEngine->evaluate($context);
if ($result['action'] === 'block') {
$this->riskEngine->blockIp($ip, 60);
return response()->json([
'message' => '登录失败,请稍后重试',
'risk_score' => $result['score']
], 403);
}
if ($result['action'] === 'challenge_captcha') {
return response()->json([
'message' => '需要验证码验证',
'captcha_required' => true
], 401);
}
// 正常登录流程
// ...
}
}
2 配置文件
<?php
// config/risk.php
return [
'enabled' => env('RISK_CONTROL_ENABLED', true),
'redis' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'ip_database' => [
'path' => storage_path('app/ip2region.xdb'),
'auto_update' => false,
'update_interval' => 7, // 天
],
'rules' => [
'frequency' => [
'enabled' => true,
'limit' => 100,
'time_window' => 60,
'weight' => 30,
],
'geography' => [
'enabled' => true,
'black_countries' => ['KP', 'IR', 'SY'],
'high_risk_countries' => ['RU', 'CN', 'NG'],
'weight' => 40,
],
'proxy' => [
'enabled' => true,
'weight' => 25,
],
'device_fingerprint' => [
'enabled' => true,
'max_devices_per_ip' => 5,
'weight' => 20,
],
'behavior' => [
'enabled' => true,
'speed_limit' => 5,
'weight' => 35,
],
],
'actions' => [
'block' => [
'duration' => 30, // 分钟
'response_code' => 403,
],
'challenge_captcha' => [
'response_code' => 401,
],
],
'logging' => [
'channel' => 'risk',
'level' => 'warning',
],
];
这个实现包含:
- IP归属查询:支持离线数据库和在线API
- 风控引擎:多维度风险评估
- 访问频率控制:基于Redis的计数器
- 地理位置风控:国家/地区黑白名单
- 代理检测:识别代理/VPN流量
- 设备指纹:设备关联分析
- 行为分析:操作速度检测
- 完整的中间件:自动执行风控检查
- 统计和监控:风控数据可视化支持
系统会根据风险评分自动采取相应措施,如封禁、验证码验证、监控等。