Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
from contextlib import asynccontextmanager
from pathlib import Path
import sys

from fastapi import Depends, FastAPI
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from tortoise import Tortoise

# Ensure the local ``core`` package can be imported when the project is not
# installed as a site package (e.g. during pytest execution). This mirrors the
# behaviour of setting ``PYTHONPATH=src`` but keeps the fix self-contained.
SRC_DIR = Path(__file__).resolve().parent
if str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))

from core.dependency import get_current_username
from core.exceptions import SettingNotFound
from core.init_app import (
Expand Down
63 changes: 28 additions & 35 deletions src/api/v1/base/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import json
import os
import platform
from datetime import UTC, datetime

from fastapi import APIRouter, Request
from fastapi import APIRouter, HTTPException, Request
from slowapi import Limiter
from slowapi.util import get_remote_address

Expand All @@ -17,14 +17,7 @@
RefreshTokenRequest,
TokenRefreshOut,
)
from schemas.response import (
CurrentUserResponse,
HealthInfo,
HealthResponse,
TokenResponse,
VersionInfo,
VersionResponse,
)
from schemas.response import CurrentUserResponse, TokenResponse
from settings import settings
from utils.jwt import create_token_pair, verify_token

Expand Down Expand Up @@ -63,8 +56,7 @@ async def login_access_token(request: Request, credentials: CredentialsSchema):
username=user.username,
expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60,
)
result = Success(data=data.model_dump())
return json.loads(result.body)
return Success(data=data.model_dump())


@router.post("/refresh_token", summary="刷新token", response_model=TokenResponse)
Expand Down Expand Up @@ -93,45 +85,46 @@ async def refresh_access_token(request: Request, refresh_request: RefreshTokenRe
expires_in=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES * 60,
)

result = Success(data=data.model_dump())
return json.loads(result.body)
return Success(data=data.model_dump())

except Exception:
result = Fail(code=401, msg="令牌无效或已过期")
return json.loads(result.body)
except Exception as exc: # noqa: BLE001 - propagate as HTTP error for clarity
raise HTTPException(status_code=401, detail="令牌无效或已过期") from exc


@router.get("/userinfo", summary="查看用户信息", response_model=CurrentUserResponse)
async def get_userinfo(current_user: User = DependAuth):
user_id = CTX_USER_ID.get()
user_obj = await user_repository.get(id=user_id)
user_dict = await user_obj.to_dict()
result = Success(data=user_dict)
return json.loads(result.body)
return Success(data=user_dict)


@router.get("/health", summary="健康检查", response_model=HealthResponse)
@router.get("/health", summary="健康检查")
async def health_check():
"""系统健康检查"""
health_data = HealthInfo(
status="healthy",
timestamp=datetime.now(UTC),
environment=settings.APP_ENV,
database="connected"
)
return HealthResponse(code=200, msg="OK", data=health_data)

return {
"status": "healthy",
"timestamp": datetime.now(UTC).isoformat(),
"version": settings.VERSION,
"environment": settings.APP_ENV,
"service": settings.PROJECT_NAME,
"database": "connected",
}


@router.get("/version", summary="版本信息", response_model=VersionResponse)
@router.get("/version", summary="版本信息")
async def get_version():
"""获取API版本信息"""
version_data = VersionInfo(
app_name=settings.APP_TITLE,
version=settings.VERSION,
api_version="v1",
environment=settings.APP_ENV
)
return VersionResponse(code=200, msg="OK", data=version_data)

return {
"version": settings.VERSION,
"app_title": settings.APP_TITLE,
"project_name": settings.PROJECT_NAME,
"build": os.getenv("APP_BUILD", "dev"),
"commit": os.getenv("GIT_COMMIT", "unknown"),
"python_version": platform.python_version(),
}


# @router.get("/usermenu", summary="查看用户菜单", dependencies=[DependAuth])
Expand Down
15 changes: 6 additions & 9 deletions src/api/v1/users/users.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import json

from fastapi import APIRouter, Body, Query

from schemas.response import (
Expand Down Expand Up @@ -31,43 +29,42 @@ async def list_user(
email=email,
dept_id=dept_id,
)
# 转换JSONResponse为字典
return json.loads(result.body)
return result


@router.get("/get", summary="查看用户", response_model=UserDetailResponse)
async def get_user(
user_id: int = Query(..., description="用户ID"),
):
result = await user_service.get_user_detail(user_id)
return json.loads(result.body)
return result


@router.post("/create", summary="创建用户", response_model=UserCreateResponse)
async def create_user(
user_in: UserCreate,
):
result = await user_service.create_user(user_in)
return json.loads(result.body)
return result


@router.post("/update", summary="更新用户", response_model=UserUpdateResponse)
async def update_user(
user_in: UserUpdate,
):
result = await user_service.update_user(user_in)
return json.loads(result.body)
return result


@router.delete("/delete", summary="删除用户", response_model=UserDeleteResponse)
async def delete_user(
user_id: int = Query(..., description="用户ID"),
):
result = await user_service.delete_user(user_id)
return json.loads(result.body)
return result


@router.post("/reset_password", summary="重置密码", response_model=ResponseBase[None])
async def reset_password(user_id: int = Body(..., description="用户ID", embed=True)):
result = await user_service.reset_user_password(user_id)
return json.loads(result.body)
return result
15 changes: 11 additions & 4 deletions src/core/dependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

import jwt
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials, HTTPBearer
from fastapi.security import (
HTTPAuthorizationCredentials,
HTTPBasic,
HTTPBasicCredentials,
HTTPBearer,
)

from core.ctx import CTX_USER_ID
from models import Role, User
from settings.config import settings

security = HTTPBasic()
bearer_scheme = HTTPBearer()
bearer_scheme = HTTPBearer(auto_error=False)


def get_current_username(
Expand All @@ -34,10 +39,12 @@ def get_current_username(

class AuthControl:
@classmethod
async def is_authed(cls, token: str = Depends(bearer_scheme)) -> Optional["User"]:
async def is_authed(
cls, token: HTTPAuthorizationCredentials | None = Depends(bearer_scheme)
) -> Optional["User"]:
try:
# 直接使用 HTTPBearer 提供的 token (已经去掉了 Bearer 前缀)
if not token:
if token is None or not token.credentials:
raise HTTPException(
status_code=401, detail="Missing authentication token"
)
Expand Down
1 change: 1 addition & 0 deletions src/core/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
from collections.abc import AsyncGenerator
from datetime import datetime
import traceback
from typing import Any

from fastapi import FastAPI
Expand Down
Loading