2025年6月30日 星期一

高效能企業教育訓練系統的實踐:Laravel 與 FastAPI 雙劍合璧,打造彈性 RBAC 與非同步任務處理架構

高效能企業教育訓練系統的實踐:Laravel 與 FastAPI 雙劍合璧,打造彈性 RBAC 與非同步任務處理架構

您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/edu-tms-prototype.git

1. 引言:為什麼需要一個更好的 EduTMS?

在數位轉型浪潮下,企業與學校對內部的教育訓練管理系統(TMS)的需求日益增加。然而,傳統的 TMS 往往面臨幾大痛點:

  • 僵化的權限系統:權限設定大多硬編碼,難以應對複雜的組織架構變動,例如新增「部門主管」角色。

  • 效能瓶頸:報表生成、影片轉檔等耗時任務會直接阻塞網頁請求,導致使用者必須長時間等待,影響體驗。

  • 部署複雜度高:系統架構綁定特定環境,難以快速部署和擴展。

為了克服這些挑戰,我們設計並開發了 EduTMS (Education Training Management System),一個採用現代化技術架構,結合 Laravel 的穩健與 FastAPI 的高效,並透過 Docker Compose 實現無縫部署的解決方案。

接下來,我將深入分享我們如何解決其中兩個最關鍵的技術挑戰:彈性的角色權限管理(RBAC)設計,以及高效的非同步任務處理。

2. 挑戰一:如何實現彈性且可擴展的角色權限管理(RBAC)?

在 EduTMS 中,我們需要處理多種使用者角色,例如 AdminInstructorStudentHR,並且每個角色都有不同的操作權限。傳統的 is_admin 布林值欄位根本無法應付這種複雜性。

我們的解決方案:基於 spatie/laravel-permission 的 RBAC 設計

我們選擇了 Laravel 生態系中廣受歡迎的 spatie/laravel-permission 套件。這個套件完美地實現了標準的 RBAC 模型,讓我們能夠在不修改程式碼核心邏輯的情況下,動態地指派或撤銷權限。

2.1 核心概念:User-Role-Permission 模型

這個模型的核心思想是將「權限」與「角色」分離,並將「角色」賦予給「使用者」。一個使用者可以有多個角色,一個角色可以有多個權限。

  • Permission (權限):最小的操作單元,如 create courses, edit own courses, view all reports

  • Role (角色):權限的集合,如 Instructor 角色包含 create coursesedit own courses 權限。

  • User (使用者):被指派角色的實體。

2.2 程式碼實作:

首先,我們透過 migration 建立了相應的資料表來儲存角色與權限的關聯。

PHP
// Database Migrations (Pseudo code)
// package: spatie/laravel-permission automatically handles these migrations
// 僅供概念說明,實際由套件生成
Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique();
    $table->timestamps();
});

Schema::create('permissions', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique();
    $table->timestamps();
});

// Pivot tables
Schema::create('model_has_roles', ...);
Schema::create('role_has_permissions', ...);

接著,在我們的業務邏輯中,權限檢查變得異常簡潔而強大。我們可以在 Middleware, Controller, 甚至是 Blade 模板中進行權限判斷。

範例:在 Controller 中檢查權限

在我們的 CourseController 中,只有擁有 create courses 權限的使用者才能新增課程。

PHP
// laravel/app/Http/Controllers/CourseController.php
use Illuminate\Support\Facades\Auth;

class CourseController extends Controller
{
    public function create()
    {
        // 透過 can() 輔助函式進行權限檢查,如果沒有權限會自動拋出 403 錯誤
        if (!Auth::user()->can('create courses')) {
            abort(403, 'Unauthorized action.');
        }

        return view('courses.create');
    }

    // ... 其他方法
}

2.3 亮點:動態儀表板與客製化 KPI

由於權限與角色是動態可控的,我們實現了根據使用者角色顯示不同儀表板的功能。Admin 可以看到總體課程數和使用者數,而 Instructor 則能看到自己創建的課程進度。

PHP
// laravel/app/Http/Controllers/DashboardController.php
class DashboardController extends Controller
{
    public function index()
    {
        $user = Auth::user();

        if ($user->hasRole('admin')) {
            // Admin 的儀表板數據
            $totalCourses = Course::count();
            $totalUsers = User::count();
            return view('dashboard.admin', compact('totalCourses', 'totalUsers'));
        }

        if ($user->hasRole('instructor')) {
            // Instructor 的儀表板數據
            $myCoursesCount = $user->courses()->count();
            // ... 更多講師專屬數據
            return view('dashboard.instructor', compact('myCoursesCount'));
        }

        // 其他角色...
        return view('dashboard.student');
    }
}

這種設計不僅讓權限管理變得彈性,也讓前端介面能根據使用者身分自動調整,提供了優異的使用者體驗。

3. 挑戰二:如何處理耗時的後台任務?

在 EduTMS 中,生成包含大量數據的課程進度報告,或處理大型影片檔案,都是非常耗時的操作。如果這些任務在 Web 請求中同步執行,使用者將會面臨漫長的等待時間,甚至導致請求超時。

我們的解決方案:Laravel + FastAPI + Celery 的非同步任務架構

我們採用了經典的任務佇列(Task Queue)模式,並將其拆分為兩個獨立的服務:

  1. Laravel:負責接收使用者請求,並將任務推送到佇列中。

  2. FastAPI Worker:專注於從佇列中取出任務並執行。

兩者之間透過 Redis 進行溝通,實現了完美的解耦。

3.1 架構圖(流程描述):

  1. 使用者在網頁上點擊「生成報表」按鈕。

  2. Laravel 收到請求,將一個 GenerateReportJob 任務推送到 Redis 佇列。

  3. Laravel 立即回傳「報表正在生成中...」的訊息給使用者,請求結束。

  4. 後台的 FastAPI Worker 持續監聽 Redis 佇列。

  5. Worker 抓取到 GenerateReportJob 任務,開始在背景執行報表生成邏輯。

  6. 報表生成完成後,Worker 可以儲存檔案,甚至發送 Email 通知使用者。

3.2 程式碼實作:

a) Laravel 任務發布

在 Laravel 中,我們定義了一個 Job Class,並在 Controller 中將它推送到佇列。

PHP
// laravel/app/Http/Controllers/CourseReportController.php
use App\Jobs\GenerateCourseReport;
use Illuminate\Http\Request;

class CourseReportController extends Controller
{
    public function generate(Request $request, Course $course)
    {
        // 將耗時的報表生成任務推送到佇列
        GenerateCourseReport::dispatch($course->id, Auth::id());
        
        // 立即回傳訊息給使用者,不阻塞請求
        return back()->with('status', '課程報表正在後台生成中,請稍後。');
    }
}

b) FastAPI Worker 任務處理

我們使用 Python 的 FastAPI 框架搭配 Celery 進行任務處理。選擇 FastAPI 的原因是其輕量、高效,且能輕鬆整合 Python 強大的資料處理(如 Pandas)和機器學習函式庫。

以下是 fastapi/main.py 的關鍵程式碼:

Python
# fastapi/main.py
from celery import Celery
# ... other imports

# Celery App 設定,連線到 Redis
app = Celery('tasks', broker='redis://redis:6377/0', backend='redis://redis:6377/0')

@app.task
def generate_course_report(course_id: int, user_id: int):
    """
    Celery 任務:在背景生成課程報告
    """
    print(f"Generating report for course ID: {course_id} for user: {user_id}")
    try:
        # 這裡執行耗時的報表生成邏輯,例如:
        # - 從資料庫查詢大量數據 (使用 SQLAlchemy 或其他 ORM)
        # - 處理數據並匯出成 Excel 或 CSV 檔案
        # - 儲存檔案到儲存空間 (S3, MinIO)
        
        # 範例:模擬耗時操作
        import time
        time.sleep(10)
        
        # 任務完成後,可以發送 Email 通知使用者
        send_email_task('user@example.com', 'Report Ready', 'Your report is ready for download.')
        print("Report generated successfully.")
        
    except Exception as e:
        print(f"Error generating report: {e}")
        # 任務失敗處理...

@app.task
def send_email_task(to_email, subject, body):
    """
    Celery 任務:發送 Email
    """
    # 這裡實作 SMTP 發送 Email 的邏輯
    print(f"Sending email to {to_email} with subject: {subject}")

4. 技術整合與部署:Docker Compose 的力量

為了讓這個多語言、多服務的架構能夠無縫協作,我們利用 Docker Compose 將所有服務容器化。這不僅簡化了開發環境的設定,也讓生產環境的部署變得一致且可靠。

docker-compose.yml 檔案清晰地定義了每個服務及其依賴關係:

YAML
# docker-compose.yml
version: '3.8'
services:
  laravel:
    # ... Laravel 服務配置
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
  fastapi_worker:
    # ... FastAPI 服務配置
    command: celery -A main worker --loglevel=info
    depends_on:
      - mysql
      - redis
  mysql:
    # ... MySQL 資料庫服務
  redis:
    # ... Redis 服務

depends_on 確保了 Worker 服務在 MySQL 和 Redis 啟動後才運行,而環境變數則讓各個服務能夠輕鬆找到彼此。

5. 總結與未來展望

透過 Laravel 的穩健生態系處理 Web 核心業務,並利用 FastAPI 的高效非同步能力處理耗時任務,我們成功打造了一個高效能、高彈性且易於部署的 EduTMS。這種多語言、多框架的架構不僅解決了我們面臨的技術挑戰,也為未來的擴展提供了無限可能,例如引入 Python 的機器學習模型進行學習行為分析,或整合更多自動化任務。

如果你對這個專案的架構或實作細節有興趣,歡迎留言交流!

沒有留言:

張貼留言

熱門文章