From ea0e10bb64d1b782c4157a9fa3bbb8707bfa0636 Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Sat, 10 May 2025 19:41:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=BB=99=E5=85=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20=E8=88=87=E5=AF=AB=E7=9B=B8=E9=97=9C=E8=A8=98?= =?UTF-8?q?=E9=8C=84=20=E4=BF=AE=E6=AD=A3=E6=BB=99=E5=85=A5=E6=99=82?= =?UTF-8?q?=E6=9A=AB=E5=AD=98=E6=AA=94=E4=B8=8D=E6=9C=83=E5=88=AA=E7=9A=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=2020250510?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Imports/DataImport.php | 8 ++- app/Jobs/ImportArtistChunkJob.php | 47 ++++++++++++--- app/Jobs/ImportSongChunkJob.php | 79 ++++++++++++++++++++----- app/Livewire/Admin/ArtistImportData.php | 13 ++-- app/Livewire/Admin/SongImportData.php | 15 ++--- 5 files changed, 117 insertions(+), 45 deletions(-) diff --git a/app/Imports/DataImport.php b/app/Imports/DataImport.php index 223199e..f23ff1f 100644 --- a/app/Imports/DataImport.php +++ b/app/Imports/DataImport.php @@ -13,6 +13,7 @@ use App\Jobs\ImportSongChunkJob; class DataImport implements ToCollection, WithHeadingRow, WithChunkReading { + protected int $con=0; protected string $modelName; public function __construct(string $modelName) { @@ -21,15 +22,16 @@ class DataImport implements ToCollection, WithHeadingRow, WithChunkReading } public function collection(Collection $rows) { + Log::warning('匯入啟動', [ 'model' => $this->modelName, - 'from_row' => $rows->keys()->first() + 2, + 'rows_id' =>++$this->con, 'rows_count' => $rows->count() ]); if($this->modelName=='Song'){ - ImportSongChunkJob::dispatch($rows); + ImportSongChunkJob::dispatch($rows,$this->con); }else if($this->modelName=='Artist'){ - ImportArtistChunkJob::dispatch($rows); + ImportArtistChunkJob::dispatch($rows,$this->con); }else{ Log::warning('未知的 modelName', ['model' => $this->modelName]); } diff --git a/app/Jobs/ImportArtistChunkJob.php b/app/Jobs/ImportArtistChunkJob.php index 902f8f6..422d084 100644 --- a/app/Jobs/ImportArtistChunkJob.php +++ b/app/Jobs/ImportArtistChunkJob.php @@ -4,28 +4,36 @@ namespace App\Jobs; use App\Models\Artist; use App\Enums\ArtistCategory; +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 ImportArtistChunkJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected Collection $rows; + protected String $id; - public function __construct(Collection $rows) + public function __construct(Collection $rows,String $id) { $this->rows = $rows; + $this->id = $id; } public function handle(): void { - $artists = collect(); - + Log::warning('匯入啟動', [ + 'model' => "ImportArtistChunkJob", + 'rows_id' =>$this->id, + ]); + $now = now(); foreach ($this->rows as $index => $row) { try { $name = trim($row['歌手姓名'] ?? ''); @@ -34,13 +42,34 @@ class ImportArtistChunkJob implements ShouldQueue continue; } - $artist = new Artist([ + // 字元處理 + $simplified = ChineseNameConverter::convertToSimplified($name); + if (!$row->has('歌手注音')) { + $phoneticAbbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified); + } else { + $phoneticAbbr = trim($row['歌手注音'] ?? ''); + } + $pinyinAbbr = ChineseNameConverter::getKTVPinyinAbbr($simplified); + if (!$row->has('歌手筆畫')) { + $chars = preg_split('//u', $name, -1, PREG_SPLIT_NO_EMPTY); + $firstChar = $chars[0] ?? null; + $strokesAbbr = ( $firstChar && preg_match('/\p{Han}/u', $firstChar) ) ? ChineseStrokesConverter::getStrokes($firstChar) : 0; + } else { + $strokesAbbr = trim($row['歌手筆畫'] ?? 0); + } + + // 準備 song 資料 + $toInsert[] = [ 'name' => $name, 'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset, - 'enable' => trim($row['狀態'] ?? 1), - ]); - - $artists->push($artist); + 'simplified' => $simplified, + 'phonetic_abbr' => $phoneticAbbr, + 'pinyin_abbr' => $pinyinAbbr, + 'strokes_abbr' => $strokesAbbr, + 'enable' =>trim($row['狀態'] ?? 1), + 'created_at' => $now, + 'updated_at' => $now, + ]; } catch (\Throwable $e) { \Log::error("Row {$index} failed: {$e->getMessage()}", [ 'row' => $row, @@ -48,6 +77,6 @@ class ImportArtistChunkJob implements ShouldQueue ]); } } - $artists->each(fn ($artist) => $artist->save()); + Artist::insert($toInsert); } } \ No newline at end of file diff --git a/app/Jobs/ImportSongChunkJob.php b/app/Jobs/ImportSongChunkJob.php index 5f051b0..ac36604 100644 --- a/app/Jobs/ImportSongChunkJob.php +++ b/app/Jobs/ImportSongChunkJob.php @@ -8,45 +8,76 @@ use App\Models\SongCategory; 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; protected array $categoryMap = []; protected array $artistCache =[]; - public function __construct(Collection $rows) + public function __construct(Collection $rows,String $id) { $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, + ]); + $ToInsert = []; + $artistMap = []; + $categoryMap = []; foreach ($this->rows as $index => $row) { $songId = trim($row['編號'] ?? ''); - if (!$songId) { - continue; - } - - // 改為即時查詢是否已有此編號 - if (Song::where('id', $songId)->exists()) { + if (!$songId || Song::where('id', $songId)->exists()) { continue; } try { + // 字元處理 + $songName=trim($row['歌名'] ?? ''); + $simplified=ChineseNameConverter::convertToSimplified($songName);// 繁體轉簡體 + if (!$row->has('注音')) { + $phoneticAbbr = ChineseNameConverter::getKTVZhuyinAbbr($simplified);// 注音符號 + } else { + $phoneticAbbr = trim($row['注音'] ?? ''); + } + if (!$row->has('拼音')) { + $pinyinAbbr = ChineseNameConverter::getKTVPinyinAbbr($simplified);// 拼音首字母 + } else { + $pinyinAbbr = trim($row['拼音'] ?? ''); + } + if (!$row->has('kk3')) {//歌名第一個字筆畫 + $chars = preg_split('//u', $songName, -1, PREG_SPLIT_NO_EMPTY); + $firstChar = $chars[0] ?? null; + $strokesAbbr=( $firstChar && preg_match('/\p{Han}/u', $firstChar) )? ChineseStrokesConverter::getStrokes($firstChar) : 0; + } else { + $strokesAbbr=trim($row['kk3'] ?? 0); + } + if (!$row->has('kk4')) {//歌名字數 + $songNumber = mb_strlen($songName, 'UTF-8'); + } else { + $songNumber=trim($row['kk4'] ?? 0); + } // 準備 song 資料 - $song = new Song([ + $ToInsert[] = [ 'id' => $songId, 'name' => $this->formatText($row['歌名']), 'adddate' => $this->parseExcelDate($row['日期'] ?? null), @@ -62,10 +93,13 @@ class ImportSongChunkJob implements ShouldQueue '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), - ]); - - $song->save(); + ]; // 處理關聯 - 歌手 $artistIds = []; @@ -76,9 +110,8 @@ class ImportSongChunkJob implements ShouldQueue // 若是歌星B,且與歌星A相同,則跳過 if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue; - $artistIds[] = $this->getOrCreateArtistId($artistName); + $artistMap[$songId][] = $this->getOrCreateArtistId($artistName); } - $song->artists()->sync($artistIds); // 分類處理(多個用 , 分隔) if (!empty($row['分類'])) { @@ -87,10 +120,9 @@ class ImportSongChunkJob implements ShouldQueue foreach ($codes as $code) { $code = trim($code); if (isset($this->categoryMap[$code])) { - $categoryIds[] = $this->categoryMap[$code]; + $categoryMap[$songId][] = $this->categoryMap[$code]; } } - $song->categories()->sync($categoryIds); } } catch (\Throwable $e) { \Log::error("Row {$index} failed: {$e->getMessage()}", [ @@ -99,6 +131,21 @@ class ImportSongChunkJob implements ShouldQueue ]); } } + // 寫入資料庫 + Song::insert($ToInsert); + // 同步關聯(建議可用事件或批次處理) + foreach ($artistMap as $songId => $artistIds) { + $song = Song::find($songId); + if ($song) { + $song->artists()->sync($artistIds); + } + } + foreach ($categoryMap as $songId => $categoryIds) { + $song = Song::find($songId); + if ($song) { + $song->categories()->sync($categoryIds); + } + } } private function getOrCreateArtistId(string $name): int { diff --git a/app/Livewire/Admin/ArtistImportData.php b/app/Livewire/Admin/ArtistImportData.php index ce267fd..492238b 100644 --- a/app/Livewire/Admin/ArtistImportData.php +++ b/app/Livewire/Admin/ArtistImportData.php @@ -24,8 +24,6 @@ class ArtistImportData extends Component public $file; public string $maxUploadSize; - public string|null $tmpPath = null; - public function mount() { $this->canCreate = Auth::user()?->can('song-edit') ?? false; @@ -40,7 +38,7 @@ class ArtistImportData extends Component public function closeModal() { $this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案 - $this->reset(['file', 'tmpPath']); + $this->reset(['file']); $this->showModal = false; } @@ -51,8 +49,6 @@ class ArtistImportData extends Component 'file' => 'required|file|mimes:csv,xlsx,xls' ]); if ($this->canCreate) { - // 取得原始 tmp 路徑 - $tmpPath = $this->file->getRealPath(); // 儲存檔案至 storage $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); @@ -65,14 +61,15 @@ class ArtistImportData extends Component 'description' => '已排入背景匯入作業,請稍候查看結果', ]); $this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔 - $this->reset(['file', 'tmpPath']); + $this->reset(['file']); $this->showModal = false; } } protected function deleteTmpFile() { - if ($this->tmpPath && File::exists($this->tmpPath)) { - File::delete($this->tmpPath); + $Path = $this->file->getRealPath(); + if ($Path && File::exists($Path)) { + File::delete($Path); } } diff --git a/app/Livewire/Admin/SongImportData.php b/app/Livewire/Admin/SongImportData.php index a823184..a1a6908 100644 --- a/app/Livewire/Admin/SongImportData.php +++ b/app/Livewire/Admin/SongImportData.php @@ -24,8 +24,6 @@ class SongImportData extends Component public $file; public string $maxUploadSize; - public string|null $tmpPath = null; - public function mount() { $this->canCreate = Auth::user()?->can('song-edit') ?? false; @@ -40,7 +38,7 @@ class SongImportData extends Component public function closeModal() { $this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案 - $this->reset(['file', 'tmpPath']); + $this->reset(['file']); $this->showModal = false; } @@ -51,8 +49,6 @@ class SongImportData extends Component 'file' => 'required|file|mimes:csv,xlsx,xls' ]); if ($this->canCreate) { - // 取得原始 tmp 路徑 - $tmpPath = $this->file->getRealPath(); // 儲存檔案至 storage $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); @@ -65,14 +61,15 @@ class SongImportData extends Component 'description' => '已排入背景匯入作業,請稍候查看結果', ]); $this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔 - $this->reset(['file', 'tmpPath']); + $this->reset(['file']); $this->showModal = false; } } protected function deleteTmpFile() - { - if ($this->tmpPath && File::exists($this->tmpPath)) { - File::delete($this->tmpPath); + { + $Path = $this->file->getRealPath(); + if ($Path && File::exists($Path)) { + File::delete($Path); } }