where('collection_name', 'attachments') ->where('custom_properties->legacy_attachmentid', $legacyId) ->latest('id') ->first(); if ($existing && ! $existing->getCustomProperty('pending_sync')) { return $existing; } $post = Post::find($postId); // 游离附件(articleid=0 或对应文章不存在)统一挂到 id=0 占位文章上 if (! $post) { $post = new Post(['id' => 0]); } $properties = [ 'downloads' => (int) ($row['downloads'] ?? 0), 'isimage' => (bool) ($row['isimage'] ?? false), 'legacy_attachmentid' => (int) ($row['attachmentid'] ?? 0), 'legacy_filepath' => $row['filepath'] ?? null, 'legacy_thumb' => $row['thumb_filepath'] ?? null, 'legacy_articleid' => (int) ($row['articleid'] ?? 0), 'pending_sync' => true, ]; $source = $this->locateSource($row['filepath'] ?? null, $attachmentsDir); if ($source && $syncNow) { try { $disk = MediaDisk::name(); $filename = basename($row['filename']) ?: basename($source); $legacyPath = ltrim($row['filepath'] ?? '', '/'); // 保留原始目录结构:date_200802/xxx.jpg if ($legacyPath) { $legacyDir = dirname($legacyPath); $storagePath = ($legacyDir !== '.' ? $legacyDir . '/' : '') . $filename; } else { $storagePath = $filename; } // 直接写文件到目标存储,不走 spatie 的自动路径生成 $content = file_get_contents($source); \Illuminate\Support\Facades\Storage::disk($disk)->put($storagePath, $content, ['visibility' => 'public']); // 手动创建 media 记录 $media = new Media; $media->model_type = Post::class; $media->model_id = $post->id; $media->uuid = (string) Str::uuid(); $media->collection_name = 'attachments'; $media->name = pathinfo($filename, PATHINFO_FILENAME) ?: 'attachment'; $media->file_name = $filename; $media->mime_type = $row['filetype'] ?: null; $media->size = (int) ($row['filesize'] ?? 0); $media->disk = $disk; $media->conversions_disk = $disk; $media->manipulations = []; $media->custom_properties = $properties; $media->generated_conversions = []; $media->responsive_images = []; $media->save(); return $media; } catch (\Throwable $e) { // 文件损坏无法入库时降级为 pending,不中断整批导入 \Illuminate\Support\Facades\Log::warning('附件导入失败,已降级为待同步', [ 'attachment' => $row['attachmentid'] ?? null, 'file' => $source, 'error' => $e->getMessage(), ]); } } return $this->createPendingRecord($post->id, $row, $properties); } private function locateSource(?string $filepath, ?string $attachmentsDir): ?string { if (! $attachmentsDir || ! $filepath) { return null; } $candidate = rtrim($attachmentsDir, '/').'/'.ltrim($filepath, '/'); return is_file($candidate) ? $candidate : null; } private function createPendingRecord(int $postId, array $row, array $properties): Media { $disk = MediaDisk::name(); $media = new Media; $media->model_type = Post::class; $media->model_id = $postId; $media->uuid = (string) Str::uuid(); $media->collection_name = 'attachments'; $media->name = pathinfo($row['filename'] ?? 'attachment', PATHINFO_FILENAME) ?: 'attachment'; $media->file_name = $row['filename'] ?: basename($row['filepath'] ?? 'attachment.bin'); $media->mime_type = $row['filetype'] ?: null; $media->size = (int) ($row['filesize'] ?? 0); $media->disk = $disk; $media->conversions_disk = $disk; $media->manipulations = []; $media->custom_properties = $properties; $media->generated_conversions = []; $media->responsive_images = []; $media->save(); return $media; } }