You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
6.1 KiB

6 days ago
<?php
namespace App\Console\Commands;
use App\Models\Activity;
use App\Models\StudyTour;
use App\Models\Venue;
use Illuminate\Console\Command;
/**
* 将库内已保存的绝对资源 URL如旧测试域名 http批量改为当前正式域名 https。
* 部署后请确保生产 .env 中 APP_URL=https://szkp-map.langye.net避免新上传仍带旧域名。
*/
class RewriteStorageMediaUrlsCommand extends Command
{
protected $signature = 'media:rewrite-domain
{--from=http://szkp-map.ali251.langye.net : 旧地址前缀(含协议)}
{--to=https://szkp-map.langye.net : 新地址前缀(含协议)}
{--dry-run : 只报告将修改的记录数,不写库}';
protected $description = '批量替换 venues / activities / study_tours 等媒体字段中的存储域名';
public function handle(): int
{
$from = (string) $this->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<string, mixed> $data
* @return array<string, mixed>
*/
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;
}
}