Song 調整歌曲名稱滙入錯誤問題

加入 sqllitToMysql
20250510
This commit is contained in:
allen.yan 2025-05-10 09:00:14 +08:00
parent 580f3c7f9b
commit 75e41c43ef
11 changed files with 108 additions and 14 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,76 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Config;
class TransferSqliteToMysql extends Command
{
protected $signature = 'transfer:sqlite-to-mysql';
protected $description = 'Transfer all data from SQLite to MySQL, optionally truncating tables first.';
public function handle(): int
{
// ✅ 自訂 SQLite 連線路徑(請修改為你實際的檔案位置)
Config::set('database.connections.sqlite', [
'driver' => '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;
}
}

View File

@ -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());
}
}

View File

@ -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)) {

View File

@ -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;
});
}

View File

@ -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');
});
}

BIN
database/.DS_Store vendored

Binary file not shown.

View File

@ -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) {

BIN
storage/.DS_Store vendored

Binary file not shown.

View File

@ -104,4 +104,10 @@ cp .env.example .env
php artisan key:generate
npm install && npm run build
php artisan migrate
php artisan migrate
php artisan migrate:rollback
php artisan migrate
php artisan transfer:sqlite-to-mysql