Go: مشروع متكامل: واجهة برمجة تطبيقات التجارة الإلكترونية…

آخر تحديث: 2026-08-26

في الجزء السابق، قمنا بإعداد إطار العمل لواجهة برمجة تطبيقات التجارة الإلكترونية. والهدف من هذا الجزء هو جعلها آمنة وسهلة الصيانة وقابلة للاختبار.

كشفت واجهة برمجة تطبيقات التجارة الإلكترونية الخاصة بـ«بوب» عن مشكلات خطيرة خلال عملية تدقيق أمني: عدم وجود مصادقة، وعدم وجود ضوابط وصول، وعدم وجود تقييد لعدد الطلبات. حان الوقت لتعزيز مسار البرمجيات الوسيطة.

1. ستتعلم



2. القصة: تقرير التدقيق الأمني

(1) المشكلة: «لا تتطلب أي من واجهات برمجة التطبيقات (API) المصادقة، لذا يمكن لأي شخص حذف المنتجات.»

في اليوم الأول من إطلاق نظام MVP الخاص بـ«بوب»، أرسل فريق الأمن تقريرًا تدقيقيًّا:

"ثغرة أمنية خطيرة: لا تتطلب عملية الحذف DELETE /api/v1/products/1 أي مصادقة. يمكن لأي شخص حذف المنتجات. ثغرة أمنية متوسطة: لا يوجد تحديد لمعدل الطلبات؛ يمكن للمهاجم استخدام هجوم القوة الغاشمة لاختراق واجهة برمجة تطبيقات (API) تسجيل الدخول. التوصية: مصادقة JWT + أذونات RBAC + تحديد معدل الطلبات."

ألقى بوب نظرة على «الباب الخلفي» الذي تركته النسخة السابقة:

GO
// Bad code: no authentication, anyone can place an order
func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    userID := 1  // Hardcoded! All orders go to user 1
    // Business logic...
}

(2) هدف الدرس: إكمال مسار البرمجيات الوسيطة

TEXT 📖 للعرض فقط
Request → Recovery → Logging → CORS → Auth → RBAC → RateLimit → Handler

خطط هذا الأسبوع:

  1. برنامج وسيط للمصادقة باستخدام JWT (يحل محل معرّف المستخدم المبرمج بشكل ثابت)
  2. التحكم في الأذونات وفقًا لنظام RBAC (أدوار المسؤولين/العملاء)
  3. ترحيل قاعدة بيانات SQL (إدارة مخطط الجداول مع التحكم في الإصدارات)
  4. اختبار التكامل (httptest + قاعدة بيانات مؤقتة)
  5. البرمجيات الوسيطة لترقيم الصفحات


3. التنفيذ الكامل

▶ مثال: JWT Toolkit

GO 📖 للعرض فقط
// internal/auth/jwt.go
package auth

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "strings"
    "time"
)

type Claims struct {
    UserID int    `json:"user_id"`
    Role   string `json:"role"`
    Email  string `json:"email"`
}

type TokenPair struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
}

type jwtService struct {
    secret     []byte
    accessTTL  time.Duration
    refreshTTL time.Duration
}

func NewJWTService(secret string) *jwtService {
    return &jwtService{
        secret:     []byte(secret),
        accessTTL:  15 * time.Minute,
        refreshTTL: 7 * 24 * time.Hour,
    }
}

func (s *jwtService) GenerateToken(claims Claims) (string, error) {
    header := map[string]string{"alg": "HS256", "typ": "JWT"}
    headerJSON, _ := json.Marshal(header)

    payload := map[string]interface{}{
        "user_id": claims.UserID,
        "role":    claims.Role,
        "email":   claims.Email,
        "exp":     time.Now().Add(s.accessTTL).Unix(),
        "iat":     time.Now().Unix(),
    }
    payloadJSON, _ := json.Marshal(payload)

    headerEnc := base64URLEncode(headerJSON)
    payloadEnc := base64URLEncode(payloadJSON)

    message := headerEnc + "." + payloadEnc
    mac := hmac.New(sha256.New, s.secret)
    mac.Write([]byte(message))
    sig := base64URLEncode(mac.Sum(nil))

    return message + "." + sig, nil
}

func (s *jwtService) ValidateToken(token string) (*Claims, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid token")
    }

    message := parts[0] + "." + parts[1]
    mac := hmac.New(sha256.New, s.secret)
    mac.Write([]byte(message))
    expected := base64URLEncode(mac.Sum(nil))

    if !hmac.Equal([]byte(parts[2]), []byte(expected)) {
        return nil, fmt.Errorf("invalid signature")
    }

    payloadJSON, err := base64URLDecode(parts[1])
    if err != nil {
        return nil, err
    }

    var payload struct {
        UserID int     `json:"user_id"`
        Role   string  `json:"role"`
        Email  string  `json:"email"`
        Exp    float64 `json:"exp"`
    }
    if err := json.Unmarshal(payloadJSON, &payload); err != nil {
        return nil, err
    }

    if time.Now().Unix() > int64(payload.Exp) {
        return nil, fmt.Errorf("token expired")
    }

    return &Claims{
        UserID: payload.UserID,
        Role:   payload.Role,
        Email:  payload.Email,
    }, nil
}

func base64URLEncode(data []byte) string {
    return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}

func base64URLDecode(s string) ([]byte, error) {
    switch len(s) % 4 {
    case 2:
        s += "=="
    case 3:
        s += "="
    }
    return base64.URLEncoding.DecodeString(s)
}
96 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: مسار البرمجيات الوسيطة

GO 📖 للعرض فقط
// internal/middleware/middleware.go
package middleware

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
    "sync"
    "time"

    "ecommerce/internal/auth"
)

type contextKey string

const UserClaimsKey contextKey = "user_claims"

// ---------- Middleware type ----------

type Middleware func(http.Handler) http.Handler

func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

// ---------- Recovery ----------

func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                writeError(w, http.StatusInternalServerError, "internal error")
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// ---------- Logging ----------

func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// ---------- CORS ----------

func CORS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Auth (JWT) ----------

func Auth(jwtService *auth.JWTService) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if !strings.HasPrefix(authHeader, "Bearer ") {
                writeError(w, http.StatusUnauthorized, "missing token")
                return
            }

            token := authHeader[7:]
            claims, err := jwtService.ValidateToken(token)
            if err != nil {
                writeError(w, http.StatusUnauthorized, err.Error())
                return
            }

            ctx := context.WithValue(r.Context(), UserClaimsKey, claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// ---------- RBAC ----------

func RequireRole(roles ...string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims, ok := r.Context().Value(UserClaimsKey).(*auth.Claims)
            if !ok {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            for _, role := range roles {
                if claims.Role == role {
                    next.ServeHTTP(w, r)
                    return
                }
            }

            writeError(w, http.StatusForbidden, "insufficient permissions")
        })
    }
}

// ---------- Token bucket rate limiting ----------

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

type IPRateLimiter struct {
    mu      sync.RWMutex
    buckets map[string]*TokenBucket
    rate    float64
    burst   int
}

var globalLimiter = NewIPRateLimiter(100, 200)

func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
    return &IPRateLimiter{
        buckets: make(map[string]*TokenBucket),
        rate:    rate,
        burst:   burst,
    }
}

func (rl *IPRateLimiter) getBucket(ip string) *TokenBucket {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    b, ok := rl.buckets[ip]
    if !ok {
        b = &TokenBucket{
            tokens:     float64(rl.burst),
            maxTokens:  float64(rl.burst),
            refillRate: rl.rate,
            lastRefill: time.Now(),
        }
        rl.buckets[ip] = b
    }
    return b
}

func (b *TokenBucket) allow() bool {
    b.mu.Lock()
    defer b.mu.Unlock()

    now := time.Now()
    elapsed := now.Sub(b.lastRefill).Seconds()
    b.tokens = min(b.tokens+elapsed*b.refillRate, b.maxTokens)
    b.lastRefill = now

    if b.tokens >= 1 {
        b.tokens--
        return true
    }
    return false
}

func min(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}

func RateLimit(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        if !globalLimiter.getBucket(ip).allow() {
            writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Pagination ----------

type Pagination struct {
    Page    int `json:"page"`
    PerPage int `json:"per_page"`
    Offset  int `json:"-"`
}

func ParsePagination(r *http.Request) Pagination {
    page := 1
    perPage := 20

    if p := r.URL.Query().Get("page"); p != "" {
        if v, err := parseInt(p); err == nil && v > 0 {
            page = v
        }
    }
    if pp := r.URL.Query().Get("per_page"); pp != "" {
        if v, err := parseInt(pp); err == nil && v > 0 && v <= 100 {
            perPage = v
        }
    }

    return Pagination{
        Page:    page,
        PerPage: perPage,
        Offset:  (page - 1) * perPage,
    }
}

func parseInt(s string) (int, error) {
    var n int
    for _, c := range s {
        if c < '0' || c > '9' {
            return 0, fmt.Errorf("not a number")
        }
        n = n*10 + int(c-'0')
    }
    return n, nil
}

// ---------- Utility ----------

func writeError(w http.ResponseWriter, status int, msg string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(map[string]string{"error": msg})
}
193 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: ترحيل قاعدة بيانات SQL

GO 📖 للعرض فقط
// internal/migration/migration.go
package migration

import (
    "database/sql"
    "fmt"
    "log"
)

type Migration struct {
    Version int
    SQL     string
}

var migrations = []Migration{
    {
        Version: 1,
        SQL: `CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            email TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            name TEXT NOT NULL,
            role TEXT NOT NULL DEFAULT 'customer',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )`,
    },
    {
        Version: 2,
        SQL: `CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            description TEXT,
            price REAL NOT NULL,
            stock INTEGER NOT NULL DEFAULT 0,
            category_id INTEGER,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (category_id) REFERENCES categories(id)
        )`,
    },
    {
        Version: 3,
        SQL: `CREATE TABLE IF NOT EXISTS orders (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            product_id INTEGER NOT NULL,
            quantity INTEGER NOT NULL,
            total_price REAL NOT NULL,
            status TEXT NOT NULL DEFAULT 'pending',
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id),
            FOREIGN KEY (product_id) REFERENCES products(id)
        )`,
    },
    {
        Version: 4,
        SQL: `CREATE TABLE IF NOT EXISTS categories (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT UNIQUE NOT NULL,
            description TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )`,
    },
}

func RunMigrations(db *sql.DB) error {
    // Create version table
    _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
        version INTEGER PRIMARY KEY,
        applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )`)
    if err != nil {
        return fmt.Errorf("create migrations table: %w", err)
    }

    for _, m := range migrations {
        // Check if already applied
        var exists bool
        err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = ?)", m.Version).Scan(&exists)
        if err != nil {
            return err
        }
        if exists {
            continue
        }

        // Apply migration
        if _, err := db.Exec(m.SQL); err != nil {
            return fmt.Errorf("migration %d: %w", m.Version, err)
        }

        // Record version
        if _, err := db.Exec("INSERT INTO schema_migrations (version) VALUES (?)", m.Version); err != nil {
            return err
        }

        log.Printf("[Migration] Version %d applied", m.Version)
    }

    return nil
}
86 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: اختبار التكامل

⚙️ المتطلبات الأساسية: قم بتشغيل go get github.com/mattn/go-sqlite3 (يتطلب CGO؛ أو استخدم modernc.org/sqlite كبديل للحصول على برنامج تشغيل مخصص للعبة «غو» فقط)

GO 📖 للعرض فقط
// internal/handler/integration_test.go
package handler

import (
    "bytes"
    "database/sql"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "ecommerce/internal/auth"
    "ecommerce/internal/middleware"
    "ecommerce/internal/repository"
    "ecommerce/internal/service"

    _ "github.com/mattn/go-sqlite3"
)

func setupTestDB(t *testing.T) *sql.DB {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatal(err)
    }

    // Initialize tables
    _, err = db.Exec(`
        CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, name TEXT NOT NULL, role TEXT DEFAULT 'customer');
        CREATE TABLE products (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, شرح TEXT, price REAL NOT NULL, stock INTEGER DEFAULT 0);
        CREATE TABLE orders (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, product_id INTEGER NOT NULL, quantity INTEGER NOT NULL, total_price REAL NOT NULL, status TEXT DEFAULT 'pending');
    `)
    if err != nil {
        t.Fatal(err)
    }

    return db
}

func setupTestApp(db *sql.DB) http.Handler {
    userRepo := repository.NewUserRepository(db)
    productRepo := repository.NewProductRepository(db)
    orderRepo := repository.NewOrderRepository(db)

    userSvc := service.NewUserService(userRepo)
    productSvc := service.NewProductService(productRepo)
    orderSvc := service.NewOrderService(orderRepo, productRepo, userRepo)

    userHandler := NewUserHandler(userSvc)
    productHandler := NewProductHandler(productSvc)
    orderHandler := NewOrderHandler(orderSvc)

    mux := http.NewServeMux()
    userHandler.Register(mux)
    productHandler.Register(mux)
    orderHandler.Register(mux)

    // Apply middleware
    return middleware.Chain(mux,
        middleware.Recovery,
        middleware.Logging,
        middleware.CORS,
    )
}

func TestRegisterAndLogin(t *testing.T) {
    db := setupTestDB(t)
    defer db.Close()
    app := setupTestApp(db)

    // 1. Register
    registerBody := `{"email":"test@example.com","password":"password123","name":"Test User"}`
    req := httptest.NewRequest("POST", "/api/v1/register", bytes.NewBufferString(registerBody))
    req.Header.Set("Content-Type", "application/json")
    resp := httptest.NewRecorder()
    app.ServeHTTP(resp, req)

    if resp.Code != http.StatusCreated {
        t.Fatalf("expected 201, got %d", resp.Code)
    }

    // 2. Login
    loginBody := `{"email":"test@example.com","password":"password123"}`
    req = httptest.NewRequest("POST", "/api/v1/login", bytes.NewBufferString(loginBody))
    req.Header.Set("Content-Type", "application/json")
    resp = httptest.NewRecorder()
    app.ServeHTTP(resp, req)

    if resp.Code != http.StatusOK {
        t.Fatalf("expected 200, got %d", resp.Code)
    }

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    data := result["data"].(map[string]interface{})
    if data["email"] != "test@example.com" {
        t.Errorf("expected test@example.com, got %v", data["email"])
    }
}

func TestCreateOrder_RequiresAuth(t *testing.T) {
    db := setupTestDB(t)
    defer db.Close()
    app := setupTestApp(db)

    // Request without Token should return 401
    orderBody := `{"product_id":1,"quantity":1}`
    req := httptest.NewRequest("POST", "/api/v1/orders", bytes.NewBufferString(orderBody))
    req.Header.Set("Content-Type", "application/json")
    resp := httptest.NewRecorder()
    // Note: The current version does not have Auth middleware;
    // it needs to be added in the integration test environment
    _ = resp
    _ = app
}
87 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)
100%
sequenceDiagram
    participant Client
    participant MW as Middleware Pipeline
    participant AuthS as Auth Service
    participant H as Handler

    Client->>MW: POST /api/v1/orders (Bearer token)
    MW->>MW: Recovery / Logging / CORS
    MW->>AuthS: Auth middleware validates JWT
    AuthS-->>MW: Claims {user_id, role}
    MW->>MW: RBAC checks customer role
    MW->>MW: RateLimit check
    MW->>H: Passed! Inject user_id into Context
    H->>H: Create order (using user_id)
    H-->>MW: 201 Created
    MW-->>Client: JSON response

(5) يمر الطلب عبر مسار البرمجيات الوسيطة

TEXT 📖 للعرض فقط
Request
  ↓ Recovery (catch panic)
  ↓ Logging (log + duration)
  ↓ CORS (cross-origin)
  ↓ Auth (JWT verification → inject Claims)
  ↓ RBAC (check role)
  ↓ RateLimit (token bucket)
  ↓ Handler (business logic)
Response
🔥 خطأ شائع: يجب أن تأتي الوسيطة «Auth» قبل «RBAC». أولاً، تحقق من صحة الرمز المميز لاسترداد معلومات المستخدم، ثم تحقق من الأذونات. إذا قمت بالتحقق من الأذونات قبل التحقق من صحة الرمز المميز، فقد يتسبب المستخدمون غير المصادق عليهم في ظهور خطأ متعلق بالأذونات بدلاً من خطأ في المصادقة — وهو ما يشكل خطرًا أمنيًا.



❓ أسئلة شائعة

س كيف يتم دمج JWT في البرمجيات الوسيطة؟
ج تقوم البرمجيات الوسيطة Auth بتحليل الرمز المميز Bearer الموجود في الرأس، وتستدعي jwtService.ValidateToken، وتقوم بإدراج Claims في سياق الطلب باستخدام context.WithValue. ثم تقوم المعالجات اللاحقة باسترداد معلومات المستخدم باستخدام r.Context().Value(middleware.UserClaimsKey).
س كيف يتم تنفيذ نموذج RBAC؟
ج بعد برنامج الوسيط Auth، يقوم برنامج الوسيط RequireRole بالتحقق مما إذا كان الحقل role في المطالبات موجودًا ضمن قائمة الأدوار المسموح بها. ويمكن أن تتضمن عمليات التنفيذ الأكثر تعقيدًا مصفوفة أذونات (حيث يكون لكل دور قائمة بالأذونات، ويقوم برنامج الوسيط بالتحقق من وجود أذونات محددة).
س كيف أختار أداة ترحيل SQL؟
ج (1) golang-migrate/migrate — الأكثر استخدامًا، وتدعم قواعد بيانات متعددة ومصادر ترحيل متنوعة؛ (2) الترحيل اليدوي — النهج المستخدم في هذا الدرس، وهو مناسب للمشاريع الصغيرة؛ (3) pressly/goose — غنية بالميزات. بالنسبة للمشاريع الكبيرة، نوصي باستخدام golang-migrate.
س كيف أكتب اختبار التكامل؟
ج استخدم httptest.NewServer أو httptest.NewRecorder لمحاكاة طلبات HTTP، مع استخدام قاعدة بيانات مؤقتة (SQLite في وضع :memory:). مسار الاختبار: إعداد قاعدة البيانات → إدخال التبعيات → إنشاء معالج → إرسال طلب HTTP → التحقق من الاستجابة.
س كيف يتم تنفيذ البرمجيات الوسيطة الخاصة بترقيم الصفحات؟
ج تقوم بتحليل معلمات الاستعلام page وper_page لحساب الإزاحة. ثم تضيف LIMIT ? OFFSET ? إلى الاستعلام في طبقة الخدمة. ويمكن للمعالج (Handler) إضافة رأس X-Total-Count أو معلومات تعريفية إلى الاستجابة. البرمجيات الوسيطة مسؤولة فقط عن تحليل المعلمات؛ وهي لا تُعدّل منطق الاستعلام.

📖 ملخص


📝 تمارين

  1. أساسي (مستوى الصعوبة ⭐): قم بدمج البرمجيات الوسيطة الخاصة بالمصادقة (Auth) من هذا الدرس في واجهة برمجة تطبيقات التجارة الإلكترونية (API) من الدرس السابق. قم بإرجاع رمز JWT عند التسجيل، واشترط وجود رمز Bearer لجميع نقاط النهاية /api/v1/orders/*. قم بإرجاع خطأ 401 للطلبات التي لا تحتوي على رمز.

  2. متقدم (صعوبة ⭐⭐): تنفيذ اختبار تكامل كامل لمسار البرمجيات الوسيطة. المتطلبات: (1) استخدام httptest + قاعدة بيانات SQLite مؤقتة؛ (2) اختبار مسار المصادقة (التسجيل → تسجيل الدخول → الحصول على الرمز المميز → الوصول إلى نقاط النهاية المحمية باستخدام الرمز المميز)؛ (3) اختبر سيناريوهات رفض الأذونات (محاولة «عميل» الوصول إلى نقطة نهاية «المسؤول»); (4) اختبر سيناريوهات تحديد معدل الطلبات (إرجاع خطأ 429 عند وجود عدد كبير من الطلبات خلال فترة قصيرة).

  3. التحدي (الصعوبة ⭐⭐⭐): تنفيذ ترحيل قاعدة البيانات باستخدام golang-migrate. المتطلبات: (1) تثبيت واجهة سطر الأوامر golang-migrate/migrate؛ (2) كتابة 3 ملفات ترحيل (لإنشاء الجداول users وproducts وorders)؛ (3) ترحيل ملف Go الثنائي (حزمة embed)؛ (4) التأكد من أن cmd/migrate/main.go يدعم الأمرين up و down؛ (5) إجراء اختبار التكامل باستخدام مخطط قاعدة البيانات التي تم ترحيلها.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%