Files
chatroom/app/Console/Commands/PurgeOldMessages.php
T

180 lines
6.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:定期清理聊天记录 Artisan 命令
*
* 用户聊天记录永久保留;仅清理可过期的游戏通知、进出播报等噪音消息。
* 通知保留天数可通过 sysparam 表的 game_message_retention_days 配置,默认 30 天。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Console\Commands;
use App\Models\Message;
use App\Models\Sysparam;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
/**
* 定期清理聊天记录命令
* 负责删除过期文本消息,并额外回收聊天图片文件。
*/
class PurgeOldMessages extends Command
{
/**
* 命令签名
*
* @var string
*/
protected $signature = 'messages:purge
{--days= : 覆盖通知消息默认保留天数}
{--image-days=3 : 聊天图片单独保留天数}
{--dry-run : 仅预览不实际删除}';
/**
* 命令描述
*
* @var string
*/
protected $description = '清理过期游戏/临时通知,并额外清理 3 天前的聊天图片文件';
/**
* 执行命令
*
* 按批次删除旧消息,避免长时间锁表。
*/
public function handle(): int
{
// 通知保留天数:命令行参数 > sysparam 配置 > 默认 30 天;普通用户聊天不再按时间删除。
$days = (int) ($this->option('days')
?: Sysparam::getValue('game_message_retention_days', '30'));
$imageDays = max(0, (int) $this->option('image-days'));
$cutoff = Carbon::now()->subDays($days);
$isDryRun = $this->option('dry-run');
$this->cleanupExpiredImages($imageDays, $isDryRun);
$expiredNoticeQuery = $this->expiredNoticeQuery($cutoff);
$totalCount = (clone $expiredNoticeQuery)->count();
if ($totalCount === 0) {
$this->info("✅ 没有超过 {$days} 天的游戏/临时通知需要清理,用户聊天记录已永久保留。");
return self::SUCCESS;
}
if ($isDryRun) {
$this->warn("🔍 [预览模式] 将删除 {$totalCount} 条超过 {$days} 天的游戏/临时通知(截止 {$cutoff->toDateTimeString()}");
return self::SUCCESS;
}
$this->info("🧹 开始清理超过 {$days} 天的游戏/临时通知(截止 {$cutoff->toDateTimeString()}...");
$this->info(" 待清理数量:{$totalCount}");
// 分批删除,每批 1000 条,避免长时间锁表
$deleted = 0;
$batchSize = 1000;
do {
$batch = $this->expiredNoticeQuery($cutoff)
->limit($batchSize)
->delete();
$deleted += $batch;
if ($batch > 0) {
$this->line(" 已删除 {$deleted}/{$totalCount} 条...");
}
} while ($batch === $batchSize);
$this->info("✅ 清理完成!共删除 {$deleted} 条游戏/临时通知,用户聊天记录未删除。");
return self::SUCCESS;
}
/**
* 构造过期通知清理查询,兼容新增字段前已经落库的旧通知。
*/
private function expiredNoticeQuery(Carbon $cutoff): \Illuminate\Database\Eloquent\Builder
{
return Message::query()
->where('sent_at', '<', $cutoff)
->where(function ($query) {
$query->whereIn('retention_type', Message::purgableRetentionTypes())
->orWhere(function ($legacyQuery) {
// 兼容迁移前默认归为 user_chat 的旧通知,避免历史游戏播报继续堆积。
$legacyQuery->where('retention_type', Message::RETENTION_USER_CHAT)
->where(function ($noticeQuery) {
$noticeQuery->whereIn('from_user', ['钓鱼播报', '星海小博士', '进出播报', '座驾播报'])
->orWhereIn('action', ['fishing_result', 'idiom_result', 'riddle_result', 'system_welcome', 'vip_presence', 'ride_presence', 'auto_save_exp']);
});
});
});
}
/**
* 清理超过图片保留天数的聊天图片文件,并把消息改成过期占位。
*/
private function cleanupExpiredImages(int $imageDays, bool $isDryRun): void
{
$imageCutoff = Carbon::now()->subDays($imageDays);
$query = Message::query()
->where('message_type', 'image')
->where('sent_at', '<', $imageCutoff)
->where(function ($builder) {
$builder->whereNotNull('image_path')->orWhereNotNull('image_thumb_path');
});
$totalCount = (clone $query)->count();
if ($totalCount === 0) {
$this->line("🖼️ 没有超过 {$imageDays} 天的聊天图片需要清理。");
return;
}
if ($isDryRun) {
$this->warn("🔍 [预览模式] 将清理 {$totalCount} 条超过 {$imageDays} 天的聊天图片(截止 {$imageCutoff->toDateTimeString()}");
return;
}
$processed = 0;
$query->orderBy('id')->chunkById(200, function ($messages) use (&$processed) {
foreach ($messages as $message) {
$paths = array_values(array_filter([
$message->image_path,
$message->image_thumb_path,
]));
// 先删物理文件,再把数据库消息降级成“图片已过期”占位,避免出现坏图。
if ($paths !== []) {
Storage::disk('public')->delete($paths);
}
$placeholder = trim((string) $message->content);
$placeholder = $placeholder !== '' ? $placeholder.' [图片已过期]' : '[图片已过期]';
$message->forceFill([
'content' => $placeholder,
'message_type' => 'expired_image',
'image_path' => null,
'image_thumb_path' => null,
'image_original_name' => null,
])->save();
$processed++;
}
});
$this->info("🖼️ 已清理 {$processed} 条超过 {$imageDays} 天的聊天图片。");
}
}