getRawOriginal('msg'));
$attachments = self::extract((string)$message->type, $messageData);
if (empty($attachments) && !$removeMissing) {
return 0;
}
DB::transaction(function () use ($message, $attachments) {
if (empty($attachments)) {
WebSocketDialogMsgAttachment::whereMsgId($message->id)->delete();
return;
}
$now = now();
$sourceType = $attachments[0]['source_type'];
$positions = [];
$rows = [];
foreach ($attachments as $attachment) {
$positions[] = $attachment['position'];
$rows[] = array_merge($attachment, [
'msg_id' => intval($message->id),
'dialog_id' => intval($message->dialog_id),
'created_at' => $message->created_at ?: $now,
'updated_at' => $now,
]);
}
DB::table('web_socket_dialog_msg_attachments')->upsert(
$rows,
['msg_id', 'source_type', 'position'],
['dialog_id', 'kind', 'name', 'ext', 'path', 'thumb', 'size', 'width', 'height', 'updated_at']
);
WebSocketDialogMsgAttachment::whereMsgId($message->id)
->where(function ($query) use ($sourceType, $positions) {
$query->where('source_type', '!=', $sourceType)
->orWhereNotIn('position', $positions);
})
->delete();
});
return count($attachments);
}
/**
* 双写失败不影响聊天主链路,历史回填命令可再次同步失败消息。
*/
public static function syncSafely(WebSocketDialogMsg $message, bool $removeMissing = false): bool
{
if (!in_array($message->type, ['file', 'text'], true)) {
return true;
}
try {
self::sync($message, $removeMissing);
return true;
} catch (\Throwable $e) {
Log::warning('Message attachment sync failed', [
'msg_id' => intval($message->id),
'dialog_id' => intval($message->dialog_id),
'message' => $e->getMessage(),
]);
return false;
}
}
private static function extractFileMessage(array $messageData): ?array
{
$path = self::normalizeStoredPath((string)($messageData['path'] ?? ''));
if ($path === '') {
return null;
}
$ext = strtolower((string)($messageData['ext'] ?? self::pathExtension($path)));
$thumb = self::normalizeStoredPath((string)($messageData['thumb'] ?? ''));
return [
'source_type' => WebSocketDialogMsgAttachment::SOURCE_FILE_MESSAGE,
'kind' => in_array($ext, File::imageExt, true)
? WebSocketDialogMsgAttachment::KIND_IMAGE
: WebSocketDialogMsgAttachment::KIND_FILE,
'position' => 0,
'name' => (string)($messageData['name'] ?? ''),
'ext' => $ext,
'path' => $path,
'thumb' => $thumb,
'size' => max(0, intval($messageData['size'] ?? 0)),
'width' => max(0, intval($messageData['width'] ?? 0)),
'height' => max(0, intval($messageData['height'] ?? 0)),
];
}
private static function extractInlineImages(string $html): array
{
if ($html === '') {
return [];
}
$attachments = [];
if (str_contains($html, 'loadHTML(
'