Files
laralog/app/Blog/Services/AttachmentImporter.php
T
gouki 13f017e806
Build and Deploy / verify (push) Successful in 31s
Build and Deploy / deploy (push) Successful in 28s
fix(import): preserve original attachment filepath structure; enable admin SPA mode
2026-10-11 19:31:11 +00:00

139 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Blog\Services;
use App\Models\Post;
use App\Support\MediaDisk;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class AttachmentImporter
{
/**
* 将一条 sablog 附件记录写入媒体库。
*
* @param array{attachmentid:int,articleid:int,dateline:int,filename:string,filetype:string,filesize:int,downloads:int,filepath:string,thumb_filepath:string,thumb_width:int,thumb_height:int,isimage:int} $row
*/
public function create(int $postId, array $row, ?string $attachmentsDir, bool $syncNow): Media
{
$legacyId = (int) ($row['attachmentid'] ?? 0);
// 幂等:同一老附件已成功导入过则直接复用
$existing = Media::query()
->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;
}
}