KTVSingle/app/Jobs/ImportSongChunkJob.php

181 lines
6.8 KiB
PHP
Raw Normal View History

2025-06-17 11:38:46 +08:00
<?php
namespace App\Jobs;
use App\Models\Song;
use App\Models\Artist;
use App\Models\SongCategory;
use App\Models\SongLibraryCache;
2025-06-17 11:38:46 +08:00
use App\Enums\ArtistCategory;
use App\Enums\SongLanguageType;
use App\Enums\SongSituation;
use App\Helpers\ChineseNameConverter;
use App\Helpers\ChineseStrokesConverter;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ImportSongChunkJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected Collection $rows;
protected string $id;
2025-06-17 11:38:46 +08:00
protected array $categoryMap = [];
protected array $artistCache = [];
2025-06-17 11:38:46 +08:00
public function __construct(Collection $rows, string $id)
2025-06-17 11:38:46 +08:00
{
$this->rows = $rows;
$this->id = $id;
$this->categoryMap = SongCategory::pluck('id', 'code')->toArray();
}
public function handle(): void
{
Log::warning('匯入啟動', ['model' => "ImportSongChunkJob", 'rows_id' => $this->id]);
2025-06-17 11:38:46 +08:00
$ToInsert = [];
$songIdMap = [];
2025-06-17 11:38:46 +08:00
$artistMap = [];
$categoryMap = [];
2025-06-17 11:38:46 +08:00
$pMap = [
'\\\\SVR01\\DISK01\\' => 'DISK01\\',
'\\\\SVR01\\DISK02\\' => 'DISK02\\',
'\\\\SVR01\\DISK03\\' => 'DISK03\\',
'\\\\SVR01\\DISK04\\' => 'DISK04\\',
'\\\\SVR01\\DISK05\\' => 'DISK05\\',
'\\\\SVR01\\DISK06\\' => 'DISK06\\',
'\\\\SVR01\\DISK07\\' => 'DISK07\\',
'\\\\SVR01\\DISK08\\' => 'DISK08\\',
'\\\\SVR01\\DISK09\\' => 'DISK09\\',
];
2025-06-17 11:38:46 +08:00
foreach ($this->rows as $index => $row) {
$songId = trim($row['編號'] ?? '');
if (!$songId || Song::where('id', $songId)->exists()) continue;
2025-06-17 11:38:46 +08:00
try {
$songName = $this->normalizeName($row['歌名'] ?? '');
$simplified = ChineseNameConverter::convertToSimplified($songName);
$phoneticAbbr = $row->has('注音') ? trim($row['注音'] ?? '') : ChineseNameConverter::getKTVZhuyinAbbr($simplified);
$pinyinAbbr = $row->has('拼音') ? trim($row['拼音'] ?? '') : ChineseNameConverter::getKTVPinyinAbbr($simplified);
$strokesAbbr = $row->has('kk3')
? trim($row['kk3'] ?? 0)
: (preg_match('/\p{Han}/u', $songName[0] ?? '') ? ChineseStrokesConverter::getStrokes($songName[0]) : 0);
$songNumber = $row->has('kk4')
? trim($row['kk4'] ?? 0)
: mb_strlen($songName, 'UTF-8');
$disk = $pMap[trim($row['路徑01'] ?? '')] ?? '';
2025-06-17 11:38:46 +08:00
$filename = trim($row['檔名'] ?? '');
2025-06-17 11:38:46 +08:00
$ToInsert[] = [
'id' => $songId,
'name' => $this->formatText($row['歌名']),
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
'filename' => $disk . $filename,
'language_type' => SongLanguageType::fromLabelOrName($row['語別'] ?? ''),
'db_change' => trim($row['kk2'] ?? 0),
'vocal' => trim($row['kk6'] ?? 0),
'situation' => SongSituation::tryFrom(trim($row['kk7'] ?? '')) ?? SongSituation::Unset,
2025-06-17 11:38:46 +08:00
'copyright01' => trim($row['版權01'] ?? ''),
'copyright02' => trim($row['版權02'] ?? ''),
'note01' => trim($row['版權03'] ?? ''),
'note02' => trim($row['版權04'] ?? ''),
'note03' => trim($row['版權05'] ?? ''),
'note04' => trim($row['版權06'] ?? ''),
'enable' => trim($row['狀態'] ?? 1),
'simplified' => $simplified,
'phonetic_abbr' => $phoneticAbbr,
'pinyin_abbr' => $pinyinAbbr,
'strokes_abbr' => $strokesAbbr,
'song_number' => $songNumber,
'song_counts' => trim($row['點播次數'] ?? 0),
];
foreach (['歌星A', '歌星B'] as $key) {
$artistName = $this->normalizeName($row[$key] ?? '');
if ($artistName === '' || ($key === '歌星B' && $artistName === $this->normalizeName($row['歌星A'] ?? ''))) continue;
2025-06-17 11:38:46 +08:00
$artistMap[$songId][] = $this->getOrCreateArtistId($artistName);
}
if (!empty($row['分類'])) {
foreach (explode(',', $row['分類']) as $code) {
2025-06-17 11:38:46 +08:00
$code = trim($code);
if (isset($this->categoryMap[$code])) {
$categoryMap[$songId][] = $this->categoryMap[$code];
}
}
}
$songIdMap[] = $songId;
2025-06-17 11:38:46 +08:00
} catch (\Throwable $e) {
Log::error("Row {$index} failed: {$e->getMessage()}", [
2025-06-17 11:38:46 +08:00
'row' => $row,
'trace' => $e->getTraceAsString()
]);
}
}
// ✅ 批次插入
collect($ToInsert)->chunk(500)->each(fn($chunk) => Song::insert($chunk->toArray()));
// ✅ 一次撈資料並同步
$songs = Song::whereIn('id', $songIdMap)->get()->keyBy('id');
foreach ($songs as $songId => $song) {
if (isset($artistMap[$songId])) {
$song->artists()->sync($artistMap[$songId]);
2025-06-17 11:38:46 +08:00
}
if (isset($categoryMap[$songId])) {
$song->categories()->sync($categoryMap[$songId]);
2025-06-17 11:38:46 +08:00
}
SongLibraryCache::syncFromSong($song);
2025-06-17 11:38:46 +08:00
}
}
2025-06-17 11:38:46 +08:00
private function getOrCreateArtistId(string $name): int
{
if (isset($this->artistCache[$name])) {
return $this->artistCache[$name];
}
$artist = Artist::firstOrCreate(['name' => $name], ['category' => ArtistCategory::Unset]);
2025-06-17 11:38:46 +08:00
return $this->artistCache[$name] = $artist->id;
}
private function normalizeName(?string $str): string
2025-06-17 11:38:46 +08:00
{
return strtoupper(mb_convert_kana(trim($str ?? ''), 'as'));
}
protected function formatText($value)
{
if (is_numeric($value) && $value < 1 && $value > 0) {
return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)->format('H:i');
2025-06-17 11:38:46 +08:00
}
return trim((string)$value);
2025-06-17 11:38:46 +08:00
}
2025-06-17 11:38:46 +08:00
private function parseExcelDate($value): ?string
{
if (is_numeric($value)) {
return \Carbon\Carbon::createFromFormat('Y-m-d', '1900-01-01')->addDays((int)$value - 2)->format('Y-m-d');
2025-06-17 11:38:46 +08:00
}
try {
return \Carbon\Carbon::parse($value)->format('Y-m-d');
} catch (\Exception) {
2025-06-17 11:38:46 +08:00
return null;
}
}
}