From 7457b754b27352f000ef5503d1061e175a591430 Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Tue, 21 Apr 2026 17:53:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RewriteStorageMediaUrlsCommand.php | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 app/Console/Commands/RewriteStorageMediaUrlsCommand.php diff --git a/app/Console/Commands/RewriteStorageMediaUrlsCommand.php b/app/Console/Commands/RewriteStorageMediaUrlsCommand.php new file mode 100644 index 0000000..0341528 --- /dev/null +++ b/app/Console/Commands/RewriteStorageMediaUrlsCommand.php @@ -0,0 +1,207 @@ +option('from'); + $to = (string) $this->option('to'); + $dry = (bool) $this->option('dry-run'); + + if ($from === $to) { + $this->error('--from 与 --to 不能相同。'); + + return self::FAILURE; + } + + $total = 0; + + $total += $this->rewriteVenues($from, $to, $dry); + $total += $this->rewriteActivities($from, $to, $dry); + $total += $this->rewriteStudyTours($from, $to, $dry); + + if ($dry) { + $this->info("[dry-run] 共将更新 {$total} 条记录(未写入数据库)。"); + } else { + $this->info("已更新 {$total} 条记录。"); + } + + return self::SUCCESS; + } + + protected function rewriteVenues(string $from, string $to, bool $dry): int + { + $n = 0; + Venue::query()->orderBy('id')->chunkById(100, function ($venues) use ($from, $to, $dry, &$n) { + foreach ($venues as $venue) { + if ($this->applyVenueOrActivity($venue, $from, $to, $dry)) { + $n++; + } + } + }); + + $this->line("venues: {$n}"); + + return $n; + } + + protected function rewriteActivities(string $from, string $to, bool $dry): int + { + $n = 0; + Activity::query()->orderBy('id')->chunkById(100, function ($activities) use ($from, $to, $dry, &$n) { + foreach ($activities as $activity) { + if ($this->applyVenueOrActivity($activity, $from, $to, $dry)) { + $n++; + } + } + }); + + $this->line("activities: {$n}"); + + return $n; + } + + protected function rewriteStudyTours(string $from, string $to, bool $dry): int + { + $n = 0; + StudyTour::query()->orderBy('id')->chunkById(100, function ($rows) use ($from, $to, $dry, &$n) { + foreach ($rows as $row) { + $dirty = false; + if ($this->replaceStringField($row, 'cover_image', $from, $to)) { + $dirty = true; + } + if ($this->replaceStringField($row, 'intro_html', $from, $to)) { + $dirty = true; + } + if ($this->replaceGalleryMedia($row, $from, $to)) { + $dirty = true; + } + if ($dirty) { + $n++; + if (! $dry) { + $row->save(); + } + } + } + }); + + $this->line("study_tours: {$n}"); + + return $n; + } + + /** @param Venue|Activity $model */ + protected function applyVenueOrActivity(Venue|Activity $model, string $from, string $to, bool $dry): bool + { + $dirty = false; + if ($this->replaceStringField($model, 'cover_image', $from, $to)) { + $dirty = true; + } + if ($this->replaceStringField($model, 'detail_html', $from, $to)) { + $dirty = true; + } + if ($this->replaceGalleryMedia($model, $from, $to)) { + $dirty = true; + } + if ($this->replaceJsonSnapshotField($model, 'last_approved_snapshot', $from, $to)) { + $dirty = true; + } + if ($dirty && ! $dry) { + $model->save(); + } + + return $dirty; + } + + protected function replaceStringField(object $model, string $attr, string $from, string $to): bool + { + $v = $model->{$attr}; + if (! is_string($v) || $v === '') { + return false; + } + $next = str_replace($from, $to, $v); + if ($next === $v) { + return false; + } + $model->{$attr} = $next; + + return true; + } + + protected function replaceGalleryMedia(object $model, string $from, string $to): bool + { + $gm = $model->gallery_media ?? null; + if (! is_array($gm) || $gm === []) { + return false; + } + $changed = false; + foreach ($gm as $i => $item) { + if (! is_array($item)) { + continue; + } + $url = $item['url'] ?? null; + if (is_string($url) && str_contains($url, $from)) { + $gm[$i]['url'] = str_replace($from, $to, $url); + $changed = true; + } + } + if ($changed) { + $model->gallery_media = array_values($gm); + } + + return $changed; + } + + protected function replaceJsonSnapshotField(object $model, string $attr, string $from, string $to): bool + { + $snap = $model->{$attr}; + if ($snap === null || $snap === []) { + return false; + } + if (! is_array($snap)) { + return false; + } + $next = $this->replaceStringsRecursive($snap, $from, $to); + if ($next === $snap) { + return false; + } + $model->{$attr} = $next; + + return true; + } + + /** + * @param array $data + * @return array + */ + protected function replaceStringsRecursive(array $data, string $from, string $to): array + { + foreach ($data as $k => $v) { + if (is_string($v)) { + $data[$k] = str_replace($from, $to, $v); + } elseif (is_array($v)) { + $data[$k] = $this->replaceStringsRecursive($v, $from, $to); + } + } + + return $data; + } +}