From 75e41c43efd8aa9f70aa25aa4d6807b8f9a7c3b4 Mon Sep 17 00:00:00 2001 From: "allen.yan" Date: Sat, 10 May 2025 09:00:14 +0800 Subject: [PATCH] =?UTF-8?q?Song=20=E8=AA=BF=E6=95=B4=E6=AD=8C=E6=9B=B2?= =?UTF-8?q?=E5=90=8D=E7=A8=B1=E6=BB=99=E5=85=A5=E9=8C=AF=E8=AA=A4=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=20=E5=8A=A0=E5=85=A5=20sqllitToMysql=2020250510?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes .../Commands/TransferSqliteToMysql.php | 76 ++++++++++++++++++ app/Jobs/ImportArtistChunkJob.php | 11 +-- app/Jobs/ImportSongChunkJob.php | 12 ++- app/Models/Artist.php | 2 +- app/Models/Song.php | 3 +- database/.DS_Store | Bin 6148 -> 6148 bytes .../0001_01_01_000002_create_jobs_table.php | 10 +-- ...der.php => KSongDatabaseToMysqlSeeder.php} | 0 storage/.DS_Store | Bin 6148 -> 6148 bytes 開發手冊.ini | 8 +- 11 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 app/Console/Commands/TransferSqliteToMysql.php rename database/seeders/{SqliteToMysqlSeeder.php => KSongDatabaseToMysqlSeeder.php} (100%) diff --git a/.DS_Store b/.DS_Store index 45ce0e800326836ccb2bfbc077e1ad86d739368c..0171a60f84f7ba51b2be8cab19fe8bfcaac73cd2 100644 GIT binary patch delta 1685 zcmeH`O>7%Q6vy9xosZeYO~#J%(XJeuI)Mq!iM|ZIkAUHrUyvS>mh{ z?0b95{h0Ar6eaArc6I#Dxnh z?VJ6-nfGSiyx*QoolKn_M$H?kcWY7OczGO`oRn|z)~4mvbhYAXH#Y&Ygl$H_BkX^MP*er zla-s)v2kT`+?dIkhl~@tvs~9HkK6ijyR5F&H5aG3f634b!wa^NGp)mV-V$S^H%8@0 zzTmA7ZocLA*qz&V4eZ@_V0A-HEm!CRP3O>ais!7YG!~Nki(*GJlKRucd8$UrsBf(8}AXA+?pU zsgl1xuM6wCl{U3q=4m!!iNf4-btqjjx8#vI!(0oED@n#Rc{H2O=vg;gx3Z1#`{c1D zw^ZRQ)^fE+xr_M_mB(jt^Yfwt%U#^3_A`E&9W8A>l(y{Q1>OApScB?4jDO4~t=V*5 zA1;`B-i;YlhZvjYwPk-P=x)uhbNTWm><_8xL@8OHrs*1Et4$4!+@l^+onpOOYqMN*knFiAxcwMe2~^Qo=1j!df9y>jjlR zqyaH7QB0cbt-@r)YmqLnUU0FN7XJ$C;NTu*@84h@{|BtU@RpeK`RfAp&5^c__?8{L z&Qp!UH7=)0a8wYaA9s!fnu@%-#06U|O@+crkXH(zT-nkpWR-x)mAADAgIp2}yRx$@ zsBn)UR4&IR+J(fL=-#R*yh5<*&h_jRl1l%JtNV6~OeOFbD@J<%B^KxDO}a$y(8fWkX&f!@+hv)G^8IsrVy4&RSrr-O=rsKVfeqWn>u{!)qMFWxi Le>eW~ZnWYze2-_I delta 53 zcmV-50LuS_P=rvBPXP?EP`eKS43i8HGLsw^b(5bMA(OHfB$N6OF0=C%6bZ3_%>}au LAo>Ke&Kd&)>5~$a diff --git a/app/Console/Commands/TransferSqliteToMysql.php b/app/Console/Commands/TransferSqliteToMysql.php new file mode 100644 index 0000000..87b6aa0 --- /dev/null +++ b/app/Console/Commands/TransferSqliteToMysql.php @@ -0,0 +1,76 @@ + 'sqlite', + 'database' => base_path('database/database.sqlite'), + 'prefix' => '', + ]); + + $this->info("🚀 Starting transfer from SQLite to MySQL..."); + + // 讀取 SQLite 資料庫的所有資料表 + $sqliteTables = DB::connection('sqlite')->select(" + SELECT name FROM sqlite_master + WHERE type='table' AND name NOT LIKE 'sqlite_%'; + "); + + if (empty($sqliteTables)) { + $this->error("❌ No tables found in SQLite database."); + return 1; + } + + // 取得 .env 中指定的 MySQL 連線名稱 + $mysqlConnection = config('database.default'); // 默認會是 'mysql',如果 .env 修改會自動更新 + + foreach ($sqliteTables as $tableObj) { + $table = $tableObj->name; + + // 忽略 Laravel 內部 migration 表 + if ($table === 'migrations') { + continue; + } + + $this->info("📦 Transferring table: {$table}"); + + try { + // 從 SQLite 資料庫讀取資料 + $records = DB::connection('sqlite')->table($table)->get(); + + if ($records->isEmpty()) { + $this->warn("⚠️ Table {$table} has no data."); + continue; + } + + // 資料分批處理,避免一次插入過多資料造成效能問題 + $chunks = $records->chunk(500); + foreach ($chunks as $chunk) { + DB::connection($mysqlConnection)->table($table)->insert( + $chunk->map(fn ($row) => (array) $row)->toArray() + ); + } + + $this->info("✅ Done: {$table}"); + } catch (\Exception $e) { + $this->error("❌ Failed to transfer {$table}: " . $e->getMessage()); + } + } + + $this->info("🎉 Transfer complete!"); + return 0; + } +} \ No newline at end of file diff --git a/app/Jobs/ImportArtistChunkJob.php b/app/Jobs/ImportArtistChunkJob.php index 5a182b8..902f8f6 100644 --- a/app/Jobs/ImportArtistChunkJob.php +++ b/app/Jobs/ImportArtistChunkJob.php @@ -24,23 +24,23 @@ class ImportArtistChunkJob implements ShouldQueue public function handle(): void { + $artists = collect(); + foreach ($this->rows as $index => $row) { try { $name = trim($row['歌手姓名'] ?? ''); - if (empty($name)) { - continue; - } - if (Artist::where('name', $name)->exists()) { + if (empty($name) || Artist::where('name', $name)->exists()) { continue; } - Artist::create([ + $artist = new Artist([ 'name' => $name, 'category' => ArtistCategory::tryFrom(trim($row['歌手分類'] ?? '未定義')) ?? ArtistCategory::Unset, 'enable' => trim($row['狀態'] ?? 1), ]); + $artists->push($artist); } catch (\Throwable $e) { \Log::error("Row {$index} failed: {$e->getMessage()}", [ 'row' => $row, @@ -48,5 +48,6 @@ class ImportArtistChunkJob implements ShouldQueue ]); } } + $artists->each(fn ($artist) => $artist->save()); } } \ No newline at end of file diff --git a/app/Jobs/ImportSongChunkJob.php b/app/Jobs/ImportSongChunkJob.php index 6810508..5f051b0 100644 --- a/app/Jobs/ImportSongChunkJob.php +++ b/app/Jobs/ImportSongChunkJob.php @@ -48,7 +48,7 @@ class ImportSongChunkJob implements ShouldQueue // 準備 song 資料 $song = new Song([ 'id' => $songId, - 'name' => trim($row['歌名'] ?? ''), + 'name' => $this->formatText($row['歌名']), 'adddate' => $this->parseExcelDate($row['日期'] ?? null), 'filename' => trim($row['檔名'] ?? ''), 'language_type' => SongLanguageType::tryFrom(trim($row['語別'] ?? '')) ?? SongLanguageType::Unset, @@ -113,6 +113,16 @@ class ImportSongChunkJob implements ShouldQueue return $this->artistCache[$name] = $artist->id; } + 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); + } private function parseExcelDate($value): ?string { if (is_numeric($value)) { diff --git a/app/Models/Artist.php b/app/Models/Artist.php index e02bfbe..de7989d 100644 --- a/app/Models/Artist.php +++ b/app/Models/Artist.php @@ -45,7 +45,7 @@ class Artist extends Model $chars = preg_split('//u', $artist->name, -1, PREG_SPLIT_NO_EMPTY); $firstChar = $chars[0] ?? null; - $artist->strokes_abbr=$firstChar ? ChineseStrokesConverter::getStrokes($firstChar) : null; + $artist->strokes_abbr=( $firstChar && preg_match('/\p{Han}/u', $firstChar) ) ? ChineseStrokesConverter::getStrokes($firstChar) : 0; }); } diff --git a/app/Models/Song.php b/app/Models/Song.php index a3a3258..762e330 100644 --- a/app/Models/Song.php +++ b/app/Models/Song.php @@ -78,7 +78,8 @@ class Song extends Model $chars = preg_split('//u', $song->name, -1, PREG_SPLIT_NO_EMPTY); $firstChar = $chars[0] ?? null; - $song->strokes_abbr=$firstChar ? ChineseStrokesConverter::getStrokes($firstChar) : null; + + $song->strokes_abbr=($firstChar && preg_match('/\p{Han}/u', $firstChar)) ? ChineseStrokesConverter::getStrokes($firstChar) : 0; $song->song_number = mb_strlen($song->name, 'UTF-8'); }); } diff --git a/database/.DS_Store b/database/.DS_Store index b1ea27f26ce3918826e777f505cab72016243956..780579599a8d83aa7068b7c2f1386b758910a918 100644 GIT binary patch delta 18 acmZoMXfc?uX5+>%_K6Lwo7p-3@&f=#ng>V# delta 20 ccmZoMXfc?uhLLgO#xVAY4J@14IsWnk08aS_NdN!< diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php index 425e705..bdd0253 100644 --- a/database/migrations/0001_01_01_000002_create_jobs_table.php +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -16,8 +16,8 @@ return new class extends Migration $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); - $table->unsignedInteger('reserved_at')->nullable(); - $table->unsignedInteger('available_at'); + $table->timestamp('reserved_at')->nullable(); + $table->timestamp('available_at'); $table->unsignedInteger('created_at'); }); @@ -29,9 +29,9 @@ return new class extends Migration $table->integer('failed_jobs'); $table->longText('failed_job_ids'); $table->mediumText('options')->nullable(); - $table->integer('cancelled_at')->nullable(); - $table->integer('created_at'); - $table->integer('finished_at')->nullable(); + $table->timestamp('cancelled_at')->nullable(); + $table->timestamp('created_at'); + $table->timestamp('finished_at')->nullable(); }); Schema::create('failed_jobs', function (Blueprint $table) { diff --git a/database/seeders/SqliteToMysqlSeeder.php b/database/seeders/KSongDatabaseToMysqlSeeder.php similarity index 100% rename from database/seeders/SqliteToMysqlSeeder.php rename to database/seeders/KSongDatabaseToMysqlSeeder.php diff --git a/storage/.DS_Store b/storage/.DS_Store index 8a1a6ecd3a9d1044f77dbf5b86edb417601b4abc..9e38deb4fa0ed377a1f57254306c0d9bd026c555 100644 GIT binary patch delta 150 zcmZoMXfc@JFT%>ez`)4BAi$8Ela!yI17tFAZdPPo!DtMUVP;5VC}1c^DMpfINMk4h z3gWa<)aocyTN)YYD3}HM+K07BjFTZ2*J0^L?&dq$x PKUpR=C~juw_{$FfhFl+8 diff --git a/開發手冊.ini b/開發手冊.ini index a93bc50..2ced5c6 100644 --- a/開發手冊.ini +++ b/開發手冊.ini @@ -104,4 +104,10 @@ cp .env.example .env php artisan key:generate npm install && npm run build -php artisan migrate \ No newline at end of file +php artisan migrate + + + +php artisan migrate:rollback +php artisan migrate +php artisan transfer:sqlite-to-mysql \ No newline at end of file