where('id', $accountId) ->find(); } if (empty($accountData)) { throw new Exception("账号不存在:{$accountId}"); } $wechatId = $accountData['wechatId'] ?? ''; if (empty($wechatId)) { throw new Exception("账号wechatId为空:{$accountId}"); } // 获取或创建评分记录 $scoreRecord = $this->getOrCreateScoreRecord($accountId, $wechatId); // 计算基础分(只计算一次,除非强制重新计算) if (!$scoreRecord['baseScoreCalculated'] || $forceRecalculateBase) { $baseScoreData = $this->calculateBaseScore($accountData, $scoreRecord); $this->updateBaseScore($accountId, $baseScoreData); // 重新获取记录以获取最新数据 $scoreRecord = $this->getScoreRecord($accountId); } // 计算动态分(每次都要重新计算) $dynamicScoreData = $this->calculateDynamicScore($accountData, $scoreRecord); // 计算总分 $baseScore = $scoreRecord['baseScore']; $dynamicScore = $dynamicScoreData['total']; $healthScore = $baseScore + $dynamicScore; // 确保健康分在合理范围内(0-100) $healthScore = max(0, min(100, $healthScore)); // 计算每日最大加人次数 $maxAddFriendPerDay = $this->getMaxAddFriendPerDay($healthScore); // 更新评分记录 $updateData = [ 'dynamicScore' => $dynamicScore, 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], 'banPenalty' => $dynamicScoreData['banPenalty'], 'lastFrequentTime' => $dynamicScoreData['lastFrequentTime'], 'frequentCount' => $dynamicScoreData['frequentCount'], 'lastNoFrequentTime' => $dynamicScoreData['lastNoFrequentTime'], 'consecutiveNoFrequentDays' => $dynamicScoreData['consecutiveNoFrequentDays'], 'isBanned' => $dynamicScoreData['isBanned'], 'healthScore' => $healthScore, 'maxAddFriendPerDay' => $maxAddFriendPerDay, 'updateTime' => time() ]; Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->update($updateData); return [ 'accountId' => $accountId, 'wechatId' => $wechatId, 'healthScore' => $healthScore, 'baseScore' => $baseScore, 'baseInfoScore' => $scoreRecord['baseInfoScore'], 'friendCountScore' => $scoreRecord['friendCountScore'], 'dynamicScore' => $dynamicScore, 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], 'banPenalty' => $dynamicScoreData['banPenalty'], 'maxAddFriendPerDay' => $maxAddFriendPerDay ]; } catch (Exception $e) { throw new Exception("计算健康分失败:" . $e->getMessage()); } } /** * 获取或创建评分记录 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @return array 评分记录 */ private function getOrCreateScoreRecord($accountId, $wechatId) { $record = Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->find(); if (empty($record)) { // 创建新记录 $data = [ 'accountId' => $accountId, 'wechatId' => $wechatId, 'baseScore' => 0, 'baseScoreCalculated' => 0, 'baseInfoScore' => 0, 'friendCountScore' => 0, 'dynamicScore' => 0, 'frequentCount' => 0, 'consecutiveNoFrequentDays' => 0, 'healthScore' => 0, 'maxAddFriendPerDay' => 0, 'createTime' => time(), 'updateTime' => time() ]; Db::table('s2_wechat_account_score')->insert($data); return $data; } return $record; } /** * 获取评分记录 * * @param int $accountId 账号ID * @return array */ private function getScoreRecord($accountId) { return Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->find() ?: []; } /** * 计算基础分(只计算一次) * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(3-12分) * * @param array $accountData 账号数据 * @param array $scoreRecord 现有评分记录 * @return array 基础分数据 */ private function calculateBaseScore($accountData, $scoreRecord = []) { $baseScore = self::DEFAULT_BASE_SCORE; // 基础信息分(已修改微信号得10分) $baseInfoScore = $this->getBaseInfoScore($accountData); $baseScore += $baseInfoScore; // 好友数量分(特殊处理:使用快照值,避免同步问题) $friendCountScore = 0; $friendCount = 0; $friendCountSource = 'manual'; // 如果已有评分记录且好友数量分已计算,使用历史值 if (!empty($scoreRecord['friendCountScore']) && $scoreRecord['friendCountScore'] > 0) { $friendCountScore = $scoreRecord['friendCountScore']; $friendCount = $scoreRecord['friendCount'] ?? 0; $friendCountSource = $scoreRecord['friendCountSource'] ?? 'manual'; } else { // 首次计算:使用当前好友数量,但标记为手动计算 $totalFriend = $accountData['totalFriend'] ?? 0; $friendCountScore = $this->getFriendCountScore($totalFriend); $friendCount = $totalFriend; $friendCountSource = 'manual'; } $baseScore += $friendCountScore; // 检查是否已修改微信号 $isModifiedAlias = $this->checkIsModifiedAlias($accountData); return [ 'baseScore' => $baseScore, 'baseInfoScore' => $baseInfoScore, 'friendCountScore' => $friendCountScore, 'friendCount' => $friendCount, 'friendCountSource' => $friendCountSource, 'isModifiedAlias' => $isModifiedAlias ? 1 : 0, 'baseScoreCalculated' => 1, 'baseScoreCalcTime' => time() ]; } /** * 更新基础分 * * @param int $accountId 账号ID * @param array $baseScoreData 基础分数据 */ private function updateBaseScore($accountId, $baseScoreData) { Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->update($baseScoreData); } /** * 获取基础信息分 * 已修改微信号:10分 * * @param array $accountData 账号数据 * @return int 基础信息分 */ private function getBaseInfoScore($accountData) { if ($this->checkIsModifiedAlias($accountData)) { return self::BASE_INFO_SCORE; } return 0; } /** * 检查是否已修改微信号 * 判断标准:wechatId和alias不一致且都不为空,则认为已修改微信号 * 注意:这里只用于评分,不修复数据 * * @param array $accountData 账号数据 * @return bool */ private function checkIsModifiedAlias($accountData) { $wechatId = trim($accountData['wechatId'] ?? ''); $alias = trim($accountData['alias'] ?? ''); // 如果wechatId和alias不一致且都不为空,则认为已修改微信号(用于评分) if (!empty($wechatId) && !empty($alias) && $wechatId !== $alias) { return true; } return false; } /** * 获取好友数量分 * 根据好友数量区间得分(最高12分) * * @param int $totalFriend 总好友数 * @return int 好友数量分 */ private function getFriendCountScore($totalFriend) { if ($totalFriend <= 50) { return self::FRIEND_COUNT_SCORE_0_50; } elseif ($totalFriend <= 500) { return self::FRIEND_COUNT_SCORE_51_500; } elseif ($totalFriend <= 3000) { return self::FRIEND_COUNT_SCORE_501_3000; } else { return self::FRIEND_COUNT_SCORE_3001_PLUS; } } /** * 手动更新好友数量分(用于处理同步问题) * * @param int $accountId 账号ID * @param int $friendCount 好友数量 * @param string $source 来源(manual=手动,sync=同步) * @return bool */ public function updateFriendCountScore($accountId, $friendCount, $source = 'manual') { $scoreRecord = $this->getScoreRecord($accountId); // 如果基础分已计算,不允许修改好友数量分(除非是手动更新) if (!empty($scoreRecord['baseScoreCalculated']) && $source === 'sync') { // 同步数据不允许修改已计算的基础分 return false; } $friendCountScore = $this->getFriendCountScore($friendCount); // 重新计算基础分 $oldBaseScore = $scoreRecord['baseScore'] ?? self::DEFAULT_BASE_SCORE; $oldFriendCountScore = $scoreRecord['friendCountScore'] ?? 0; $baseInfoScore = $scoreRecord['baseInfoScore'] ?? 0; $newBaseScore = self::DEFAULT_BASE_SCORE + $baseInfoScore + $friendCountScore; $updateData = [ 'friendCountScore' => $friendCountScore, 'friendCount' => $friendCount, 'friendCountSource' => $source, 'baseScore' => $newBaseScore, 'updateTime' => time() ]; // 如果基础分已计算,需要更新总分 if (!empty($scoreRecord['baseScoreCalculated'])) { $dynamicScore = $scoreRecord['dynamicScore'] ?? 0; $healthScore = $newBaseScore + $dynamicScore; $healthScore = max(0, min(100, $healthScore)); $updateData['healthScore'] = $healthScore; $updateData['maxAddFriendPerDay'] = $this->getMaxAddFriendPerDay($healthScore); } Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->update($updateData); return true; } /** * 计算动态分 * 动态分 = 扣分 + 加分 * * @param array $accountData 账号数据 * @param array $scoreRecord 现有评分记录 * @return array 动态分数据 */ private function calculateDynamicScore($accountData, $scoreRecord) { $result = [ 'total' => 0, 'frequentPenalty' => 0, 'noFrequentBonus' => 0, 'banPenalty' => 0, 'lastFrequentTime' => null, 'frequentCount' => 0, 'lastNoFrequentTime' => null, 'consecutiveNoFrequentDays' => 0, 'isBanned' => 0 ]; $accountId = $accountData['id'] ?? 0; $wechatId = $accountData['wechatId'] ?? ''; if (empty($accountId) || empty($wechatId)) { return $result; } // 继承现有数据 if (!empty($scoreRecord)) { $result['lastFrequentTime'] = $scoreRecord['lastFrequentTime'] ?? null; $result['frequentCount'] = $scoreRecord['frequentCount'] ?? 0; $result['lastNoFrequentTime'] = $scoreRecord['lastNoFrequentTime'] ?? null; $result['consecutiveNoFrequentDays'] = $scoreRecord['consecutiveNoFrequentDays'] ?? 0; $result['frequentPenalty'] = $scoreRecord['frequentPenalty'] ?? 0; $result['noFrequentBonus'] = $scoreRecord['noFrequentBonus'] ?? 0; $result['banPenalty'] = $scoreRecord['banPenalty'] ?? 0; } // 1. 检查频繁记录(从s2_friend_task表查询,只统计近30天) $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord); $result['lastFrequentTime'] = $frequentData['lastFrequentTime'] ?? null; $result['frequentCount'] = $frequentData['frequentCount'] ?? 0; $result['frequentPenalty'] = $frequentData['frequentPenalty'] ?? 0; // 2. 检查封号记录(从s2_wechat_message表查询) $banData = $this->checkBannedFromMessage($accountId, $wechatId); if (!empty($banData)) { $result['isBanned'] = $banData['isBanned']; $result['banPenalty'] = $banData['banPenalty']; } // 3. 计算不频繁加分(基于近30天的频繁记录,反向参考频繁规则) $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData); $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0; $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0; $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null; // 计算总分 $result['total'] = $result['frequentPenalty'] + $result['noFrequentBonus'] + $result['banPenalty']; return $result; } /** * 从s2_friend_task表检查频繁记录 * extra字段包含"操作过于频繁"即需要扣分 * 只统计近30天的数据 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @param array $scoreRecord 现有评分记录 * @return array|null */ private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord) { // 计算30天前的时间戳 $thirtyDaysAgo = time() - (30 * 24 * 3600); // 查询包含"操作过于频繁"的记录(只统计近30天) // extra字段可能是文本或JSON格式,使用LIKE查询 $frequentTasks = Db::table('s2_friend_task') ->where('wechatAccountId', $accountId) ->where('createTime', '>=', $thirtyDaysAgo) ->where(function($query) use ($wechatId) { if (!empty($wechatId)) { $query->where('wechatId', $wechatId); } }) ->where(function($query) { // 检查extra字段是否包含"操作过于频繁"(可能是文本或JSON) $query->where('extra', 'like', '%操作过于频繁%') ->whereOr('extra', 'like', '%"操作过于频繁"%'); }) ->order('createTime', 'desc') ->field('id, createTime, extra') ->select(); // 获取最新的频繁时间 $latestFrequentTime = !empty($frequentTasks) ? $frequentTasks[0]['createTime'] : null; // 计算频繁次数(统计近30天内包含"操作过于频繁"的记录) $frequentCount = count($frequentTasks); // 如果30天内没有频繁记录,清除扣分 if (empty($frequentTasks)) { return [ 'lastFrequentTime' => null, 'frequentCount' => 0, 'frequentPenalty' => 0 ]; } // 根据30天内的频繁次数计算扣分 $penalty = 0; if ($frequentCount == 1) { $penalty = self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分 } elseif ($frequentCount >= 2) { $penalty = self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分 } return [ 'lastFrequentTime' => $latestFrequentTime, 'frequentCount' => $frequentCount, 'frequentPenalty' => $penalty ]; } /** * 从s2_wechat_message表检查封号记录 * content包含"你的账号被限制"且msgType为10000 * 只统计近30天的数据 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @return array|null */ private function checkBannedFromMessage($accountId, $wechatId) { // 计算30天前的时间戳 $thirtyDaysAgo = time() - (30 * 24 * 3600); // 查询封号消息(只统计近30天) $banMessage = Db::table('s2_wechat_message') ->where('wechatAccountId', $accountId) ->where('msgType', 10000) ->where('content', 'like', '%你的账号被限制%') ->where('isDeleted', 0) ->where('createTime', '>=', $thirtyDaysAgo) ->order('createTime', 'desc') ->find(); if (!empty($banMessage)) { return [ 'isBanned' => 1, 'banPenalty' => self::PENALTY_BANNED // 封号-60分 ]; } return [ 'isBanned' => 0, 'banPenalty' => 0 ]; } /** * 计算不频繁加分 * 反向参考频繁规则:查询近30天的频繁记录,计算连续不频繁天数 * 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) * 如果连续3天没有频繁,则每天+5分 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @param array $frequentData 频繁数据(包含lastFrequentTime和frequentCount) * @return array 包含bonus、consecutiveDays、lastNoFrequentTime */ private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData) { $result = [ 'bonus' => 0, 'consecutiveDays' => 0, 'lastNoFrequentTime' => null ]; if (empty($accountId) || empty($wechatId)) { return $result; } // 计算30天前的时间戳 $thirtyDaysAgo = time() - (30 * 24 * 3600); $currentTime = time(); // 获取最后一次频繁时间(30天内最后一次频繁的时间) $lastFrequentTime = $frequentData['lastFrequentTime'] ?? null; // 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) if (empty($lastFrequentTime)) { // 情况1:30天内没有频繁记录,说明30天内连续不频繁 // 计算从30天前到现在的连续不频繁天数(最多30天) $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400)); } else { // 情况2:30天内有频繁记录,从最后一次频繁时间开始重新计算连续不频繁天数 // 只要有一次频繁,连续不频繁天数就从最后一次频繁时间开始重新计算 // 计算从最后一次频繁时间到现在,连续多少天没有频繁 $timeDiff = $currentTime - $lastFrequentTime; $consecutiveDays = floor($timeDiff / 86400); // 向下取整,得到完整的天数 // 边界情况:如果最后一次频繁时间在30天前(理论上不应该发生,因为查询已经限制了30天),则按30天处理 if ($lastFrequentTime < $thirtyDaysAgo) { $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400)); } } // 如果连续3天或以上没有频繁,则每天+5分 if ($consecutiveDays >= 3) { $bonus = $consecutiveDays * self::BONUS_NO_FREQUENT_PER_DAY; $result['bonus'] = $bonus; $result['consecutiveDays'] = $consecutiveDays; $result['lastNoFrequentTime'] = $currentTime; } else { $result['consecutiveDays'] = $consecutiveDays; } return $result; } /** * 根据健康分计算每日最大加人次数 * 公式:每日最大加人次数 = 健康分 * 0.2 * * @param int $healthScore 健康分 * @return int 每日最大加人次数 */ public function getMaxAddFriendPerDay($healthScore) { return (int)floor($healthScore * 0.2); } /** * 批量计算并更新多个账号的健康分 * * @param array $accountIds 账号ID数组(为空则处理所有账号) * @param int $batchSize 每批处理数量 * @param bool $forceRecalculateBase 是否强制重新计算基础分 * @return array 处理结果统计 */ public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false) { $stats = [ 'total' => 0, 'success' => 0, 'failed' => 0, 'errors' => [] ]; // 如果没有指定账号ID,则处理所有账号 if (empty($accountIds)) { $accountIds = Db::table('s2_wechat_account') ->where('isDeleted', 0) ->column('id'); } $stats['total'] = count($accountIds); // 分批处理 $batches = array_chunk($accountIds, $batchSize); foreach ($batches as $batch) { foreach ($batch as $accountId) { try { $this->calculateAndUpdate($accountId, null, $forceRecalculateBase); $stats['success']++; } catch (Exception $e) { $stats['failed']++; $stats['errors'][] = [ 'accountId' => $accountId, 'error' => $e->getMessage() ]; } } } return $stats; } /** * 记录频繁事件(已废弃,改为从s2_friend_task表自动检测) * 保留此方法以兼容旧代码,实际频繁检测在calculateDynamicScore中完成 * * @param int $accountId 账号ID * @return bool */ public function recordFrequent($accountId) { // 频繁检测已改为从s2_friend_task表自动检测 // 直接重新计算健康分即可 try { $this->calculateAndUpdate($accountId); return true; } catch (\Exception $e) { return false; } } /** * 记录不频繁事件(用于加分) * * @param int $accountId 账号ID * @return bool */ public function recordNoFrequent($accountId) { $scoreRecord = $this->getScoreRecord($accountId); if (empty($scoreRecord)) { // 如果记录不存在,先创建 $accountData = Db::table('s2_wechat_account') ->where('id', $accountId) ->find(); if (empty($accountData)) { return false; } $this->getOrCreateScoreRecord($accountId, $accountData['wechatId']); $scoreRecord = $this->getScoreRecord($accountId); } $lastNoFrequentTime = $scoreRecord['lastNoFrequentTime'] ?? null; $consecutiveNoFrequentDays = $scoreRecord['consecutiveNoFrequentDays'] ?? 0; $currentTime = time(); // 如果上次不频繁时间是昨天或更早,则增加连续天数 if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) { // 如果间隔超过2天,重置为1天 if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) { $consecutiveNoFrequentDays = 1; } else { $consecutiveNoFrequentDays++; } } // 计算加分(连续3天及以上才加分) $bonus = 0; if ($consecutiveNoFrequentDays >= 3) { $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY; } $updateData = [ 'lastNoFrequentTime' => $currentTime, 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays, 'noFrequentBonus' => $bonus, 'updateTime' => $currentTime ]; Db::table('s2_wechat_account_score') ->where('accountId', $accountId) ->update($updateData); // 重新计算健康分 $this->calculateAndUpdate($accountId); return true; } /** * 获取账号健康分信息 * * @param int $accountId 账号ID * @return array|null */ public function getHealthScore($accountId) { $scoreRecord = $this->getScoreRecord($accountId); if (empty($scoreRecord)) { return null; } return [ 'accountId' => $scoreRecord['accountId'], 'wechatId' => $scoreRecord['wechatId'], 'healthScore' => $scoreRecord['healthScore'] ?? 0, 'baseScore' => $scoreRecord['baseScore'] ?? 0, 'baseInfoScore' => $scoreRecord['baseInfoScore'] ?? 0, 'friendCountScore' => $scoreRecord['friendCountScore'] ?? 0, 'friendCount' => $scoreRecord['friendCount'] ?? 0, 'dynamicScore' => $scoreRecord['dynamicScore'] ?? 0, 'frequentPenalty' => $scoreRecord['frequentPenalty'] ?? 0, 'noFrequentBonus' => $scoreRecord['noFrequentBonus'] ?? 0, 'banPenalty' => $scoreRecord['banPenalty'] ?? 0, 'maxAddFriendPerDay' => $scoreRecord['maxAddFriendPerDay'] ?? 0, 'baseScoreCalculated' => $scoreRecord['baseScoreCalculated'] ?? 0, 'lastFrequentTime' => $scoreRecord['lastFrequentTime'] ?? null, 'frequentCount' => $scoreRecord['frequentCount'] ?? 0, 'isBanned' => $scoreRecord['isBanned'] ?? 0 ]; } }