客服健康分提交

This commit is contained in:
wong
2025-11-21 14:38:29 +08:00
parent beabe1c191
commit 3faee0d8be
8 changed files with 1129 additions and 298 deletions

View File

@@ -39,4 +39,7 @@ return [
'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务
'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务
'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知
'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分
'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录
];

View File

@@ -0,0 +1,311 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
use app\common\service\WechatAccountHealthScoreService;
/**
* 统一计算微信账号健康分命令
* 一个命令完成所有评分工作:
* 1. 初始化未计算的账号(基础分只计算一次)
* 2. 更新评分记录根据wechatId和alias不一致情况
* 3. 批量更新健康分(只更新动态分)
*/
class CalculateWechatAccountScoreCommand extends Command
{
protected function configure()
{
$this->setName('wechat:calculate-score')
->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)');
}
protected function execute(Input $input, Output $output)
{
$output->writeln("==========================================");
$output->writeln("开始统一计算微信账号健康分...");
$output->writeln("==========================================");
$startTime = time();
$service = new WechatAccountHealthScoreService();
try {
// 步骤1: 初始化未计算基础分的账号
$output->writeln("\n[步骤1] 初始化未计算基础分的账号...");
$initStats = $this->initUncalculatedAccounts($service, $output);
$output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']}");
// 步骤2: 更新评分记录根据wechatId和alias不一致情况
$output->writeln("\n[步骤2] 更新评分记录根据wechatId和alias不一致情况...");
$updateStats = $this->updateScoreRecords($service, $output);
$output->writeln("更新完成:处理了 {$updateStats['total']} 条记录");
// 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分)
$output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)...");
$batchStats = $this->batchUpdateHealthScore($service, $output);
$output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']}");
// 统计信息
$endTime = time();
$duration = $endTime - $startTime;
$output->writeln("\n==========================================");
$output->writeln("任务完成!");
$output->writeln("==========================================");
$output->writeln("总耗时: {$duration}");
$output->writeln("初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']}");
$output->writeln("更新评分记录: {$updateStats['total']}");
$output->writeln("批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']}");
if (!empty($initStats['errors'])) {
$output->writeln("\n初始化错误详情:");
foreach (array_slice($initStats['errors'], 0, 10) as $error) {
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
}
if (count($initStats['errors']) > 10) {
$output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误");
}
}
if (!empty($batchStats['errors'])) {
$output->writeln("\n批量更新错误详情:");
foreach (array_slice($batchStats['errors'], 0, 10) as $error) {
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
}
if (count($batchStats['errors']) > 10) {
$output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误");
}
}
} catch (\Exception $e) {
$output->writeln("\n错误: " . $e->getMessage());
$output->writeln($e->getTraceAsString());
}
}
/**
* 初始化未计算基础分的账号
*
* @param WechatAccountHealthScoreService $service
* @param Output $output
* @return array
*/
private function initUncalculatedAccounts($service, $output)
{
$stats = [
'total' => 0,
'success' => 0,
'failed' => 0,
'errors' => []
];
// 获取所有未计算基础分的账号
$accounts = Db::table('s2_wechat_account')
->alias('a')
->leftJoin(['s2_wechat_account_score' => 's'], 's.accountId = a.id')
->where('a.isDeleted', 0)
->where(function($query) {
$query->whereNull('s.id')
->whereOr('s.baseScoreCalculated', 0);
})
->field('a.id, a.wechatId')
->select();
$stats['total'] = count($accounts);
if ($stats['total'] == 0) {
$output->writeln("没有需要初始化的账号");
return $stats;
}
$output->writeln("找到 {$stats['total']} 个需要初始化的账号");
$batchSize = 100;
$batches = array_chunk($accounts, $batchSize);
foreach ($batches as $batchIndex => $batch) {
foreach ($batch as $account) {
try {
$service->calculateAndUpdate($account['id']);
$stats['success']++;
if ($stats['success'] % 100 == 0) {
$output->write(".");
}
} catch (\Exception $e) {
$stats['failed']++;
$stats['errors'][] = [
'accountId' => $account['id'],
'error' => $e->getMessage()
];
}
}
if (($batchIndex + 1) % 10 == 0) {
$output->writeln(" 已处理 " . ($batchIndex + 1) * $batchSize . "");
}
}
return $stats;
}
/**
* 更新评分记录根据wechatId和alias不一致情况
*
* @param WechatAccountHealthScoreService $service
* @param Output $output
* @return array
*/
private function updateScoreRecords($service, $output)
{
$stats = ['total' => 0];
// 查找wechatId和alias不一致的账号
$inconsistentAccounts = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId != alias')
->field('id, wechatId, alias')
->select();
// 查找wechatId和alias一致的账号
$consistentAccounts = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId = alias')
->field('id, wechatId, alias')
->select();
$allAccounts = array_merge($inconsistentAccounts, $consistentAccounts);
$stats['total'] = count($allAccounts);
if ($stats['total'] == 0) {
$output->writeln("没有需要更新的账号");
return $stats;
}
$output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . "");
$updatedCount = 0;
foreach ($allAccounts as $account) {
$isModifiedAlias = in_array($account['id'], array_column($inconsistentAccounts, 'id'));
$this->updateScoreRecord($account['id'], $isModifiedAlias, $service);
$updatedCount++;
if ($updatedCount % 100 == 0) {
$output->write(".");
}
}
if ($updatedCount > 0 && $updatedCount % 100 == 0) {
$output->writeln("");
}
return $stats;
}
/**
* 批量更新健康分(只更新动态分)
*
* @param WechatAccountHealthScoreService $service
* @param Output $output
* @return array
*/
private function batchUpdateHealthScore($service, $output)
{
// 获取所有已计算基础分的账号
$accountIds = Db::table('s2_wechat_account_score')
->where('baseScoreCalculated', 1)
->column('accountId');
$total = count($accountIds);
if ($total == 0) {
$output->writeln("没有需要更新的账号");
return ['success' => 0, 'failed' => 0, 'errors' => []];
}
$output->writeln("找到 {$total} 个需要更新动态分的账号");
$stats = $service->batchCalculateAndUpdate($accountIds, 100, false);
return $stats;
}
/**
* 更新评分记录
*
* @param int $accountId 账号ID
* @param bool $isModifiedAlias 是否已修改微信号
* @param WechatAccountHealthScoreService $service 评分服务
*/
private function updateScoreRecord($accountId, $isModifiedAlias, $service)
{
// 获取账号数据
$accountData = Db::table('s2_wechat_account')
->where('id', $accountId)
->find();
if (empty($accountData)) {
return;
}
// 确保评分记录存在
$scoreRecord = Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->find();
if (empty($scoreRecord)) {
// 如果记录不存在,创建并计算基础分
$service->calculateAndUpdate($accountId);
$scoreRecord = Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->find();
}
if (empty($scoreRecord)) {
return;
}
// 更新isModifiedAlias字段
$updateData = [
'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
'updateTime' => time()
];
// 如果基础分已计算,需要更新基础信息分和基础分
if ($scoreRecord['baseScoreCalculated']) {
$oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
$newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
if ($oldBaseInfoScore != $newBaseInfoScore) {
$oldBaseScore = $scoreRecord['baseScore'] ?? 60;
$newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
$updateData['baseInfoScore'] = $newBaseInfoScore;
$updateData['baseScore'] = $newBaseScore;
// 重新计算健康分
$dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
$healthScore = $newBaseScore + $dynamicScore;
$healthScore = max(0, min(100, $healthScore));
$updateData['healthScore'] = $healthScore;
$updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
}
} else {
// 基础分未计算,只更新标记和基础信息分
$updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
}
Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->update($updateData);
}
}

View File

@@ -1,159 +0,0 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
use app\common\service\WechatAccountHealthScoreService;
/**
* 检测和修复s2_wechat_account表中wechatId和alias不一致的问题
* 同时更新isModifiedAlias字段和健康分
*/
class CheckAndFixWechatAccountCommand extends Command
{
protected function configure()
{
$this->setName('wechat:check-and-fix')
->setDescription('检测和修复s2_wechat_account表中wechatId和alias不一致的问题并更新健康分');
}
protected function execute(Input $input, Output $output)
{
$output->writeln("开始检测和修复s2_wechat_account表...");
try {
// 1. 检测wechatId和alias不一致的记录
$output->writeln("步骤1: 检测wechatId和alias不一致的记录...");
$inconsistentAccounts = $this->findInconsistentAccounts();
$output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录");
if (empty($inconsistentAccounts)) {
$output->writeln("没有发现不一致的记录,任务完成!");
return;
}
// 2. 修复不一致的记录
$output->writeln("步骤2: 修复不一致的记录...");
$fixedCount = $this->fixInconsistentAccounts($inconsistentAccounts);
$output->writeln("已修复 " . $fixedCount . " 条记录");
// 3. 更新isModifiedAlias字段
$output->writeln("步骤3: 更新isModifiedAlias字段...");
$updatedCount = $this->updateModifiedAliasFlag();
$output->writeln("已更新 " . $updatedCount . " 条记录的isModifiedAlias字段");
// 4. 重新计算健康分
$output->writeln("步骤4: 重新计算健康分...");
$healthScoreService = new WechatAccountHealthScoreService();
$accountIds = array_column($inconsistentAccounts, 'id');
$stats = $healthScoreService->batchCalculateAndUpdate($accountIds, 100);
$output->writeln("健康分计算完成:成功 " . $stats['success'] . " 条,失败 " . $stats['failed'] . "");
if (!empty($stats['errors'])) {
$output->writeln("错误详情:");
foreach ($stats['errors'] as $error) {
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
}
}
$output->writeln("任务完成!");
} catch (\Exception $e) {
$output->writeln("错误: " . $e->getMessage());
$output->writeln($e->getTraceAsString());
}
}
/**
* 查找wechatId和alias不一致的记录
*
* @return array
*/
private function findInconsistentAccounts()
{
// 查找wechatId和alias不一致的记录
// 条件wechatId不为空alias不为空且wechatId != alias
$accounts = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId != alias')
->field('id, wechatId, alias, nickname, isModifiedAlias')
->select();
return $accounts ?: [];
}
/**
* 修复不一致的记录
* 策略从s2_wechat_friend表中查找最新的alias值来更新
*
* @param array $accounts 不一致的账号列表
* @return int 修复数量
*/
private function fixInconsistentAccounts($accounts)
{
$fixedCount = 0;
foreach ($accounts as $account) {
$wechatId = $account['wechatId'];
// 从s2_wechat_friend表中查找最新的alias值
$latestAlias = Db::table('s2_wechat_friend')
->where('wechatId', $wechatId)
->where('alias', '<>', '')
->order('updateTime', 'desc')
->value('alias');
// 如果找到了最新的alias则更新
if (!empty($latestAlias) && $latestAlias !== $account['alias']) {
Db::table('s2_wechat_account')
->where('id', $account['id'])
->update([
'alias' => $latestAlias,
'updateTime' => time()
]);
$fixedCount++;
}
}
return $fixedCount;
}
/**
* 更新isModifiedAlias字段
* 如果wechatId和alias不一致则标记为已修改
*
* @return int 更新数量
*/
private function updateModifiedAliasFlag()
{
// 更新isModifiedAlias字段wechatId != alias 的记录标记为1
$updatedCount = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId != alias')
->update([
'isModifiedAlias' => 1,
'updateTime' => time()
]);
// 更新isModifiedAlias字段wechatId == alias 的记录标记为0
Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId = alias')
->update([
'isModifiedAlias' => 0,
'updateTime' => time()
]);
return $updatedCount;
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
use app\common\service\WechatAccountHealthScoreService;
/**
* 更新微信账号评分记录
* 根据wechatId和alias是否不一致来更新isModifiedAlias字段仅用于评分不修复数据
*/
class UpdateWechatAccountScoreCommand extends Command
{
protected function configure()
{
$this->setName('wechat:update-score')
->setDescription('更新微信账号评分记录根据wechatId和alias不一致情况更新isModifiedAlias字段仅用于评分');
}
protected function execute(Input $input, Output $output)
{
$output->writeln("开始更新微信账号评分记录...");
try {
// 1. 查找所有需要更新的账号
$output->writeln("步骤1: 查找需要更新的账号...");
// 查找wechatId和alias不一致的账号
$inconsistentAccounts = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId != alias')
->field('id, wechatId, alias')
->select();
// 查找wechatId和alias一致的账号
$consistentAccounts = Db::table('s2_wechat_account')
->where('isDeleted', 0)
->where('wechatId', '<>', '')
->where('alias', '<>', '')
->whereRaw('wechatId = alias')
->field('id, wechatId, alias')
->select();
$output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录(已修改微信号)");
$output->writeln("发现 " . count($consistentAccounts) . " 条一致记录(未修改微信号)");
// 2. 更新评分记录表中的isModifiedAlias字段
$output->writeln("步骤2: 更新评分记录表...");
$updatedCount = 0;
$healthScoreService = new WechatAccountHealthScoreService();
// 更新不一致的记录
foreach ($inconsistentAccounts as $account) {
$this->updateScoreRecord($account['id'], true, $healthScoreService);
$updatedCount++;
}
// 更新一致的记录
foreach ($consistentAccounts as $account) {
$this->updateScoreRecord($account['id'], false, $healthScoreService);
$updatedCount++;
}
$output->writeln("已更新 " . $updatedCount . " 条评分记录");
// 3. 重新计算健康分(只更新基础信息分,不重新计算基础分)
$output->writeln("步骤3: 重新计算健康分...");
$allAccountIds = array_merge(
array_column($inconsistentAccounts, 'id'),
array_column($consistentAccounts, 'id')
);
if (!empty($allAccountIds)) {
$stats = $healthScoreService->batchCalculateAndUpdate($allAccountIds, 100, false);
$output->writeln("健康分计算完成:成功 " . $stats['success'] . " 条,失败 " . $stats['failed'] . "");
if (!empty($stats['errors'])) {
$output->writeln("错误详情:");
foreach ($stats['errors'] as $error) {
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
}
}
}
$output->writeln("任务完成!");
} catch (\Exception $e) {
$output->writeln("错误: " . $e->getMessage());
$output->writeln($e->getTraceAsString());
}
}
/**
* 更新评分记录
*
* @param int $accountId 账号ID
* @param bool $isModifiedAlias 是否已修改微信号
* @param WechatAccountHealthScoreService $service 评分服务
*/
private function updateScoreRecord($accountId, $isModifiedAlias, $service)
{
// 获取或创建评分记录
$accountData = Db::table('s2_wechat_account')
->where('id', $accountId)
->find();
if (empty($accountData)) {
return;
}
// 确保评分记录存在
$scoreRecord = Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->find();
if (empty($scoreRecord)) {
// 如果记录不存在,创建并计算基础分
$service->calculateAndUpdate($accountId);
$scoreRecord = Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->find();
}
if (empty($scoreRecord)) {
return;
}
// 更新isModifiedAlias字段
$updateData = [
'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
'updateTime' => time()
];
// 如果基础分已计算,需要更新基础信息分和基础分
if ($scoreRecord['baseScoreCalculated']) {
$oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
$newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
if ($oldBaseInfoScore != $newBaseInfoScore) {
$oldBaseScore = $scoreRecord['baseScore'] ?? 60;
$newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
$updateData['baseInfoScore'] = $newBaseInfoScore;
$updateData['baseScore'] = $newBaseScore;
// 重新计算健康分
$dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
$healthScore = $newBaseScore + $dynamicScore;
$healthScore = max(0, min(100, $healthScore));
$updateData['healthScore'] = $healthScore;
$updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
}
} else {
// 基础分未计算,只更新标记和基础信息分
$updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
}
Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->update($updateData);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace app\common\model;
use think\Model;
/**
* 微信账号评分记录模型类
*/
class WechatAccountScore extends Model
{
// 设置表名
protected $name = 'wechat_account_score';
protected $table = 's2_wechat_account_score';
// 自动写入时间戳
protected $autoWriteTimestamp = false;
// 定义字段类型
protected $type = [
'accountId' => 'integer',
'baseScore' => 'integer',
'baseScoreCalculated' => 'integer',
'baseInfoScore' => 'integer',
'friendCountScore' => 'integer',
'friendCount' => 'integer',
'dynamicScore' => 'integer',
'frequentCount' => 'integer',
'frequentPenalty' => 'integer',
'consecutiveNoFrequentDays' => 'integer',
'noFrequentBonus' => 'integer',
'banPenalty' => 'integer',
'healthScore' => 'integer',
'maxAddFriendPerDay' => 'integer',
'isModifiedAlias' => 'integer',
'isBanned' => 'integer',
'lastFrequentTime' => 'integer',
'lastNoFrequentTime' => 'integer',
'baseScoreCalcTime' => 'integer',
'createTime' => 'integer',
'updateTime' => 'integer',
];
}

View File

@@ -6,9 +6,15 @@ use think\Db;
use think\Exception;
/**
* 微信账号健康分评分服务
* 微信账号健康分评分服务(优化版)
* 基于《微信健康分规则v2.md》实现
*
* 优化点:
* 1. 基础分只计算一次
* 2. 各个评分维度独立存储
* 3. 使用独立的评分记录表
* 4. 好友数量评分特殊处理(避免同步问题)
*
* 健康分 = 基础分 + 动态分
* 基础分60-100分默认60分 + 基础信息10分 + 好友数量30分
* 动态分:扣分和加分规则
@@ -18,13 +24,14 @@ 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 FRIEND_COUNT_SCORE_0_50 = 3;
const FRIEND_COUNT_SCORE_51_500 = 6;
const FRIEND_COUNT_SCORE_501_3000 = 8;
const FRIEND_COUNT_SCORE_3001_PLUS = 12;
// 动态分扣分规则
const PENALTY_FIRST_FREQUENT = -15; // 首次频繁扣15分
@@ -39,9 +46,10 @@ class WechatAccountHealthScoreService
*
* @param int $accountId 账号IDs2_wechat_account表的id
* @param array $accountData 账号数据(可选,如果不传则从数据库查询)
* @param bool $forceRecalculateBase 是否强制重新计算基础分默认false
* @return array 返回评分结果
*/
public function calculateAndUpdate($accountId, $accountData = null)
public function calculateAndUpdate($accountId, $accountData = null, $forceRecalculateBase = false)
{
try {
// 获取账号数据
@@ -55,39 +63,68 @@ class WechatAccountHealthScoreService
throw new Exception("账号不存在:{$accountId}");
}
// 计算基础分
$baseScore = $this->calculateBaseScore($accountData);
$wechatId = $accountData['wechatId'] ?? '';
if (empty($wechatId)) {
throw new Exception("账号wechatId为空{$accountId}");
}
// 计算动态分
$dynamicScore = $this->calculateDynamicScore($accountData);
// 获取或创建评分记录
$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 = [
'healthScore' => $healthScore,
'baseScore' => $baseScore,
'dynamicScore' => $dynamicScore,
'scoreUpdateTime' => time()
'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')
->where('id', $accountId)
Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->update($updateData);
return [
'accountId' => $accountId,
'wechatId' => $accountData['wechatId'] ?? '',
'wechatId' => $wechatId,
'healthScore' => $healthScore,
'baseScore' => $baseScore,
'baseInfoScore' => $scoreRecord['baseInfoScore'],
'friendCountScore' => $scoreRecord['friendCountScore'],
'dynamicScore' => $dynamicScore,
'baseInfoScore' => $this->getBaseInfoScore($accountData),
'friendCountScore' => $this->getFriendCountScore($accountData['totalFriend'] ?? 0),
'maxAddFriendPerDay' => $this->getMaxAddFriendPerDay($healthScore)
'frequentPenalty' => $dynamicScoreData['frequentPenalty'],
'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'],
'banPenalty' => $dynamicScoreData['banPenalty'],
'maxAddFriendPerDay' => $maxAddFriendPerDay
];
} catch (Exception $e) {
@@ -96,24 +133,119 @@ class WechatAccountHealthScoreService
}
/**
* 计算基础分
* 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(30分)
* 获取或创建评分记录
*
* @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 账号数据
* @return int 基础分
* @param array $scoreRecord 现有评分记录
* @return array 基础分数据
*/
private function calculateBaseScore($accountData)
private function calculateBaseScore($accountData, $scoreRecord = [])
{
$baseScore = self::DEFAULT_BASE_SCORE;
// 基础信息分已修改微信号得10分
$baseScore += $this->getBaseInfoScore($accountData);
$baseInfoScore = $this->getBaseInfoScore($accountData);
$baseScore += $baseInfoScore;
// 好友数量分(最高30分
$totalFriend = $accountData['totalFriend'] ?? 0;
$baseScore += $this->getFriendCountScore($totalFriend);
// 好友数量分(特殊处理:使用快照值,避免同步问题
$friendCountScore = 0;
$friendCount = 0;
$friendCountSource = 'manual';
return $baseScore;
// 如果已有评分记录且好友数量分已计算,使用历史值
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);
}
/**
@@ -125,23 +257,36 @@ class WechatAccountHealthScoreService
*/
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)) {
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;
}
/**
* 获取好友数量分
* 根据好友数量区间得分(最高30分)
* 根据好友数量区间得分(最高12分)
*
* @param int $totalFriend 总好友数
* @return int 好友数量分
@@ -149,92 +294,292 @@ class WechatAccountHealthScoreService
private function getFriendCountScore($totalFriend)
{
if ($totalFriend <= 50) {
return 3; // 0-50: 3分
return self::FRIEND_COUNT_SCORE_0_50;
} elseif ($totalFriend <= 500) {
return 6; // 51-500: 6分
return self::FRIEND_COUNT_SCORE_51_500;
} elseif ($totalFriend <= 3000) {
return 8; // 501-3000: 8分
return self::FRIEND_COUNT_SCORE_501_3000;
} else {
return 12; // 3001以上: 12分
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 账号数据
* @return int 动态分
* @param array $scoreRecord 现有评分记录
* @return array 动态分数据
*/
private function calculateDynamicScore($accountData)
private function calculateDynamicScore($accountData, $scoreRecord)
{
$dynamicScore = 0;
$result = [
'total' => 0,
'frequentPenalty' => 0,
'noFrequentBonus' => 0,
'banPenalty' => 0,
'lastFrequentTime' => null,
'frequentCount' => 0,
'lastNoFrequentTime' => null,
'consecutiveNoFrequentDays' => 0,
'isBanned' => 0
];
// 处理扣分
$dynamicScore += $this->calculatePenalty($accountData);
$accountId = $accountData['id'] ?? 0;
$wechatId = $accountData['wechatId'] ?? '';
// 处理加分
$dynamicScore += $this->calculateBonus($accountData);
if (empty($accountId) || empty($wechatId)) {
return $result;
}
return $dynamicScore;
// 继承现有数据
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;
}
/**
* 计算扣分
* 首次频繁:-15
* 再次频繁:-25分
* 封号:-60分
* 从s2_friend_task表检查频繁记录
* extra字段包含"操作过于频繁"即需要扣
* 只统计近30天的数据
*
* @param array $accountData 账号数据
* @return int 扣分数
* @param int $accountId 账号ID
* @param string $wechatId 微信ID
* @param array $scoreRecord 现有评分记录
* @return array|null
*/
private function calculatePenalty($accountData)
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分
}
// 检查是否有频繁记录
$lastFrequentTime = $accountData['lastFrequentTime'] ?? null;
$frequentCount = $accountData['frequentCount'] ?? 0;
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);
if (!empty($lastFrequentTime)) {
// 判断是首次频繁还是再次频繁
if ($frequentCount == 1) {
$penalty += self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分
} elseif ($frequentCount >= 2) {
$penalty += self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分
// 查询封号消息只统计近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)) {
// 情况130天内没有频繁记录说明30天内连续不频繁
// 计算从30天前到现在的连续不频繁天数最多30天
$consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400));
} else {
// 情况230天内有频繁记录从最后一次频繁时间开始重新计算连续不频繁天数
// 只要有一次频繁,连续不频繁天数就从最后一次频繁时间开始重新计算
// 计算从最后一次频繁时间到现在,连续多少天没有频繁
$timeDiff = $currentTime - $lastFrequentTime;
$consecutiveDays = floor($timeDiff / 86400); // 向下取整,得到完整的天数
// 边界情况如果最后一次频繁时间在30天前理论上不应该发生因为查询已经限制了30天则按30天处理
if ($lastFrequentTime < $thirtyDaysAgo) {
$consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400));
}
}
// 检查是否封号这里需要根据实际业务逻辑判断比如status字段或其他标识
// 假设status=0表示封号
$status = $accountData['status'] ?? 1;
if ($status == 0) {
$penalty += self::PENALTY_BANNED; // 封号-60分
// 如果连续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 $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;
return $result;
}
/**
@@ -252,11 +597,12 @@ class WechatAccountHealthScoreService
/**
* 批量计算并更新多个账号的健康分
*
* @param array $accountIds 账号ID数组
* @param array $accountIds 账号ID数组(为空则处理所有账号)
* @param int $batchSize 每批处理数量
* @param bool $forceRecalculateBase 是否强制重新计算基础分
* @return array 处理结果统计
*/
public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100)
public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false)
{
$stats = [
'total' => 0,
@@ -280,7 +626,7 @@ class WechatAccountHealthScoreService
foreach ($batches as $batch) {
foreach ($batch as $accountId) {
try {
$this->calculateAndUpdate($accountId);
$this->calculateAndUpdate($accountId, null, $forceRecalculateBase);
$stats['success']++;
} catch (Exception $e) {
$stats['failed']++;
@@ -296,38 +642,22 @@ class WechatAccountHealthScoreService
}
/**
* 记录频繁事件
* 记录频繁事件已废弃改为从s2_friend_task表自动检测
* 保留此方法以兼容旧代码实际频繁检测在calculateDynamicScore中完成
*
* @param int $accountId 账号ID
* @return bool
*/
public function recordFrequent($accountId)
{
$accountData = Db::table('s2_wechat_account')
->where('id', $accountId)
->find();
if (empty($accountData)) {
// 频繁检测已改为从s2_friend_task表自动检测
// 直接重新计算健康分即可
try {
$this->calculateAndUpdate($accountId);
return true;
} catch (\Exception $e) {
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;
}
/**
@@ -338,21 +668,29 @@ class WechatAccountHealthScoreService
*/
public function recordNoFrequent($accountId)
{
$accountData = Db::table('s2_wechat_account')
->where('id', $accountId)
->find();
$scoreRecord = $this->getScoreRecord($accountId);
if (empty($accountData)) {
return false;
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 = $accountData['lastNoFrequentTime'] ?? null;
$consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0;
$lastNoFrequentTime = $scoreRecord['lastNoFrequentTime'] ?? null;
$consecutiveNoFrequentDays = $scoreRecord['consecutiveNoFrequentDays'] ?? 0;
$currentTime = time();
// 如果上次不频繁时间是昨天或更早,则增加连续天数
if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) {
// 如果间隔超过1重置为1天
// 如果间隔超过2重置为1天
if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) {
$consecutiveNoFrequentDays = 1;
} else {
@@ -360,13 +698,21 @@ class WechatAccountHealthScoreService
}
}
// 计算加分连续3天及以上才加分
$bonus = 0;
if ($consecutiveNoFrequentDays >= 3) {
$bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY;
}
$updateData = [
'lastNoFrequentTime' => $currentTime,
'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays
'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays,
'noFrequentBonus' => $bonus,
'updateTime' => $currentTime
];
Db::table('s2_wechat_account')
->where('id', $accountId)
Db::table('s2_wechat_account_score')
->where('accountId', $accountId)
->update($updateData);
// 重新计算健康分
@@ -374,5 +720,38 @@ class WechatAccountHealthScoreService
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
];
}
}

View File

@@ -81,6 +81,8 @@
# 消息提醒
*/1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1
# 客服评分
0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechat:calculate-score >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/calculate_score.log 2>&1
@@ -107,4 +109,10 @@
```bash
crontab -l
```
```bash
- 本地: php think worker:server
- 线上: php think worker:server -d (自带守护进程无需搭配Supervisor 之类的工具)
- php think worker:server stop php think worker:server status
```

View File

@@ -18,6 +18,7 @@ use think\facade\Config;
use think\facade\Log;
use app\api\controller\FriendTaskController;
use app\common\service\AuthService;
use app\common\service\WechatAccountHealthScoreService;
use app\api\controller\WebSocketController;
use Workerman\Lib\Timer;
@@ -180,13 +181,11 @@ class Adapter implements WeChatServiceInterface
->select();
$taskData = array_merge($taskData, $tasks);
}
if ($taskData) {
foreach ($taskData as $task) {
$task_id = $task['task_id'];
$task_info = $this->getCustomerAcquisitionTask($task_id);
if (empty($task_info['status']) || empty($task_info['reqConf']) || empty($task_info['reqConf']['device'])) {
continue;
}
@@ -213,9 +212,86 @@ class Adapter implements WeChatServiceInterface
continue;
}
// 判断24h内加的好友数量friend_task 先固定10个人 getLast24hAddedFriendsCount
// 根据健康分判断24h内加的好友数量限制
$healthScoreService = new WechatAccountHealthScoreService();
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
// 如果健康分记录不存在,先计算一次
if (empty($healthScoreInfo)) {
try {
$healthScoreService->calculateAndUpdate($accountId);
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
} catch (\Exception $e) {
Log::error("计算健康分失败 (accountId: {$accountId}): " . $e->getMessage());
// 如果计算失败使用默认值5作为兜底
$maxAddFriendPerDay = 5;
}
}
// 获取每日最大加人次数(基于健康分)
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 5;
// 如果健康分为0或很低不允许添加好友
if ($maxAddFriendPerDay <= 0) {
Log::info("账号健康分过低,不允许添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")");
continue;
}
// 检查频繁暂停限制首次频繁或再次频繁暂停24小时
$lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
$frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
if (!empty($lastFrequentTime) && $frequentCount > 0) {
$frequentPauseHours = 24; // 频繁暂停24小时
$frequentPauseTime = $lastFrequentTime + ($frequentPauseHours * 3600);
$currentTime = time();
if ($currentTime < $frequentPauseTime) {
$remainingHours = ceil(($frequentPauseTime - $currentTime) / 3600);
Log::info("账号频繁,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, frequentCount: {$frequentCount}, 剩余暂停时间: {$remainingHours}小时)");
continue;
}
}
// 检查封号暂停限制封号暂停72小时
$isBanned = $healthScoreInfo['isBanned'] ?? 0;
if ($isBanned == 1) {
// 查询封号时间从s2_wechat_message表查询最近一次封号消息
$banMessage = Db::table('s2_wechat_message')
->where('wechatAccountId', $accountId)
->where('msgType', 10000)
->where('content', 'like', '%你的账号被限制%')
->where('isDeleted', 0)
->order('createTime', 'desc')
->find();
if (!empty($banMessage)) {
$banTime = $banMessage['createTime'] ?? 0;
$banPauseHours = 72; // 封号暂停72小时
$banPauseTime = $banTime + ($banPauseHours * 3600);
$currentTime = time();
if ($currentTime < $banPauseTime) {
$remainingHours = ceil(($banPauseTime - $currentTime) / 3600);
Log::info("账号封号,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, 剩余暂停时间: {$remainingHours}小时)");
continue;
}
}
}
// 判断今天添加的好友数量,使用健康分计算的每日最大加人次数
// 优先使用今天添加的好友数量(更符合"每日"限制)
$todayAddedFriendsCount = $this->getTodayAddedFriendsCount($wechatId);
if ($todayAddedFriendsCount >= $maxAddFriendPerDay) {
Log::info("今天添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$todayAddedFriendsCount}, max: {$maxAddFriendPerDay}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")");
continue;
}
// 如果今天添加数量未达上限再检查24小时内的数量作为额外保护
$last24hAddedFriendsCount = $this->getLast24hAddedFriendsCount($wechatId);
if ($last24hAddedFriendsCount >= 20) {
// 24小时内的限制可以稍微宽松一些设置为每日限制的1.2倍(防止跨天累积)
$max24hLimit = (int)ceil($maxAddFriendPerDay * 1.2);
if ($last24hAddedFriendsCount >= $max24hLimit) {
Log::info("24小时内添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$last24hAddedFriendsCount}, max24h: {$max24hLimit}, maxDaily: {$maxAddFriendPerDay})");
continue;
}
@@ -828,6 +904,7 @@ class Adapter implements WeChatServiceInterface
if (empty($deviceIds)) {
return [];
}
$records = Db::table('s2_wechat_account')
->where('deviceAlive', 1)
->where('wechatAlive', 1)