新增聊天室成就系统与消息保留策略
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
/**
|
||||
* 文件功能:定期清理聊天记录 Artisan 命令
|
||||
*
|
||||
* 每天自动清理超过指定天数的聊天记录,保持数据库体积可控。
|
||||
* 保留天数可通过 sysparam 表的 message_retention_days 配置,默认 30 天。
|
||||
* 用户聊天记录永久保留;仅清理可过期的游戏通知、进出播报等噪音消息。
|
||||
* 通知保留天数可通过 sysparam 表的 game_message_retention_days 配置,默认 30 天。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
@@ -31,7 +31,7 @@ class PurgeOldMessages extends Command
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'messages:purge
|
||||
{--days= : 覆盖默认保留天数}
|
||||
{--days= : 覆盖通知消息默认保留天数}
|
||||
{--image-days=3 : 聊天图片单独保留天数}
|
||||
{--dry-run : 仅预览不实际删除}';
|
||||
|
||||
@@ -40,7 +40,7 @@ class PurgeOldMessages extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '清理过期聊天记录,并额外清理 3 天前的聊天图片文件';
|
||||
protected $description = '清理过期游戏/临时通知,并额外清理 3 天前的聊天图片文件';
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
@@ -49,9 +49,9 @@ class PurgeOldMessages extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// 保留天数:命令行参数 > sysparam 配置 > 默认 30 天
|
||||
// 通知保留天数:命令行参数 > sysparam 配置 > 默认 30 天;普通用户聊天不再按时间删除。
|
||||
$days = (int) ($this->option('days')
|
||||
?: Sysparam::getValue('message_retention_days', '30'));
|
||||
?: Sysparam::getValue('game_message_retention_days', '30'));
|
||||
$imageDays = max(0, (int) $this->option('image-days'));
|
||||
|
||||
$cutoff = Carbon::now()->subDays($days);
|
||||
@@ -59,22 +59,22 @@ class PurgeOldMessages extends Command
|
||||
|
||||
$this->cleanupExpiredImages($imageDays, $isDryRun);
|
||||
|
||||
// 统计待清理数量
|
||||
$totalCount = Message::where('sent_at', '<', $cutoff)->count();
|
||||
$expiredNoticeQuery = $this->expiredNoticeQuery($cutoff);
|
||||
$totalCount = (clone $expiredNoticeQuery)->count();
|
||||
|
||||
if ($totalCount === 0) {
|
||||
$this->info("✅ 没有超过 {$days} 天的聊天记录需要清理。");
|
||||
$this->info("✅ 没有超过 {$days} 天的游戏/临时通知需要清理,用户聊天记录已永久保留。");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($isDryRun) {
|
||||
$this->warn("🔍 [预览模式] 将删除 {$totalCount} 条超过 {$days} 天的聊天记录(截止 {$cutoff->toDateTimeString()})");
|
||||
$this->warn("🔍 [预览模式] 将删除 {$totalCount} 条超过 {$days} 天的游戏/临时通知(截止 {$cutoff->toDateTimeString()})");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info("🧹 开始清理超过 {$days} 天的聊天记录(截止 {$cutoff->toDateTimeString()})...");
|
||||
$this->info("🧹 开始清理超过 {$days} 天的游戏/临时通知(截止 {$cutoff->toDateTimeString()})...");
|
||||
$this->info(" 待清理数量:{$totalCount} 条");
|
||||
|
||||
// 分批删除,每批 1000 条,避免长时间锁表
|
||||
@@ -82,7 +82,7 @@ class PurgeOldMessages extends Command
|
||||
$batchSize = 1000;
|
||||
|
||||
do {
|
||||
$batch = Message::where('sent_at', '<', $cutoff)
|
||||
$batch = $this->expiredNoticeQuery($cutoff)
|
||||
->limit($batchSize)
|
||||
->delete();
|
||||
|
||||
@@ -93,11 +93,31 @@ class PurgeOldMessages extends Command
|
||||
}
|
||||
} while ($batch === $batchSize);
|
||||
|
||||
$this->info("✅ 清理完成!共删除 {$deleted} 条聊天记录。");
|
||||
$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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理超过图片保留天数的聊天图片文件,并把消息改成过期占位。
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:扫描并补算用户成就的 Artisan 命令。
|
||||
*
|
||||
* 支持单用户、全量与最近活跃用户三种扫描方式,便于定时任务和后台补算复用。
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\AchievementService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* 类功能:通过命令行批量检查用户成就进度并写入解锁记录。
|
||||
*/
|
||||
class ScanAchievementsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* 命令签名。
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'achievements:scan
|
||||
{--user= : 指定用户 ID 或用户名}
|
||||
{--all : 扫描全部用户}
|
||||
{--notify : 解锁时向用户推送本人可见通知}
|
||||
{--dry-run : 仅预览,不写入成就记录}';
|
||||
|
||||
/**
|
||||
* 命令描述。
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '扫描聊天室用户成就进度并补齐解锁记录';
|
||||
|
||||
/**
|
||||
* 创建命令依赖。
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly AchievementService $achievementService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行成就扫描命令。
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$notify = (bool) $this->option('notify');
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
if ($this->option('user')) {
|
||||
$user = $this->resolveUser((string) $this->option('user'));
|
||||
if (! $user) {
|
||||
$this->error('未找到指定用户。');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$result = $this->achievementService->scanUser($user, $notify, $dryRun);
|
||||
$this->info("已扫描用户 {$user->username}:检查 {$result['checked']} 项,解锁 {$result['unlocked']} 项,更新 {$result['updated']} 项。");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$query = User::query()->orderBy('id');
|
||||
if (! $this->option('all')) {
|
||||
// 默认只扫最近活跃用户,避免定时任务每次全表扫描。
|
||||
$query->where('updated_at', '>=', now()->subDay())->limit(200);
|
||||
}
|
||||
|
||||
$summary = ['users' => 0, 'checked' => 0, 'unlocked' => 0, 'updated' => 0, 'dry_run' => $dryRun];
|
||||
$query->chunkById(100, function ($users) use (&$summary, $notify, $dryRun): void {
|
||||
$chunkSummary = $this->achievementService->scanUsers($users, $notify, $dryRun);
|
||||
$summary['users'] += $chunkSummary['users'];
|
||||
$summary['checked'] += $chunkSummary['checked'];
|
||||
$summary['unlocked'] += $chunkSummary['unlocked'];
|
||||
$summary['updated'] += $chunkSummary['updated'];
|
||||
});
|
||||
|
||||
$this->info("成就扫描完成:用户 {$summary['users']} 人,检查 {$summary['checked']} 项,解锁 {$summary['unlocked']} 项,更新 {$summary['updated']} 项。");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 或用户名解析用户。
|
||||
*/
|
||||
private function resolveUser(string $value): ?User
|
||||
{
|
||||
return User::query()
|
||||
->when(is_numeric($value), fn ($query) => $query->where('id', (int) $value), fn ($query) => $query->where('username', $value))
|
||||
->first();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user