Merge branch 'release/rating' into develop
This commit is contained in:
@@ -7,5 +7,27 @@ use think\Model;
|
||||
class WechatAccountModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_account';
|
||||
protected $table = 's2_wechat_account';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'healthScore' => 'integer',
|
||||
'baseScore' => 'integer',
|
||||
'dynamicScore' => 'integer',
|
||||
'isModifiedAlias' => 'integer',
|
||||
'frequentCount' => 'integer',
|
||||
'consecutiveNoFrequentDays' => 'integer',
|
||||
'lastFrequentTime' => 'integer',
|
||||
'lastNoFrequentTime' => 'integer',
|
||||
'scoreUpdateTime' => 'integer',
|
||||
];
|
||||
|
||||
// 允许批量赋值的字段
|
||||
protected $field = [
|
||||
'id', 'wechatId', 'alias', 'nickname', 'avatar', 'gender', 'region', 'signature',
|
||||
'healthScore', 'baseScore', 'dynamicScore', 'isModifiedAlias',
|
||||
'lastFrequentTime', 'frequentCount', 'lastNoFrequentTime',
|
||||
'consecutiveNoFrequentDays', 'scoreUpdateTime',
|
||||
'createTime', 'updateTime', 'status', 'isDeleted'
|
||||
];
|
||||
}
|
||||
@@ -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', // 更新微信账号评分记录
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal file
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
44
Server/application/common/model/WechatAccountScore.php
Normal file
44
Server/application/common/model/WechatAccountScore.php
Normal 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',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,757 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 微信账号健康分评分服务(优化版)
|
||||
* 基于《微信健康分规则v2.md》实现
|
||||
*
|
||||
* 优化点:
|
||||
* 1. 基础分只计算一次
|
||||
* 2. 各个评分维度独立存储
|
||||
* 3. 使用独立的评分记录表
|
||||
* 4. 好友数量评分特殊处理(避免同步问题)
|
||||
*
|
||||
* 健康分 = 基础分 + 动态分
|
||||
* 基础分:60-100分(默认60分 + 基础信息10分 + 好友数量30分)
|
||||
* 动态分:扣分和加分规则
|
||||
*/
|
||||
class WechatAccountHealthScoreService
|
||||
{
|
||||
// 默认基础分
|
||||
const DEFAULT_BASE_SCORE = 60;
|
||||
|
||||
// 基础信息分数
|
||||
const BASE_INFO_SCORE = 10;
|
||||
|
||||
// 好友数量分数区间
|
||||
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分
|
||||
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 账号数据(可选,如果不传则从数据库查询)
|
||||
* @param bool $forceRecalculateBase 是否强制重新计算基础分(默认false)
|
||||
* @return array 返回评分结果
|
||||
*/
|
||||
public function calculateAndUpdate($accountId, $accountData = null, $forceRecalculateBase = false)
|
||||
{
|
||||
try {
|
||||
// 获取账号数据
|
||||
if (empty($accountData)) {
|
||||
$accountData = Db::table('s2_wechat_account')
|
||||
->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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2816,6 +2816,8 @@ class WorkbenchController extends Controller
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告
|
||||
$status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败
|
||||
$userId = $this->request->userInfo['id'];
|
||||
|
||||
// 构建工作台查询条件
|
||||
@@ -2840,10 +2842,11 @@ class WorkbenchController extends Controller
|
||||
$workbenchWhere[] = ['w.id', '=', $workbenchId];
|
||||
}
|
||||
|
||||
// 按内容ID、工作台ID和时间分组,统计每次推送
|
||||
$query = Db::name('workbench_group_push_item')
|
||||
// 1. 先查询所有已执行的推送记录(按推送时间分组)
|
||||
$pushHistoryQuery = Db::name('workbench_group_push_item')
|
||||
->alias('wgpi')
|
||||
->join('workbench w', 'w.id = wgpi.workbenchId', 'left')
|
||||
->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left')
|
||||
->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
|
||||
->join('content_library cl', 'cl.id = ci.libraryId', 'left')
|
||||
->where($workbenchWhere)
|
||||
@@ -2853,52 +2856,57 @@ class WorkbenchController extends Controller
|
||||
'wgpi.contentId',
|
||||
'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime',
|
||||
'wgpi.targetType',
|
||||
'wgp.groupPushSubType',
|
||||
'MIN(wgpi.createTime) as createTime',
|
||||
'COUNT(DISTINCT wgpi.id) as totalCount',
|
||||
'cl.name as contentLibraryName'
|
||||
])
|
||||
->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType');
|
||||
->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType');
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$query->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
|
||||
$pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->order('createTime', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 对于有 group by 的查询,统计总数需要重新查询
|
||||
$totalQuery = Db::name('workbench_group_push_item')
|
||||
->alias('wgpi')
|
||||
->join('workbench w', 'w.id = wgpi.workbenchId', 'left')
|
||||
->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
|
||||
->join('content_library cl', 'cl.id = ci.libraryId', 'left')
|
||||
->where($workbenchWhere);
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$totalQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 统计分组后的记录数(使用子查询)
|
||||
$subQuery = $totalQuery
|
||||
$pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select();
|
||||
|
||||
// 2. 查询所有任务(包括未执行的)
|
||||
$allTasksQuery = Db::name('workbench')
|
||||
->alias('w')
|
||||
->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left')
|
||||
->where($workbenchWhere)
|
||||
->field([
|
||||
'wgpi.workbenchId',
|
||||
'wgpi.contentId',
|
||||
'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime',
|
||||
'wgpi.targetType'
|
||||
])
|
||||
->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType')
|
||||
->buildSql();
|
||||
|
||||
$total = Db::table('(' . $subQuery . ') as temp')->count();
|
||||
'w.id as workbenchId',
|
||||
'w.name as workbenchName',
|
||||
'w.createTime',
|
||||
'wgp.targetType',
|
||||
'wgp.groupPushSubType',
|
||||
'wgp.groups',
|
||||
'wgp.friends',
|
||||
'wgp.trafficPools'
|
||||
]);
|
||||
|
||||
// 处理每条记录
|
||||
foreach ($list as &$item) {
|
||||
if (!empty($keyword)) {
|
||||
$allTasksQuery->where('w.name', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$allTasks = $allTasksQuery->select();
|
||||
|
||||
// 3. 合并数据:已执行的推送记录 + 未执行的任务
|
||||
$resultList = [];
|
||||
$executedWorkbenchIds = [];
|
||||
|
||||
// 处理已执行的推送记录
|
||||
foreach ($pushHistoryList as $item) {
|
||||
$itemWorkbenchId = $item['workbenchId'];
|
||||
$contentId = $item['contentId'];
|
||||
$pushTime = $item['pushTime'];
|
||||
$targetType = intval($item['targetType']);
|
||||
$groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1;
|
||||
|
||||
// 标记该工作台已有执行记录
|
||||
if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) {
|
||||
$executedWorkbenchIds[] = $itemWorkbenchId;
|
||||
}
|
||||
|
||||
// 将时间字符串转换为时间戳范围(小时级别)
|
||||
$pushTimeStart = strtotime($pushTime);
|
||||
@@ -2937,23 +2945,149 @@ class WorkbenchController extends Controller
|
||||
$failCount = 0; // 简化处理,实际需要从发送状态获取
|
||||
|
||||
// 状态判断
|
||||
$status = $successCount > 0 ? 'success' : 'failed';
|
||||
$itemStatus = $successCount > 0 ? 'success' : 'failed';
|
||||
if ($failCount > 0 && $successCount > 0) {
|
||||
$status = 'partial';
|
||||
$itemStatus = 'partial';
|
||||
}
|
||||
|
||||
$item['pushType'] = $targetType == 1 ? '群推送' : '好友推送';
|
||||
$item['pushTypeCode'] = $targetType;
|
||||
$item['targetCount'] = $targetCount;
|
||||
$item['successCount'] = $successCount;
|
||||
$item['failCount'] = $failCount;
|
||||
$item['status'] = $status;
|
||||
$item['statusText'] = $status == 'success' ? '成功' : ($status == 'partial' ? '部分成功' : '失败');
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
// 任务名称(工作台名称)
|
||||
$item['taskName'] = $item['workbenchName'] ?? '';
|
||||
// 推送类型判断
|
||||
$pushTypeText = '';
|
||||
$pushTypeCode = '';
|
||||
if ($targetType == 1) {
|
||||
// 群推送
|
||||
if ($groupPushSubType == 2) {
|
||||
$pushTypeText = '群公告';
|
||||
$pushTypeCode = 'announcement';
|
||||
} else {
|
||||
$pushTypeText = '群消息';
|
||||
$pushTypeCode = 'group';
|
||||
}
|
||||
} else {
|
||||
// 好友推送
|
||||
$pushTypeText = '好友消息';
|
||||
$pushTypeCode = 'friend';
|
||||
}
|
||||
|
||||
$resultList[] = [
|
||||
'workbenchId' => $itemWorkbenchId,
|
||||
'taskName' => $item['workbenchName'] ?? '',
|
||||
'pushType' => $pushTypeText,
|
||||
'pushTypeCode' => $pushTypeCode,
|
||||
'targetCount' => $targetCount,
|
||||
'successCount' => $successCount,
|
||||
'failCount' => $failCount,
|
||||
'status' => $itemStatus,
|
||||
'statusText' => $this->getStatusText($itemStatus),
|
||||
'createTime' => date('Y-m-d H:i:s', $item['createTime']),
|
||||
'contentLibraryName' => $item['contentLibraryName'] ?? ''
|
||||
];
|
||||
}
|
||||
unset($item);
|
||||
|
||||
// 处理未执行的任务
|
||||
foreach ($allTasks as $task) {
|
||||
$taskWorkbenchId = $task['workbenchId'];
|
||||
|
||||
// 如果该任务已有执行记录,跳过(避免重复)
|
||||
if (in_array($taskWorkbenchId, $executedWorkbenchIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetType = isset($task['targetType']) ? intval($task['targetType']) : 1;
|
||||
$groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1;
|
||||
|
||||
// 计算目标数量(从配置中获取)
|
||||
$targetCount = 0;
|
||||
if ($targetType == 1) {
|
||||
// 群推送:统计配置的群数量
|
||||
$groups = json_decode($task['groups'] ?? '[]', true);
|
||||
$targetCount = is_array($groups) ? count($groups) : 0;
|
||||
} else {
|
||||
// 好友推送:统计配置的好友数量或流量池数量
|
||||
$friends = json_decode($task['friends'] ?? '[]', true);
|
||||
$trafficPools = json_decode($task['trafficPools'] ?? '[]', true);
|
||||
$friendCount = is_array($friends) ? count($friends) : 0;
|
||||
$poolCount = is_array($trafficPools) ? count($trafficPools) : 0;
|
||||
// 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计)
|
||||
$targetCount = $friendCount > 0 ? $friendCount : $poolCount;
|
||||
}
|
||||
|
||||
// 推送类型判断
|
||||
$pushTypeText = '';
|
||||
$pushTypeCode = '';
|
||||
if ($targetType == 1) {
|
||||
// 群推送
|
||||
if ($groupPushSubType == 2) {
|
||||
$pushTypeText = '群公告';
|
||||
$pushTypeCode = 'announcement';
|
||||
} else {
|
||||
$pushTypeText = '群消息';
|
||||
$pushTypeCode = 'group';
|
||||
}
|
||||
} else {
|
||||
// 好友推送
|
||||
$pushTypeText = '好友消息';
|
||||
$pushTypeCode = 'friend';
|
||||
}
|
||||
|
||||
$resultList[] = [
|
||||
'workbenchId' => $taskWorkbenchId,
|
||||
'taskName' => $task['workbenchName'] ?? '',
|
||||
'pushType' => $pushTypeText,
|
||||
'pushTypeCode' => $pushTypeCode,
|
||||
'targetCount' => $targetCount,
|
||||
'successCount' => 0,
|
||||
'failCount' => 0,
|
||||
'status' => 'pending',
|
||||
'statusText' => '进行中',
|
||||
'createTime' => date('Y-m-d H:i:s', $task['createTime']),
|
||||
'contentLibraryName' => ''
|
||||
];
|
||||
}
|
||||
|
||||
// 应用筛选条件
|
||||
$filteredList = [];
|
||||
foreach ($resultList as $item) {
|
||||
// 推送类型筛选
|
||||
if (!empty($pushType)) {
|
||||
if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') {
|
||||
continue;
|
||||
}
|
||||
if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') {
|
||||
continue;
|
||||
}
|
||||
if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (!empty($status)) {
|
||||
if ($status === 'success' && $item['status'] !== 'success') {
|
||||
continue;
|
||||
}
|
||||
if ($status === 'progress') {
|
||||
// 进行中:包括 partial 和 pending
|
||||
if ($item['status'] !== 'partial' && $item['status'] !== 'pending') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ($status === 'failed' && $item['status'] !== 'failed') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$filteredList[] = $item;
|
||||
}
|
||||
|
||||
// 按创建时间倒序排序
|
||||
usort($filteredList, function($a, $b) {
|
||||
return strtotime($b['createTime']) - strtotime($a['createTime']);
|
||||
});
|
||||
|
||||
// 分页处理
|
||||
$total = count($filteredList);
|
||||
$offset = ($page - 1) * $limit;
|
||||
$list = array_slice($filteredList, $offset, $limit);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
@@ -2967,5 +3101,21 @@ class WorkbenchController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
* @param string $status 状态码
|
||||
* @return string 状态文本
|
||||
*/
|
||||
private function getStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
'success' => '已完成',
|
||||
'partial' => '进行中',
|
||||
'pending' => '进行中',
|
||||
'failed' => '失败'
|
||||
];
|
||||
return $statusMap[$status] ?? '未知';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
2301
Server/sql.sql
Normal file
2301
Server/sql.sql
Normal file
File diff suppressed because it is too large
Load Diff
58
Server/微信健康分规则v2.md
Normal file
58
Server/微信健康分规则v2.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 微信健康分规则 v2
|
||||
|
||||
## 一、定义
|
||||
|
||||
当客户收到手机设备后,登录了微信号,我们将对其微信号进行健康分的评估。
|
||||
|
||||
**健康分 = 基础分 + 动态分**
|
||||
|
||||
健康分只与系统中的"每日自动添加好友次数"这个功能相关联。\
|
||||
通过健康分体系来定义一个微信号每日**最佳、最稳定的添加次数**。\
|
||||
后期还可将健康分作为标签属性,用于快速筛选微信号。
|
||||
|
||||
**公式:每日最大加人次数 = 健康分 × 0.2**
|
||||
|
||||
## 二、基础分
|
||||
|
||||
基础分为 **60--100 分**。
|
||||
|
||||
由 `60 + 40(基础加成分)` 四个维度参数组成,每个参数具有不同权重。
|
||||
|
||||
### 基础分组成
|
||||
|
||||
类型 权重 分数
|
||||
------------ ------ ------
|
||||
基础信息 0.2 10
|
||||
好友数量 0.3 30
|
||||
默认基础分 --- 60
|
||||
|
||||
### 1. 基础信息(权重 0.2,满分 10)
|
||||
|
||||
类型 权重 分数
|
||||
-------------- ------ ------
|
||||
已修改微信号 1 10
|
||||
|
||||
### 2. 好友数量(权重 0.3,满分 30)
|
||||
|
||||
好友数量范围 权重 分数
|
||||
-------------- ------ ------
|
||||
0--50 0.1 3
|
||||
51--500 0.2 6
|
||||
501--3000 0.3 8
|
||||
3001 以上 0.4 12
|
||||
|
||||
## 三、动态分规则
|
||||
|
||||
### 扣分规则
|
||||
|
||||
场景 扣分 处罚
|
||||
---------- ------ --------------
|
||||
首次频繁 15 暂停 24 小时
|
||||
再次频繁 25 暂停 24 小时
|
||||
封号 60 暂停 72 小时
|
||||
|
||||
### 加分规则
|
||||
|
||||
场景 加分
|
||||
--------------------- ------
|
||||
连续 3 天不触发频繁 5/日
|
||||
Reference in New Issue
Block a user