客服健康分提交

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

@@ -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);
}
}