瀏覽代碼

migrazione multidb

FabioFratini 7 月之前
父節點
當前提交
f92b45788f

+ 26 - 0
app/Console/Commands/MigrateMaster.php

@@ -0,0 +1,26 @@
+<?php
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\MigrationService;
+
+class MigrateMaster extends Command
+{
+    protected $signature = 'migrate:master {--rollback} {--step=1}';
+    protected $description = 'Run migrations on master database only';
+
+    public function handle(MigrationService $migrationService)
+    {
+        if ($this->option('rollback')) {
+            $result = $migrationService->rollbackMasterMigrations($this->option('step'));
+            $this->info($result['message']);
+            $this->line($result['output']);
+        } else {
+            $result = $migrationService->runMasterMigrations();
+            $this->info($result['message']);
+            if (!empty($result['migrations'])) {
+                $this->table(['Master Migrations'], array_map(fn($m) => [$m], $result['migrations']));
+            }
+        }
+    }
+}

+ 30 - 0
app/Console/Commands/MigrateTenants.php

@@ -0,0 +1,30 @@
+<?php
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use App\Services\MigrationService;
+
+class MigrateTenants extends Command
+{
+    protected $signature = 'migrate:tenants {--rollback} {--step=1}';
+    protected $description = 'Run migrations on all tenant databases';
+
+    public function handle(MigrationService $migrationService)
+    {
+        if ($this->option('rollback')) {
+            $result = $migrationService->rollbackTenantMigrations($this->option('step'));
+        } else {
+            $result = $migrationService->runTenantMigrations();
+        }
+
+        $this->info($result['message']);
+
+        if (!empty($result['results'])) {
+            $tableData = [];
+            foreach ($result['results'] as $r) {
+                $tableData[] = [$r['tenant'], $r['status'], $r['message']];
+            }
+            $this->table(['Tenant Database', 'Status', 'Message'], $tableData);
+        }
+    }
+}

+ 17 - 0
app/Database/Migrations/MasterMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class MasterMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'master';
+    }
+
+    public function isMasterMigration(): bool
+    {
+        return true;
+    }
+}

+ 17 - 0
app/Database/Migrations/TenantMigration.php

@@ -0,0 +1,17 @@
+<?php
+namespace App\Database\Migrations;
+
+use Illuminate\Database\Migrations\Migration as BaseMigration;
+
+abstract class TenantMigration extends BaseMigration
+{
+    public function getConnection()
+    {
+        return 'tenant';
+    }
+
+    public function isTenantMigration(): bool
+    {
+        return true;
+    }
+}

+ 2 - 0
app/Http/Middleware/TenantMiddleware.php

@@ -51,6 +51,8 @@ class TenantMiddleware
             DB::purge('tenant');
             DB::reconnect('tenant');
 
+            session(['currentClient' => $user->tenant_database]);
+
             Log::info('Current database after setup: ' . DB::connection()->getDatabaseName());
             Log::info('Current default connection: ' . DB::getDefaultConnection());
         }

+ 24 - 0
app/Providers/MigrationServiceProvider.php

@@ -0,0 +1,24 @@
+<?php
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use App\Console\Commands\MigrateMaster;
+use App\Console\Commands\MigrateTenants;
+
+class MigrationServiceProvider extends ServiceProvider
+{
+    public function register()
+    {
+        $this->app->singleton(\App\Services\MigrationService::class);
+    }
+
+    public function boot()
+    {
+        if ($this->app->runningInConsole()) {
+            $this->commands([
+                MigrateMaster::class,
+                MigrateTenants::class,
+            ]);
+        }
+    }
+}

+ 252 - 0
app/Services/MigrationService.php

@@ -0,0 +1,252 @@
+<?php
+namespace App\Services;
+
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Artisan;
+use App\Models\User;
+use App\Database\Migrations\MasterMigration;
+use App\Database\Migrations\TenantMigration;
+
+class MigrationService
+{
+    protected $masterDatabase = 'leezard_master';
+
+    public function runMasterMigrations()
+    {
+        $this->setupMasterConnection();
+
+        $masterMigrations = $this->getMasterMigrationFiles();
+
+        if (empty($masterMigrations)) {
+            return ['message' => 'No master migrations found.', 'migrations' => []];
+        }
+
+        // Create a temporary directory for master migrations only
+        $tempPath = storage_path('app/temp_master_migrations');
+        if (!File::exists($tempPath)) {
+            File::makeDirectory($tempPath, 0755, true);
+        }
+
+        // Copy only master migrations to temp directory
+        foreach ($masterMigrations as $migration) {
+            $source = database_path('migrations/' . $migration);
+            $destination = $tempPath . '/' . $migration;
+            File::copy($source, $destination);
+        }
+
+        try {
+            // Run migrations from temp directory
+            Artisan::call('migrate', [
+                '--database' => 'master',
+                '--path' => 'storage/app/temp_master_migrations'
+            ]);
+
+            $output = Artisan::output();
+        } finally {
+            // Clean up temp directory
+            File::deleteDirectory($tempPath);
+        }
+
+        return [
+            'message' => 'Master migrations completed successfully!',
+            'database' => $this->masterDatabase,
+            'migrations' => $masterMigrations,
+            'output' => $output
+        ];
+    }
+
+    public function runTenantMigrations()
+    {
+        $tenants = $this->getTenants();
+        $results = [];
+
+        if ($tenants->isEmpty()) {
+            return ['message' => 'No tenants found.', 'results' => []];
+        }
+
+        $tenantMigrations = $this->getTenantMigrationFiles();
+
+        if (empty($tenantMigrations)) {
+            return ['message' => 'No tenant migrations found.', 'results' => []];
+        }
+
+        // Create a temporary directory for tenant migrations only
+        $tempPath = storage_path('app/temp_tenant_migrations');
+        if (!File::exists($tempPath)) {
+            File::makeDirectory($tempPath, 0755, true);
+        }
+
+        // Copy only tenant migrations to temp directory
+        foreach ($tenantMigrations as $migration) {
+            $source = database_path('migrations/' . $migration);
+            $destination = $tempPath . '/' . $migration;
+            File::copy($source, $destination);
+        }
+
+        foreach ($tenants as $tenant) {
+            try {
+                $this->setupTenantConnection($tenant);
+
+                Artisan::call('migrate', [
+                    '--database' => 'tenant',
+                    '--path' => 'storage/app/temp_tenant_migrations'
+                ]);
+
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'success',
+                    'message' => 'Migrations completed successfully'
+                ];
+
+            } catch (\Exception $e) {
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'error',
+                    'message' => $e->getMessage()
+                ];
+            }
+        }
+
+        // Clean up temp directory
+        File::deleteDirectory($tempPath);
+
+        return [
+            'message' => 'Tenant migrations process completed',
+            'results' => $results
+        ];
+    }
+
+    public function rollbackMasterMigrations($steps = 1)
+    {
+        $this->setupMasterConnection();
+
+        Artisan::call('migrate:rollback', [
+            '--database' => 'master',
+            '--step' => $steps
+        ]);
+
+        return [
+            'message' => "Master database rolled back {$steps} step(s)",
+            'database' => $this->masterDatabase,
+            'output' => Artisan::output()
+        ];
+    }
+
+    public function rollbackTenantMigrations($steps = 1)
+    {
+        $tenants = $this->getTenants();
+        $results = [];
+
+        foreach ($tenants as $tenant) {
+            try {
+                $this->setupTenantConnection($tenant);
+
+                Artisan::call('migrate:rollback', [
+                    '--database' => 'tenant',
+                    '--step' => $steps
+                ]);
+
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'success',
+                    'message' => "Rolled back {$steps} step(s)"
+                ];
+
+            } catch (\Exception $e) {
+                $results[] = [
+                    'tenant' => $tenant->tenant_database,
+                    'status' => 'error',
+                    'message' => $e->getMessage()
+                ];
+            }
+        }
+
+        return [
+            'message' => 'Tenant rollback process completed',
+            'results' => $results
+        ];
+    }
+
+    protected function setupMasterConnection()
+    {
+        config(['database.connections.master' => [
+            'driver' => 'mysql',
+            'host' => env('DB_HOST', '127.0.0.1'),
+            'port' => env('DB_PORT', '3306'),
+            'database' => $this->masterDatabase,
+            'username' => env('DB_USERNAME'),
+            'password' => env('DB_PASSWORD'),
+            'charset' => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix' => '',
+            'strict' => true,
+            'engine' => null,
+        ]]);
+
+        config(['database.default' => 'master']);
+        DB::purge('master');
+        DB::reconnect('master');
+    }
+
+    protected function setupTenantConnection($tenant)
+    {
+        config(['database.connections.tenant' => [
+            'driver' => 'mysql',
+            'host' => env('DB_HOST', '127.0.0.1'),
+            'port' => env('DB_PORT', '3306'),
+            'database' => $tenant->tenant_database,
+            'username' => $tenant->tenant_username,
+            'password' => $tenant->tenant_password,
+            'charset' => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix' => '',
+            'strict' => true,
+            'engine' => null,
+        ]]);
+
+        config(['database.default' => 'tenant']);
+        DB::purge('tenant');
+        DB::reconnect('tenant');
+    }
+
+    protected function getTenants()
+    {
+        return User::select('tenant_database', 'tenant_username', 'tenant_password')
+            ->whereNotNull('tenant_database')
+            ->distinct()
+            ->get();
+    }
+
+    protected function getMasterMigrationFiles()
+    {
+        $migrationPath = database_path('migrations');
+        $files = File::glob($migrationPath . '/*.php');
+        $masterMigrations = [];
+
+        foreach ($files as $file) {
+            $content = File::get($file);
+            if (strpos($content, 'extends MasterMigration') !== false) {
+                $masterMigrations[] = basename($file);
+            }
+        }
+
+        return $masterMigrations;
+    }
+
+    protected function getTenantMigrationFiles()
+    {
+        $migrationPath = database_path('migrations');
+        $files = File::glob($migrationPath . '/*.php');
+        $tenantMigrations = [];
+
+        foreach ($files as $file) {
+            $content = File::get($file);
+            if (strpos($content, 'extends TenantMigration') !== false) {
+                $tenantMigrations[] = basename($file);
+            }
+        }
+
+        return $tenantMigrations;
+    }
+}

+ 50 - 4
routes/web.php

@@ -8,7 +8,7 @@ use Illuminate\Support\Facades\Artisan;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Mail;
 use Illuminate\Support\Facades\Storage;
-
+use App\Services\MigrationService;
 /*
 |--------------------------------------------------------------------------
 | Web Routes
@@ -40,6 +40,7 @@ Route::post('/login', function () {
 })->name('login');
 
 Route::get('/logout', function () {
+    session()->forget('currentClient');
 
     Auth::logout();
     return redirect('/');
@@ -1572,9 +1573,54 @@ function getColor($months, $m)
     return $class;
 }
 
-Route::get('/migrate', function () {
-    Artisan::call('migrate');
-    dd('migrated!');
+Route::get('/migrate/master', function (MigrationService $migrationService) {
+    try {
+        $result = $migrationService->runMasterMigrations();
+        return response()->json($result);
+    } catch (\Exception $e) {
+        return response()->json([
+            'message' => 'Master migration failed: ' . $e->getMessage(),
+            'success' => false
+        ], 500);
+    }
+});
+
+Route::get('/migrate/tenants', function (MigrationService $migrationService) {
+    try {
+        $result = $migrationService->runTenantMigrations();
+        return response()->json($result);
+    } catch (\Exception $e) {
+        return response()->json([
+            'message' => 'Tenant migrations failed: ' . $e->getMessage(),
+            'success' => false
+        ], 500);
+    }
+});
+Route::get('/migrate/master/rollback', function (MigrationService $migrationService) {
+    $steps = request('steps', 1);
+    try {
+        $result = $migrationService->rollbackMasterMigrations($steps);
+        return response()->json($result);
+    } catch (\Exception $e) {
+        return response()->json([
+            'message' => 'Master rollback failed: ' . $e->getMessage(),
+            'success' => false
+        ], 500);
+    }
+});
+
+// Tenant rollback
+Route::get('/migrate/tenants/rollback', function (MigrationService $migrationService) {
+    $steps = request('steps', 1);
+    try {
+        $result = $migrationService->rollbackTenantMigrations($steps);
+        return response()->json($result);
+    } catch (\Exception $e) {
+        return response()->json([
+            'message' => 'Tenant rollback failed: ' . $e->getMessage(),
+            'success' => false
+        ], 500);
+    }
 });
 
 Route::get('/updateData', function () {