from typing import Optional import requests from fastapi import Header, HTTPException, status, Cookie from pydantic import BaseModel from .config import settings class AuthedUser(BaseModel): id: int reg_step: int = 1 gender: int = 0 # 0/1/2 nickname: str = "" token: str = "" def _fetch_user_from_php(token: str) -> Optional[dict]: """通过 PHP/FastAdmin 接口获取用户信息。""" import logging logger = logging.getLogger(__name__) # 从配置读取 PHP 服务地址,如果配置不可用则使用本地地址 try: from lover.config import settings user_info_api = settings.USER_INFO_API except: # 默认使用本地地址 user_info_api = "http://127.0.0.1:30100/api/user_basic/get_user_basic" logger.info(f"用户中心调试 - 调用接口: {user_info_api}") logger.info(f"用户中心调试 - token: {token}") try: resp = requests.get( user_info_api, headers={"token": token}, timeout=3, # 减少超时时间到3秒 ) logger.info(f"用户中心调试 - 响应状态码: {resp.status_code}") logger.info(f"用户中心调试 - 响应内容: {resp.text[:200]}...") except requests.exceptions.Timeout: logger.error(f"用户中心调试 - 请求超时(3秒)") raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail="用户中心接口超时", ) except requests.exceptions.ConnectionError as exc: logger.error(f"用户中心调试 - 连接失败: {exc}") raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail="无法连接到用户中心", ) except Exception as exc: # 其他异常 logger.error(f"用户中心调试 - 请求异常: {exc}") raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail="用户中心接口不可用", ) from exc if resp.status_code != 200: logger.error(f"用户中心调试 - 状态码非200: {resp.status_code}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户中心返回非 200", ) data = resp.json() logger.info(f"用户中心调试 - 解析JSON成功: {data}") # 兼容常见 FastAdmin 响应结构 if isinstance(data, dict): payload = data.get("data") or data.get("user") or data else: payload = None if not payload: logger.error(f"用户中心调试 - 未找到用户数据: {data}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户中心未返回用户信息", ) return payload def get_current_user( authorization: Optional[str] = Header(default=None), x_token: Optional[str] = Header(default=None, alias="X-Token"), token_header: Optional[str] = Header(default=None, alias="token"), token_cookie: Optional[str] = Cookie(default=None, alias="token"), x_user_id: Optional[int] = Header(default=None, alias="X-User-Id"), ): """ 鉴权顺序: 1) Authorization: Bearer 2) X-Token: 3) token: (header) 4) token: (cookie) 5) X-User-Id(仅调试用,不经过 PHP 鉴权) 生产流程:直接调用 USER_INFO_API 拉取用户信息,不再依赖本地 nf_user 缓存。 """ import logging logger = logging.getLogger(__name__) logger.info(f"认证调试 - APP_ENV: {settings.APP_ENV}, DEBUG: {settings.DEBUG}") logger.info(f"认证调试 - authorization: {authorization}, x_token: {x_token}, token_header: {token_header}, token_cookie: {token_cookie}, x_user_id: {x_user_id}") token = None if authorization and authorization.lower().startswith("bearer "): token = authorization.split(" ", 1)[1].strip() if not token and x_token: token = x_token if not token and token_header: token = token_header if not token and token_cookie: token = token_cookie logger.info(f"认证调试 - 提取的 token: {token}") if token: try: payload = _fetch_user_from_php(token) user_id = payload.get("id") or payload.get("user_id") reg_step = payload.get("reg_step") or payload.get("stage") or 1 gender = payload.get("gender") or 0 nickname = payload.get("nickname") or payload.get("username") or "" # 开发环境:自动提升 reg_step 到 2,方便测试 if settings.APP_ENV == "development" and settings.DEBUG and reg_step < 2: logger.warning(f"开发环境:用户 reg_step={reg_step},自动提升到 2") reg_step = 2 if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户中心缺少用户ID" ) return AuthedUser( id=user_id, reg_step=reg_step, gender=gender, nickname=nickname, token=token, ) except HTTPException as e: # 如果是开发环境,token 验证失败时也返回测试用户 if settings.APP_ENV == "development" and settings.DEBUG: logger.warning(f"开发环境:token 验证失败({e.detail}),使用测试用户") return AuthedUser(id=70, reg_step=2, gender=0, nickname="test-user", token="") raise # 调试兜底:仅凭 X-User-Id 不校验 PHP,方便联调 if x_user_id is not None: return AuthedUser(id=x_user_id, reg_step=2, gender=0, nickname="debug-user", token="") # 开发环境兜底:如果没有任何认证信息,返回默认测试用户 if settings.APP_ENV == "development" and settings.DEBUG: return AuthedUser(id=70, reg_step=2, gender=0, nickname="test-user", token="") raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户不存在或未授权")