用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/seikaikyo/dash-skills --skill fastapi-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | fastapi-patterns |
| description | FastAPI + SQLModel + Neon PostgreSQL 後端開發最佳實踐。適用於 Render 部署的 API 服務。涵蓋路由設計、資料庫操作、錯誤處理、認證授權。 |
| source | custom |
| updated | "2025-01-16T00:00:00.000Z" |
project/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 應用入口
│ ├── config.py # 環境設定
│ ├── database.py # 資料庫連線
│ ├── dependencies.py # 共用依賴
│ ├── models/ # SQLModel 資料模型
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── work_order.py
│ │ └── user.py
│ ├── schemas/ # Pydantic 請求/回應 schema
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── work_order.py
│ ├── routers/ # API 路由
│ │ ├── __init__.py
│ │ ├── work_orders.py
│ │ └── users.py
│ ├── services/ # 業務邏輯
│ │ ├── __init__.py
│ │ └── work_order_service.py
│ └── utils/ # 工具函數
│ ├── __init__.py
│ └── audit.py
├── requirements.txt
├── render.yaml
└── .env.example
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
# 資料庫
database_url: str
# API
api_prefix: str = "/api"
debug: bool = False
# 認證
jwt_secret: str
jwt_algorithm: str = "HS256"
jwt_expire_minutes: int = 60 * 24 # 24 小時
# CORS
cors_origins: list[str] = ["*"]
class Config:
env_file = ".env"
@lru_cache
def get_settings() -> Settings:
return Settings()
from sqlmodel import SQLModel, create_engine, Session
from app.config import get_settings
settings = get_settings()
# Neon PostgreSQL 連線
engine = create_engine(
settings.database_url,
echo=settings.debug,
pool_pre_ping=True, # 連線健康檢查
pool_recycle=300, # 5 分鐘回收連線
)
def init_db():
"""建立所有資料表"""
SQLModel.metadata.create_all(engine)
def get_session():
"""取得資料庫 session(依賴注入用)"""
with Session(engine) as session:
yield session
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from app.config import get_settings
from app.database import init_db
from app.routers import work_orders, users
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
# 啟動時
init_db()
yield
# 關閉時(清理資源)
app = FastAPI(
title="MES API",
version="1.0.0",
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 路由
app.include_router(work_orders.router, prefix=f"{settings.api_prefix}/work-orders", tags=["工單"])
app.include_router(users.router, prefix=f"{settings.api_prefix}/users", tags=["使用者"])
@app.get("/health")
def health_check():
"""健康檢查(Render 用)"""
return {"status": "ok"}
from sqlmodel import SQLModel, Field
from datetime import datetime
from typing import Optional
import uuid
class BaseModel(SQLModel):
"""基礎模型"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: Optional[datetime] = Field(default=None)
class Config:
json_encoders = {
datetime: lambda v: v.isoformat()
}
from sqlmodel import Field, Relationship
from typing import Optional, List
from datetime import datetime
from enum import Enum
from app.models.base import BaseModel
class WorkOrderStatus(str, Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
CANCELLED = "cancelled"
class WorkOrder(BaseModel, table=True):
"""工單資料表"""
__tablename__ = "work_orders"
order_number: str = Field(unique=True, index=True)
customer_id: str = Field(foreign_key="customers.id", index=True)
product_model: str
quantity: int = Field(ge=1)
status: WorkOrderStatus = Field(default=WorkOrderStatus.PENDING, index=True)
priority: int = Field(default=5, ge=1, le=10)
due_date: Optional[datetime] = None
note: Optional[str] = None
# 關聯
dispatches: List["Dispatch"] = Relationship(back_populates="work_order")
from pydantic import BaseModel
from typing import TypeVar, Generic, Optional, List
T = TypeVar("T")
class ApiResponse(BaseModel, Generic[T]):
"""統一回應格式"""
success: bool
data: Optional[T] = None
error: Optional[dict] = None
message: Optional[str] = None
class PaginatedResponse(ApiResponse[List[T]], Generic[T]):
"""分頁回應"""
pagination: Optional[dict] = None
def success_response(data: T, message: str = "成功") -> ApiResponse[T]:
return ApiResponse(success=True, data=data, message=message)
def error_response(code: str, message: str) -> ApiResponse:
return ApiResponse(success=False, error={"code": code, "message": message})
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
from app.models.work_order import WorkOrderStatus
class WorkOrderCreate(BaseModel):
"""新增工單請求"""
order_number: str = Field(..., min_length=1, max_length=50)
customer_id: str
product_model: str
quantity: int = Field(..., ge=1)
priority: int = Field(default=5, ge=1, le=10)
due_date: Optional[datetime] = None
note: Optional[str] = None
class WorkOrderUpdate(BaseModel):
"""更新工單請求"""
product_model: Optional[str] = None
quantity: Optional[int] = Field(default=None, ge=1)
status: Optional[WorkOrderStatus] = None
priority: Optional[int] = Field(default=None, ge=1, le=10)
due_date: Optional[datetime] = None
note: Optional[] =
():
:
order_number:
customer_id:
product_model:
quantity:
status: WorkOrderStatus
priority:
due_date: [datetime]
note: []
created_at: datetime
updated_at: [datetime]
:
from_attributes =
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel import Session, select
from typing import List, Optional
from datetime import datetime
from app.database import get_session
from app.models.work_order import WorkOrder, WorkOrderStatus
from app.schemas.base import ApiResponse, PaginatedResponse, success_response, error_response
from app.schemas.work_order import WorkOrderCreate, WorkOrderUpdate, WorkOrderResponse
from app.dependencies import get_current_user
from app.utils.audit import log_audit
router = APIRouter()
@router.get("", response_model=PaginatedResponse[WorkOrderResponse])
def list_work_orders(
status: Optional[WorkOrderStatus] = None,
customer_id: Optional[str] = None,
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
session: Session = Depends(get_session),
):
"""取得工單列表"""
query = select(WorkOrder)
# 篩選條件
if status:
query = query.where(WorkOrder.status == status)
if customer_id:
query = query.where(WorkOrder.customer_id == customer_id)
total = session.(select(func.count()).select_from(query.subquery())).one()
offset = (page - ) * limit
query = query.offset(offset).limit(limit).order_by(WorkOrder.created_at.desc())
items = session.(query).()
PaginatedResponse(
success=,
data=[WorkOrderResponse.from_orm(item) item items],
pagination={: total, : page, : limit}
)
():
work_order = session.get(WorkOrder, work_order_id)
work_order:
HTTPException(status_code=, detail=)
success_response(WorkOrderResponse.from_orm(work_order))
():
existing = session.(
select(WorkOrder).where(WorkOrder.order_number == data.order_number)
).first()
existing:
HTTPException(status_code=, detail=)
work_order = WorkOrder(**data.model_dump())
session.add(work_order)
session.commit()
session.refresh(work_order)
log_audit(
session=session,
user_id=current_user[],
action=,
resource_type=,
resource_id=work_order.,
details=data.model_dump()
)
success_response(WorkOrderResponse.from_orm(work_order), )
():
work_order = session.get(WorkOrder, work_order_id)
work_order:
HTTPException(status_code=, detail=)
update_data = data.model_dump(exclude_unset=)
key, value update_data.items():
(work_order, key, value)
work_order.updated_at = datetime.utcnow()
session.add(work_order)
session.commit()
session.refresh(work_order)
log_audit(
session=session,
user_id=current_user[],
action=,
resource_type=,
resource_id=work_order.,
details=update_data
)
success_response(WorkOrderResponse.from_orm(work_order), )
():
work_order = session.get(WorkOrder, work_order_id)
work_order:
HTTPException(status_code=, detail=)
session.delete(work_order)
session.commit()
log_audit(
session=session,
user_id=current_user[],
action=,
resource_type=,
resource_id=work_order_id,
)
success_response(, )
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime
from app.config import get_settings
settings = get_settings()
security = HTTPBearer()
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
"""驗證 JWT Token 並取得當前使用者"""
token = credentials.credentials
try:
payload = jwt.decode(
token,
settings.jwt_secret,
algorithms=[settings.jwt_algorithm]
)
# 檢查過期
exp = payload.get("exp")
if exp and datetime.utcnow().timestamp() > exp:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token 已過期"
)
return payload
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="無效的 Token"
)
def require_role(allowed_roles: list[str]):
"""角色權限檢查"""
def role_checker(current_user: dict = Depends(get_current_user)):
user_role = current_user.get("role", "")
if user_role not allowed_roles:
HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=
)
current_user
role_checker
from sqlmodel import Session
from datetime import datetime
from typing import Optional
import json
from app.models.audit import AuditLog
def log_audit(
session: Session,
user_id: str,
action: str,
resource_type: str,
resource_id: str,
details: Optional[dict] = None,
ip_address: Optional[str] = None,
):
"""記錄稽核日誌"""
audit_log = AuditLog(
user_id=user_id,
action=action,
resource_type=resource_type,
resource_id=resource_id,
details=json.dumps(details) if details else None,
ip_address=ip_address,
timestamp=datetime.utcnow(),
)
session.add(audit_log)
session.commit()
from fastapi import Request
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"error": {
"code": str(exc.status_code),
"message": exc.detail
}
}
)
@app.exception_handler(IntegrityError)
async def integrity_error_handler(request: Request, exc: IntegrityError):
return JSONResponse(
status_code=400,
content={
"success": False,
"error": {
"code": "INTEGRITY_ERROR",
"message": "資料完整性錯誤,可能是重複的資料"
}
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
# 記錄錯誤
print(f"Unhandled error: {exc}")
return JSONResponse(
status_code=,
content={
: ,
: {
: ,
:
}
}
)
services:
- type: web
name: mes-api
runtime: python
buildCommand: pip install -r requirements.txt
startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT
envVars:
- key: DATABASE_URL
fromDatabase:
name: mes-db
property: connectionString
- key: JWT_SECRET
generateValue: true
healthCheckPath: /health
autoDeploy: true
databases:
- name: mes-db
plan: free
databaseName: mes
user: mes_user
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
sqlmodel>=0.0.14
pydantic-settings>=2.1.0
python-jose[cryptography]>=3.3.0
psycopg2-binary>=2.9.9
| 指標 | 目標 |
|---|---|
| API 回應時間 | < 500ms |
| 資料庫查詢 | < 100ms |
| 啟動時間 | < 30s |