379 lines
12 KiB
PHP
379 lines
12 KiB
PHP
<?php
|
||
|
||
namespace app\common\service;
|
||
|
||
use think\Db;
|
||
use think\Exception;
|
||
|
||
/**
|
||
* 微信账号健康分评分服务
|
||
* 基于《微信健康分规则v2.md》实现
|
||
*
|
||
* 健康分 = 基础分 + 动态分
|
||
* 基础分:60-100分(默认60分 + 基础信息10分 + 好友数量30分)
|
||
* 动态分:扣分和加分规则
|
||
*/
|
||
class WechatAccountHealthScoreService
|
||
{
|
||
// 默认基础分
|
||
const DEFAULT_BASE_SCORE = 60;
|
||
|
||
// 基础信息权重和分数
|
||
const BASE_INFO_WEIGHT = 0.2;
|
||
const BASE_INFO_SCORE = 10;
|
||
|
||
// 好友数量权重和分数
|
||
const FRIEND_COUNT_WEIGHT = 0.3;
|
||
const FRIEND_COUNT_MAX_SCORE = 30;
|
||
|
||
// 动态分扣分规则
|
||
const PENALTY_FIRST_FREQUENT = -15; // 首次频繁扣15分
|
||
const PENALTY_SECOND_FREQUENT = -25; // 再次频繁扣25分
|
||
const PENALTY_BANNED = -60; // 封号扣60分
|
||
|
||
// 动态分加分规则
|
||
const BONUS_NO_FREQUENT_PER_DAY = 5; // 连续3天不触发频繁,每天+5分
|
||
|
||
/**
|
||
* 计算并更新账号健康分
|
||
*
|
||
* @param int $accountId 账号ID(s2_wechat_account表的id)
|
||
* @param array $accountData 账号数据(可选,如果不传则从数据库查询)
|
||
* @return array 返回评分结果
|
||
*/
|
||
public function calculateAndUpdate($accountId, $accountData = null)
|
||
{
|
||
try {
|
||
// 获取账号数据
|
||
if (empty($accountData)) {
|
||
$accountData = Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->find();
|
||
}
|
||
|
||
if (empty($accountData)) {
|
||
throw new Exception("账号不存在:{$accountId}");
|
||
}
|
||
|
||
// 计算基础分
|
||
$baseScore = $this->calculateBaseScore($accountData);
|
||
|
||
// 计算动态分
|
||
$dynamicScore = $this->calculateDynamicScore($accountData);
|
||
|
||
// 计算总分
|
||
$healthScore = $baseScore + $dynamicScore;
|
||
|
||
// 确保健康分在合理范围内(0-100)
|
||
$healthScore = max(0, min(100, $healthScore));
|
||
|
||
// 更新数据库
|
||
$updateData = [
|
||
'healthScore' => $healthScore,
|
||
'baseScore' => $baseScore,
|
||
'dynamicScore' => $dynamicScore,
|
||
'scoreUpdateTime' => time()
|
||
];
|
||
|
||
Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->update($updateData);
|
||
|
||
return [
|
||
'accountId' => $accountId,
|
||
'wechatId' => $accountData['wechatId'] ?? '',
|
||
'healthScore' => $healthScore,
|
||
'baseScore' => $baseScore,
|
||
'dynamicScore' => $dynamicScore,
|
||
'baseInfoScore' => $this->getBaseInfoScore($accountData),
|
||
'friendCountScore' => $this->getFriendCountScore($accountData['totalFriend'] ?? 0),
|
||
'maxAddFriendPerDay' => $this->getMaxAddFriendPerDay($healthScore)
|
||
];
|
||
|
||
} catch (Exception $e) {
|
||
throw new Exception("计算健康分失败:" . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算基础分
|
||
* 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(30分)
|
||
*
|
||
* @param array $accountData 账号数据
|
||
* @return int 基础分
|
||
*/
|
||
private function calculateBaseScore($accountData)
|
||
{
|
||
$baseScore = self::DEFAULT_BASE_SCORE;
|
||
|
||
// 基础信息分(已修改微信号得10分)
|
||
$baseScore += $this->getBaseInfoScore($accountData);
|
||
|
||
// 好友数量分(最高30分)
|
||
$totalFriend = $accountData['totalFriend'] ?? 0;
|
||
$baseScore += $this->getFriendCountScore($totalFriend);
|
||
|
||
return $baseScore;
|
||
}
|
||
|
||
/**
|
||
* 获取基础信息分
|
||
* 已修改微信号:10分
|
||
*
|
||
* @param array $accountData 账号数据
|
||
* @return int 基础信息分
|
||
*/
|
||
private function getBaseInfoScore($accountData)
|
||
{
|
||
// 检查是否已修改微信号
|
||
// 如果isModifiedAlias字段为1,或者wechatId和alias不一致,则认为已修改
|
||
$isModifiedAlias = isset($accountData['isModifiedAlias']) ? (int)$accountData['isModifiedAlias'] : 0;
|
||
$wechatId = trim($accountData['wechatId'] ?? '');
|
||
$alias = trim($accountData['alias'] ?? '');
|
||
|
||
// 如果字段标记为已修改,或者wechatId和alias不一致,则得分
|
||
if ($isModifiedAlias == 1 || (!empty($wechatId) && !empty($alias) && $wechatId !== $alias)) {
|
||
return self::BASE_INFO_SCORE;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* 获取好友数量分
|
||
* 根据好友数量区间得分(最高30分)
|
||
*
|
||
* @param int $totalFriend 总好友数
|
||
* @return int 好友数量分
|
||
*/
|
||
private function getFriendCountScore($totalFriend)
|
||
{
|
||
if ($totalFriend <= 50) {
|
||
return 3; // 0-50: 3分
|
||
} elseif ($totalFriend <= 500) {
|
||
return 6; // 51-500: 6分
|
||
} elseif ($totalFriend <= 3000) {
|
||
return 8; // 501-3000: 8分
|
||
} else {
|
||
return 12; // 3001以上: 12分
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算动态分
|
||
* 动态分 = 扣分 + 加分
|
||
*
|
||
* @param array $accountData 账号数据
|
||
* @return int 动态分
|
||
*/
|
||
private function calculateDynamicScore($accountData)
|
||
{
|
||
$dynamicScore = 0;
|
||
|
||
// 处理扣分
|
||
$dynamicScore += $this->calculatePenalty($accountData);
|
||
|
||
// 处理加分
|
||
$dynamicScore += $this->calculateBonus($accountData);
|
||
|
||
return $dynamicScore;
|
||
}
|
||
|
||
/**
|
||
* 计算扣分
|
||
* 首次频繁:-15分
|
||
* 再次频繁:-25分
|
||
* 封号:-60分
|
||
*
|
||
* @param array $accountData 账号数据
|
||
* @return int 扣分数
|
||
*/
|
||
private function calculatePenalty($accountData)
|
||
{
|
||
$penalty = 0;
|
||
|
||
// 检查是否有频繁记录
|
||
$lastFrequentTime = $accountData['lastFrequentTime'] ?? null;
|
||
$frequentCount = $accountData['frequentCount'] ?? 0;
|
||
|
||
if (!empty($lastFrequentTime)) {
|
||
// 判断是首次频繁还是再次频繁
|
||
if ($frequentCount == 1) {
|
||
$penalty += self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分
|
||
} elseif ($frequentCount >= 2) {
|
||
$penalty += self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分
|
||
}
|
||
}
|
||
|
||
// 检查是否封号(这里需要根据实际业务逻辑判断,比如status字段或其他标识)
|
||
// 假设status=0表示封号
|
||
$status = $accountData['status'] ?? 1;
|
||
if ($status == 0) {
|
||
$penalty += self::PENALTY_BANNED; // 封号-60分
|
||
}
|
||
|
||
return $penalty;
|
||
}
|
||
|
||
/**
|
||
* 计算加分
|
||
* 连续3天不触发频繁:每天+5分
|
||
*
|
||
* @param array $accountData 账号数据
|
||
* @return int 加分数
|
||
*/
|
||
private function calculateBonus($accountData)
|
||
{
|
||
$bonus = 0;
|
||
|
||
$lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null;
|
||
$consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0;
|
||
|
||
// 如果连续不频繁天数>=3,则每天+5分
|
||
if ($consecutiveNoFrequentDays >= 3) {
|
||
$bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY;
|
||
}
|
||
|
||
return $bonus;
|
||
}
|
||
|
||
/**
|
||
* 根据健康分计算每日最大加人次数
|
||
* 公式:每日最大加人次数 = 健康分 * 0.2
|
||
*
|
||
* @param int $healthScore 健康分
|
||
* @return int 每日最大加人次数
|
||
*/
|
||
public function getMaxAddFriendPerDay($healthScore)
|
||
{
|
||
return (int)floor($healthScore * 0.2);
|
||
}
|
||
|
||
/**
|
||
* 批量计算并更新多个账号的健康分
|
||
*
|
||
* @param array $accountIds 账号ID数组
|
||
* @param int $batchSize 每批处理数量
|
||
* @return array 处理结果统计
|
||
*/
|
||
public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100)
|
||
{
|
||
$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);
|
||
$stats['success']++;
|
||
} catch (Exception $e) {
|
||
$stats['failed']++;
|
||
$stats['errors'][] = [
|
||
'accountId' => $accountId,
|
||
'error' => $e->getMessage()
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $stats;
|
||
}
|
||
|
||
/**
|
||
* 记录频繁事件
|
||
*
|
||
* @param int $accountId 账号ID
|
||
* @return bool
|
||
*/
|
||
public function recordFrequent($accountId)
|
||
{
|
||
$accountData = Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->find();
|
||
|
||
if (empty($accountData)) {
|
||
return false;
|
||
}
|
||
|
||
$frequentCount = ($accountData['frequentCount'] ?? 0) + 1;
|
||
|
||
$updateData = [
|
||
'lastFrequentTime' => time(),
|
||
'frequentCount' => $frequentCount,
|
||
'consecutiveNoFrequentDays' => 0, // 重置连续不频繁天数
|
||
'lastNoFrequentTime' => null
|
||
];
|
||
|
||
Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->update($updateData);
|
||
|
||
// 重新计算健康分
|
||
$this->calculateAndUpdate($accountId);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 记录不频繁事件(用于加分)
|
||
*
|
||
* @param int $accountId 账号ID
|
||
* @return bool
|
||
*/
|
||
public function recordNoFrequent($accountId)
|
||
{
|
||
$accountData = Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->find();
|
||
|
||
if (empty($accountData)) {
|
||
return false;
|
||
}
|
||
|
||
$lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null;
|
||
$consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0;
|
||
$currentTime = time();
|
||
|
||
// 如果上次不频繁时间是昨天或更早,则增加连续天数
|
||
if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) {
|
||
// 如果间隔超过1天,重置为1天
|
||
if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) {
|
||
$consecutiveNoFrequentDays = 1;
|
||
} else {
|
||
$consecutiveNoFrequentDays++;
|
||
}
|
||
}
|
||
|
||
$updateData = [
|
||
'lastNoFrequentTime' => $currentTime,
|
||
'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays
|
||
];
|
||
|
||
Db::table('s2_wechat_account')
|
||
->where('id', $accountId)
|
||
->update($updateData);
|
||
|
||
// 重新计算健康分
|
||
$this->calculateAndUpdate($accountId);
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|