diff --git a/.DS_Store b/.DS_Store index 2cfe8e5..bfd9de2 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app/.DS_Store b/app/.DS_Store index a5cbae3..4e2be58 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/app/Imports/ArtistDataImport.php b/app/Imports/ArtistDataImport.php deleted file mode 100644 index 4680585..0000000 --- a/app/Imports/ArtistDataImport.php +++ /dev/null @@ -1,90 +0,0 @@ -all())); - } - - $toInsert = []; - - foreach ($rows as $row) { - $name=trim($row['歌手姓名'] ?? ''); - if (empty($name)) continue; - - // 若資料庫已有該名稱,跳過 - if (isset($existingNames[$name])) continue; - - // 字元處理 - $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 ? ChineseStrokesConverter::getStrokes($firstChar) : null; - } else { - $strokesAbbr = trim($row['歌手筆畫']); - } - // 準備 song 資料 - $now = now(); - $toInsert[] = [ - 'name' => $name, - 'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset, - 'simplified' => $simplified, - 'phonetic_abbr' => $phoneticAbbr, - 'pinyin_abbr' => $pinyinAbbr, - 'strokes_abbr' => $strokesAbbr, - 'enable' =>trim($row['狀態'] ?? 1), - 'created_at' => $now, - 'updated_at' => $now, - ]; - - // 新增到快取,避免後面重複匯入 - $existingNames[$name] = true; - } - Artist::insert($toInsert); - } - - public function chunkSize(): int - { - return 100; - } - public function batchSize(): int - { - return 100; - } - - public function headingRow(): int - { - return 1; - } -} \ No newline at end of file diff --git a/app/Imports/DataImport.php b/app/Imports/DataImport.php new file mode 100644 index 0000000..8cecd47 --- /dev/null +++ b/app/Imports/DataImport.php @@ -0,0 +1,40 @@ +modelName= $modelName; + } + public function collection(Collection $rows) + { + if($this->modelName=='Song'){ + ImportSongChunkJob::dispatch($rows); + }else if($this->modelName=='Artist'){ + ImportArtistChunkJob::dispatch($rows); + }else{ + + } + } + public function chunkSize(): int + { + return 1000; + } + + public function headingRow(): int + { + return 1; + } +} \ No newline at end of file diff --git a/app/Imports/SongDataImport.php b/app/Imports/SongDataImport.php deleted file mode 100644 index 72305ee..0000000 --- a/app/Imports/SongDataImport.php +++ /dev/null @@ -1,119 +0,0 @@ -categoryMap = SongCategory::pluck('id', 'code')->toArray(); - } - - public function collection(Collection $rows) - { - foreach ($rows as $row) { - $songId = trim($row['編號'] ?? ''); - - if (!$songId) { - continue; - } - // 改為即時查詢是否已有此編號 - if (Song::where('id', $songId)->exists()) { - continue; - } - // 準備 song 資料 - $song = new Song([ - 'id' => $songId, - 'name' => $songName, - 'adddate' => trim($row['日期'] ?? null), - 'filename' => trim($row['檔名'] ?? ''), - 'language_type' => SongLanguageType::tryFrom(trim($row['語別'] ?? '')) ?? SongLanguageType::Unset, - 'db_change' => trim($row['kk2'] ?? 0),//分貝增減 - 'vocal' => trim($row['kk6'] ?? 0),//人聲 - 'situation' => SongSituation::tryFrom(trim($row['kk7'] ?? '')) ?? SongSituation::Unset,//情境 - '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), - 'song_counts' => trim($row['點播次數'] ?? 0), - ]); - - $song->save(); - - // 處理關聯 - 歌手 - $artistIds = []; - foreach (['歌星A', '歌星B'] as $key) { - $artistName = trim($row[$key] ?? ''); - if ($artistName === '') continue; - - // 若是歌星B,且與歌星A相同,則跳過 - if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue; - - $artistIds[] = $this->getOrCreateArtistId($artistName); - } - $song->artists()->sync($artistIds); - - // 分類處理(多個用 , 分隔) - if (!empty($row['分類'])) { - $categoryIds = []; - $codes = explode(',', $row['分類']); - foreach ($codes as $code) { - $code = trim($code); - if (isset($this->categoryMap[$code])) { - $categoryIds[] = $this->categoryMap[$code]; - } - } - $song->categories()->sync($categoryIds); - } - } - } - - protected function getOrCreateArtistId(string $name): int - { - if (isset($this->artistCache[$name])) { - return $this->artistCache[$name]; - } - - $artist = Artist::firstOrCreate( - ['name' => $name], - ['category' => ArtistCategory::Unset] - ); - - return $this->artistCache[$name] = $artist->id; - } - - public function chunkSize(): int - { - return 100; - } - - public function headingRow(): int - { - return 1; - } -} \ No newline at end of file diff --git a/app/Jobs/ImportArtistChunkJob.php b/app/Jobs/ImportArtistChunkJob.php new file mode 100644 index 0000000..5a182b8 --- /dev/null +++ b/app/Jobs/ImportArtistChunkJob.php @@ -0,0 +1,52 @@ +rows = $rows; + } + + public function handle(): void + { + foreach ($this->rows as $index => $row) { + try { + $name = trim($row['歌手姓名'] ?? ''); + + if (empty($name)) { + continue; + } + if (Artist::where('name', $name)->exists()) { + continue; + } + + Artist::create([ + 'name' => $name, + 'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset, + 'enable' => trim($row['狀態'] ?? 1), + ]); + + } catch (\Throwable $e) { + \Log::error("Row {$index} failed: {$e->getMessage()}", [ + 'row' => $row, + 'trace' => $e->getTraceAsString() + ]); + } + } + } +} \ No newline at end of file diff --git a/app/Jobs/ImportArtistJob.php b/app/Jobs/ImportArtistJob.php deleted file mode 100644 index 22d5a1c..0000000 --- a/app/Jobs/ImportArtistJob.php +++ /dev/null @@ -1,44 +0,0 @@ -filePath = $filePath; - } - - /** - * Execute the job. - */ - public function handle(): void - { - ini_set('memory_limit', '-1'); // ✅ 增加記憶體限制 - - Excel::import(new ArtistDataImport, $this->filePath); - // 匯入完成後刪除檔案 - if (Storage::exists($this->filePath)) { - Storage::delete($this->filePath); - } - } -} diff --git a/app/Jobs/ImportJob.php b/app/Jobs/ImportJob.php new file mode 100644 index 0000000..8264354 --- /dev/null +++ b/app/Jobs/ImportJob.php @@ -0,0 +1,59 @@ +filePath = $filePath; + $this->modelName= $modelName; + } + + /** + * Execute the job. + */ + public function handle(): void + { + ini_set('memory_limit', '512M'); // ✅ 增加記憶體限制 + Log::info('[ImportJob] 開始處理檔案:' . $this->filePath); + + try { + if (!Storage::exists($this->filePath)) { + Log::warning('[ImportJob] 檔案不存在:' . $this->filePath); + return; + } + + Excel::import(new DataImport($this->modelName), $this->filePath); + Log::info('[ImportJob] 已提交所有 chunk 匯入任務。'); + + Storage::delete($this->filePath); + Log::info('[ImportJob] 已刪除檔案:' . $this->filePath); + } catch (\Throwable $e) { + Log::error("[ImportJob] 匯入失敗:{$e->getMessage()}", [ + 'file' => $this->filePath, + 'trace' => $e->getTraceAsString(), + ]); + } + } +} \ No newline at end of file diff --git a/app/Jobs/ImportSongChunkJob.php b/app/Jobs/ImportSongChunkJob.php new file mode 100644 index 0000000..5e6b0ca --- /dev/null +++ b/app/Jobs/ImportSongChunkJob.php @@ -0,0 +1,102 @@ +rows = $rows; + $this->categoryMap = SongCategory::pluck('id', 'code')->toArray(); + } + + public function handle(): void + { + foreach ($this->rows as $index => $row) { + $songId = trim($row['編號'] ?? ''); + + if (!$songId) { + continue; + } + + // 改為即時查詢是否已有此編號 + if (Song::where('id', $songId)->exists()) { + continue; + } + + try { + // 準備 song 資料 + $song = new Song([ + 'id' => $songId, + 'name' => trim($row['歌名'] ?? ''), + 'adddate' => trim($row['日期'] ?? null), + 'filename' => trim($row['檔名'] ?? ''), + 'language_type' => SongLanguageType::tryFrom(trim($row['語別'] ?? '')) ?? SongLanguageType::Unset, + 'db_change' => trim($row['kk2'] ?? 0),//分貝增減 + 'vocal' => trim($row['kk6'] ?? 0),//人聲 + 'situation' => SongSituation::tryFrom(trim($row['kk7'] ?? '')) ?? SongSituation::Unset,//情境 + '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), + 'song_counts' => trim($row['點播次數'] ?? 0), + ]); + + $song->save(); + + // 處理關聯 - 歌手 + $artistIds = []; + foreach (['歌星A', '歌星B'] as $key) { + $artistName = trim($row[$key] ?? ''); + if ($artistName === '') continue; + + // 若是歌星B,且與歌星A相同,則跳過 + if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue; + + $artistIds[] = $this->getOrCreateArtistId($artistName); + } + $song->artists()->sync($artistIds); + + // 分類處理(多個用 , 分隔) + if (!empty($row['分類'])) { + $categoryIds = []; + $codes = explode(',', $row['分類']); + foreach ($codes as $code) { + $code = trim($code); + if (isset($this->categoryMap[$code])) { + $categoryIds[] = $this->categoryMap[$code]; + } + } + $song->categories()->sync($categoryIds); + } + } catch (\Throwable $e) { + \Log::error("Row {$index} failed: {$e->getMessage()}", [ + 'row' => $row, + 'trace' => $e->getTraceAsString() + ]); + } + } + } +} \ No newline at end of file diff --git a/app/Jobs/ImportSongJob.php b/app/Jobs/ImportSongJob.php deleted file mode 100644 index e988316..0000000 --- a/app/Jobs/ImportSongJob.php +++ /dev/null @@ -1,57 +0,0 @@ -filePath = $filePath; - } - - /** - * Execute the job. - */ - public function handle(): void - { - //ini_set('memory_limit', '-1'); // 無限記憶體,適合大量匯入 - - try { - Excel::import(new SongDataImport, $this->filePath); - - // 匯入成功後再刪檔案 - if (Storage::exists($this->filePath)) { - Storage::delete($this->filePath); - } - - } catch (\Throwable $e) { - // 寫入錯誤日誌 - \Log::error('ImportSongJob failed: ' . $e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - 'file' => $this->filePath - ]); - - // ❗重要:不要刪除檔案,讓失敗時可以 retry 使用同一份檔案 - throw $e; // 讓 Laravel Queue 系統可以 retry - } - } -} diff --git a/app/Livewire/Admin/ArtistImportData.php b/app/Livewire/Admin/ArtistImportData.php index 08be024..c76e0d1 100644 --- a/app/Livewire/Admin/ArtistImportData.php +++ b/app/Livewire/Admin/ArtistImportData.php @@ -8,7 +8,7 @@ use Illuminate\Support\Facades\File; use WireUi\Traits\WireUiActions; use Livewire\Component; use Livewire\WithFileUploads; -use App\Jobs\ImportArtistJob; +use App\Jobs\ImportJob; class ArtistImportData extends Component @@ -55,7 +55,7 @@ class ArtistImportData extends Component $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); // 丟到 queue 執行 - ImportArtistJob::dispatch($path); + ImportJob::dispatch($path,'Artist'); $this->notification()->send([ 'icon' => 'info', diff --git a/app/Livewire/Admin/SongImportData.php b/app/Livewire/Admin/SongImportData.php index 443ecb2..7960832 100644 --- a/app/Livewire/Admin/SongImportData.php +++ b/app/Livewire/Admin/SongImportData.php @@ -8,7 +8,7 @@ use Illuminate\Support\Facades\File; use WireUi\Traits\WireUiActions; use Livewire\Component; use Livewire\WithFileUploads; -use App\Jobs\ImportSongJob; +use App\Jobs\ImportJob; class SongImportData extends Component @@ -55,7 +55,7 @@ class SongImportData extends Component $path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName()); // 丟到 queue 執行 - ImportSongJob::dispatch($path); + ImportJob::dispatch($path,'Song'); $this->notification()->send([ 'icon' => 'info', diff --git a/database/migrations/2025_04_24_031630_create_songs_table.php b/database/migrations/2025_04_24_031630_create_songs_table.php new file mode 100644 index 0000000..c8d7009 --- /dev/null +++ b/database/migrations/2025_04_24_031630_create_songs_table.php @@ -0,0 +1,47 @@ +integer('song_id')->primary(); + $table->string('song_name', 255)->nullable(); + $table->string('artistA', 255)->nullable(); + $table->string('artistB', 255)->nullable(); + $table->string('song_filename', 255)->nullable(); + $table->string('artistA_simplified', 255)->nullable(); + $table->string('ArtistB_simplified', 255)->nullable(); + $table->string('artistA_category', 255)->nullable(); + $table->string('artistB_category', 255)->nullable(); + $table->string('song_simplified', 255)->nullable(); + $table->string('situations', 255)->nullable(); + $table->string('vocal', 255)->nullable(); + $table->string('language_name', 50)->nullable(); + $table->string('phonetic_abbr', 255)->nullable(); + $table->string('artistA_phonetic', 255)->nullable(); + $table->string('artistB_phonetic', 255)->nullable(); + $table->string('artistA_pinyin', 255)->nullable(); + $table->string('artistB_pinyin', 255)->nullable(); + $table->string('pinyin_abbr', 255)->nullable(); + $table->integer('song_counts')->nullable(); + $table->dateTime('add_date')->nullable(); + $table->dateTime('updated_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('song_library_cache'); + } +}; diff --git a/storage/.DS_Store b/storage/.DS_Store index fadd49a..e1da388 100644 Binary files a/storage/.DS_Store and b/storage/.DS_Store differ diff --git a/開發手冊.ini b/開發手冊.ini index af6e0d7..c5e2657 100644 --- a/開發手冊.ini +++ b/開發手冊.ini @@ -96,8 +96,8 @@ php artisan make:observer RoomObserver --model=Room -php artisan queue:work - +php artisan queue:work --timeout=600 --memory=1024 +php artisan queue:work --daemon --timeout=3600 --memory=5120 --tries=1 --queue=default composer install cp .env.example .env