本文目录导读:

这是一个关于PHP项目风控规则引擎设计的专业问题,一个好的规则引擎是风控系统的核心,它决定了系统的灵活性、性能和可维护性。
下面我将从核心概念、架构设计、数据结构、规则执行流程、关键实现细节和进阶方案这几个方面,为你提供一个全面的设计思路。
核心概念与目标
- 规则 (Rule): 一个具体的判断逻辑,
年龄 > 18 AND 登录IP属于黑名单,由条件和动作组成。 - 条件 (Condition): 对输入数据(事件)的原子性判断,
amount > 1000。 - 动作 (Action): 当规则被触发时执行的操作,
拒绝交易、标记为高风险、发送短信验证。 - 事件 (Event): 需要被风控系统评估的原始数据,
用户注册事件、支付请求事件。 - 策略 (Policy) / 规则集 (Rule Set): 一组规则的集合,
高风险交易拦截策略,通常有优先级和组合逻辑(AND/OR)。 - 风控结果 (Result): 引擎执行完毕后输出的结论,通常包含
通过、拒绝、人工审核等,以及命中的规则详情。
设计目标:
- 灵活性:业务人员(非开发)能通过后台配置规则,快速应对新的欺诈场景。
- 高性能:每个请求的处理时间应尽可能短(毫秒级),避免成为性能瓶颈。
- 可扩展性:易于添加新的算子(判断逻辑)、数据源、动作类型。
- 可观测性:能记录每次评估的详细过程,便于排查和调优。
整体架构设计(分层清晰)
一个典型的PHP风控引擎架构可以分为四层:
[触发层] ---> [接入层/API] ---> [决策层/引擎] ---> [存储层/数据服务]
|
+---> [执行层/规则计算]
- 触发层: 各种业务系统(订单、用户、支付)通过API调用发起风控请求。
- 接入层/API: 统一认证、参数校验、将业务数据转换为引擎能理解的标准事件。
- 决策层/引擎核心:
- 特征计算: 从事件数据或外部数据源(数据库、缓存、第三方API)计算需要的特征值。
- 规则匹配: 加载匹配的策略和规则,进行条件计算。
- 决策输出: 聚合所有规则的匹配结果,生成最终决策(通过/拒绝/人工)。
- 存储层/数据服务:
- 配置: 存储规则、名单、策略配置(如MySQL + Redis)。
- 动态数据: 存储用户历史行为、设备指纹、实时计数器(如Redis)。
核心数据模型设计
规则配置 (存储在 MySQL/PostgreSQL 中)
-- 规则基础表
CREATE TABLE `rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rule_code` varchar(64) NOT NULL COMMENT '规则编码,唯一',
`name` varchar(128) NOT NULL COMMENT '规则名称',
`priority` int(11) NOT NULL DEFAULT 0 COMMENT '优先级,数字越小越优先',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:1启用 0禁用',
`action` varchar(32) NOT NULL COMMENT '命中后的动作: REJECT, REVIEW, ALLOW',
`action_value` varchar(255) DEFAULT NULL COMMENT '动作参数(如拒绝原因码、是否需要验证码等)',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_rule_code` (`rule_code`),
KEY `idx_priority` (`priority`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 规则条件表 (支持AND/OR组合)
CREATE TABLE `rule_condition` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rule_id` int(11) NOT NULL COMMENT '关联规则ID',
`group_id` int(11) NOT NULL DEFAULT 0 COMMENT '条件组ID,同一组内用AND连接,不同组间用OR连接',
`operator` varchar(32) NOT NULL COMMENT '算子: eq, neq, gt, gte, lt, lte, in, not_in, contains, starts_with, match_regex, etc.',
`field` varchar(128) NOT NULL COMMENT '特征字段名(如 ip, amount, user_id)',
`value` text NOT NULL COMMENT '比对值(如 192.168.1.1, 1000, [1,2,3])',
`value_type` varchar(16) NOT NULL DEFAULT 'string' COMMENT '值类型: string, number, list, regex',
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_rule_id` (`rule_id`),
KEY `idx_group_id` (`rule_id`, `group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特征与事件传递(内存对象)
// 1. 标准事件对象 (由触发层传入)
class Event {
// 事件类型: user.register, order.pay, loan.apply
public string $eventType;
// 事件携带的原始数据
public array $rawData = [];
// ... getter/setter
}
// 2. 特征上下文 (引擎内部使用)
class FeatureContext {
// 已经计算的特征值
private array $features = [];
// 获取特征(支持懒加载)
public function get(string $name): mixed {
if (!array_key_exists($name, $this->features)) {
// 如果特征未计算,调用FeatureProvider
$this->features[$name] = FeatureProvider::calculate($name, $this->event);
}
return $this->features[$name];
}
}
规则执行流程(核心逻辑)
以一个支付风控为例,伪代码实现如下:
<?php
class RiskEngine {
private RuleLoader $ruleLoader;
private FeatureCalculator $featureCalculator;
private ActionResult $result;
public function evaluate(Event $event): ActionResult {
$this->result = new ActionResult();
$context = new EvaluationContext($event);
// 1. 加载所有启用的规则
$rules = $this->ruleLoader->loadEnabledRules();
// 2. 如果没有规则,默认通过
if (empty($rules)) {
$this->result->setDecision(Decision::ALLOW);
return $this->result;
}
// 3. 按优先级排序
usort($rules, function($a, $b) {
return $a->priority <=> $b->priority;
});
// 4. 逐规则匹配
foreach ($rules as $rule) {
// 4.1 检查规则是否需要此事件类型(可选,通常是按策略匹配)
// if (!in_array($event->eventType, $rule->getEventTypes())) continue;
// 4.2 执行规则条件判断
if ($this->matchRule($rule, $context)) {
// 4.3 命中规则,记录命中详情
$this->result->addHitRule($rule);
// 4.4 执行规则动作(如果动作是REJECT,直接短路,不再继续)
if ($rule->action === 'REJECT') {
$this->result->setDecision(Decision::REJECT);
$this->result->setRejectReason($rule->action_value);
// 记录日志
return $this->result;
} elseif ($rule->action === 'REVIEW') {
// 如果需人工审核,继续评估其他规则,但最终结果标记为REVIEW
$this->result->setDecision(Decision::REVIEW);
}
}
}
// 5. 如果所有规则都未触发REJECT,返回最终决策(可能是ALLOW或REVIEW)
if (!$this->result->getDecision()) {
$this->result->setDecision(Decision::ALLOW);
}
return $this->result;
}
private function matchRule(Rule $rule, EvaluationContext $context): bool {
// 规则内的条件是 AND/OR 组合关系
// 假设条件按group_id分组,同一组内AND,不同组间OR
$groupsResult = [];
foreach ($rule->getConditionGroups() as $groupId => $conditions) {
$groupResult = true;
foreach ($conditions as $condition) {
// 获取特征值(延迟计算)
$featureValue = $context->getFeature($condition->getField());
// 执行条件比对
if (!$this->evaluateCondition($condition, $featureValue)) {
$groupResult = false;
// 一个组内AND,有一个为false则整个组为false
break;
}
}
$groupsResult[] = $groupResult;
}
// 不同组之间是OR关系
return in_array(true, $groupsResult);
}
private function evaluateCondition(Condition $cond, $featureValue): bool {
// 这里实现具体的算子逻辑
return OperatorFactory::create($cond->getOperator())->evaluate($featureValue, $cond->getValue());
}
}
// 输出结果
class ActionResult {
private string $decision; // ALLOW, REJECT, REVIEW
private array $hitRules = [];
private string $rejectReason = '';
// ... getter/setter
}
关键技术实现细节
-
算子设计 (Operator Pattern)
- 定义
OperatorInterface,包含evaluate($featureValue, $ruleValue): bool方法。 - 实现
EqualOperator、GreaterThanOperator、InListOperator、RegexMatchOperator等。 - 好处:易于扩展,新增一个算子只需写一个新类并注册到
OperatorFactory。
- 定义
-
特征计算 (Feature Provider)
- 设计中不要直接写死特征计算逻辑。
- 采用延迟加载,只在特征被规则需要时才计算。
- 使用缓存,同一个事件中相同特征只计算一次。
- 示例:
user_total_deposit:从数据库或缓存中读取。ip_risk_score:调用第三方API。device_fingerprint:从事件中提取。
- 可以做成插件化、可配置的。
-
名单/黑白名单处理
- 将名单库(黑名单手机号、IP等)存储在Redis Set 或 Bloom Filter 中,实现O(1)时间复杂度判定。
- 通常在特征计算阶段,计算出
is_uin_blacklisted这样的特征,然后规则直接使用。
-
软规则 vs 硬规则
- 硬规则:拒绝类,命中即返回。
- 软规则:打分、标记,累积分数或标签,最后根据总分决策。
- 设计时考虑两者结合:
// 在规则中添加一个 score 字段 $rule->score = 20; // 每条规则配置分值 // 引擎评估 $this->result->addHitRule($rule); $this->result->addScore($rule->score); // 最后根据总分决策 if ($this->result->getScore() > 80) { $this->result->setDecision(Decision::REJECT); } elseif ($this->result->getScore() > 50) { $this->result->setDecision(Decision::REVIEW); } -
性能优化
- 规则缓存:将规则配置在项目启动时加载到内存(如PHP数组/Redis),避免每次请求读数据库。
- 规则索引:根据事件类型(
event_type)或规则条件中常用的字段(如user_id,ip)建立索引,减少需要匹配的规则数量。 - 惰性特征计算:只计算规则确实用到的特征。
进阶与扩展
-
可视化规则编辑器
- 后台用拖拽式界面生成规则(条件组合、动作配置),前端生成JSON/DSL,后端解析。
- 支持
AND/OR的树状组合。
-
AB测试与灰度发布
- 对规则可以设置
流量百分比/白名单用户,一部分流量走新规则,一部分走老逻辑,对比效果。
- 对规则可以设置
-
实时监控与告警
监控风控调用量、命中率、规则执行耗时、Top N 风险用户/IP,当某类规则命中率突然飙升时通过钉钉/邮件告警。
-
支持脚本/DSL
- 对于非常复杂的规则(如依赖循环计算、动态阈值),可以考虑嵌入 表达式引擎,如
symfony/expression-language或luasandbox(Lua沙箱),让规则能够执行一小段自定义逻辑。注意安全,严禁执行系统命令。
- 对于非常复杂的规则(如依赖循环计算、动态阈值),可以考虑嵌入 表达式引擎,如
一个简单的 operator 内部实现示例
<?php
interface OperatorInterface {
public function evaluate(mixed $featureValue, mixed $ruleValue): bool;
}
class InListOperator implements OperatorInterface {
public function evaluate(mixed $featureValue, mixed $ruleValue): bool {
// ruleValue 通常是 JSON 字符串,如 "[1,2,3]"
$list = json_decode($ruleValue, true);
if (!is_array($list)) {
return false;
}
return in_array($featureValue, $list, true);
}
}
class RegexMatchOperator implements OperatorInterface {
public function evaluate(mixed $featureValue, mixed $ruleValue): bool {
// 注意:ruleValue 是正则表达式,需要做好安全过滤,避免ReDoS攻击
// 使用 @ 抑制错误,或使用 try-catch
try {
return preg_match('/' . $ruleValue . '/', $featureValue) === 1;
} catch (\Exception $e) {
// 记录日志 LOG_WARN("Regex error: rule=%s, value=%s", $ruleValue, $featureValue);
return false;
}
}
}
// 工厂类
class OperatorFactory {
private static array $map = [
'eq' => EqualOperator::class,
'neq' => NotEqualOperator::class,
'gt' => GreaterThanOperator::class,
'in' => InListOperator::class,
'match_regex' => RegexMatchOperator::class,
// ...
];
public static function create(string $operator): OperatorInterface {
if (!isset(self::$map[$operator])) {
throw new \InvalidArgumentException("Unknown operator: $operator");
}
return new self::$map[$operator]();
}
}
一个好的PHP风控规则引擎设计,核心在于可配置化(DRY原则)、高性能(缓存与惰性计算) 和易于扩展(策略模式/算子模式),不要一开始就追求大而全,可以先从最核心的规则执行引擎、特征计算模块和结果输出处理做起,配合一个简单的后台规则管理页面,就能应对大部分业务场景。