mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 05:02:57 +00:00
refactor(auth): 优化IP归属地处理逻辑,调整缓存与展示
1. 调整IP归属地缓存TTL从7天改为30天 2. 优化登录IP列展示,增加whitespace-nowrap类并加宽最小宽度 3. 新增异步补全OAuth/微信登录会话归属地逻辑 4. 重构IP归属地工具类,统一降级文案与查询决策逻辑 5. 修复部分类型检查与异常处理问题
This commit is contained in:
@@ -169,6 +169,7 @@ async def oauth_login_redirect_controller(
|
||||
@AuthRouter.get("/oauth/{provider}/callback", summary="第三方OAuth回调", include_in_schema=False)
|
||||
async def oauth_callback_controller(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
redis: Annotated[Redis, Depends(redis_getter)],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
provider: Annotated[OAuthProvider, Path(description="wechat | qq | github | gitee")],
|
||||
@@ -205,6 +206,7 @@ async def oauth_callback_controller(
|
||||
provider=provider,
|
||||
code=code,
|
||||
state=state,
|
||||
background_tasks=background_tasks,
|
||||
)
|
||||
success_url = oauth_service_frontend_redirect_from_token(fe, token)
|
||||
return RedirectContentResponse(url=success_url, status_code=302)
|
||||
@@ -224,6 +226,7 @@ async def wx_mini_login_controller(
|
||||
redis: Annotated[Redis, Depends(redis_getter)],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
body: WxLoginSchema,
|
||||
background_tasks: BackgroundTasks,
|
||||
) -> JSONResponse:
|
||||
"""微信小程序登录(code2Session)。
|
||||
|
||||
@@ -252,6 +255,7 @@ async def wx_mini_login_controller(
|
||||
redis=redis,
|
||||
user=user,
|
||||
login_type="wx_mini",
|
||||
background_tasks=background_tasks,
|
||||
)
|
||||
|
||||
user_info = {
|
||||
@@ -282,6 +286,7 @@ async def wx_mini_phone_login_controller(
|
||||
redis: Annotated[Redis, Depends(redis_getter)],
|
||||
db: Annotated[AsyncSession, Depends(db_getter)],
|
||||
body: WxPhoneLoginSchema,
|
||||
background_tasks: BackgroundTasks,
|
||||
) -> JSONResponse:
|
||||
"""微信小程序手机号快速登录。
|
||||
|
||||
@@ -335,6 +340,7 @@ async def wx_mini_phone_login_controller(
|
||||
redis=redis,
|
||||
user=user,
|
||||
login_type="wx_mini_phone",
|
||||
background_tasks=background_tasks,
|
||||
)
|
||||
|
||||
user_info = {
|
||||
|
||||
@@ -13,7 +13,7 @@ from typing import Any, Literal
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
import httpx
|
||||
from fastapi import Request
|
||||
from fastapi import BackgroundTasks, Request
|
||||
from redis.asyncio.client import Redis
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -347,6 +347,7 @@ async def complete_oauth_login(
|
||||
provider: OAuthProvider,
|
||||
code: str,
|
||||
state: str,
|
||||
background_tasks: BackgroundTasks | None = None,
|
||||
) -> tuple[JWTOutSchema, str]:
|
||||
rc = RedisCURD(redis)
|
||||
raw = await rc.get(f"{STATE_PREFIX}{state}")
|
||||
@@ -394,7 +395,7 @@ async def complete_oauth_login(
|
||||
raise CustomException(msg="用户不存在")
|
||||
|
||||
login_type = f"oauth_{provider}"
|
||||
token = await LoginService.create_token(request=request, redis=redis, user=user, login_type=login_type)
|
||||
token = await LoginService.create_token(request=request, redis=redis, user=user, login_type=login_type, background_tasks=background_tasks)
|
||||
return token, frontend
|
||||
finally:
|
||||
await rc.delete(f"{STATE_PREFIX}{state}")
|
||||
|
||||
@@ -84,6 +84,30 @@ async def _async_fill_login_location(redis, login_log_id: int, ip: str | None) -
|
||||
logger.warning(f"异步补全登录归属地失败: {e}")
|
||||
|
||||
|
||||
async def _async_fill_session_location(redis, session_id: str, ip: str | None) -> None:
|
||||
"""后台异步补全会话缓存中的归属地(微信/OAuth 登录不写登录日志,需单独更新 Redis 会话)。"""
|
||||
if not ip:
|
||||
return
|
||||
try:
|
||||
location = await IpLocalUtil.resolve_location_async(redis, ip)
|
||||
logger.info(f"异步解析IP归属地结果: ip={ip}, session_id={session_id}, location={location}")
|
||||
if location == "归属地查询中" or not location:
|
||||
return
|
||||
key = f"{RedisInitKeyConfig.USER_SESSION.key}:{session_id}"
|
||||
raw = await RedisCURD(redis).get(key)
|
||||
if not raw:
|
||||
return
|
||||
if isinstance(raw, bytes):
|
||||
raw = raw.decode("utf-8")
|
||||
session_dict = json.loads(raw)
|
||||
session_dict["login_location"] = location
|
||||
ttl = await RedisCURD(redis).ttl(key)
|
||||
await RedisCURD(redis).set(key, json.dumps(session_dict, default=str), expire=max(int(ttl), 1))
|
||||
logger.info(f"会话归属地已更新: session_id={session_id}, location={location}")
|
||||
except Exception as e:
|
||||
logger.warning(f"异步补全会话归属地失败: {e}")
|
||||
|
||||
|
||||
class LoginService:
|
||||
"""登录认证服务"""
|
||||
|
||||
@@ -275,7 +299,14 @@ class LoginService:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
async def create_token(cls, request: Request, redis: Redis, user: UserModel, login_type: str) -> JWTOutSchema:
|
||||
async def create_token(
|
||||
cls,
|
||||
request: Request,
|
||||
redis: Redis,
|
||||
user: UserModel,
|
||||
login_type: str,
|
||||
background_tasks: BackgroundTasks | None = None,
|
||||
) -> JWTOutSchema:
|
||||
"""创建访问令牌和刷新令牌"""
|
||||
session_id = str(uuid.uuid4())
|
||||
ua_result = ua_parser.parse(request.headers.get("user-agent") or "")
|
||||
@@ -336,6 +367,10 @@ class LoginService:
|
||||
expire=int(refresh_expires.total_seconds()),
|
||||
)
|
||||
|
||||
# 归属地为待解析时后台补全会话中的 login_location(微信/OAuth 登录路径)
|
||||
if background_tasks and login_location == "归属地查询中":
|
||||
background_tasks.add_task(_async_fill_session_location, redis, session_id, request_ip)
|
||||
|
||||
return JWTOutSchema(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
|
||||
Reference in New Issue
Block a user