2025-05-09 09:34:09 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Jobs;
|
|
|
|
|
|
|
|
|
|
use App\Models\Song;
|
|
|
|
|
use App\Models\Artist;
|
|
|
|
|
use App\Models\SongCategory;
|
|
|
|
|
use App\Enums\ArtistCategory;
|
|
|
|
|
use App\Enums\SongLanguageType;
|
|
|
|
|
use App\Enums\SongSituation;
|
2025-05-10 19:41:43 +08:00
|
|
|
|
use App\Helpers\ChineseNameConverter;
|
|
|
|
|
use App\Helpers\ChineseStrokesConverter;
|
2025-05-09 09:34:09 +08:00
|
|
|
|
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;
|
2025-05-10 19:41:43 +08:00
|
|
|
|
use Illuminate\Support\Facades\Log;
|
2025-05-09 09:34:09 +08:00
|
|
|
|
|
|
|
|
|
class ImportSongChunkJob implements ShouldQueue
|
|
|
|
|
{
|
|
|
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
|
|
|
|
|
|
protected Collection $rows;
|
2025-05-10 19:41:43 +08:00
|
|
|
|
protected String $id;
|
2025-05-09 09:34:09 +08:00
|
|
|
|
protected array $categoryMap = [];
|
2025-05-09 13:01:23 +08:00
|
|
|
|
protected array $artistCache =[];
|
2025-05-09 09:34:09 +08:00
|
|
|
|
|
2025-05-10 19:41:43 +08:00
|
|
|
|
public function __construct(Collection $rows,String $id)
|
2025-05-09 09:34:09 +08:00
|
|
|
|
{
|
|
|
|
|
$this->rows = $rows;
|
2025-05-10 19:41:43 +08:00
|
|
|
|
$this->id = $id;
|
2025-05-09 09:34:09 +08:00
|
|
|
|
$this->categoryMap = SongCategory::pluck('id', 'code')->toArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function handle(): void
|
|
|
|
|
{
|
2025-05-10 19:41:43 +08:00
|
|
|
|
Log::warning('匯入啟動', [
|
|
|
|
|
'model' => "ImportSongChunkJob",
|
|
|
|
|
'rows_id' =>$this->id,
|
|
|
|
|
]);
|
|
|
|
|
$ToInsert = [];
|
|
|
|
|
$artistMap = [];
|
|
|
|
|
$categoryMap = [];
|
2025-05-09 09:34:09 +08:00
|
|
|
|
foreach ($this->rows as $index => $row) {
|
|
|
|
|
$songId = trim($row['編號'] ?? '');
|
|
|
|
|
|
2025-05-10 19:41:43 +08:00
|
|
|
|
if (!$songId || Song::where('id', $songId)->exists()) {
|
2025-05-09 09:34:09 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2025-05-10 19:41:43 +08:00
|
|
|
|
// 字元處理
|
|
|
|
|
$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);
|
|
|
|
|
}
|
2025-05-09 09:34:09 +08:00
|
|
|
|
// 準備 song 資料
|
2025-05-10 19:41:43 +08:00
|
|
|
|
$ToInsert[] = [
|
2025-05-09 09:34:09 +08:00
|
|
|
|
'id' => $songId,
|
2025-05-10 09:00:14 +08:00
|
|
|
|
'name' => $this->formatText($row['歌名']),
|
2025-05-09 10:44:04 +08:00
|
|
|
|
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
|
2025-05-09 09:34:09 +08:00
|
|
|
|
'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),
|
2025-05-10 19:41:43 +08:00
|
|
|
|
'simplified' => $simplified,
|
|
|
|
|
'phonetic_abbr' => $phoneticAbbr,
|
|
|
|
|
'pinyin_abbr' => $pinyinAbbr,
|
|
|
|
|
'strokes_abbr' => $strokesAbbr,
|
|
|
|
|
'song_number' => $songNumber,
|
2025-05-09 09:34:09 +08:00
|
|
|
|
'song_counts' => trim($row['點播次數'] ?? 0),
|
2025-05-10 19:41:43 +08:00
|
|
|
|
];
|
2025-05-09 09:34:09 +08:00
|
|
|
|
|
|
|
|
|
// 處理關聯 - 歌手
|
|
|
|
|
$artistIds = [];
|
|
|
|
|
foreach (['歌星A', '歌星B'] as $key) {
|
|
|
|
|
$artistName = trim($row[$key] ?? '');
|
|
|
|
|
if ($artistName === '') continue;
|
|
|
|
|
|
|
|
|
|
// 若是歌星B,且與歌星A相同,則跳過
|
|
|
|
|
if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue;
|
|
|
|
|
|
2025-05-10 19:41:43 +08:00
|
|
|
|
$artistMap[$songId][] = $this->getOrCreateArtistId($artistName);
|
2025-05-09 09:34:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 分類處理(多個用 , 分隔)
|
|
|
|
|
if (!empty($row['分類'])) {
|
|
|
|
|
$categoryIds = [];
|
|
|
|
|
$codes = explode(',', $row['分類']);
|
|
|
|
|
foreach ($codes as $code) {
|
|
|
|
|
$code = trim($code);
|
|
|
|
|
if (isset($this->categoryMap[$code])) {
|
2025-05-10 19:41:43 +08:00
|
|
|
|
$categoryMap[$songId][] = $this->categoryMap[$code];
|
2025-05-09 09:34:09 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
|
|
|
|
'row' => $row,
|
|
|
|
|
'trace' => $e->getTraceAsString()
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-10 19:41:43 +08:00
|
|
|
|
// 寫入資料庫
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-09 09:34:09 +08:00
|
|
|
|
}
|
2025-05-09 13:01:23 +08:00
|
|
|
|
private function getOrCreateArtistId(string $name): int
|
|
|
|
|
{
|
|
|
|
|
if (isset($this->artistCache[$name])) {
|
|
|
|
|
return $this->artistCache[$name];
|
|
|
|
|
}
|
2025-05-09 10:44:04 +08:00
|
|
|
|
|
2025-05-09 13:01:23 +08:00
|
|
|
|
$artist = Artist::firstOrCreate(
|
|
|
|
|
['name' => $name],
|
|
|
|
|
['category' => ArtistCategory::Unset]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $this->artistCache[$name] = $artist->id;
|
|
|
|
|
}
|
2025-05-10 09:00:14 +08:00
|
|
|
|
protected function formatText($value)
|
|
|
|
|
{
|
|
|
|
|
if (is_numeric($value) && $value < 1 && $value > 0) {
|
|
|
|
|
// 嘗試判斷為時間類型的小數,轉為時間字串
|
|
|
|
|
$time = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value);
|
|
|
|
|
return $time->format('H:i');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trim((string) $value);
|
|
|
|
|
}
|
2025-05-09 10:44:04 +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');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return \Carbon\Carbon::parse($value)->format('Y-m-d');
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-09 09:34:09 +08:00
|
|
|
|
}
|