refactor(auth): 优化IP归属地处理逻辑,调整缓存与展示

1. 调整IP归属地缓存TTL从7天改为30天
2. 优化登录IP列展示,增加whitespace-nowrap类并加宽最小宽度
3. 新增异步补全OAuth/微信登录会话归属地逻辑
4. 重构IP归属地工具类,统一降级文案与查询决策逻辑
5. 修复部分类型检查与异常处理问题
This commit is contained in:
zhangtao
2026-08-16 02:57:19 +08:00
parent 61336936a0
commit 11160b05e4
6 changed files with 112 additions and 32 deletions
@@ -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,