修正滙入功能 與寫相關記錄
修正滙入時暫存檔不會刪的問題 20250510
This commit is contained in:
parent
75e41c43ef
commit
ea0e10bb64
@ -13,6 +13,7 @@ use App\Jobs\ImportSongChunkJob;
|
|||||||
|
|
||||||
class DataImport implements ToCollection, WithHeadingRow, WithChunkReading
|
class DataImport implements ToCollection, WithHeadingRow, WithChunkReading
|
||||||
{
|
{
|
||||||
|
protected int $con=0;
|
||||||
protected string $modelName;
|
protected string $modelName;
|
||||||
public function __construct(string $modelName)
|
public function __construct(string $modelName)
|
||||||
{
|
{
|
||||||
@ -21,15 +22,16 @@ class DataImport implements ToCollection, WithHeadingRow, WithChunkReading
|
|||||||
}
|
}
|
||||||
public function collection(Collection $rows)
|
public function collection(Collection $rows)
|
||||||
{
|
{
|
||||||
|
|
||||||
Log::warning('匯入啟動', [
|
Log::warning('匯入啟動', [
|
||||||
'model' => $this->modelName,
|
'model' => $this->modelName,
|
||||||
'from_row' => $rows->keys()->first() + 2,
|
'rows_id' =>++$this->con,
|
||||||
'rows_count' => $rows->count()
|
'rows_count' => $rows->count()
|
||||||
]);
|
]);
|
||||||
if($this->modelName=='Song'){
|
if($this->modelName=='Song'){
|
||||||
ImportSongChunkJob::dispatch($rows);
|
ImportSongChunkJob::dispatch($rows,$this->con);
|
||||||
}else if($this->modelName=='Artist'){
|
}else if($this->modelName=='Artist'){
|
||||||
ImportArtistChunkJob::dispatch($rows);
|
ImportArtistChunkJob::dispatch($rows,$this->con);
|
||||||
}else{
|
}else{
|
||||||
Log::warning('未知的 modelName', ['model' => $this->modelName]);
|
Log::warning('未知的 modelName', ['model' => $this->modelName]);
|
||||||
}
|
}
|
||||||
|
@ -4,28 +4,36 @@ namespace App\Jobs;
|
|||||||
|
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Enums\ArtistCategory;
|
use App\Enums\ArtistCategory;
|
||||||
|
use App\Helpers\ChineseNameConverter;
|
||||||
|
use App\Helpers\ChineseStrokesConverter;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class ImportArtistChunkJob implements ShouldQueue
|
class ImportArtistChunkJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected Collection $rows;
|
protected Collection $rows;
|
||||||
|
protected String $id;
|
||||||
|
|
||||||
public function __construct(Collection $rows)
|
public function __construct(Collection $rows,String $id)
|
||||||
{
|
{
|
||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
|
$this->id = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$artists = collect();
|
Log::warning('匯入啟動', [
|
||||||
|
'model' => "ImportArtistChunkJob",
|
||||||
|
'rows_id' =>$this->id,
|
||||||
|
]);
|
||||||
|
$now = now();
|
||||||
foreach ($this->rows as $index => $row) {
|
foreach ($this->rows as $index => $row) {
|
||||||
try {
|
try {
|
||||||
$name = trim($row['歌手姓名'] ?? '');
|
$name = trim($row['歌手姓名'] ?? '');
|
||||||
@ -34,13 +42,34 @@ class ImportArtistChunkJob implements ShouldQueue
|
|||||||
continue;
|
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,
|
'name' => $name,
|
||||||
'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset,
|
'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset,
|
||||||
'enable' => trim($row['狀態'] ?? 1),
|
'simplified' => $simplified,
|
||||||
]);
|
'phonetic_abbr' => $phoneticAbbr,
|
||||||
|
'pinyin_abbr' => $pinyinAbbr,
|
||||||
$artists->push($artist);
|
'strokes_abbr' => $strokesAbbr,
|
||||||
|
'enable' =>trim($row['狀態'] ?? 1),
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
];
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
||||||
'row' => $row,
|
'row' => $row,
|
||||||
@ -48,6 +77,6 @@ class ImportArtistChunkJob implements ShouldQueue
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$artists->each(fn ($artist) => $artist->save());
|
Artist::insert($toInsert);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,45 +8,76 @@ use App\Models\SongCategory;
|
|||||||
use App\Enums\ArtistCategory;
|
use App\Enums\ArtistCategory;
|
||||||
use App\Enums\SongLanguageType;
|
use App\Enums\SongLanguageType;
|
||||||
use App\Enums\SongSituation;
|
use App\Enums\SongSituation;
|
||||||
|
use App\Helpers\ChineseNameConverter;
|
||||||
|
use App\Helpers\ChineseStrokesConverter;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class ImportSongChunkJob implements ShouldQueue
|
class ImportSongChunkJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected Collection $rows;
|
protected Collection $rows;
|
||||||
|
protected String $id;
|
||||||
protected array $categoryMap = [];
|
protected array $categoryMap = [];
|
||||||
protected array $artistCache =[];
|
protected array $artistCache =[];
|
||||||
|
|
||||||
public function __construct(Collection $rows)
|
public function __construct(Collection $rows,String $id)
|
||||||
{
|
{
|
||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
|
$this->id = $id;
|
||||||
$this->categoryMap = SongCategory::pluck('id', 'code')->toArray();
|
$this->categoryMap = SongCategory::pluck('id', 'code')->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
Log::warning('匯入啟動', [
|
||||||
|
'model' => "ImportSongChunkJob",
|
||||||
|
'rows_id' =>$this->id,
|
||||||
|
]);
|
||||||
|
$ToInsert = [];
|
||||||
|
$artistMap = [];
|
||||||
|
$categoryMap = [];
|
||||||
foreach ($this->rows as $index => $row) {
|
foreach ($this->rows as $index => $row) {
|
||||||
$songId = trim($row['編號'] ?? '');
|
$songId = trim($row['編號'] ?? '');
|
||||||
|
|
||||||
if (!$songId) {
|
if (!$songId || Song::where('id', $songId)->exists()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 改為即時查詢是否已有此編號
|
|
||||||
if (Song::where('id', $songId)->exists()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 資料
|
||||||
$song = new Song([
|
$ToInsert[] = [
|
||||||
'id' => $songId,
|
'id' => $songId,
|
||||||
'name' => $this->formatText($row['歌名']),
|
'name' => $this->formatText($row['歌名']),
|
||||||
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
|
'adddate' => $this->parseExcelDate($row['日期'] ?? null),
|
||||||
@ -62,10 +93,13 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
'note03' => trim($row['版權05'] ?? ''),
|
'note03' => trim($row['版權05'] ?? ''),
|
||||||
'note04' => trim($row['版權06'] ?? ''),
|
'note04' => trim($row['版權06'] ?? ''),
|
||||||
'enable' => trim($row['狀態'] ?? 1),
|
'enable' => trim($row['狀態'] ?? 1),
|
||||||
|
'simplified' => $simplified,
|
||||||
|
'phonetic_abbr' => $phoneticAbbr,
|
||||||
|
'pinyin_abbr' => $pinyinAbbr,
|
||||||
|
'strokes_abbr' => $strokesAbbr,
|
||||||
|
'song_number' => $songNumber,
|
||||||
'song_counts' => trim($row['點播次數'] ?? 0),
|
'song_counts' => trim($row['點播次數'] ?? 0),
|
||||||
]);
|
];
|
||||||
|
|
||||||
$song->save();
|
|
||||||
|
|
||||||
// 處理關聯 - 歌手
|
// 處理關聯 - 歌手
|
||||||
$artistIds = [];
|
$artistIds = [];
|
||||||
@ -76,9 +110,8 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
// 若是歌星B,且與歌星A相同,則跳過
|
// 若是歌星B,且與歌星A相同,則跳過
|
||||||
if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue;
|
if ($key === '歌星B' && $artistName === trim($row['歌星A'] ?? '')) continue;
|
||||||
|
|
||||||
$artistIds[] = $this->getOrCreateArtistId($artistName);
|
$artistMap[$songId][] = $this->getOrCreateArtistId($artistName);
|
||||||
}
|
}
|
||||||
$song->artists()->sync($artistIds);
|
|
||||||
|
|
||||||
// 分類處理(多個用 , 分隔)
|
// 分類處理(多個用 , 分隔)
|
||||||
if (!empty($row['分類'])) {
|
if (!empty($row['分類'])) {
|
||||||
@ -87,10 +120,9 @@ class ImportSongChunkJob implements ShouldQueue
|
|||||||
foreach ($codes as $code) {
|
foreach ($codes as $code) {
|
||||||
$code = trim($code);
|
$code = trim($code);
|
||||||
if (isset($this->categoryMap[$code])) {
|
if (isset($this->categoryMap[$code])) {
|
||||||
$categoryIds[] = $this->categoryMap[$code];
|
$categoryMap[$songId][] = $this->categoryMap[$code];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$song->categories()->sync($categoryIds);
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Log::error("Row {$index} failed: {$e->getMessage()}", [
|
\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
|
private function getOrCreateArtistId(string $name): int
|
||||||
{
|
{
|
||||||
|
@ -24,8 +24,6 @@ class ArtistImportData extends Component
|
|||||||
public $file;
|
public $file;
|
||||||
public string $maxUploadSize;
|
public string $maxUploadSize;
|
||||||
|
|
||||||
public string|null $tmpPath = null;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->canCreate = Auth::user()?->can('song-edit') ?? false;
|
$this->canCreate = Auth::user()?->can('song-edit') ?? false;
|
||||||
@ -40,7 +38,7 @@ class ArtistImportData extends Component
|
|||||||
public function closeModal()
|
public function closeModal()
|
||||||
{
|
{
|
||||||
$this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案
|
$this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案
|
||||||
$this->reset(['file', 'tmpPath']);
|
$this->reset(['file']);
|
||||||
$this->showModal = false;
|
$this->showModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +49,6 @@ class ArtistImportData extends Component
|
|||||||
'file' => 'required|file|mimes:csv,xlsx,xls'
|
'file' => 'required|file|mimes:csv,xlsx,xls'
|
||||||
]);
|
]);
|
||||||
if ($this->canCreate) {
|
if ($this->canCreate) {
|
||||||
// 取得原始 tmp 路徑
|
|
||||||
$tmpPath = $this->file->getRealPath();
|
|
||||||
// 儲存檔案至 storage
|
// 儲存檔案至 storage
|
||||||
$path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName());
|
$path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName());
|
||||||
|
|
||||||
@ -65,14 +61,15 @@ class ArtistImportData extends Component
|
|||||||
'description' => '已排入背景匯入作業,請稍候查看結果',
|
'description' => '已排入背景匯入作業,請稍候查看結果',
|
||||||
]);
|
]);
|
||||||
$this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔
|
$this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔
|
||||||
$this->reset(['file', 'tmpPath']);
|
$this->reset(['file']);
|
||||||
$this->showModal = false;
|
$this->showModal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected function deleteTmpFile()
|
protected function deleteTmpFile()
|
||||||
{
|
{
|
||||||
if ($this->tmpPath && File::exists($this->tmpPath)) {
|
$Path = $this->file->getRealPath();
|
||||||
File::delete($this->tmpPath);
|
if ($Path && File::exists($Path)) {
|
||||||
|
File::delete($Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +24,6 @@ class SongImportData extends Component
|
|||||||
public $file;
|
public $file;
|
||||||
public string $maxUploadSize;
|
public string $maxUploadSize;
|
||||||
|
|
||||||
public string|null $tmpPath = null;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->canCreate = Auth::user()?->can('song-edit') ?? false;
|
$this->canCreate = Auth::user()?->can('song-edit') ?? false;
|
||||||
@ -40,7 +38,7 @@ class SongImportData extends Component
|
|||||||
public function closeModal()
|
public function closeModal()
|
||||||
{
|
{
|
||||||
$this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案
|
$this->deleteTmpFile(); // 關閉 modal 時刪除暫存檔案
|
||||||
$this->reset(['file', 'tmpPath']);
|
$this->reset(['file']);
|
||||||
$this->showModal = false;
|
$this->showModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +49,6 @@ class SongImportData extends Component
|
|||||||
'file' => 'required|file|mimes:csv,xlsx,xls'
|
'file' => 'required|file|mimes:csv,xlsx,xls'
|
||||||
]);
|
]);
|
||||||
if ($this->canCreate) {
|
if ($this->canCreate) {
|
||||||
// 取得原始 tmp 路徑
|
|
||||||
$tmpPath = $this->file->getRealPath();
|
|
||||||
// 儲存檔案至 storage
|
// 儲存檔案至 storage
|
||||||
$path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName());
|
$path = $this->file->storeAs('imports', uniqid() . '_' . $this->file->getClientOriginalName());
|
||||||
|
|
||||||
@ -65,14 +61,15 @@ class SongImportData extends Component
|
|||||||
'description' => '已排入背景匯入作業,請稍候查看結果',
|
'description' => '已排入背景匯入作業,請稍候查看結果',
|
||||||
]);
|
]);
|
||||||
$this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔
|
$this->deleteTmpFile(); // 匯入後也順便刪除 tmp 檔
|
||||||
$this->reset(['file', 'tmpPath']);
|
$this->reset(['file']);
|
||||||
$this->showModal = false;
|
$this->showModal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected function deleteTmpFile()
|
protected function deleteTmpFile()
|
||||||
{
|
{
|
||||||
if ($this->tmpPath && File::exists($this->tmpPath)) {
|
$Path = $this->file->getRealPath();
|
||||||
File::delete($this->tmpPath);
|
if ($Path && File::exists($Path)) {
|
||||||
|
File::delete($Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user