2026-02-26 13:35:38 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 文件功能:聊天消息模型
|
|
|
|
|
*
|
|
|
|
|
* 对应原 ASP 文件:内存 Application("_says") 变量 (用数据库做持久化归档)
|
|
|
|
|
*
|
|
|
|
|
* @author ChatRoom Laravel
|
|
|
|
|
*
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
|
2026-04-12 14:04:18 +08:00
|
|
|
/**
|
|
|
|
|
* 聊天消息模型
|
|
|
|
|
* 负责承载聊天室文本消息、图片消息与过期图片占位消息。
|
|
|
|
|
*/
|
2026-02-26 13:35:38 +08:00
|
|
|
class Message extends Model
|
|
|
|
|
{
|
2026-04-30 16:19:49 +08:00
|
|
|
public const RETENTION_USER_CHAT = 'user_chat';
|
|
|
|
|
|
|
|
|
|
public const RETENTION_SYSTEM_NOTICE = 'system_notice';
|
|
|
|
|
|
|
|
|
|
public const RETENTION_GAME_NOTICE = 'game_notice';
|
|
|
|
|
|
|
|
|
|
public const RETENTION_EPHEMERAL_NOTICE = 'ephemeral_notice';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 可按过期策略清理的消息保留类型。
|
|
|
|
|
*
|
|
|
|
|
* @return array<int, string>
|
|
|
|
|
*/
|
|
|
|
|
public static function purgableRetentionTypes(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
self::RETENTION_GAME_NOTICE,
|
|
|
|
|
self::RETENTION_EPHEMERAL_NOTICE,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据广播消息载荷推断数据库保留类型。
|
|
|
|
|
*
|
|
|
|
|
* @param array<string, mixed> $messageData 聊天室消息载荷
|
|
|
|
|
*/
|
|
|
|
|
public static function resolveRetentionType(array $messageData): string
|
|
|
|
|
{
|
|
|
|
|
$explicitType = (string) ($messageData['retention_type'] ?? '');
|
|
|
|
|
if (in_array($explicitType, [
|
|
|
|
|
self::RETENTION_USER_CHAT,
|
|
|
|
|
self::RETENTION_SYSTEM_NOTICE,
|
|
|
|
|
self::RETENTION_GAME_NOTICE,
|
|
|
|
|
self::RETENTION_EPHEMERAL_NOTICE,
|
|
|
|
|
], true)) {
|
|
|
|
|
return $explicitType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fromUser = (string) ($messageData['from_user'] ?? '');
|
|
|
|
|
$action = (string) ($messageData['action'] ?? '');
|
|
|
|
|
$messageType = (string) ($messageData['message_type'] ?? 'text');
|
|
|
|
|
|
|
|
|
|
if (self::isEphemeralNotice($fromUser, $action)) {
|
|
|
|
|
return self::RETENTION_EPHEMERAL_NOTICE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self::isGameNotice($fromUser, $action, $messageType, $messageData)) {
|
|
|
|
|
return self::RETENTION_GAME_NOTICE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self::isSystemNotice($fromUser)) {
|
|
|
|
|
return self::RETENTION_SYSTEM_NOTICE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self::RETENTION_USER_CHAT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断消息是否属于可短期保留的进出场类通知。
|
|
|
|
|
*/
|
|
|
|
|
public static function isEphemeralNotice(string $fromUser, string $action = ''): bool
|
|
|
|
|
{
|
|
|
|
|
return in_array($fromUser, ['进出播报', '座驾播报'], true)
|
|
|
|
|
|| in_array($action, ['system_welcome', 'vip_presence', 'ride_presence', 'auto_save_exp'], true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断消息是否属于游戏或玩法通知。
|
|
|
|
|
*
|
|
|
|
|
* @param array<string, mixed> $messageData 聊天室消息载荷
|
|
|
|
|
*/
|
|
|
|
|
public static function isGameNotice(string $fromUser, string $action, string $messageType = 'text', array $messageData = []): bool
|
|
|
|
|
{
|
|
|
|
|
$gameSenders = ['钓鱼播报', '星海小博士'];
|
|
|
|
|
$gameActions = [
|
|
|
|
|
'fishing_result',
|
|
|
|
|
'idiom_result',
|
|
|
|
|
'riddle_result',
|
|
|
|
|
'ride_purchase',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (in_array($fromUser, $gameSenders, true) || in_array($action, $gameActions, true)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isset($messageData['toast_notification'])) {
|
|
|
|
|
$title = (string) data_get($messageData, 'toast_notification.title', '');
|
|
|
|
|
|
|
|
|
|
return str_contains($title, '下注')
|
|
|
|
|
|| str_contains($title, '赛马')
|
|
|
|
|
|| str_contains($title, '百家乐')
|
|
|
|
|
|| str_contains($title, '双色球')
|
|
|
|
|
|| str_contains($title, '红包')
|
|
|
|
|
|| str_contains($title, '结算');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return in_array($messageType, ['game_notice'], true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断消息是否来自系统发送者。
|
|
|
|
|
*/
|
|
|
|
|
public static function isSystemNotice(string $fromUser): bool
|
|
|
|
|
{
|
|
|
|
|
return in_array($fromUser, [
|
|
|
|
|
'系统',
|
|
|
|
|
'系统公告',
|
|
|
|
|
'系统传音',
|
|
|
|
|
'系统播报',
|
|
|
|
|
'送花播报',
|
|
|
|
|
'AI小班长',
|
|
|
|
|
], true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:35:38 +08:00
|
|
|
/**
|
|
|
|
|
* The attributes that are mass assignable.
|
|
|
|
|
*
|
|
|
|
|
* @var array<int, string>
|
|
|
|
|
*/
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
'room_id',
|
|
|
|
|
'from_user',
|
|
|
|
|
'to_user',
|
|
|
|
|
'content',
|
|
|
|
|
'is_secret',
|
|
|
|
|
'font_color',
|
|
|
|
|
'action',
|
2026-04-12 14:04:18 +08:00
|
|
|
'message_type',
|
|
|
|
|
'image_path',
|
|
|
|
|
'image_thumb_path',
|
|
|
|
|
'image_original_name',
|
2026-04-30 16:19:49 +08:00
|
|
|
'retention_type',
|
2026-02-26 13:35:38 +08:00
|
|
|
'sent_at',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-12 14:04:18 +08:00
|
|
|
* 返回模型字段的类型转换配置。
|
2026-02-26 13:35:38 +08:00
|
|
|
*
|
|
|
|
|
* @return array<string, string>
|
|
|
|
|
*/
|
|
|
|
|
protected function casts(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'sent_at' => 'datetime',
|
|
|
|
|
'is_secret' => 'boolean',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
}
|