404 Not Found

404 Not Found


nginx

تمرين المرحلة الثانية الشامل — تنفيذ حلقة وظائف PriceTracker الأساسية

المرحلة الثانية مثل التجميع النهائي للسيارة—المحرك (قاعدة البيانات)، وعجلة القيادة (التوجيه)، وأحزمة الأمان (المصادقة)، ولوحة القيادة (البرمجيات الوسيطة) تم بناؤها جميعًا، والآن يتم تجميعها في سيارة كاملة جاهزة للانطلاق على الطريق.

1. ما ستتعلمه


2. القصة الحقيقية لـ Alice

(1) نقطة الألم: الوظائف مبعثرة ولا يمكن دمجها للاختبار

نفذت Alice البرمجيات الوسيطة وحقن الاعتماد وقاعدة البيانات و CRUD ومصادقة JWT بشكل منفصل، ولكن كل ميزة تم اختبارها بشكل مستقل. عندما حاولت دمج جميع هذه الميزات، اكتشفت أن ترتيب تنفيذ برمجية CORS الوسيطة ومصادقة JWT يتعارض، وأن جلسة DB تم إنشاؤها مرتين في سلسلة DI، وأن منطق تحديد المعدل وفحوصات الصلاحيات للمستخدمين Pro يتجاوز بعضها البعض.

(2) نهج منظم ومتكامل

تمرين المرحلة الثانية الشامل يدمج جميع الوحدات بالترتيب الهرمي الصحيح: CORS (الطبقة الخارجية) → تحديد المعدل → المصادقة → منطق الأعمال → قاعدة البيانات (الطبقة الداخلية)، مع مسؤوليات محددة بوضوح لكل طبقة تعمل بشكل مستقل عن الأخرى.

(3) العائد

خدمة PriceTracker الأساسية المدمجة تعمل بالكامل: نجحت الواجهة الأمامية لـ Bob في الوصول إلى الموارد عبر النطاقات، وجميع نقاط النهاية المحمية تتطلب رمز JWT، وتم استيراد المستخدمين Pro بشكل مجمّع بدون مشاكل، والمستخدمون المجانيون محدودون بالمعدل بشكل صحيح.


3. نظرة عامة على بنية المرحلة الثانية

(1) تدفق الطلب الكامل

100%
flowchart TD
    Client[Bob الواجهة الأمامية] --> CORS[برمجية CORS الوسيطة]
    CORS --> RateLimit[برمجية تحديد المعدل الوسيطة]
    RateLimit --> Router[FastAPI Router]
    Router --> AuthDI{هل المصادقة مطلوبة؟}
    AuthDI -->|نعم| GetDB[اعتماد get_db]
    AuthDI -->|لا| Handler[المعالج]
    GetDB --> GetUser[get_current_user]
    GetUser --> CheckSub[require_subscription]
    CheckSub --> Handler
    Handler --> Repo[طبقة Repository]
    Repo --> DB[(PostgreSQL)]
    
    RateLimit -.->|429| Reject[محدود المعدل]
    GetUser -.->|401| Unauth[غير مصادق]
    CheckSub -.->|403| Forbid[محظور]

(2) سياسة مجموعة التوجيه

مجموعة التوجيه البادئة المصادقة الوصف
عامة / بدون health، docs
api_v1 /api/v1 JWT مطلوب جميع نقاط نهاية الأعمال
admin /api/v1/admin JWT + مسؤول نقاط نهاية الإدارة

4. دمج الكود الكامل

(1) نقاط دخول التطبيق وتكوين البرمجيات الوسيطة

(1) ▶ مثال: تكوين كامل لـ main.py

PYTHON
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

from app.api.routes import products, prices, auth
from app.core.config import settings

app = FastAPI(
    title=settings.app_name,
    version="1.0.0",
    description="PriceTracker SaaS API - خدمة تتبع أسعار التجارة الإلكترونية",
)

# ترتيب البرمجيات الوسيطة: آخر تسجيل = الأعمق (ينفذ أولًا عند الطلب)
# يجب أن تكون CORS في الطبقة الخارجية (تُسجل أخيرًا)
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.allowed_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# تضمين الموجهات - العامة أولًا، ثم المحمية
app.include_router(auth.router, tags=["auth"])
app.include_router(
    products.router,
    prefix="/api/v1",
    dependencies=[Depends(get_current_user)],
    tags=["products"],
)
app.include_router(
    prices.router,
    prefix="/api/v1",
    dependencies=[Depends(get_current_user)],
    tags=["prices"],
)

@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": "pricetracker"}

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) تنفيذ كامل لسلسلة DI ثلاثية الطبقات

(2) ▶ مثال: app/core/deps.py

PYTHON
from fastapi import Depends, HTTPException, Header
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from jose import jwt, JWTError

from app.db import async_session
from app.core.config import settings
from app.core.security import verify_password
from app.models import User
from sqlalchemy import select

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

# المستوى 1: جلسة قاعدة البيانات
async def get_db():
    async with async_session() as session:
        try:
            yield session
        except Exception:
            await session.rollback()
            raise

# المستوى 2: المستخدم الحالي
async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
):
    credentials_exception = HTTPException(
        status_code=401,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(
            token, settings.secret_key, algorithms=[settings.algorithm]
        )
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    stmt = select(User).where(User.email == username)
    result = await db.execute(stmt)
    user = result.scalar_one_or_none()
    if user is None:
        raise credentials_exception
    return user

# المستوى 3: فحص الاشتراك
def require_subscription(min_level: str = "free"):
    LEVELS = {"free": 0, "pro": 1, "enterprise": 2}

    async def check(user: User = Depends(get_current_user)):
        if LEVELS.get(user.subscription, 0) < LEVELS.get(min_level, 0):
            raise HTTPException(
                status_code=403,
                detail=f"Requires {min_level} plan. Current: {user.subscription}",
            )
        return user
    return check

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(3) عملية التحقق من الصلاحيات

100%
flowchart LR
    Request[طلب مع رمز] --> ParseToken[تحليل رمز JWT]
    ParseToken --> ValidToken{الرمز صالح؟}
    ValidToken -->|لا| Return401[إرجاع 401]
    ValidToken -->|نعم| QueryUser[الاستعلام عن المستخدم من DB]
    QueryUser --> UserExists{تم العثور على المستخدم؟}
    UserExists -->|لا| Return401
    UserExists -->|نعم| CheckSub{مستوى الاشتراك؟}
    CheckSub -->|مجاني| AllowBasic[السماح بنقاط النهاية الأساسية]
    CheckSub -->|Pro| AllowPro[السماح بنقاط نهاية Pro]
    CheckSub -->|Enterprise| AllowAll[السماح بجميع نقاط النهاية]

(3) ▶ مثال: توجيه المنتجات المحمي

PYTHON
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional

from app.core.deps import get_db, get_current_user, require_subscription
from app.schemas import ProductCreate, ProductResponse, ProductUpdate
from app.services import ProductService

router = APIRouter()

@router.get("/products", response_model=list[ProductResponse])
async def list_products(
    category: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    db: AsyncSession = Depends(get_db),
    user=Depends(get_current_user),
):
    service = ProductService(db)
    return await service.list_products(category=category, skip=skip, limit=limit)

@router.post("/products", response_model=ProductResponse, status_code=201)
async def create_product(
    product: ProductCreate,
    db: AsyncSession = Depends(get_db),
    user=Depends(get_current_user),
):
    service = ProductService(db)
    return await service.create_product(product, user_id=user.id)

@router.get("/products/{product_id}", response_model=ProductResponse)
async def get_product(
    product_id: int = Path(gt=0),
    db: AsyncSession = Depends(get_db),
    user=Depends(get_current_user),
):
    service = ProductService(db)
    product = await service.get_product(product_id)
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")
    return product

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(4) ▶ مثال: استيراد مجمّع لنقاط النهاية (بما في ذلك حدود الاشتراك)

PYTHON
@router.post("/products/{product_id}/prices/bulk")
async def bulk_import_prices(
    product_id: int = Path(gt=0),
    prices: list[PriceCreate] = ...,
    db: AsyncSession = Depends(get_db),
    user=Depends(require_subscription("free")),  # حتى المستخدمون المجانيون يمكنهم الاستيراد
):
    # خطة مجانية: حد أقصى 1000 سعر لكل استيراد
    limits = {"free": 1000, "pro": 100000, "enterprise": 1000000}
    max_import = limits.get(user.subscription, 1000)
    
    if len(prices) > max_import:
        raise HTTPException(
            status_code=403,
            detail=f"Import limit: {max_import} for {user.subscription} plan. Upgrade at /pricing",
        )
    
    service = PriceService(db)
    return await service.bulk_import_prices(product_id, prices)

الناتج:

TEXT
# تم تعريف الدالة بنجاح

❓ أسئلة شائعة

س لماذا يتم تسجيل CORS أخيرًا في تسلسل البرمجيات الوسيطة؟
ج تحتاج CORS إلى إضافة رؤوس CORS لجميع الاستجابات (بما في ذلك أخطاء 4xx). إذا لم تكن CORS في الطبقة الخارجية، فقد تفتقر استجابة 429 التي تُرجعها برمجية تحديد المعدل الوسيطة إلى رأس CORS، مما يمنع الواجهة الأمامية من قراءة رسالة الخطأ.
س هل يتم تنفيذ اعتمادات مجموعة التوجيه واعتمادات نقطة النهاية عدة مرات؟
ج لا. FastAPI يخزن نفس الاعتماد على مستوى الطلب. Depends(get_current_user) لمجموعة التوجيه و Depends(get_current_user) لنقطة النهاية يتشاركان نفس نتيجة التنفيذ.
س إذا تم تعريف get_db عدة مرات في سلسلة DI، فهل سيكون هناك اتصالات متعددة؟
ج لا. يتم تنفيذ get_db مرة واحدة فقط لكل طلب، وجميع الاعتمادات تتشارك نفس AsyncSession.
س كيف يمكن للمستخدمين المجانيين الترقية إلى Pro؟
ج تكامل دفع Stripe (خارج نطاق هذا الدرس). هنا نستخدم حقل User.subscription للمحاكاة؛ في بيئة الإنتاج، يتم تحديثه عبر رد الاتصال بالدفع.
س كيف أختبر تدفق الطلب الكامل؟
ج استبدل get_db بـ TestClient + dependency_overrides واختبر خطوة بخطوة: بدون رمز → 401، رمز مجاني → 200 (لكن مع قيود)، رمز Pro → صلاحيات كاملة.
س ما الخطوة التالية بعد إكمال المرحلة الثانية؟
ج تقدم المرحلة الثالثة ميزات متقدمة—دفع WebSocket في الوقت الفعلي، ومهام Celery غير المتزامنة، وتخزين Redis المؤقت، ورفع الملفات، واختبار pytest، وتخصيص OpenAPI.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة ⭐): استبدل تنفيذ CRUD في الذاكرة من المرحلة الأولى بتنفيذ SQLAlchemy غير المتزامن، مع التأكد من أن جميع نقاط النهاية تحقن جلسة قاعدة البيانات عبر Depends(get_db). تلميح: نمط Repository + AsyncSession
  2. تمرين متقدم (الصعوبة ⭐⭐): نفذ مجموعات التوجيه—المجموعة "عامة" (/health، /auth/login) لا تتطلب مصادقة، بينما جميع نقاط نهاية الأعمال تحت "api/v1" محمية بـ JWT. استخدم Swagger UI لاختبار تسجيل الدخول والوصول إلى نقاط النهاية المحمية. تلميح: APIRouter(dependencies=[Depends(get_current_user)])
  3. تحدي (الصعوبة ⭐⭐⭐): نفذ سلسلة DI ثلاثية الطبقات الكاملة + استيراد مجمّع لنقاط النهاية: يمكن للمستخدمين Pro استيراد 100,000 سجل سعر؛ سيتلقى المستخدمون المجانيون خطأ 403 إذا تجاوزوا 1000 سجل. أيضًا، اعرض عملية التسجيل → تسجيل الدخول → الاستيراد بالكامل في Swagger UI. تلميح: require_subscription("free") + قاموس limits

---|

Web-Tutorial.com

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

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

100%