後台資料庫 往中控傳 20250526
This commit is contained in:
parent
8bdab7c415
commit
4f37c90a0b
46
app/Console/Commands/ClearMachineStatuses.php
Normal file
46
app/Console/Commands/ClearMachineStatuses.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class ClearMachineStatuses extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:clear-machine-statuses';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '備份並清空 machine_statuses 表,每週保留一次';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$day = now()->format('l'); // e.g. "Monday"
|
||||||
|
$targetTable = "machine_statuses_" . $day;
|
||||||
|
|
||||||
|
DB::statement("CREATE TABLE IF NOT EXISTS _machine_statuses LIKE machine_statuses");
|
||||||
|
|
||||||
|
// 先刪除舊表(如存在)
|
||||||
|
DB::statement("DROP TABLE IF EXISTS {$targetTable}");
|
||||||
|
|
||||||
|
// 改名備份
|
||||||
|
DB::statement("RENAME TABLE machine_statuses TO {$targetTable}");
|
||||||
|
|
||||||
|
// 空表回命名
|
||||||
|
DB::statement("RENAME TABLE _machine_statuses TO machine_statuses");
|
||||||
|
|
||||||
|
$this->info("Machine statuses backed up to {$targetTable} and table cleared.");
|
||||||
|
}
|
||||||
|
}
|
88
app/Console/Commands/ExportSqlite.php
Normal file
88
app/Console/Commands/ExportSqlite.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Jobs\ExportSqliteUserJob;
|
||||||
|
use App\Jobs\ExportSqliteSongJob;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
|
||||||
|
class ExportSqlite extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 指令名稱與參數定義
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'export:sqlite
|
||||||
|
{type : The type of data to export (song, user, all)}
|
||||||
|
{--sync : Run the export job synchronously (without queue)}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指令描述
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Export data from the database to SQLite (song, user, or both).';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 執行指令
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$start = now();
|
||||||
|
$type = strtolower($this->argument('type'));
|
||||||
|
|
||||||
|
$this->info("[Export] 開始匯出資料類型: {$type}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!in_array($type, ['song', 'user', 'all'])) {
|
||||||
|
$this->error('[Export] 無效的 type,請使用:song、user 或 all');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sync = $this->option('sync');
|
||||||
|
|
||||||
|
if ($sync) {
|
||||||
|
$this->warn('[Export] 使用同步模式執行...');
|
||||||
|
|
||||||
|
if ($type === 'song' || $type === 'all') {
|
||||||
|
(new ExportSqliteSongJob())->handle();
|
||||||
|
}
|
||||||
|
if ($type === 'FavoriteSongs' || $type === 'all') {
|
||||||
|
(new ExportSqliteFavoriteJob())->handle();
|
||||||
|
}
|
||||||
|
if ($type === 'user' || $type === 'all') {
|
||||||
|
(new ExportSqliteUserJob())->handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('[Export] 匯出完成(同步)');
|
||||||
|
} else {
|
||||||
|
if ($type === 'all') {
|
||||||
|
// 確保 user -> song 順序
|
||||||
|
Bus::chain([
|
||||||
|
new ExportSqliteUserJob(),
|
||||||
|
new ExportSqliteFavoriteJob(),
|
||||||
|
new ExportSqliteSongJob(),
|
||||||
|
])->dispatch();
|
||||||
|
} elseif ($type === 'song') {
|
||||||
|
} elseif ($type === 'FavoriteSongs') {
|
||||||
|
ExportSqliteSongJob::dispatch();
|
||||||
|
} elseif ($type === 'user') {
|
||||||
|
ExportSqliteUserJob::dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('[Export] 匯出任務已派送至 queue');
|
||||||
|
}
|
||||||
|
|
||||||
|
$duration = now()->diffInSeconds($start);
|
||||||
|
$this->info("[Export] 執行完成,用時 {$duration} 秒");
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->error('[Export] 發生錯誤:' . $e->getMessage());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
71
app/Console/Commands/SendSqliteFile.php
Normal file
71
app/Console/Commands/SendSqliteFile.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Services\ApiClient;
|
||||||
|
|
||||||
|
class SendSqliteFile extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'sqlite:send
|
||||||
|
{filename : The sqlite filename (e.g. tempUser.sqlite)}
|
||||||
|
{--url=https://ktvcentral.test/api/upload-sqlite : Target full API URL}
|
||||||
|
{--token= : Optional Bearer Token}
|
||||||
|
{--param=* : Optional extra POST data in key=value format (e.g. user_id=123)}';
|
||||||
|
|
||||||
|
protected $description = 'Send a sqlite file to remote server with optional token and data';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$filename = $this->argument('filename');
|
||||||
|
$url = $this->option('url');
|
||||||
|
$token = $this->option('token');
|
||||||
|
$params = $this->option('param'); // key=value
|
||||||
|
|
||||||
|
$filePath = storage_path("app/database/{$filename}");
|
||||||
|
|
||||||
|
if (!file_exists($filePath)) {
|
||||||
|
$this->error("❌ 檔案不存在: {$filePath}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpoint = parse_url($url, PHP_URL_PATH);
|
||||||
|
$baseUrl = str_replace($endpoint, '', $url);
|
||||||
|
|
||||||
|
// 處理額外參數
|
||||||
|
$data = [];
|
||||||
|
foreach ($params as $pair) {
|
||||||
|
if (str_contains($pair, '=')) {
|
||||||
|
[$key, $value] = explode('=', $pair, 2);
|
||||||
|
$data[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("📤 傳送檔案 {$filename} 到 {$url} 中...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = new ApiClient($token, $baseUrl);
|
||||||
|
$response = $client->upload($endpoint, ['file' => $filePath], $data);
|
||||||
|
|
||||||
|
if ($response->successful()) {
|
||||||
|
$this->info("✅ 傳送成功!");
|
||||||
|
$this->info("🔁 回應內容: " . $response->body());
|
||||||
|
} else {
|
||||||
|
$this->error("❌ 傳送失敗:HTTP {$response->status()}");
|
||||||
|
$this->error($response->body());
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error("❌ 發生錯誤:" . $e->getMessage());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
php artisan sqlite:send tempUser.sqlite \
|
||||||
|
--url=https://ktvcentral.test/api/upload-sqlite \
|
||||||
|
--token=abc123 \
|
||||||
|
--param=user_id=888 --param=env=prod
|
||||||
|
*/
|
62
app/Jobs/ExportSqliteFavoriteJob.php
Normal file
62
app/Jobs/ExportSqliteFavoriteJob.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Services\SqliteExportService;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
|
||||||
|
class ExportSqliteFavoriteJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 600; // 可依資料量調整 timeout 秒數
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$sqlitePath = storage_path('app/database/tempFavorite.sqlite');
|
||||||
|
|
||||||
|
// 確保資料夾存在
|
||||||
|
if (!file_exists(dirname($sqlitePath))) {
|
||||||
|
mkdir(dirname($sqlitePath), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果檔案不存在就建立空檔案
|
||||||
|
if (!file_exists($sqlitePath)) {
|
||||||
|
file_put_contents($sqlitePath, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['database.connections.tempsqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'database' => $sqlitePath,
|
||||||
|
'prefix' => '',
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$exporter = new SqliteExportService();
|
||||||
|
$exporter->exportMultiple([
|
||||||
|
'FavoriteSongs' => [
|
||||||
|
'query' => fn () => DB::table('FavoriteSongs'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('songNumber',20);
|
||||||
|
$table->string('userPhone', 10);
|
||||||
|
$table->timestamps();
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'songNumber' => $row->songNumber,
|
||||||
|
'userPhone' => $row->userPhone,
|
||||||
|
'created_at' => $row->created_at,
|
||||||
|
'updated_at' => $row->updated_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
SendSqliteFileJob::dispatch($sqlitePath);
|
||||||
|
}
|
||||||
|
}
|
149
app/Jobs/ExportSqliteSongJob.php
Normal file
149
app/Jobs/ExportSqliteSongJob.php
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Song;
|
||||||
|
use App\Models\Artist;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\SqliteExportService;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
|
||||||
|
class ExportSqliteSongJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 600; // 可依資料量調整 timeout 秒數
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$sqlitePath = storage_path('app/database/tempSong.sqlite');
|
||||||
|
|
||||||
|
// 確保資料夾存在
|
||||||
|
if (!file_exists(dirname($sqlitePath))) {
|
||||||
|
mkdir(dirname($sqlitePath), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果檔案不存在就建立空檔案
|
||||||
|
if (!file_exists($sqlitePath)) {
|
||||||
|
file_put_contents($sqlitePath, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['database.connections.tempsqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'database' => $sqlitePath,
|
||||||
|
'prefix' => '',
|
||||||
|
]]);
|
||||||
|
|
||||||
|
Schema::connection('tempsqlite')->dropIfExists('song_library_cache');
|
||||||
|
Schema::connection('tempsqlite')->create('song_library_cache', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('song_id')->comment('歌曲編號');
|
||||||
|
$table->string('song_name')->nullable()->index()->comment('歌曲檔名');
|
||||||
|
$table->string('song_simplified')->nullable()->index()->comment('歌曲簡體');
|
||||||
|
$table->string('phonetic_abbr')->nullable()->index()->comment('歌曲注音');
|
||||||
|
$table->string('pinyin_abbr')->nullable()->index()->comment('歌曲拼音');
|
||||||
|
$table->integer('strokes_abbr')->default(0)->index()->comment('歌曲筆劃');
|
||||||
|
$table->integer('song_number')->default(0)->index()->comment('歌曲字數');
|
||||||
|
$table->string('artistA')->nullable()->index()->comment('歌星名稱A');
|
||||||
|
$table->string('artistB')->nullable()->index()->comment('歌星名稱B');
|
||||||
|
$table->string('artistA_simplified')->nullable()->index()->comment('歌星簡體名稱A');
|
||||||
|
$table->string('artistB_simplified')->nullable()->index()->comment('歌星簡體名稱B');
|
||||||
|
$table->string('artistA_category')->nullable()->default('未定義')->index()->comment('歌星類別A');
|
||||||
|
$table->string('artistB_category')->nullable()->default('未定義')->index()->comment('歌星類別B');
|
||||||
|
$table->string('artist_category')->nullable()->default('未定義')->index()->comment('歌星類別');
|
||||||
|
$table->string('song_filename')->nullable()->comment('歌曲檔名');
|
||||||
|
$table->string('song_category')->nullable()->comment('歌曲分類');
|
||||||
|
$table->string('language_name')->nullable()->default('未定義')->index()->comment('語別');
|
||||||
|
$table->date('add_date')->nullable()->index()->comment('新增日期');
|
||||||
|
$table->string('situation')->nullable()->default('未定義')->index()->comment('情境');
|
||||||
|
$table->tinyInteger('vocal')->default(0)->index()->comment('人聲'); // 0,1
|
||||||
|
$table->integer('db_change')->default(0)->index()->comment('DB加減');
|
||||||
|
$table->integer('song_counts')->default(0)->index()->comment('點播次數');
|
||||||
|
$table->dateTime('updated_at')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$totalInserted = 0;
|
||||||
|
|
||||||
|
Song::with(['artists', 'categories'])->chunk(500, function ($songs) use (&$totalInserted)
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
foreach ($songs as $song) {
|
||||||
|
$sortedArtists = $song->artists->sortBy('id')->values();
|
||||||
|
$artistA = $sortedArtists->get(0);
|
||||||
|
$artistB = $sortedArtists->get(1);
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
'song_id' => $song->id,
|
||||||
|
'song_name' => $song->name,
|
||||||
|
'song_simplified' => $song->simplified ,
|
||||||
|
'phonetic_abbr' => $song->phonetic_abbr ?? '',
|
||||||
|
'pinyin_abbr' => $song->pinyin_abbr ?? '',
|
||||||
|
'strokes_abbr' => $song->strokes_abbr ?? 0,
|
||||||
|
'song_number' => $song->song_number ?? 0,
|
||||||
|
'artistA' => $artistA?->name,
|
||||||
|
'artistB' => $artistB?->name,
|
||||||
|
'artistA_simplified' => $artistA?->simplified,
|
||||||
|
'artistB_simplified' => $artistB?->simplified,
|
||||||
|
'artistA_category' => $artistA?->category?->value ?? '未定義',
|
||||||
|
'artistB_category' => $artistB?->category?->value ?? '未定義',
|
||||||
|
'artist_category' => in_array(\App\Enums\ArtistCategory::Group->value, [
|
||||||
|
$artistA?->category?->value,
|
||||||
|
$artistB?->category?->value,
|
||||||
|
]) ? '團' : '未定義',
|
||||||
|
'song_filename' => $song->filename,
|
||||||
|
'song_category'=>$song->categories->pluck('code')->unique()->sort()->implode(', '),
|
||||||
|
'language_name' => $song->language_type ?? '未定義',
|
||||||
|
'add_date' => $song->adddate,
|
||||||
|
'situation' => $song->situation?->value ?? '未定義',
|
||||||
|
'vocal' => $song->vocal,
|
||||||
|
'db_change' => $song->db_change,
|
||||||
|
'song_counts' => $song->song_counts ?? 0,
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
collect($rows)->chunk(1000)->each(function ($chunk) use (&$totalInserted) {
|
||||||
|
DB::connection('tempsqlite')->table('song_library_cache')->insert($chunk->toArray());
|
||||||
|
$totalInserted += $chunk->count();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$exporter = new SqliteExportService();
|
||||||
|
$exporter->exportMultiple([
|
||||||
|
'artists' => [
|
||||||
|
'modelClass' => Artist::class,
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('category')->default('未定義')->index();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->string('simplified')->index();
|
||||||
|
$table->string('phonetic_abbr')->index();
|
||||||
|
$table->string('pinyin_abbr')->index();
|
||||||
|
$table->integer('strokes_abbr')->index();
|
||||||
|
$table->tinyInteger('enable')->default(1);
|
||||||
|
$table->dateTime('updated_at')->nullable();
|
||||||
|
},
|
||||||
|
'transformer' => fn (Artist $artist) => [
|
||||||
|
'id' => $artist->id,
|
||||||
|
'category' => $artist->category?->value ?? '未定義',
|
||||||
|
'name' => $artist->name,
|
||||||
|
'simplified' => $artist->simplified,
|
||||||
|
'phonetic_abbr' => $artist->phonetic_abbr ?? '',
|
||||||
|
'pinyin_abbr' => $artist->pinyin_abbr ?? '',
|
||||||
|
'strokes_abbr' => $artist->strokes_abbr ?? 0,
|
||||||
|
'enable' => $artist->enable,
|
||||||
|
'updated_at' => now(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
SendSqliteFileJob::dispatch($sqlitePath);
|
||||||
|
}
|
||||||
|
}
|
205
app/Jobs/ExportSqliteUserJob.php
Normal file
205
app/Jobs/ExportSqliteUserJob.php
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Song;
|
||||||
|
use App\Models\Artist;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\SqliteExportService;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
|
||||||
|
class ExportSqliteUserJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 600; // 可依資料量調整 timeout 秒數
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$sqlitePath = storage_path('app/database/tempUser.sqlite');
|
||||||
|
|
||||||
|
// 確保資料夾存在
|
||||||
|
if (!file_exists(dirname($sqlitePath))) {
|
||||||
|
mkdir(dirname($sqlitePath), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果檔案不存在就建立空檔案
|
||||||
|
if (!file_exists($sqlitePath)) {
|
||||||
|
file_put_contents($sqlitePath, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['database.connections.tempsqlite' => [
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'database' => $sqlitePath,
|
||||||
|
'prefix' => '',
|
||||||
|
]]);
|
||||||
|
|
||||||
|
$exporter = new SqliteExportService();
|
||||||
|
$exporter->exportMultiple([
|
||||||
|
// --- users ---
|
||||||
|
'users' => [
|
||||||
|
'modelClass' => User::class,
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('phone', 10)->unique();
|
||||||
|
$table->date('birthday')->nullable(); // 生日
|
||||||
|
$table->string('gender')->default('unset'); // 性別
|
||||||
|
$table->tinyInteger('status')->default(0); // 啟動
|
||||||
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
|
$table->string('password');
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->text('api_plain_token')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
},
|
||||||
|
'transformer' => fn (User $user) => [
|
||||||
|
'name' => $user->name,
|
||||||
|
'email' => $user->email,
|
||||||
|
'phone' => $user->phone,
|
||||||
|
'birthday' => $user->birthday,
|
||||||
|
'gender' => $user->gender?->value ?? 'unset',
|
||||||
|
'status' => $user->status,
|
||||||
|
'email_verified_at' => $user->email_verified_at,
|
||||||
|
'password' => $user->password,
|
||||||
|
'remember_token' => $user->remember_token,
|
||||||
|
'api_plain_token' => $user->api_plain_token,
|
||||||
|
'created_at' => $user->created_at,
|
||||||
|
'updated_at' => $user->updated_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// --- password_reset_tokens ---
|
||||||
|
'password_reset_tokens' => [
|
||||||
|
'query' => fn () => DB::table('password_reset_tokens'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->string('email')->index();
|
||||||
|
$table->string('token');
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'email' => $row->email,
|
||||||
|
'token' => $row->token,
|
||||||
|
'created_at' => $row->created_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// --- personal_access_tokens ---
|
||||||
|
'personal_access_tokens' => [
|
||||||
|
'query' => fn () => DB::table('personal_access_tokens'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->morphs('tokenable');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->text('abilities')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'id' => $row->id,
|
||||||
|
'tokenable_type' => $row->tokenable_type,
|
||||||
|
'tokenable_id' => $row->tokenable_id,
|
||||||
|
'name' => $row->name,
|
||||||
|
'token' => $row->token,
|
||||||
|
'abilities' => $row->abilities,
|
||||||
|
'last_used_at' => $row->last_used_at,
|
||||||
|
'expires_at' => $row->expires_at,
|
||||||
|
'created_at' => $row->created_at,
|
||||||
|
'updated_at' => $row->updated_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// --- roles ---
|
||||||
|
'roles' => [
|
||||||
|
'query' => fn () => DB::table('roles'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('guard_name');
|
||||||
|
$table->timestamps();
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'id' => $row->id,
|
||||||
|
'name' => $row->name,
|
||||||
|
'guard_name' => $row->guard_name,
|
||||||
|
'created_at' => $row->created_at,
|
||||||
|
'updated_at' => $row->updated_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// --- permissions ---
|
||||||
|
'permissions' => [
|
||||||
|
'query' => fn () => DB::table('permissions'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('guard_name');
|
||||||
|
$table->timestamps();
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'id' => $row->id,
|
||||||
|
'name' => $row->name,
|
||||||
|
'guard_name' => $row->guard_name,
|
||||||
|
'created_at' => $row->created_at,
|
||||||
|
'updated_at' => $row->updated_at,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// --- role_has_permissions ---
|
||||||
|
'role_has_permissions' => [
|
||||||
|
'query' => fn () => DB::table('role_has_permissions'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('permission_id');
|
||||||
|
$table->unsignedBigInteger('role_id');
|
||||||
|
$table->primary(['permission_id', 'role_id']);
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'permission_id' => $row->permission_id,
|
||||||
|
'role_id' => $row->role_id,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// --- model_has_roles ---
|
||||||
|
'model_has_roles' => [
|
||||||
|
'query' => fn () => DB::table('model_has_roles'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('role_id');
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger('model_id');
|
||||||
|
$table->primary(['role_id', 'model_id', 'model_type'], 'model_has_roles_primary');
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'role_id' => $row->role_id,
|
||||||
|
'model_type' => $row->model_type,
|
||||||
|
'model_id' => $row->model_id,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// --- model_has_permissions ---
|
||||||
|
'model_has_permissions' => [
|
||||||
|
'query' => fn () => DB::table('model_has_permissions'),
|
||||||
|
'tableSchema' => function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('permission_id');
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger('model_id');
|
||||||
|
$table->index(['model_id', 'model_type']);
|
||||||
|
$table->primary(['permission_id', 'model_id', 'model_type']);
|
||||||
|
},
|
||||||
|
'transformer' => fn ($row) => [
|
||||||
|
'permission_id' => $row->permission_id,
|
||||||
|
'model_type' => $row->model_type,
|
||||||
|
'model_id' => $row->model_id,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
SendSqliteFileJob::dispatch($sqlitePath);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
55
app/Jobs/SendSqliteFileJob.php
Normal file
55
app/Jobs/SendSqliteFileJob.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Branch;
|
||||||
|
use App\Services\ApiClient;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class SendSqliteFileJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected string $filename;
|
||||||
|
protected ?int $branchId;
|
||||||
|
|
||||||
|
public function __construct(string $filename, ?int $branchId = null)
|
||||||
|
{
|
||||||
|
$this->filename = $filename;
|
||||||
|
$this->branchId = $branchId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$path = storage_path($this->filename);
|
||||||
|
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
Log::error("❌ SQLite 檔案不存在: {$path}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$user = \App\Models\User::find(2);
|
||||||
|
$token = $user->api_plain_token;
|
||||||
|
|
||||||
|
$branches = $this->branchId
|
||||||
|
? Branch::where('id', $this->branchId)->where('enabled', true)->get()
|
||||||
|
: Branch::where('enabled', true)->cursor();
|
||||||
|
|
||||||
|
foreach ($branches as $branch) {
|
||||||
|
$client = new ApiClient($token, $branch->external_ip);
|
||||||
|
|
||||||
|
$response = $client->upload('/api/upload-sqlite', ['file' => $path]);
|
||||||
|
|
||||||
|
if ($response->successful()) {
|
||||||
|
Log::info("✅ 檔案 {$this->filename} 傳送成功");
|
||||||
|
} else {
|
||||||
|
Log::error("❌ 傳送失敗:HTTP {$response->status()}");
|
||||||
|
Log::error($response->body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,34 +8,51 @@ class ApiClient
|
|||||||
protected string $baseUrl;
|
protected string $baseUrl;
|
||||||
protected string $token;
|
protected string $token;
|
||||||
|
|
||||||
public function __construct(string $token = null)
|
public function __construct(string $token = null, ?string $baseUrl = null)
|
||||||
{
|
{
|
||||||
$this->baseUrl = config('services.room_api.base_url', 'https://ktv.test/api');
|
$this->baseUrl = rtrim($baseUrl ?? config('services.room_api.base_url', 'https://ktv.test/api'), '/');
|
||||||
$this->token = $token ?? config('services.room_api.token');
|
$this->token = $token ?? config('services.room_api.token');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setToken(string $token): self
|
public function setToken(string $token): self
|
||||||
{
|
{
|
||||||
$this->token = $token;
|
$this->token = $token;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setBaseUrl(string $url): self
|
||||||
|
{
|
||||||
|
$this->baseUrl = rtrim($url, '/');
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function withDefaultHeaders(): \Illuminate\Http\Client\PendingRequest
|
public function withDefaultHeaders(): \Illuminate\Http\Client\PendingRequest
|
||||||
{
|
{
|
||||||
return Http::withHeaders([
|
return Http::withHeaders([
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Authorization' => 'Bearer ' . config('services.room_api.token'),
|
'Authorization' => 'Bearer ' . $this->token,
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function post(string $endpoint, array $data = [])
|
|
||||||
{
|
|
||||||
return $this->withDefaultHeaders()->post($this->baseUrl . $endpoint, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get(string $endpoint, array $query = [])
|
public function get(string $endpoint, array $query = [])
|
||||||
{
|
{
|
||||||
return $this->withDefaultHeaders()->get($this->baseUrl . $endpoint, $query);
|
return $this->withDefaultHeaders()->get("{$this->baseUrl}{$endpoint}", $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function post(string $endpoint, array $data = [])
|
||||||
|
{
|
||||||
|
return $this->withDefaultHeaders()->post("{$this->baseUrl}{$endpoint}", $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upload(string $endpoint, array $files = [], array $data = [])
|
||||||
|
{
|
||||||
|
$request = $this->withDefaultHeaders();
|
||||||
|
|
||||||
|
foreach ($files as $key => $filePath) {
|
||||||
|
$filename = basename($filePath);
|
||||||
|
$request = $request->attach($key, fopen($filePath, 'r'), $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->withoutVerifying()->post("{$this->baseUrl}{$endpoint}", $data);
|
||||||
|
}
|
||||||
}
|
}
|
99
app/Services/SqliteExportService.php
Normal file
99
app/Services/SqliteExportService.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
class SqliteExportService
|
||||||
|
{
|
||||||
|
protected string $connection;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->connection = 'tempsqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匯出單一模型資料到 SQLite 表。
|
||||||
|
*
|
||||||
|
* @param class-string<Model> $modelClass
|
||||||
|
* @param string $tableName
|
||||||
|
* @param Closure(Blueprint): void $tableSchema
|
||||||
|
* @param Closure(Model): array $transformer
|
||||||
|
* @param int $chunkSize
|
||||||
|
*/
|
||||||
|
public function exportTableFromModel(
|
||||||
|
string $modelClass,
|
||||||
|
string $tableName,
|
||||||
|
Closure $tableSchema,
|
||||||
|
Closure $transformer,
|
||||||
|
int $chunkSize = 1000
|
||||||
|
): void {
|
||||||
|
$this->dropAndCreateTable($tableName, $tableSchema);
|
||||||
|
|
||||||
|
$modelInstance = new $modelClass;
|
||||||
|
|
||||||
|
$modelInstance->newQuery()->orderBy('id')->chunk($chunkSize, function (Collection $chunk) use ($tableName, $transformer) {
|
||||||
|
$rows = $chunk->map($transformer)->toArray();
|
||||||
|
$this->insertData($tableName, $rows);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批次匯出多張表
|
||||||
|
*
|
||||||
|
* @param array<string, array{
|
||||||
|
* modelClass?: class-string<Model>,
|
||||||
|
* query?: Closure(): \Illuminate\Support\Collection,
|
||||||
|
* tableSchema: Closure(Blueprint): void,
|
||||||
|
* transformer: Closure(Model): array,
|
||||||
|
* chunkSize?: int
|
||||||
|
* }> $tables
|
||||||
|
*/
|
||||||
|
public function exportMultiple(array $tables): void
|
||||||
|
{
|
||||||
|
foreach ($tables as $tableName => $config) {
|
||||||
|
$this->dropAndCreateTable($tableName, $config['tableSchema']);
|
||||||
|
$transformer = $config['transformer'] ?? fn($row) => (array)$row;
|
||||||
|
|
||||||
|
if (isset($config['modelClass'])) {
|
||||||
|
$modelClass = $config['modelClass'];
|
||||||
|
$chunkSize = $config['chunkSize'] ?? 1000;
|
||||||
|
$modelInstance = new $modelClass;
|
||||||
|
|
||||||
|
$modelInstance->newQuery()->chunk($chunkSize, function (Collection $chunk) use ($tableName, $transformer) {
|
||||||
|
$rows = $chunk->map($transformer)->toArray();
|
||||||
|
$this->insertData($tableName, $rows);
|
||||||
|
});
|
||||||
|
} elseif (isset($config['query']) && is_callable($config['query'])) {
|
||||||
|
$rows = call_user_func($config['query']);
|
||||||
|
|
||||||
|
if ($rows instanceof \Illuminate\Database\Query\Builder || $rows instanceof \Illuminate\Database\Eloquent\Builder) {
|
||||||
|
$rows = $rows->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $rows->map($transformer)->toArray();
|
||||||
|
$this->insertData($tableName, $data);
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException("Each table config must define either 'modelClass' or 'query'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function dropAndCreateTable(string $table, Closure $schema): void
|
||||||
|
{
|
||||||
|
Schema::connection($this->connection)->dropIfExists($table);
|
||||||
|
Schema::connection($this->connection)->create($table, $schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function insertData(string $table, array $rows): void
|
||||||
|
{
|
||||||
|
if (empty($rows)) return;
|
||||||
|
DB::connection($this->connection)->table($table)->insert($rows);
|
||||||
|
}
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('song_library_cache', function (Blueprint $table) {
|
|
||||||
$table->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');
|
|
||||||
}
|
|
||||||
};
|
|
@ -29,6 +29,9 @@ class CreateAdminUserSeeder extends Seeder
|
|||||||
'password' => bcrypt('aa147258-')
|
'password' => bcrypt('aa147258-')
|
||||||
]);
|
]);
|
||||||
$user->assignRole('Machine');
|
$user->assignRole('Machine');
|
||||||
|
$token = $user->createToken('pc-heartbeat')->plainTextToken;
|
||||||
|
$user->api_plain_token = $token;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => 'Allen Yan(User)',
|
'name' => 'Allen Yan(User)',
|
||||||
|
@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
use Illuminate\Foundation\Inspiring;
|
use Illuminate\Foundation\Inspiring;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Artisan::command('inspire', function () {
|
Artisan::command('inspire', function () {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote');
|
})->purpose('Display an inspiring quote');
|
||||||
|
|
||||||
|
|
||||||
|
Schedule::command('machine_statuses:clear')->dailyAt('12:00'); // 每天凌晨 12:10 執行
|
||||||
|
//首次部署或有新增命令時)建立或更新任務排程 Crontab
|
||||||
|
// 檢查是否已有下列 crontab 設定(crontab -e):
|
||||||
|
//分鐘 小時 日 月 星期 指令
|
||||||
|
// * * * * * cd /Users/allen.yan/work/KTV && php artisan schedule:run >> /dev/null 2>&1
|
49
更新後部署流程(建議步驟).ini
Normal file
49
更新後部署流程(建議步驟).ini
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
✅ Laravel 更新後部署流程(建議步驟)
|
||||||
|
|
||||||
|
1. 拉取新版程式碼
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
2. 安裝依賴套件
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
|
||||||
|
|
||||||
|
3. 執行資料庫 migration(如有 schema 變更)
|
||||||
|
php artisan migrate
|
||||||
|
|
||||||
|
4. 清除並重新快取設定與路由
|
||||||
|
php artisan config:clear
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:clear
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:clear
|
||||||
|
php artisan view:cache
|
||||||
|
|
||||||
|
5. (首次部署或有新增命令時)建立或更新任務排程 Crontab
|
||||||
|
檢查是否已有下列 crontab 設定(crontab -e):
|
||||||
|
分鐘 小時 日 月 星期 指令
|
||||||
|
* * * * * cd /path/to/your/project && php artisan schedule:run >> /dev/null 2>&1
|
||||||
|
這樣 Laravel 才能自動執行你在 routes/console.php 中定義的排程任務。
|
||||||
|
|
||||||
|
6. (選擇性)部署完立即執行某些 Artisan 指令
|
||||||
|
例如你可能希望部署後立即重建一次機器狀態資料表,可以執行:
|
||||||
|
php artisan machine_statuses:clear
|
||||||
|
|
||||||
|
7. 權限與快取設定(根據伺服器環境)
|
||||||
|
確認 storage 和 bootstrap/cache 目錄權限正確:
|
||||||
|
chmod -R 775 storage bootstrap/cache
|
||||||
|
chown -R www-data:www-data storage bootstrap/cache
|
||||||
|
|
||||||
|
✅ 完整部署腳本範例(可寫成 deploy.sh)
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd /var/www/your-project
|
||||||
|
|
||||||
|
git pull origin main
|
||||||
|
composer install --no-dev --optimize-autoloader
|
||||||
|
php artisan migrate --force
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan machine_statuses:clear
|
||||||
|
|
||||||
|
echo "✅ Laravel 專案已更新並執行完成。"
|
6
開發手冊.ini
6
開發手冊.ini
@ -129,3 +129,9 @@ composer require "darkaonline/l5-swagger"
|
|||||||
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
|
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
|
||||||
php -d memory_limit=512M artisan l5-swagger:generate
|
php -d memory_limit=512M artisan l5-swagger:generate
|
||||||
|
|
||||||
|
php artisan export:sqlite song --sync # 同步匯出歌曲
|
||||||
|
php artisan export:sqlite user --sync # 同步匯出歌曲
|
||||||
|
php artisan export:sqlite song # 同步匯出歌曲
|
||||||
|
php artisan export:sqlite user # 非同步匯出使用者
|
||||||
|
php artisan export:sqlite all # 非同步匯出所有
|
||||||
|
php artisan export:sqlite all --sync # 同步匯出所有
|
Loading…
x
Reference in New Issue
Block a user