RiskControlService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. <?php
  2. namespace app\common\service;
  3. use think\Db;
  4. use think\Cache;
  5. use think\Exception;
  6. class RiskControlService
  7. {
  8. // 缓存前缀
  9. const CACHE_PREFIX = 'risk_control:';
  10. // 缓存时间(秒)
  11. const CACHE_TIME = 3600;
  12. /**
  13. * @var RuleCheckService
  14. */
  15. private $ruleCheckService;
  16. /**
  17. * 构造函数
  18. */
  19. public function __construct()
  20. {
  21. $this->ruleCheckService = new RuleCheckService();
  22. }
  23. /**
  24. * 处理风控事件
  25. * @param array $eventData 事件数据
  26. * @return array 处理结果
  27. */
  28. public function processEvent(array $eventData)
  29. {
  30. try {
  31. // 1. 数据验证
  32. $this->validateEventData($eventData);
  33. // 2. 检查黑白名单
  34. if ($this->checkBlackList($eventData)) {
  35. return ['is_blocked' => true, 'msg' => '账号已被封禁'];
  36. }
  37. // 3. 执行策略检查
  38. $strategyResults = $this->executeStrategies($eventData);
  39. // 4. 处理异常数据
  40. if (!empty($strategyResults['exceptions'])) {
  41. $this->handleExceptions($strategyResults['exceptions'], $eventData);
  42. }
  43. // 5. 清理过期数据
  44. $this->cleanExpiredData();
  45. // 6. 检查异常程度
  46. $riskLevel = $this->checkRiskLevel($eventData);
  47. return [
  48. 'is_blocked' => $riskLevel['is_blocked'],
  49. 'is_risky' => $riskLevel['is_risky'],
  50. 'risk_level' => $riskLevel['level'],
  51. 'msg' => $riskLevel['msg']
  52. ];
  53. } catch (Exception $e) {
  54. // 记录错误日志
  55. \think\Log::error('风控处理异常:' . $e->getMessage());
  56. return ['is_blocked' => false, 'is_risky' => false, 'msg' => '系统异常'];
  57. }
  58. }
  59. /**
  60. * 验证事件数据
  61. * @param array $eventData
  62. * @throws Exception
  63. */
  64. private function validateEventData(array $eventData)
  65. {
  66. $requiredFields = ['event_type', 'member_id', 'ip', 'imei', 'timestamp'];
  67. foreach ($requiredFields as $field) {
  68. if (!isset($eventData[$field]) || empty($eventData[$field])) {
  69. throw new Exception("缺少必要字段:{$field}");
  70. }
  71. }
  72. }
  73. /**
  74. * 检查黑名单
  75. * @param array $eventData
  76. * @return bool
  77. */
  78. private function checkBlackList(array $eventData)
  79. {
  80. $cacheKey = self::CACHE_PREFIX . 'blacklist:';
  81. // 检查IP
  82. if ($this->checkInList($eventData['ip'], 'ip', 'black', $cacheKey)) {
  83. return true;
  84. }
  85. // 检查IMEI
  86. if ($this->checkInList($eventData['imei'], 'imei', 'black', $cacheKey)) {
  87. return true;
  88. }
  89. // 检查用户ID
  90. if ($this->checkInList($eventData['member_id'], 'member_id', 'black', $cacheKey)) {
  91. return true;
  92. }
  93. return false;
  94. }
  95. /**
  96. * 检查是否在名单中
  97. * @param string $value
  98. * @param string $type
  99. * @param string $listType
  100. * @param string $cacheKey
  101. * @return bool
  102. */
  103. private function checkInList($value, $type, $listType, $cacheKey)
  104. {
  105. $key = $cacheKey . $listType . ':' . $type . ':' . $value;
  106. // 先查缓存
  107. if (Cache::get($key)) {
  108. return true;
  109. }
  110. // 查数据库
  111. $exists = Db::name('fk_roster_lists')
  112. ->where([
  113. 'object_type' => $type,
  114. 'object_value' => $value,
  115. 'list_type' => $listType,
  116. 'status' => 1
  117. ])
  118. ->where(function ($query) {
  119. $query->where('expire_time', '>', time())
  120. ->whereOr('expire_time', 0);
  121. })
  122. ->find();
  123. if ($exists) {
  124. Cache::set($key, 1, self::CACHE_TIME);
  125. return true;
  126. }
  127. return false;
  128. }
  129. /**
  130. * 执行策略检查
  131. * @param array $eventData
  132. * @return array
  133. */
  134. private function executeStrategies(array $eventData)
  135. {
  136. $exceptions = [];
  137. // 获取相关策略
  138. $strategies = Db::name('fk_strategies')
  139. ->where('status', 1)
  140. ->where('event_type', $eventData['event_type'])
  141. ->order('weight', 'desc')
  142. ->select();
  143. foreach ($strategies as $strategy) {
  144. // 获取策略详情
  145. $details = Db::name('fk_strategy_details')
  146. ->alias('d')
  147. ->join('fk_rules r', 'd.rule_id = r.id')
  148. ->where('d.strategy_id', $strategy['id'])
  149. ->where('r.status', 1)
  150. ->select();
  151. dump($details->toArray());
  152. foreach ($details as $detail) {
  153. if ($this->checkRule($detail, $eventData)) {
  154. $exceptions[] = [
  155. 'strategy_id' => $strategy['id'],
  156. 'rule_id' => $detail['rule_id'],
  157. 'object_type' => $detail['object_type'],
  158. 'object_value' => $eventData[$detail['object_type']],
  159. 'exception_level' => $detail['exception_level'],
  160. 'expire_time' => time() + $detail['expire_time']
  161. ];
  162. }
  163. }
  164. }
  165. return ['exceptions' => $exceptions];
  166. }
  167. /**
  168. * 检查规则
  169. * @param array $detail
  170. * @param array $eventData
  171. * @return bool
  172. */
  173. private function checkRule(array $detail, array $eventData)
  174. {
  175. // 根据规则类型执行不同的检查逻辑
  176. switch ($detail['rule_type']) {
  177. case 'frequency':
  178. return $this->ruleCheckService->checkFrequencyRule($detail, $eventData);
  179. case 'statistics':
  180. return $this->ruleCheckService->checkStatisticsRule($detail, $eventData);
  181. default:
  182. return false;
  183. }
  184. }
  185. /**
  186. * 处理异常数据
  187. * @param array $exceptions
  188. * @param array $eventData
  189. */
  190. private function handleExceptions(array $exceptions, array $eventData)
  191. {
  192. Db::startTrans();
  193. try {
  194. foreach ($exceptions as $exception) {
  195. // 记录异常记录
  196. Db::name('fk_exception_records')->insert([
  197. 'strategy_id' => $exception['strategy_id'],
  198. 'rule_id' => $exception['rule_id'],
  199. 'object_type' => $exception['object_type'],
  200. 'object_value' => $exception['object_value'],
  201. 'exception_level' => $exception['exception_level'],
  202. 'expire_time' => $exception['expire_time'],
  203. 'create_time' => time()
  204. ]);
  205. // 更新异常对象
  206. $this->updateExceptionObject($exception);
  207. }
  208. Db::commit();
  209. } catch (Exception $e) {
  210. Db::rollback();
  211. throw $e;
  212. }
  213. }
  214. /**
  215. * 更新异常对象
  216. * @param array $exception
  217. */
  218. private function updateExceptionObject(array $exception)
  219. {
  220. $object = Db::name('fk_exception_objects')
  221. ->where([
  222. 'object_type' => $exception['object_type'],
  223. 'object_value' => $exception['object_value']
  224. ])
  225. ->find();
  226. if ($object) {
  227. // 更新异常程度
  228. $newLevel = min(100, $object['exception_level'] + $exception['exception_level']);
  229. Db::name('fk_exception_objects')
  230. ->where('id', $object['id'])
  231. ->update([
  232. 'exception_level' => $newLevel,
  233. 'update_time' => time()
  234. ]);
  235. } else {
  236. // 创建新异常对象
  237. Db::name('fk_exception_objects')->insert([
  238. 'object_type' => $exception['object_type'],
  239. 'object_value' => $exception['object_value'],
  240. 'exception_level' => $exception['exception_level'],
  241. 'create_time' => time(),
  242. 'update_time' => time()
  243. ]);
  244. }
  245. }
  246. /**
  247. * 清理过期数据
  248. */
  249. private function cleanExpiredData()
  250. {
  251. $now = time();
  252. // 清理过期异常记录
  253. Db::name('fk_exception_records')
  254. ->where('expire_time', '<', $now)
  255. ->where('expire_time', '>', 0)
  256. ->delete();
  257. // 更新异常对象异常程度
  258. $expiredRecords = Db::name('fk_exception_records')
  259. ->where('expire_time', '<', $now)
  260. ->where('expire_time', '>', 0)
  261. ->select();
  262. foreach ($expiredRecords as $record) {
  263. $object = Db::name('fk_exception_objects')
  264. ->where([
  265. 'object_type' => $record['object_type'],
  266. 'object_value' => $record['object_value']
  267. ])
  268. ->find();
  269. if ($object) {
  270. $newLevel = max(0, $object['exception_level'] - $record['exception_level']);
  271. Db::name('fk_exception_objects')
  272. ->where('id', $object['id'])
  273. ->update([
  274. 'exception_level' => $newLevel,
  275. 'update_time' => $now
  276. ]);
  277. }
  278. }
  279. }
  280. /**
  281. * 检查风险等级
  282. * @param array $eventData
  283. * @return array
  284. */
  285. private function checkRiskLevel(array $eventData)
  286. {
  287. $riskLevel = 0;
  288. $isBlocked = false;
  289. $isRisky = false;
  290. $msg = '';
  291. // 检查各个维度的异常程度
  292. $dimensions = ['ip', 'imei', 'member_id'];
  293. if (isset($eventData['id_card'])) {
  294. $dimensions[] = 'id_card';
  295. }
  296. foreach ($dimensions as $dimension) {
  297. $object = Db::name('fk_exception_objects')
  298. ->where([
  299. 'object_type' => $dimension,
  300. 'object_value' => $eventData[$dimension]
  301. ])
  302. ->find();
  303. if ($object) {
  304. $riskLevel = max($riskLevel, $object['exception_level']);
  305. // 如果异常程度超过80,加入黑名单
  306. if ($object['exception_level'] >= 80) {
  307. $this->addToBlackList($dimension, $eventData[$dimension]);
  308. $isBlocked = true;
  309. $msg = '账号已被封禁';
  310. }
  311. // 如果异常程度超过50,标记为风险
  312. elseif ($object['exception_level'] >= 50) {
  313. $isRisky = true;
  314. $msg = '需要验证';
  315. }
  316. }
  317. }
  318. return [
  319. 'is_blocked' => $isBlocked,
  320. 'is_risky' => $isRisky,
  321. 'level' => $riskLevel,
  322. 'msg' => $msg
  323. ];
  324. }
  325. /**
  326. * 添加到黑名单
  327. * @param string $type
  328. * @param string $value
  329. */
  330. private function addToBlackList($type, $value)
  331. {
  332. // 检查是否已在黑名单
  333. $exists = Db::name('fk_roster_lists')
  334. ->where([
  335. 'object_type' => $type,
  336. 'object_value' => $value,
  337. 'list_type' => 'black'
  338. ])
  339. ->find();
  340. if (!$exists) {
  341. Db::name('fk_roster_lists')->insert([
  342. 'object_type' => $type,
  343. 'object_value' => $value,
  344. 'list_type' => 'black',
  345. 'create_time' => time(),
  346. 'status' => 1
  347. ]);
  348. // 清除缓存
  349. Cache::rm(self::CACHE_PREFIX . 'blacklist:black:' . $type . ':' . $value);
  350. }
  351. }
  352. }