Преглед изворни кода

用户访问后token续签处理

lingmin_package@163.com пре 2 недеља
родитељ
комит
fce2874667

+ 1 - 1
src/app/config/config.ini

@@ -37,7 +37,7 @@ REFRESH_TOKEN_EXPIRE_DAYS=30
 JWT_SECRET_KEY=dev-jwt-secret-key-change-in-production-12345678901234567890
 JWT_SECRET_KEY=dev-jwt-secret-key-change-in-production-12345678901234567890
 
 
 # 后台管理Token配置
 # 后台管理Token配置
-ADMIN_TOKEN_EXPIRE_MINUTES=60
+ADMIN_TOKEN_EXPIRE_MINUTES=3
 ADMIN_REFRESH_TOKEN_EXPIRE_HOURS=24
 ADMIN_REFRESH_TOKEN_EXPIRE_HOURS=24
 
 
 # OAuth2配置
 # OAuth2配置

+ 3 - 2
src/app/middleware/token_refresh_middleware.py

@@ -56,6 +56,7 @@ class TokenRefreshMiddleware(BaseHTTPMiddleware):
             
             
             if not payload:
             if not payload:
                 # Token无效,返回401错误
                 # Token无效,返回401错误
+                logger.warning(f"无效的访问令牌,路径: {request.url.path}")
                 return JSONResponse(
                 return JSONResponse(
                     status_code=401,
                     status_code=401,
                     content={
                     content={
@@ -72,10 +73,10 @@ class TokenRefreshMiddleware(BaseHTTPMiddleware):
             if new_token:
             if new_token:
                 response.headers["X-New-Token"] = new_token
                 response.headers["X-New-Token"] = new_token
                 response.headers["X-Token-Refreshed"] = "true"
                 response.headers["X-Token-Refreshed"] = "true"
-                logger.info(f"Token已刷新,用户: {payload.get('username', 'unknown')}")
+                logger.info(f"Token已刷新,用户: {payload.get('username', 'unknown')}, 路径: {request.url.path}")
             
             
             return response
             return response
             
             
         except Exception as e:
         except Exception as e:
-            logger.error(f"Token刷新中间件错误: {e}")
+            logger.error(f"Token刷新中间件错误: {e}, 路径: {request.url.path}")
             return await call_next(request)
             return await call_next(request)

+ 49 - 0
src/app/server/app.py

@@ -146,8 +146,57 @@ app.add_middleware(
     allow_credentials=True,
     allow_credentials=True,
     allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
     allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
     allow_headers=["*"],
     allow_headers=["*"],
+    expose_headers=["X-New-Token", "X-Token-Refreshed"],  # 关键:暴露自定义响应头
 )
 )
 
 
+# 添加Token刷新中间件
+from app.middleware.token_refresh_middleware import TokenRefreshMiddleware
+
+app.add_middleware(
+    TokenRefreshMiddleware,
+    exclude_paths=[
+        "/auth/login",
+        "/auth/captcha",
+        "/auth/refresh",
+        "/docs",
+        "/openapi.json",
+        "/redoc",
+        "/favicon.ico",
+        "/health",
+        "/"
+    ]
+)
+
+logger.info("✅ Token刷新中间件已注册")
+
+# 添加响应处理中间件 - 用于在响应头中添加新token
+@app.middleware("http")
+async def add_new_token_to_response(request: Request, call_next):
+    """
+    响应处理中间件
+    如果request.state中有new_token,将其添加到响应头
+    """
+    logger.info(f">>> [响应中间件] 处理请求: {request.url.path}")
+    
+    response = await call_next(request)
+    
+    logger.info(f">>> [响应中间件] 请求处理完成,检查是否有新token")
+    logger.info(f">>> [响应中间件] hasattr(request.state, 'new_token'): {hasattr(request.state, 'new_token')}")
+    
+    # 检查是否有新token需要返回
+    if hasattr(request.state, "new_token"):
+        logger.info(f">>> [响应中间件] 发现新token,添加到响应头")
+        response.headers["X-New-Token"] = request.state.new_token
+        response.headers["X-Token-Refreshed"] = "true"
+        logger.info(f">>> [响应中间件] 已在响应头中添加新token")
+        logger.info(f">>> [响应中间件] X-New-Token前20字符: {request.state.new_token[:20]}...")
+    else:
+        logger.info(f">>> [响应中间件] 没有新token需要添加")
+    
+    return response
+
+logger.info("✅ Token响应处理中间件已注册")
+
 # --- 调试中间件 ---
 # --- 调试中间件 ---
 """
 """
 @app.middleware("http")
 @app.middleware("http")

+ 34 - 5
src/app/services/jwt_token.py

@@ -110,6 +110,9 @@ def verify_and_refresh_token(token: str) -> tuple[Optional[dict], Optional[str]]
     Returns:
     Returns:
         tuple: (payload, new_token) - 如果需要刷新则返回新token,否则new_token为None
         tuple: (payload, new_token) - 如果需要刷新则返回新token,否则new_token为None
     """
     """
+    import logging
+    logger = logging.getLogger(__name__)
+    
     try:
     try:
         payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
         payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
         
         
@@ -121,28 +124,54 @@ def verify_and_refresh_token(token: str) -> tuple[Optional[dict], Optional[str]]
             current_time = datetime.now(timezone.utc)
             current_time = datetime.now(timezone.utc)
             exp_time = datetime.fromtimestamp(exp, tz=timezone.utc)
             exp_time = datetime.fromtimestamp(exp, tz=timezone.utc)
             iat_time = datetime.fromtimestamp(iat, tz=timezone.utc)
             iat_time = datetime.fromtimestamp(iat, tz=timezone.utc)
+            # 如果需要显示本地时间
+            local_iat_time = iat_time.astimezone()  # 转换为本地时区
+            local_exp_time = exp_time.astimezone()  # 转换为本地时区
             
             
-            # 获取token总有效期
+            # 获取token总有效期(分钟)
             admin_expire_minutes = config_handler.get_int("admin_app", "ADMIN_TOKEN_EXPIRE_MINUTES", None)
             admin_expire_minutes = config_handler.get_int("admin_app", "ADMIN_TOKEN_EXPIRE_MINUTES", None)
             total_expire_minutes = admin_expire_minutes if admin_expire_minutes is not None else ACCESS_TOKEN_EXPIRE_MINUTES
             total_expire_minutes = admin_expire_minutes if admin_expire_minutes is not None else ACCESS_TOKEN_EXPIRE_MINUTES
             
             
-            # 计算token已使用时间
+            # 计算token已使用时间和剩余时间
             token_age = current_time - iat_time
             token_age = current_time - iat_time
+            token_age_seconds = token_age.total_seconds()
+            token_age_minutes = token_age_seconds / 60
+            
+            time_remaining = exp_time - current_time
+            time_remaining_seconds = time_remaining.total_seconds()
+            time_remaining_minutes = time_remaining_seconds / 60
+
+            
+            
+            # 获取用户名用于日志
+            username = payload.get('username', 'unknown')
+            
+            # 打印详细的token信息日志
+            logger.info(f"Token验证信息 - 用户: {username}")
+            logger.info(f"  Token配置时长: {total_expire_minutes} 分钟")
+            logger.info(f"  Token创建时间: {local_iat_time.strftime('%Y-%m-%d %H:%M:%S')}")
+            logger.info(f"  Token过期时间: {local_exp_time.strftime('%Y-%m-%d %H:%M:%S')}")
+            logger.info(f"  Token已使用时长: {token_age_minutes:.2f} 分钟 ({token_age_seconds:.0f} 秒)")
+            logger.info(f"  Token剩余时长: {time_remaining_minutes:.2f} 分钟 ({time_remaining_seconds:.0f} 秒)")
             
             
             # 如果token使用时间超过总有效期的一半,则刷新token
             # 如果token使用时间超过总有效期的一半,则刷新token
-            if token_age.total_seconds() > (total_expire_minutes * 60 / 2):
+            refresh_threshold_seconds = total_expire_minutes * 60 / 2
+            if token_age_seconds > refresh_threshold_seconds:
                 # 创建新的token数据,保持原有信息
                 # 创建新的token数据,保持原有信息
                 new_token_data = {k: v for k, v in payload.items() if k not in ['exp', 'iat']}
                 new_token_data = {k: v for k, v in payload.items() if k not in ['exp', 'iat']}
                 new_token = create_access_token(new_token_data)
                 new_token = create_access_token(new_token_data)
+                logger.info(f"  Token刷新: 已使用时长超过阈值 ({refresh_threshold_seconds/60:.2f} 分钟),生成新token")
                 return payload, new_token
                 return payload, new_token
+            else:
+                logger.info(f"  Token刷新: 未达到刷新阈值 ({refresh_threshold_seconds/60:.2f} 分钟),继续使用当前token")
         
         
         return payload, None
         return payload, None
         
         
     except JWT_ERROR as e:
     except JWT_ERROR as e:
-        print(f"Token验证失败: {e}")
+        logger.error(f"Token验证失败: {e}")
         return None, None
         return None, None
     except Exception as e:
     except Exception as e:
-        print(f"Token验证异常: {e}")
+        logger.error(f"Token验证异常: {e}")
         return None, None
         return None, None
 
 
 
 

+ 7 - 5
src/app/services/snippet_service.py

@@ -43,12 +43,12 @@ class SnippetService:
         kb_name_map = {}
         kb_name_map = {}
         try:
         try:
             # 修正引用路径
             # 修正引用路径
-            from app.base.async_mysql_connection import get_db_connection
+            from app.base.async_mysql_connection import get_db
             from app.sample.models.knowledge_base import KnowledgeBase
             from app.sample.models.knowledge_base import KnowledgeBase
             from sqlalchemy import select
             from sqlalchemy import select
             
             
-            # 使用 get_db_connection 上下文管理器
-            async with get_db_connection() as db:
+            # 使用异步数据库会话
+            async for db in get_db():
                 res = await db.execute(select(KnowledgeBase.collection_name_parent, KnowledgeBase.collection_name_children, KnowledgeBase.name))
                 res = await db.execute(select(KnowledgeBase.collection_name_parent, KnowledgeBase.collection_name_children, KnowledgeBase.name))
                 for row in res.all():
                 for row in res.all():
                     # 映射 collection_name_parent -> name
                     # 映射 collection_name_parent -> name
@@ -57,6 +57,7 @@ class SnippetService:
                     # 映射 collection_name_children -> name
                     # 映射 collection_name_children -> name
                     if row[1]:
                     if row[1]:
                         kb_name_map[row[1]] = row[2]
                         kb_name_map[row[1]] = row[2]
+                break  # 只需要一次迭代
                         
                         
         except Exception as e:
         except Exception as e:
             logger.exception("Failed to load KB map")
             logger.exception("Failed to load KB map")
@@ -267,15 +268,16 @@ class SnippetService:
         if doc_ids:
         if doc_ids:
             try:
             try:
                 # 修正引用路径
                 # 修正引用路径
-                from app.base.async_mysql_connection import get_db_connection
+                from app.base.async_mysql_connection import get_db
                 from app.sample.models.base_info import DocumentMain
                 from app.sample.models.base_info import DocumentMain
                 from sqlalchemy import select
                 from sqlalchemy import select
 
 
-                async with get_db_connection() as db:
+                async for db in get_db():
                     stmt = select(DocumentMain.id, DocumentMain.title).where(DocumentMain.id.in_(list(doc_ids)))
                     stmt = select(DocumentMain.id, DocumentMain.title).where(DocumentMain.id.in_(list(doc_ids)))
                     result = await db.execute(stmt)
                     result = await db.execute(stmt)
                     rows = result.all()
                     rows = result.all()
                     doc_name_map = {str(row[0]): row[1] for row in rows}
                     doc_name_map = {str(row[0]): row[1] for row in rows}
+                    break  # 只需要一次迭代
                 
                 
                 if doc_name_map:
                 if doc_name_map:
                     for item in items:
                     for item in items:

+ 100 - 0
src/app/utils/auth_dependency.py

@@ -0,0 +1,100 @@
+"""
+认证依赖函数
+统一处理token验证和刷新
+"""
+import logging
+from fastapi import Depends, HTTPException, Request
+from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
+from app.services.jwt_token import verify_and_refresh_token
+
+logger = logging.getLogger(__name__)
+
+security = HTTPBearer()
+
+
+async def get_current_user_with_refresh(
+    request: Request,
+    credentials: HTTPAuthorizationCredentials = Depends(security)
+) -> dict:
+    """
+    获取当前用户并处理token刷新
+    
+    这个依赖函数会:
+    1. 验证token是否有效
+    2. 检查是否需要刷新token
+    3. 如果需要刷新,生成新token并存储到request.state
+    4. 返回用户信息(payload)
+    
+    Args:
+        request: FastAPI请求对象
+        credentials: HTTP Bearer认证凭据
+        
+    Returns:
+        dict: Token payload(包含用户信息)
+        
+    Raises:
+        HTTPException: Token无效时抛出401错误
+    """
+    logger.info(f">>> [依赖函数] get_current_user_with_refresh 被调用")
+    logger.info(f">>> [依赖函数] 请求路径: {request.url.path}")
+    
+    token = credentials.credentials
+    logger.info(f">>> [依赖函数] Token前20字符: {token[:20]}...")
+    
+    # 验证token并检查是否需要刷新
+    logger.info(f">>> [依赖函数] 调用 verify_and_refresh_token")
+    payload, new_token = verify_and_refresh_token(token)
+    logger.info(f">>> [依赖函数] verify_and_refresh_token 返回: payload={'存在' if payload else '无'}, new_token={'存在' if new_token else '无'}")
+    
+    if not payload:
+        logger.warning(f"无效的访问令牌,路径: {request.url.path}")
+        raise HTTPException(
+            status_code=401,
+            detail="无效的访问令牌"
+        )
+    
+    logger.info(f">>> [依赖函数] Token验证成功,用户: {payload.get('username', 'unknown')}")
+    
+    # 如果生成了新token,存储到request.state中
+    # 稍后在响应中间件中会将其添加到响应头
+    if new_token:
+        request.state.new_token = new_token
+        request.state.token_refreshed = True
+        logger.info(f">>> [依赖函数] Token已刷新,新token已存储到request.state")
+        logger.info(f">>> [依赖函数] 新Token前20字符: {new_token[:20]}...")
+        logger.info(f"Token已刷新,用户: {payload.get('username', 'unknown')}, 路径: {request.url.path}")
+    else:
+        logger.info(f">>> [依赖函数] Token未刷新(未达到阈值或其他原因)")
+    
+    return payload
+
+
+async def get_current_user(
+    credentials: HTTPAuthorizationCredentials = Depends(security)
+) -> dict:
+    """
+    获取当前用户(不刷新token)
+    
+    用于不需要token刷新的场景
+    
+    Args:
+        credentials: HTTP Bearer认证凭据
+        
+    Returns:
+        dict: Token payload(包含用户信息)
+        
+    Raises:
+        HTTPException: Token无效时抛出401错误
+    """
+    from app.services.jwt_token import verify_token
+    
+    token = credentials.credentials
+    payload = verify_token(token)
+    
+    if not payload:
+        raise HTTPException(
+            status_code=401,
+            detail="无效的访问令牌"
+        )
+    
+    return payload

+ 15 - 50
src/views/image_view.py

@@ -9,6 +9,7 @@ from app.services.image_service import ImageService
 from app.services.jwt_token import verify_token
 from app.services.jwt_token import verify_token
 from app.schemas.base import ApiResponse
 from app.schemas.base import ApiResponse
 from pydantic import BaseModel
 from pydantic import BaseModel
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 # 获取logger
 # 获取logger
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -46,13 +47,9 @@ class BatchAddRequest(BaseModel):
 # --- 分类管理 API ---
 # --- 分类管理 API ---
 
 
 @router.get("/categories")
 @router.get("/categories")
-async def get_categories(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_categories(current_user: dict = Depends(get_current_user_with_refresh)):
     """获取所有图片分类树"""
     """获取所有图片分类树"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
         service = ImageService()
         service = ImageService()
         data = await service.get_categories()
         data = await service.get_categories()
         return ApiResponse(code=0, message="成功", data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=0, message="成功", data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
@@ -61,14 +58,10 @@ async def get_categories(credentials: HTTPAuthorizationCredentials = Depends(sec
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/categories")
 @router.post("/categories")
-async def add_category(data: CategoryAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def add_category(data: CategoryAdd, current_user: dict = Depends(get_current_user_with_refresh)):
     """新增分类"""
     """新增分类"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
-        user_id = payload.get("sub", "system")
+        user_id = current_user.get("sub", "system")
         service = ImageService()
         service = ImageService()
         success, message, category_id = await service.add_category(data.model_dump(), user_id)
         success, message, category_id = await service.add_category(data.model_dump(), user_id)
         
         
@@ -81,14 +74,10 @@ async def add_category(data: CategoryAdd, credentials: HTTPAuthorizationCredenti
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.put("/categories/{category_id}")
 @router.put("/categories/{category_id}")
-async def edit_category(category_id: str, data: CategoryUpdate, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def edit_category(category_id: str, data: CategoryUpdate, current_user: dict = Depends(get_current_user_with_refresh)):
     """编辑分类"""
     """编辑分类"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
-        user_id = payload.get("sub", "system")
+        user_id = current_user.get("sub", "system")
         service = ImageService()
         service = ImageService()
         success, message = await service.edit_category(category_id, data.model_dump(), user_id)
         success, message = await service.edit_category(category_id, data.model_dump(), user_id)
         
         
@@ -101,13 +90,9 @@ async def edit_category(category_id: str, data: CategoryUpdate, credentials: HTT
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.delete("/categories/{category_id}")
 @router.delete("/categories/{category_id}")
-async def delete_category(category_id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def delete_category(category_id: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """删除分类"""
     """删除分类"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
         service = ImageService()
         service = ImageService()
         success, message = await service.delete_category(category_id)
         success, message = await service.delete_category(category_id)
         
         
@@ -126,14 +111,10 @@ async def get_image_list(
     category_id: Optional[str] = None, 
     category_id: Optional[str] = None, 
     page: int = 1, 
     page: int = 1, 
     page_size: int = 10, 
     page_size: int = 10, 
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取图片列表"""
     """获取图片列表"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
         service = ImageService()
         service = ImageService()
         data = await service.get_image_list(category_id, page, page_size)
         data = await service.get_image_list(category_id, page, page_size)
         return ApiResponse(code=0, message="成功", data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=0, message="成功", data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
@@ -142,14 +123,10 @@ async def get_image_list(
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("")
 @router.post("")
-async def add_image(data: ImageAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def add_image(data: ImageAdd, current_user: dict = Depends(get_current_user_with_refresh)):
     """保存图片信息"""
     """保存图片信息"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
-        user_id = payload.get("sub", "system")
+        user_id = current_user.get("sub", "system")
         service = ImageService()
         service = ImageService()
         success, message = await service.add_image(data.model_dump(), user_id)
         success, message = await service.add_image(data.model_dump(), user_id)
         
         
@@ -162,13 +139,9 @@ async def add_image(data: ImageAdd, credentials: HTTPAuthorizationCredentials =
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.delete("/{image_id}")
 @router.delete("/{image_id}")
-async def delete_image(image_id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def delete_image(image_id: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """删除图片"""
     """删除图片"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
         service = ImageService()
         service = ImageService()
         success, message = await service.delete_image(image_id)
         success, message = await service.delete_image(image_id)
         
         
@@ -181,13 +154,9 @@ async def delete_image(image_id: str, credentials: HTTPAuthorizationCredentials
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/upload-url")
 @router.post("/upload-url")
-async def get_upload_url(req: UploadUrlRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_upload_url(req: UploadUrlRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """获取 MinIO 预签名上传 URL"""
     """获取 MinIO 预签名上传 URL"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
         service = ImageService()
         service = ImageService()
         success, message, data = await service.get_upload_url(req.filename, req.content_type, prefix=req.prefix)
         success, message, data = await service.get_upload_url(req.filename, req.content_type, prefix=req.prefix)
         
         
@@ -200,15 +169,11 @@ async def get_upload_url(req: UploadUrlRequest, credentials: HTTPAuthorizationCr
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/batch-add-to-task")
 @router.post("/batch-add-to-task")
-async def batch_add_to_task(req: BatchAddRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def batch_add_to_task(req: BatchAddRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """批量加入任务中心 (设置 whether_to_task = 1)"""
     """批量加入任务中心 (设置 whether_to_task = 1)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
-        
-        user_id = payload.get("sub", "admin")
-        username = payload.get("username", user_id)
+        user_id = current_user.get("sub", "admin")
+        username = current_user.get("username", user_id)
         
         
         service = ImageService()
         service = ImageService()
         success, message = await service.batch_add_to_task(req.ids, username, req.project_name, tags=req.tags)
         success, message = await service.batch_add_to_task(req.ids, username, req.project_name, tags=req.tags)

+ 10 - 42
src/views/knowledge_base_view.py

@@ -1,7 +1,7 @@
 """
 """
 知识库视图路由
 知识库视图路由
 """
 """
-from fastapi import APIRouter, Depends, Query, Path
+from fastapi import APIRouter, Depends, Query, Path, Request
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.ext.asyncio import AsyncSession
 from typing import Optional
 from typing import Optional
 
 
@@ -15,6 +15,7 @@ from app.sample.schemas.knowledge_base import (
 from app.schemas.base import PaginatedResponseSchema, ResponseSchema
 from app.schemas.base import PaginatedResponseSchema, ResponseSchema
 from app.services.jwt_token import verify_token
 from app.services.jwt_token import verify_token
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 router = APIRouter(prefix="/sample/knowledge-base", tags=["样本中心-知识库"])
 router = APIRouter(prefix="/sample/knowledge-base", tags=["样本中心-知识库"])
 security = HTTPBearer()
 security = HTTPBearer()
@@ -26,14 +27,9 @@ async def get_knowledge_bases(
     keyword: str = Query(None, description="搜索关键词"),
     keyword: str = Query(None, description="搜索关键词"),
     status: str = Query(None, description="状态筛选"),
     status: str = Query(None, description="状态筛选"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取知识库列表"""
     """获取知识库列表"""
-    # 鉴权
-    payload = verify_token(credentials.credentials)
-    if not payload:
-        return PaginatedResponseSchema(code=401, message="无效的访问令牌", data=[], meta=None)
-
     items, meta = await knowledge_base_service.get_list(
     items, meta = await knowledge_base_service.get_list(
         db, page=page, page_size=page_size, keyword=keyword, status=status
         db, page=page, page_size=page_size, keyword=keyword, status=status
     )
     )
@@ -48,13 +44,9 @@ async def get_knowledge_bases(
 @router.get("/list", response_model=ResponseSchema)
 @router.get("/list", response_model=ResponseSchema)
 async def get_knowledge_base_simple_list(
 async def get_knowledge_base_simple_list(
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取知识库简单列表 (用于下拉选择)"""
     """获取知识库简单列表 (用于下拉选择)"""
-    payload = verify_token(credentials.credentials)
-    if not payload:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     # 只获取状态正常的知识库
     # 只获取状态正常的知识库
     items, _ = await knowledge_base_service.get_list(db, page=1, page_size=1000, status="normal")
     items, _ = await knowledge_base_service.get_list(db, page=1, page_size=1000, status="normal")
     return ResponseSchema(
     return ResponseSchema(
@@ -67,13 +59,9 @@ async def get_knowledge_base_simple_list(
 async def create_knowledge_base(
 async def create_knowledge_base(
     payload: KnowledgeBaseCreate,
     payload: KnowledgeBaseCreate,
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建新知识库"""
     """创建新知识库"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     try:
     try:
         new_kb = await knowledge_base_service.create(db, payload)
         new_kb = await knowledge_base_service.create(db, payload)
         return ResponseSchema(code=0, message="创建成功", data=KnowledgeBaseResponse.model_validate(new_kb))
         return ResponseSchema(code=0, message="创建成功", data=KnowledgeBaseResponse.model_validate(new_kb))
@@ -87,13 +75,9 @@ async def update_knowledge_base(
     payload: KnowledgeBaseUpdate,
     payload: KnowledgeBaseUpdate,
     id: str = Path(..., description="知识库ID"),
     id: str = Path(..., description="知识库ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新知识库信息"""
     """更新知识库信息"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     try:
     try:
         kb = await knowledge_base_service.update(db, id, payload)
         kb = await knowledge_base_service.update(db, id, payload)
         return ResponseSchema(code=0, message="更新成功", data=KnowledgeBaseResponse.model_validate(kb))
         return ResponseSchema(code=0, message="更新成功", data=KnowledgeBaseResponse.model_validate(kb))
@@ -107,13 +91,9 @@ async def update_knowledge_base_status(
     id: str = Path(..., description="知识库ID"),
     id: str = Path(..., description="知识库ID"),
     status: str = Query(..., description="状态: normal/test/disabled"),
     status: str = Query(..., description="状态: normal/test/disabled"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新知识库状态"""
     """更新知识库状态"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     await knowledge_base_service.update_status(db, id, status)
     await knowledge_base_service.update_status(db, id, status)
     return ResponseSchema(code=0, message=f"状态已更新为 {status}")
     return ResponseSchema(code=0, message=f"状态已更新为 {status}")
 
 
@@ -121,13 +101,9 @@ async def update_knowledge_base_status(
 async def delete_knowledge_base(
 async def delete_knowledge_base(
     id: str = Path(..., description="知识库ID"),
     id: str = Path(..., description="知识库ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除知识库"""
     """删除知识库"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     await knowledge_base_service.delete(db, id)
     await knowledge_base_service.delete(db, id)
     return ResponseSchema(code=0, message="删除成功")
     return ResponseSchema(code=0, message="删除成功")
 
 
@@ -135,13 +111,9 @@ async def delete_knowledge_base(
 async def get_knowledge_base_metadata(
 async def get_knowledge_base_metadata(
     id: str = Path(..., description="知识库ID"),
     id: str = Path(..., description="知识库ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取知识库的元数据字段定义和自定义Schema"""
     """获取知识库的元数据字段定义和自定义Schema"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     try:
     try:
         data = await knowledge_base_service.get_metadata_and_schema(db, id)
         data = await knowledge_base_service.get_metadata_and_schema(db, id)
         return ResponseSchema(code=0, message="获取成功", data=data)
         return ResponseSchema(code=0, message="获取成功", data=data)
@@ -154,13 +126,9 @@ async def get_knowledge_base_metadata(
 async def sync_knowledge_base(
 async def sync_knowledge_base(
     id: str = Path(..., description="知识库ID"),
     id: str = Path(..., description="知识库ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """同步创建Milvus集合"""
     """同步创建Milvus集合"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     try:
     try:
         await knowledge_base_service.sync_to_milvus(db, id)
         await knowledge_base_service.sync_to_milvus(db, id)
         return ResponseSchema(code=0, message="同步成功")
         return ResponseSchema(code=0, message="同步成功")

+ 5 - 12
src/views/oauth_view.py

@@ -20,6 +20,7 @@ from datetime import datetime, timezone
 from app.services.jwt_token import verify_token, create_access_token, ACCESS_TOKEN_EXPIRE_MINUTES
 from app.services.jwt_token import verify_token, create_access_token, ACCESS_TOKEN_EXPIRE_MINUTES
 from app.services.oauth_service import OAuthService
 from app.services.oauth_service import OAuthService
 from app.base.async_mysql_connection import get_db_connection
 from app.base.async_mysql_connection import get_db_connection
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 # 获取logger
 # 获取logger
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -536,20 +537,12 @@ async def oauth_token(request: Request):
         }
         }
 
 
 @router.get("/userinfo")
 @router.get("/userinfo")
-async def oauth_userinfo(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def oauth_userinfo(current_user: dict = Depends(get_current_user_with_refresh)):
     """OAuth2用户信息端点"""
     """OAuth2用户信息端点"""
     try:
     try:
-        # 验证令牌
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return {
-                "error": "invalid_token",
-                "error_description": "Invalid or expired access token"
-            }
-        
-        user_id = payload.get("sub")
-        client_id = payload.get("client_id")
-        scope = payload.get("scope", "").split()
+        user_id = current_user.get("sub")
+        client_id = current_user.get("client_id")
+        scope = current_user.get("scope", "").split()
         
         
         logger.info(f"用户信息请求: user_id={user_id}, client_id={client_id}, scope={scope}")
         logger.info(f"用户信息请求: user_id={user_id}, client_id={client_id}, scope={scope}")
         
         

+ 37 - 113
src/views/sample_view.py

@@ -16,6 +16,7 @@ from app.services.sample_service import SampleService
 from app.services.jwt_token import verify_token
 from app.services.jwt_token import verify_token
 from app.schemas.base import ApiResponse
 from app.schemas.base import ApiResponse
 from app.base import get_mineru_manager
 from app.base import get_mineru_manager
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 from app.services.task_service import task_service
 from app.services.task_service import task_service
 
 
@@ -29,13 +30,9 @@ security_optional = HTTPBearer(auto_error=False)
 
 
 
 
 @router.get("/tasks")
 @router.get("/tasks")
-async def get_tasks(type: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_tasks(type: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """获取任务项目列表 (聚合显示)"""
     """获取任务项目列表 (聚合显示)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
-        
         projects = await task_service.get_task_list(type)
         projects = await task_service.get_task_list(type)
         return ApiResponse(code=0, message="成功", data=projects).model_dump()
         return ApiResponse(code=0, message="成功", data=projects).model_dump()
     except Exception as e:
     except Exception as e:
@@ -44,13 +41,9 @@ async def get_tasks(type: str, credentials: HTTPAuthorizationCredentials = Depen
 
 
 
 
 @router.get("/tasks/details")
 @router.get("/tasks/details")
-async def get_task_details(project_id: str, type: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_task_details(project_id: str, type: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """获取项目下的文件详情"""
     """获取项目下的文件详情"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
-        
         files = await task_service.get_project_details(project_id, type)
         files = await task_service.get_project_details(project_id, type)
         return ApiResponse(code=0, message="成功", data=files).model_dump()
         return ApiResponse(code=0, message="成功", data=files).model_dump()
     except Exception as e:
     except Exception as e:
@@ -61,15 +54,11 @@ async def get_task_details(project_id: str, type: str, credentials: HTTPAuthoriz
 # --- 外部联动接口 API ---
 # --- 外部联动接口 API ---
 
 
 @router.post("/external/projects/init", status_code=201)
 @router.post("/external/projects/init", status_code=201)
-async def init_external_project(request: Request, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def init_external_project(request: Request, current_user: dict = Depends(get_current_user_with_refresh)):
     """项目初始化接口:由标注平台调用,同步数据"""
     """项目初始化接口:由标注平台调用,同步数据"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
-        
         # 简单验证是否为管理员(根据业务需求调整)
         # 简单验证是否为管理员(根据业务需求调整)
-        if not payload.get("is_superuser") and payload.get("role") != "admin":
+        if not current_user.get("is_superuser") and current_user.get("role") != "admin":
             return ApiResponse(code=403, message="权限不足").model_dump()
             return ApiResponse(code=403, message="权限不足").model_dump()
 
 
         data = await request.json()
         data = await request.json()
@@ -87,14 +76,10 @@ async def init_external_project(request: Request, credentials: HTTPAuthorization
 @router.get("/external/projects/progress")
 @router.get("/external/projects/progress")
 async def get_external_project_progress(
 async def get_external_project_progress(
     project_id: Optional[str] = None, 
     project_id: Optional[str] = None, 
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """查询项目进度"""
     """查询项目进度"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
-            
         if not project_id:
         if not project_id:
             return ApiResponse(code=400, message="缺少项目ID").model_dump()
             return ApiResponse(code=400, message="缺少项目ID").model_dump()
 
 
@@ -112,15 +97,10 @@ async def get_external_project_progress(
 async def export_external_project(
 async def export_external_project(
     req: ExportRequest, 
     req: ExportRequest, 
     project_id: Optional[str] = None,
     project_id: Optional[str] = None,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """导出项目已完成的标注数据"""
     """导出项目已完成的标注数据"""
     try:
     try:
-        logger.debug(f"收到导出请求: project_id={project_id}, req={req.model_dump()}, has_credentials={bool(credentials.credentials)}")
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            logger.warning(f"导出请求 Token 验证失败: {credentials.credentials[:10]}...")
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
         
         
         # 优先从路径获取 project_id,否则从 body 获取
         # 优先从路径获取 project_id,否则从 body 获取
         actual_project_id = project_id or req.project_id
         actual_project_id = project_id or req.project_id
@@ -146,12 +126,9 @@ async def export_external_project(
         return ApiResponse(code=500, message=str(e)).model_dump()
         return ApiResponse(code=500, message=str(e)).model_dump()
 
 
 @router.get("/external/download-proxy")
 @router.get("/external/download-proxy")
-async def download_proxy(url: str, filename: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def download_proxy(url: str, filename: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """后端中转下载外部平台文件,解决跨域和 Token 携带问题"""
     """后端中转下载外部平台文件,解决跨域和 Token 携带问题"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            raise HTTPException(status_code=401, detail="无效的访问令牌")
             
             
         # 确保 URL 是完整的
         # 确保 URL 是完整的
         if not url.startswith('http'):
         if not url.startswith('http'):
@@ -166,7 +143,7 @@ async def download_proxy(url: str, filename: str, credentials: HTTPAuthorization
         admin_token = config_handler.get("external_api", "admin_token", "")
         admin_token = config_handler.get("external_api", "admin_token", "")
         if not admin_token:
         if not admin_token:
             # 如果配置中没有,则尝试使用当前用户的 Token(兜底)
             # 如果配置中没有,则尝试使用当前用户的 Token(兜底)
-            admin_token = credentials.credentials
+            admin_token = current_user.credentials
         
         
         client = httpx.AsyncClient(timeout=60.0, follow_redirects=True)
         client = httpx.AsyncClient(timeout=60.0, follow_redirects=True)
         headers = {
         headers = {
@@ -204,13 +181,9 @@ async def download_proxy(url: str, filename: str, credentials: HTTPAuthorization
         return ApiResponse(code=500, message=str(e)).model_dump()
         return ApiResponse(code=500, message=str(e)).model_dump()
 
 
 @router.post("/documents/upload-url")
 @router.post("/documents/upload-url")
-async def get_upload_url(req: UploadUrlRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_upload_url(req: UploadUrlRequest, credentials: dict = Depends(get_current_user_with_refresh)):
     """获取 MinIO 预签名上传 URL"""
     """获取 MinIO 预签名上传 URL"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌").model_dump()
-        
         sample_service = SampleService()
         sample_service = SampleService()
         success, message, data = await sample_service.get_upload_url(req.filename, req.content_type, prefix=req.prefix)
         success, message, data = await sample_service.get_upload_url(req.filename, req.content_type, prefix=req.prefix)
         
         
@@ -226,22 +199,19 @@ async def get_upload_url(req: UploadUrlRequest, credentials: HTTPAuthorizationCr
 async def proxy_view(url: str, token: Optional[str] = None, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional)):
 async def proxy_view(url: str, token: Optional[str] = None, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional)):
     """抓取外部文档内容并返回,支持 HTML 和 PDF 等二进制文件。支持从 Header 或 Query 参数获取 Token。"""
     """抓取外部文档内容并返回,支持 HTML 和 PDF 等二进制文件。支持从 Header 或 Query 参数获取 Token。"""
     try:
     try:
+        logger.info(f"token={token},credentials={credentials}")
         # 确保 URL 已解码
         # 确保 URL 已解码
         url = urllib.parse.unquote(url)
         url = urllib.parse.unquote(url)
-        
-        # 优先从 Header 获取,如果没有则从参数获取
+         # 优先从 Header 获取,如果没有则从参数获取
         actual_token = None
         actual_token = None
         if credentials:
         if credentials:
             actual_token = credentials.credentials
             actual_token = credentials.credentials
         elif token:
         elif token:
             actual_token = token
             actual_token = token
-            
+
         if not actual_token:
         if not actual_token:
             return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
-        payload = verify_token(actual_token)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
         # 增加超时时间,支持大文件下载
         # 增加超时时间,支持大文件下载
         async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
         async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
@@ -348,9 +318,6 @@ async def download_document(url: str, filename: Optional[str] = None, token: Opt
         if not actual_token:
         if not actual_token:
             return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="未提供认证令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
-        payload = verify_token(actual_token)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
         # 增加超时时间,支持大文件下载
         # 增加超时时间,支持大文件下载
         async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
         async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
@@ -379,14 +346,11 @@ async def download_document(url: str, filename: Optional[str] = None, token: Opt
         return ApiResponse(code=500, message=f"下载失败: {str(e)} (URL: {url})", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=f"下载失败: {str(e)} (URL: {url})", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/batch-enter")
 @router.post("/documents/batch-enter")
-async def batch_enter_knowledge_base(req: BatchEnterRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def batch_enter_knowledge_base(req: BatchEnterRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """批量将文档加入知识库"""
     """批量将文档加入知识库"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        username = payload.get("sub")
+        username = current_user.get("sub")
         if not username:
         if not username:
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -408,12 +372,9 @@ async def batch_enter_knowledge_base(req: BatchEnterRequest, credentials: HTTPAu
         return ApiResponse(code=500, message=f"批量操作失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=f"批量操作失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/batch-delete")
 @router.post("/documents/batch-delete")
-async def batch_delete_documents(req: BatchDeleteRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def batch_delete_documents(req: BatchDeleteRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """批量删除文档"""
     """批量删除文档"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         affected_rows, message = await sample_service.batch_delete_documents(req.ids)
         affected_rows, message = await sample_service.batch_delete_documents(req.ids)
@@ -435,18 +396,15 @@ class BatchAddTaskRequest(BaseModel):
     tags: Optional[List[str]] = None
     tags: Optional[List[str]] = None
 
 
 @router.post("/documents/batch-add-to-task")
 @router.post("/documents/batch-add-to-task")
-async def batch_add_to_task(req: BatchAddTaskRequest, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def batch_add_to_task(req: BatchAddTaskRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """批量加入任务中心 (设置 whether_to_task = 1)"""
     """批量加入任务中心 (设置 whether_to_task = 1)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        user_id = payload.get("sub")
+        user_id = current_user.get("sub")
         if not user_id:
         if not user_id:
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        username = payload.get("username", user_id)
+        username = current_user.get("username", user_id)
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         success, message = await sample_service.batch_add_to_task(req.doc_ids, username, req.project_name, task_tags=req.tags)
         success, message = await sample_service.batch_add_to_task(req.doc_ids, username, req.project_name, task_tags=req.tags)
@@ -461,12 +419,9 @@ async def batch_add_to_task(req: BatchAddTaskRequest, credentials: HTTPAuthoriza
         return ApiResponse(code=500, message=f"批量加入任务失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=f"批量加入任务失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/convert")
 @router.post("/documents/convert")
-async def convert_document(req: ConvertRequest, background_tasks: BackgroundTasks, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def convert_document(req: ConvertRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user_with_refresh)):
     """启动文档转换 (使用 MinerUManager 在后台执行)"""
     """启动文档转换 (使用 MinerUManager 在后台执行)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         doc_id = str(req.id)
         doc_id = str(req.id)
         sample_service = SampleService()
         sample_service = SampleService()
@@ -508,14 +463,11 @@ async def convert_document(req: ConvertRequest, background_tasks: BackgroundTask
         return ApiResponse(code=500, message=f"启动转换失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=f"启动转换失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/add")
 @router.post("/documents/add")
-async def add_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def add_document(doc: DocumentAdd, current_user: dict = Depends(get_current_user_with_refresh)):
     """添加新文档 (同步主表和子表)"""
     """添加新文档 (同步主表和子表)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        user_id = payload.get("sub")
+        user_id = current_user.get("sub")
         if not user_id:
         if not user_id:
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -535,12 +487,9 @@ async def add_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentia
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.get("/documents/detail/{doc_id}")
 @router.get("/documents/detail/{doc_id}")
-async def get_document_detail(doc_id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_document_detail(doc_id: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """获取文档详情 (关联查询子表)"""
     """获取文档详情 (关联查询子表)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         doc = await sample_service.get_document_detail(doc_id)
         doc = await sample_service.get_document_detail(doc_id)
@@ -566,13 +515,10 @@ async def get_document_list(
     level_4_classification: Optional[str] = None,
     level_4_classification: Optional[str] = None,
     page: int = 1, 
     page: int = 1, 
     size: int = 50,
     size: int = 50,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取文档列表 (从主表查询)"""
     """获取文档列表 (从主表查询)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         items, total, all_total, total_entered = await sample_service.get_document_list(
         items, total, all_total, total_entered = await sample_service.get_document_list(
@@ -606,12 +552,9 @@ async def get_document_list(
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/edit")
 @router.post("/documents/edit")
-async def edit_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def edit_document(doc: DocumentAdd, current_user: dict = Depends(get_current_user_with_refresh)):
     """编辑文档 (同步主表和子表)"""
     """编辑文档 (同步主表和子表)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
         if not doc.id:
         if not doc.id:
             return ApiResponse(code=400, message="缺少ID参数", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=400, message="缺少ID参数", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
@@ -620,7 +563,7 @@ async def edit_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredenti
         sample_service = SampleService()
         sample_service = SampleService()
         
         
         # 获取更新人ID
         # 获取更新人ID
-        updater_id = payload.get("sub", "admin")
+        updater_id = current_user.get("sub", "admin")
         
         
         # 将 DocumentAdd 对象转换为字典,包含所有字段
         # 将 DocumentAdd 对象转换为字典,包含所有字段
         doc_data = doc.model_dump()
         doc_data = doc.model_dump()
@@ -636,15 +579,14 @@ async def edit_document(doc: DocumentAdd, credentials: HTTPAuthorizationCredenti
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/documents/enter")
 @router.post("/documents/enter")
-async def enter_document(data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def enter_document(data: dict, current_user: dict = Depends(get_current_user_with_refresh)):
     """文档入库"""
     """文档入库"""
     try:
     try:
         doc_id = data.get("id")
         doc_id = data.get("id")
         if not doc_id:
         if not doc_id:
             return ApiResponse(code=400, message="缺少ID", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=400, message="缺少ID", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
-        payload = verify_token(credentials.credentials)
-        username = payload.get("sub", "admin") if payload else "admin"
+        username = current_user.get("sub", "admin") if payload else "admin"
         
         
         # 调用 service 层
         # 调用 service 层
         sample_service = SampleService()
         sample_service = SampleService()
@@ -679,13 +621,10 @@ async def get_basic_info_list(
     level_2_classification: Optional[str] = None,
     level_2_classification: Optional[str] = None,
     level_3_classification: Optional[str] = None,
     level_3_classification: Optional[str] = None,
     level_4_classification: Optional[str] = None,
     level_4_classification: Optional[str] = None,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取基本信息列表 (支持多条件检索)"""
     """获取基本信息列表 (支持多条件检索)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         
         
@@ -738,14 +677,11 @@ async def get_basic_info_list(
         return ApiResponse(code=500, message=f"服务器内部错误: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=f"服务器内部错误: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/basic-info/add")
 @router.post("/basic-info/add")
-async def add_basic_info(type: str, data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def add_basic_info(type: str, data: dict, current_user: dict = Depends(get_current_user_with_refresh)):
     """新增基本信息"""
     """新增基本信息"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        user_id = payload.get("sub")
+        user_id = current_user.get("sub")
         if not user_id:
         if not user_id:
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -761,14 +697,11 @@ async def add_basic_info(type: str, data: dict, credentials: HTTPAuthorizationCr
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/basic-info/edit")
 @router.post("/basic-info/edit")
-async def edit_basic_info(type: str, id: str, data: dict, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def edit_basic_info(type: str, id: str, data: dict, current_user: dict = Depends(get_current_user_with_refresh)):
     """编辑基本信息"""
     """编辑基本信息"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        user_id = payload.get("sub")
+        user_id = current_user.get("sub")
         if not user_id:
         if not user_id:
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
@@ -784,12 +717,9 @@ async def edit_basic_info(type: str, id: str, data: dict, credentials: HTTPAutho
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.post("/basic-info/delete")
 @router.post("/basic-info/delete")
-async def delete_basic_info(type: str, id: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def delete_basic_info(type: str, id: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """删除基本信息"""
     """删除基本信息"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload:
-            return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
         sample_service = SampleService()
         sample_service = SampleService()
         success, message = await sample_service.delete_basic_info(type, id)
         success, message = await sample_service.delete_basic_info(type, id)
@@ -803,12 +733,9 @@ async def delete_basic_info(type: str, id: str, credentials: HTTPAuthorizationCr
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.get("/documents/categories/primary")
 @router.get("/documents/categories/primary")
-async def get_primary_categories(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_primary_categories(current_user: dict = Depends(get_current_user_with_refresh)):
     """获取所有一级分类(仅保留指定的分类)"""
     """获取所有一级分类(仅保留指定的分类)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
         # 仅保留用户要求的分类
         # 仅保留用户要求的分类
         default_categories = ["办公制度", "行业标准", "法律法规", "施工方案", "施工图片"]
         default_categories = ["办公制度", "行业标准", "法律法规", "施工方案", "施工图片"]
@@ -818,12 +745,9 @@ async def get_primary_categories(credentials: HTTPAuthorizationCredentials = Dep
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 
 @router.get("/documents/categories/secondary")
 @router.get("/documents/categories/secondary")
-async def get_secondary_categories(primaryId: str, credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_secondary_categories(primaryId: str, current_user: dict = Depends(get_current_user_with_refresh)):
     """根据一级分类获取二级分类(仅保留指定的分类)"""
     """根据一级分类获取二级分类(仅保留指定的分类)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
-        if not payload or not payload.get("is_superuser"):
-            return ApiResponse(code=403, message="权限不足", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             
             
         # 针对“办公制度”的预设二级分类,其他分类暂时没有二级分类
         # 针对“办公制度”的预设二级分类,其他分类暂时没有二级分类
         categories = []
         categories = []
@@ -842,7 +766,7 @@ async def search_documents(
     table_type: Optional[str] = "standard",
     table_type: Optional[str] = "standard",
     page: int = 1, 
     page: int = 1, 
     size: int = 50,
     size: int = 50,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """关键词搜索文档,统一调用 get_document_list 以支持组合过滤"""
     """关键词搜索文档,统一调用 get_document_list 以支持组合过滤"""
     return await get_document_list(
     return await get_document_list(

+ 8 - 32
src/views/search_engine_view.py

@@ -1,7 +1,7 @@
 """
 """
 检索引擎视图路由
 检索引擎视图路由
 """
 """
-from fastapi import APIRouter, Depends, Query, Path, Body
+from fastapi import APIRouter, Depends, Query, Path, Body, Request
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.ext.asyncio import AsyncSession
 
 
 from app.base.async_mysql_connection import get_db
 from app.base.async_mysql_connection import get_db
@@ -16,6 +16,7 @@ from app.sample.schemas.search_engine import (
 from app.schemas.base import PaginatedResponseSchema, ResponseSchema
 from app.schemas.base import PaginatedResponseSchema, ResponseSchema
 from app.services.jwt_token import verify_token
 from app.services.jwt_token import verify_token
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 router = APIRouter(prefix="/sample/search-engine", tags=["样本中心-检索引擎"])
 router = APIRouter(prefix="/sample/search-engine", tags=["样本中心-检索引擎"])
 security = HTTPBearer()
 security = HTTPBearer()
@@ -25,13 +26,9 @@ security = HTTPBearer()
 async def search_knowledge_base(
 async def search_knowledge_base(
     payload: KBSearchRequest,
     payload: KBSearchRequest,
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """知识库语义搜索"""
     """知识库语义搜索"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     try:
     try:
         result = await search_engine_service.search_kb(db, payload)
         result = await search_engine_service.search_kb(db, payload)
         return ResponseSchema(code=0, message="搜索成功", data=result)
         return ResponseSchema(code=0, message="搜索成功", data=result)
@@ -49,14 +46,9 @@ async def get_search_engines(
     keyword: str = Query(None, description="搜索关键词"),
     keyword: str = Query(None, description="搜索关键词"),
     status: str = Query(None, description="状态筛选"),
     status: str = Query(None, description="状态筛选"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取检索引擎列表"""
     """获取检索引擎列表"""
-    # 鉴权
-    payload = verify_token(credentials.credentials)
-    if not payload:
-        return PaginatedResponseSchema(code=401, message="无效的访问令牌", data=[], meta=None)
-
     items, meta = await search_engine_service.get_list(
     items, meta = await search_engine_service.get_list(
         db, page=page, page_size=page_size, keyword=keyword, status=status
         db, page=page, page_size=page_size, keyword=keyword, status=status
     )
     )
@@ -72,13 +64,9 @@ async def get_search_engines(
 async def create_search_engine(
 async def create_search_engine(
     payload: SearchEngineCreate,
     payload: SearchEngineCreate,
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建新检索引擎"""
     """创建新检索引擎"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     new_engine = await search_engine_service.create(db, payload)
     new_engine = await search_engine_service.create(db, payload)
     return ResponseSchema(code=0, message="创建成功", data=SearchEngineResponse.model_validate(new_engine))
     return ResponseSchema(code=0, message="创建成功", data=SearchEngineResponse.model_validate(new_engine))
 
 
@@ -87,13 +75,9 @@ async def update_search_engine(
     payload: SearchEngineUpdate,
     payload: SearchEngineUpdate,
     id: str = Path(..., description="检索引擎ID"),
     id: str = Path(..., description="检索引擎ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新检索引擎信息"""
     """更新检索引擎信息"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     engine = await search_engine_service.update(db, id, payload)
     engine = await search_engine_service.update(db, id, payload)
     return ResponseSchema(code=0, message="更新成功", data=SearchEngineResponse.model_validate(engine))
     return ResponseSchema(code=0, message="更新成功", data=SearchEngineResponse.model_validate(engine))
 
 
@@ -102,13 +86,9 @@ async def update_search_engine_status(
     id: str = Path(..., description="检索引擎ID"),
     id: str = Path(..., description="检索引擎ID"),
     status: str = Query(..., description="状态: normal/disabled"),
     status: str = Query(..., description="状态: normal/disabled"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新检索引擎状态"""
     """更新检索引擎状态"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     await search_engine_service.update_status(db, id, status)
     await search_engine_service.update_status(db, id, status)
     return ResponseSchema(code=0, message=f"状态已更新为 {status}")
     return ResponseSchema(code=0, message=f"状态已更新为 {status}")
 
 
@@ -116,12 +96,8 @@ async def update_search_engine_status(
 async def delete_search_engine(
 async def delete_search_engine(
     id: str = Path(..., description="检索引擎ID"),
     id: str = Path(..., description="检索引擎ID"),
     db: AsyncSession = Depends(get_db),
     db: AsyncSession = Depends(get_db),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除检索引擎"""
     """删除检索引擎"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     await search_engine_service.delete(db, id)
     await search_engine_service.delete(db, id)
     return ResponseSchema(code=0, message="删除成功")
     return ResponseSchema(code=0, message="删除成功")

+ 9 - 32
src/views/snippet_view.py

@@ -1,7 +1,7 @@
 """
 """
 知识片段视图路由
 知识片段视图路由
 """
 """
-from fastapi import APIRouter, Depends, Query, Path, Body
+from fastapi import APIRouter, Depends, Query, Path, Body, Request
 from fastapi.responses import StreamingResponse
 from fastapi.responses import StreamingResponse
 from typing import Optional, Dict, Any
 from typing import Optional, Dict, Any
 from datetime import datetime
 from datetime import datetime
@@ -15,6 +15,7 @@ from app.schemas.base import ResponseSchema, PaginatedResponseSchema
 from app.services.jwt_token import verify_token
 from app.services.jwt_token import verify_token
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from pydantic import BaseModel
 from pydantic import BaseModel
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 router = APIRouter(prefix="/document/snippet", tags=["样本中心-知识片段"])
 router = APIRouter(prefix="/document/snippet", tags=["样本中心-知识片段"])
 security = HTTPBearer()
 security = HTTPBearer()
@@ -40,7 +41,7 @@ async def get_snippets(
     kb: Optional[str] = Query(None, description="知识库集合名称"),
     kb: Optional[str] = Query(None, description="知识库集合名称"),
     keyword: Optional[str] = Query(None),
     keyword: Optional[str] = Query(None),
     status: Optional[str] = Query(None),
     status: Optional[str] = Query(None),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取知识片段列表 (跨集合查询)"""
     """获取知识片段列表 (跨集合查询)"""
     items, meta = await snippet_service.get_list(page, page_size, kb, keyword, status)
     items, meta = await snippet_service.get_list(page, page_size, kb, keyword, status)
@@ -55,13 +56,9 @@ async def get_snippets(
 @router.get("/document/{doc_id}", response_model=ResponseSchema)
 @router.get("/document/{doc_id}", response_model=ResponseSchema)
 async def get_document_chunks(
 async def get_document_chunks(
     doc_id: str = Path(..., description="文档ID"),
     doc_id: str = Path(..., description="文档ID"),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """提取单文档的所有知识片段"""
     """提取单文档的所有知识片段"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-        
     chunks = await snippet_service.get_chunks_by_document(doc_id)
     chunks = await snippet_service.get_chunks_by_document(doc_id)
     
     
     return ResponseSchema(
     return ResponseSchema(
@@ -74,14 +71,10 @@ async def get_document_chunks(
 async def get_snippet_detail(
 async def get_snippet_detail(
     kb: str = Query(..., description="知识库名称"),
     kb: str = Query(..., description="知识库名称"),
     id: str = Query(..., description="片段ID (document_id 或 pk)"),
     id: str = Query(..., description="片段ID (document_id 或 pk)"),
-    credentials: HTTPAuthorizationCredentials = Depends(security),
+    current_user: dict = Depends(get_current_user_with_refresh),
     db: AsyncSession = Depends(get_db)
     db: AsyncSession = Depends(get_db)
 ):
 ):
     """获取知识片段详情"""
     """获取知识片段详情"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-        
     data = await snippet_service.get_by_id(db, kb, id)
     data = await snippet_service.get_by_id(db, kb, id)
     if not data:
     if not data:
         return ResponseSchema(code=404, message="未找到该片段")
         return ResponseSchema(code=404, message="未找到该片段")
@@ -93,13 +86,9 @@ async def export_snippets(
     kb: Optional[str] = Query(None, description="知识库集合名称"),
     kb: Optional[str] = Query(None, description="知识库集合名称"),
     keyword: Optional[str] = Query(None),
     keyword: Optional[str] = Query(None),
     status: Optional[str] = Query(None),
     status: Optional[str] = Query(None),
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """导出知识片段"""
     """导出知识片段"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     filename = f"snippets_export_{datetime.now().strftime('%Y%m%d%H%M%S')}.csv"
     filename = f"snippets_export_{datetime.now().strftime('%Y%m%d%H%M%S')}.csv"
     encoded_filename = urllib.parse.quote(filename)
     encoded_filename = urllib.parse.quote(filename)
     
     
@@ -114,14 +103,10 @@ async def export_snippets(
 @router.post("", response_model=ResponseSchema)
 @router.post("", response_model=ResponseSchema)
 async def create_snippet(
 async def create_snippet(
     payload: SnippetCreate,
     payload: SnippetCreate,
-    credentials: HTTPAuthorizationCredentials = Depends(security),
+    current_user: dict = Depends(get_current_user_with_refresh),
     db: AsyncSession = Depends(get_db)
     db: AsyncSession = Depends(get_db)
 ):
 ):
     """创建知识片段"""
     """创建知识片段"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     data = await snippet_service.create(db, payload)
     data = await snippet_service.create(db, payload)
     return ResponseSchema(code=0, message="创建成功", data=data)
     return ResponseSchema(code=0, message="创建成功", data=data)
 
 
@@ -129,14 +114,10 @@ async def create_snippet(
 async def update_snippet(
 async def update_snippet(
     id: str,
     id: str,
     payload: SnippetUpdate,
     payload: SnippetUpdate,
-    credentials: HTTPAuthorizationCredentials = Depends(security),
+    current_user: dict = Depends(get_current_user_with_refresh),
     db: AsyncSession = Depends(get_db)
     db: AsyncSession = Depends(get_db)
 ):
 ):
     """更新知识片段"""
     """更新知识片段"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-
     msg = await snippet_service.update(db, id, payload)
     msg = await snippet_service.update(db, id, payload)
     return ResponseSchema(code=0, message=msg)
     return ResponseSchema(code=0, message=msg)
 
 
@@ -144,14 +125,10 @@ async def update_snippet(
 async def delete_snippet(
 async def delete_snippet(
     id: str, 
     id: str, 
     kb: str = Query(..., description="知识库名称"), 
     kb: str = Query(..., description="知识库名称"), 
-    credentials: HTTPAuthorizationCredentials = Depends(security),
+    current_user: dict = Depends(get_current_user_with_refresh),
     db: AsyncSession = Depends(get_db)
     db: AsyncSession = Depends(get_db)
 ):
 ):
     """删除知识片段"""
     """删除知识片段"""
-    payload_token = verify_token(credentials.credentials)
-    if not payload_token:
-        return ResponseSchema(code=401, message="无效的访问令牌")
-        
     snippet_service.delete(id, kb)
     snippet_service.delete(id, kb)
 
 
     # 更新知识库文档数量并返回最新计数,便于前端立即刷新展示
     # 更新知识库文档数量并返回最新计数,便于前端立即刷新展示

+ 82 - 66
src/views/system_view.py

@@ -29,6 +29,7 @@ from app.schemas.base import ApiResponse
 from app.utils.security import hash_password, verify_password
 from app.utils.security import hash_password, verify_password
 from app.services.system_service import SystemService
 from app.services.system_service import SystemService
 from app.services.system_service_ext import SystemServiceExt
 from app.services.system_service_ext import SystemServiceExt
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 # 获取logger
 # 获取logger
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
@@ -39,7 +40,8 @@ security = HTTPBearer()
 
 
 @router.get("/dashboard")
 @router.get("/dashboard")
 async def get_dashboard(
 async def get_dashboard(
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    request: Request,
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取仪表盘数据(支持滑动过期)"""
     """获取仪表盘数据(支持滑动过期)"""
     try:
     try:
@@ -142,7 +144,7 @@ async def get_logs(
     page: int = 1,
     page: int = 1,
     page_size: int = 20,
     page_size: int = 20,
     log_type: str = "",
     log_type: str = "",
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取系统日志"""
     """获取系统日志"""
     return {
     return {
@@ -160,13 +162,15 @@ async def get_logs(
 
 
 
 
 @router.get("/users/profile")
 @router.get("/users/profile")
-async def get_user_profile(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_user_profile( request: Request,
+                            credentials: dict = Depends(get_current_user_with_refresh)
+    ):
     """获取用户资料"""
     """获取用户资料"""
     try:
     try:
 
 
         # 验证令牌
         # 验证令牌
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -215,12 +219,12 @@ async def get_user_profile(credentials: HTTPAuthorizationCredentials = Depends(s
 async def update_user_profile(
 async def update_user_profile(
     request: Request,
     request: Request,
     profile_data: dict,
     profile_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新用户资料"""
     """更新用户资料"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -261,12 +265,12 @@ async def update_user_profile(
 async def change_user_password(
 async def change_user_password(
     request: Request,
     request: Request,
     password_data: dict,
     password_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """修改用户密码"""
     """修改用户密码"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -359,10 +363,13 @@ def verify_password_simple(plain_password: str, hashed_password: str) -> bool:
 
 
 # RBAC权限管理API
 # RBAC权限管理API
 @router.get("/user/menus")
 @router.get("/user/menus")
-async def api_get_user_menus(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def api_get_user_menus(
+    request: Request,
+    credentials: dict = Depends(get_current_user_with_refresh)
+    ):
     """获取用户菜单"""
     """获取用户菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -396,14 +403,17 @@ async def api_get_user_menus(credentials: HTTPAuthorizationCredentials = Depends
 
 
 @router.get("/admin/menus")
 @router.get("/admin/menus")
 async def api_get_all_menus(
 async def api_get_all_menus(
+    request: Request,
+    credentials: dict = Depends(get_current_user_with_refresh),
     page: int = 1,
     page: int = 1,
     page_size: int = 1000,  # 增大默认页面大小,确保返回所有菜单
     page_size: int = 1000,  # 增大默认页面大小,确保返回所有菜单
     keyword: Optional[str] = None,
     keyword: Optional[str] = None,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
 ):
 ):
     """获取所有菜单(管理员)"""
     """获取所有菜单(管理员)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        #payload = verify_token(credentials.credentials)
+        payload = credentials
+        logger.info(f"credentials={credentials}")
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -441,14 +451,17 @@ async def api_get_all_menus(
 
 
 @router.get("/admin/roles")
 @router.get("/admin/roles")
 async def api_get_all_roles(
 async def api_get_all_roles(
+    request: Request,
+    credentials: dict = Depends(get_current_user_with_refresh),
     page: int = 1,
     page: int = 1,
     page_size: int = 20,
     page_size: int = 20,
     keyword: Optional[str] = None,
     keyword: Optional[str] = None,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
 ):
 ):
     """获取所有角色"""
     """获取所有角色"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        #payload = verify_token(credentials.credentials)
+        payload = credentials
+        logger.info(f"credentials={credentials}")
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -484,10 +497,10 @@ async def api_get_all_roles(
 
 
 
 
 @router.get("/user/permissions")
 @router.get("/user/permissions")
-async def api_get_user_permissions(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def api_get_user_permissions(credentials: dict = Depends(get_current_user_with_refresh)):
     """获取用户权限"""
     """获取用户权限"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -523,18 +536,19 @@ async def api_get_user_permissions(credentials: HTTPAuthorizationCredentials = D
 # 用户管理API
 # 用户管理API
 @router.get("/admin/users")
 @router.get("/admin/users")
 async def get_users(
 async def get_users(
+    request: Request,
+    credentials: dict = Depends(get_current_user_with_refresh),
     page: int = 1,
     page: int = 1,
     page_size: int = 20,
     page_size: int = 20,
-    keyword: Optional[str] = None,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    keyword: Optional[str] = None
 ):
 ):
     """获取用户列表"""
     """获取用户列表"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
-        
         # 调用 service 层
         # 调用 service 层
         system_service_ext = SystemServiceExt()
         system_service_ext = SystemServiceExt()
         users, total = await system_service_ext.get_users(page, page_size, keyword)
         users, total = await system_service_ext.get_users(page, page_size, keyword)
@@ -557,12 +571,14 @@ async def get_users(
 
 
 @router.post("/admin/users")
 @router.post("/admin/users")
 async def create_user(
 async def create_user(
+    request: Request,
     user_data: dict,
     user_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建用户"""
     """创建用户"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        #payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -592,11 +608,11 @@ async def create_user(
 @router.get("/admin/users/{user_id}")
 @router.get("/admin/users/{user_id}")
 async def get_user_detail(
 async def get_user_detail(
     user_id: str,
     user_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取用户详情"""
     """获取用户详情"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -622,11 +638,11 @@ async def get_user_detail(
 async def update_user(
 async def update_user(
     user_id: str,
     user_id: str,
     user_data: dict,
     user_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新用户"""
     """更新用户"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -657,11 +673,11 @@ async def update_user(
 @router.delete("/admin/users/{user_id}")
 @router.delete("/admin/users/{user_id}")
 async def delete_user(
 async def delete_user(
     user_id: str,
     user_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除用户"""
     """删除用户"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -695,11 +711,11 @@ async def delete_user(
 @router.post("/admin/roles")
 @router.post("/admin/roles")
 async def create_role(
 async def create_role(
     role_data: dict,
     role_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建角色"""
     """创建角色"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -723,11 +739,11 @@ async def create_role(
 async def update_role(
 async def update_role(
     role_id: str,
     role_id: str,
     role_data: dict,
     role_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新角色"""
     """更新角色"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -753,11 +769,11 @@ async def update_role(
 @router.delete("/admin/roles/{role_id}")
 @router.delete("/admin/roles/{role_id}")
 async def delete_role(
 async def delete_role(
     role_id: str,
     role_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除角色"""
     """删除角色"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -783,11 +799,11 @@ async def delete_role(
 @router.get("/admin/roles/{role_id}/menus")
 @router.get("/admin/roles/{role_id}/menus")
 async def get_role_menus(
 async def get_role_menus(
     role_id: str,
     role_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取角色的菜单权限"""
     """获取角色的菜单权限"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -827,11 +843,11 @@ async def get_role_menus(
 async def update_role_menus(
 async def update_role_menus(
     role_id: str,
     role_id: str,
     request: Request,
     request: Request,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新角色的菜单权限"""
     """更新角色的菜单权限"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -884,11 +900,11 @@ async def update_role_menus(
 @router.post("/admin/menus")
 @router.post("/admin/menus")
 async def create_menu(
 async def create_menu(
     request: Request,
     request: Request,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建菜单"""
     """创建菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -939,11 +955,11 @@ async def create_menu(
 async def update_menu(
 async def update_menu(
     menu_id: str,
     menu_id: str,
     request: Request,
     request: Request,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新菜单"""
     """更新菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -983,11 +999,11 @@ async def update_menu(
 @router.delete("/admin/menus/{menu_id}")
 @router.delete("/admin/menus/{menu_id}")
 async def delete_menu(
 async def delete_menu(
     menu_id: str,
     menu_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除菜单"""
     """删除菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=401,
                 code=401,
@@ -1022,11 +1038,11 @@ async def delete_menu(
 @router.post("/admin/menus")
 @router.post("/admin/menus")
 async def create_menu(
 async def create_menu(
     menu_data: dict,
     menu_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建菜单"""
     """创建菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -1052,11 +1068,11 @@ async def create_menu(
 async def update_menu(
 async def update_menu(
     menu_id: str,
     menu_id: str,
     menu_data: dict,
     menu_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新菜单"""
     """更新菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -1082,11 +1098,11 @@ async def update_menu(
 @router.delete("/admin/menus/{menu_id}")
 @router.delete("/admin/menus/{menu_id}")
 async def delete_menu(
 async def delete_menu(
     menu_id: str,
     menu_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除菜单"""
     """删除菜单"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -1109,10 +1125,10 @@ async def delete_menu(
 
 
 # 获取所有角色(用于下拉选择)
 # 获取所有角色(用于下拉选择)
 @router.get("/roles/all")
 @router.get("/roles/all")
-async def get_all_roles_simple(credentials: HTTPAuthorizationCredentials = Depends(security)):
+async def get_all_roles_simple(credentials: dict = Depends(get_current_user_with_refresh)):
     """获取所有角色(简化版,用于下拉选择)"""
     """获取所有角色(简化版,用于下拉选择)"""
     try:
     try:
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         
@@ -1136,12 +1152,12 @@ async def get_apps(
     page_size: int = 20,
     page_size: int = 20,
     keyword: str = "",
     keyword: str = "",
     status: str = "",
     status: str = "",
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取应用列表"""
     """获取应用列表"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1181,12 +1197,12 @@ async def get_apps(
 @router.get("/apps/{app_id}")
 @router.get("/apps/{app_id}")
 async def get_app_detail(
 async def get_app_detail(
     app_id: str,
     app_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """获取应用详情(包含密钥)"""
     """获取应用详情(包含密钥)"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1226,12 +1242,12 @@ async def get_app_detail(
 async def create_app(
 async def create_app(
     request: Request,
     request: Request,
     app_data: dict,
     app_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """创建应用"""
     """创建应用"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1279,12 +1295,12 @@ async def create_app(
 async def toggle_app_status(
 async def toggle_app_status(
     app_id: str,
     app_id: str,
     status_data: dict,
     status_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """切换应用状态"""
     """切换应用状态"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1331,12 +1347,12 @@ async def toggle_app_status(
 async def update_app(
 async def update_app(
     app_id: str,
     app_id: str,
     app_data: dict,
     app_data: dict,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """更新应用信息"""
     """更新应用信息"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1384,12 +1400,12 @@ async def update_app(
 @router.delete("/apps/{app_id}")
 @router.delete("/apps/{app_id}")
 async def delete_app(
 async def delete_app(
     app_id: str,
     app_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """删除应用"""
     """删除应用"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,
@@ -1437,12 +1453,12 @@ async def delete_app(
 @router.post("/apps/{app_id}/reset-secret")
 @router.post("/apps/{app_id}/reset-secret")
 async def reset_app_secret(
 async def reset_app_secret(
     app_id: str,
     app_id: str,
-    credentials: HTTPAuthorizationCredentials = Depends(security)
+    credentials: dict = Depends(get_current_user_with_refresh)
 ):
 ):
     """重置应用密钥"""
     """重置应用密钥"""
     try:
     try:
         # 验证令牌
         # 验证令牌
-        payload = verify_token(credentials.credentials)
+        payload = credentials
         if not payload:
         if not payload:
             return ApiResponse(
             return ApiResponse(
                 code=200002,
                 code=200002,

+ 4 - 6
src/views/tag_view.py

@@ -2,7 +2,7 @@
 标签分类视图路由
 标签分类视图路由
 处理标签分类的 API 接口
 处理标签分类的 API 接口
 """
 """
-from fastapi import APIRouter, Query, Path, Depends
+from fastapi import APIRouter, Query, Path, Depends, Request
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.ext.asyncio import AsyncSession
 
 
@@ -18,19 +18,17 @@ from app.sample.schemas.tag_category import (
     TagCategoryTreeResponse
     TagCategoryTreeResponse
 )
 )
 from app.schemas.base import ResponseSchema, PaginatedResponseSchema
 from app.schemas.base import ResponseSchema, PaginatedResponseSchema
+from app.utils.auth_dependency import get_current_user_with_refresh
 
 
 router = APIRouter(prefix="/sample/tag", tags=["样本中心-标签分类"])
 router = APIRouter(prefix="/sample/tag", tags=["样本中心-标签分类"])
 security = HTTPBearer()
 security = HTTPBearer()
 
 
 
 
 async def get_current_user_id(
 async def get_current_user_id(
-    credentials: HTTPAuthorizationCredentials = Depends(security),
-    session: AsyncSession = Depends(get_db)
+    current_user: dict = Depends(get_current_user_with_refresh)
 ) -> str:
 ) -> str:
     """获取当前登录用户UUID"""
     """获取当前登录用户UUID"""
-    auth_service = AuthService(session)
-    user , token = await auth_service.get_current_user(credentials.credentials)
-    return user.id
+    return current_user.get("sub")
 
 
 
 
 @router.post("/create", response_model=ResponseSchema)
 @router.post("/create", response_model=ResponseSchema)

+ 400 - 0
项目/Token机制调整完成报告.md

@@ -0,0 +1,400 @@
+#### 本次调整说明
+  - 1、LQAdminFront 前端调用接口返回401 无效的访问令牌,直接调整到登录页面
+  - 2、LQAdminPlatform 后台管理调整
+    - 2.1、验证token是否按预期处理,用户每次请求后台接口时,token的时长将重置,只有用户在token设置时间范围内都没有请求 才作为token超时(需要重新登录)
+    - 2.2、用户每次访问后台接口时,日志打印token配置时长、当前token的创建时间、使用时长、剩余时长、刷新token时长信息,用于验证token是否按预期处理
+
+
+
+
+#### token 再最大时间范围内自动重置处理问题
+  - 测试情况如下: 
+    - 首次登录后 18:00:00
+      - token配置时长:5 分钟
+      - Token创建时间: 18:00:00
+      - Token过期时间: 18:05:00
+    - 时间到 18:04:00 访问一次后台请求,可以正常访问
+    - 时间到 18:05:10 访问二次后台请求时 token已经失效,需要重新登录 
+    - 正常情况下本次情况可以正常访问,因为18:04的时候访问过一次,这时token时间已经重置,并且token需要等到 18:10:10分才会过期
+    - 并且为什么每次后台请求的时候 没有打印token验证信息呢?按理说每次后台API请求的时候都会验证token,都会打印token验证信息的
+
+
+用户列表接口和角色列表接口 都按要求增加get_current_user_with_refresh ,token还是同样的问题,未得到解决
+
+
+@router.get("/admin/users")
+async def get_users(
+    request: Request,
+    current_user: dict = Depends(get_current_user_with_refresh),
+    page: int = 1,
+    page_size: int = 20,
+    keyword: Optional[str] = None
+):
+
+
+
+@router.get("/admin/roles")
+async def api_get_all_roles(
+    request: Request,
+    current_user: dict = Depends(get_current_user_with_refresh),
+    page: int = 1,
+    page_size: int = 20,
+    keyword: Optional[str] = None,
+    #credentials: HTTPAuthorizationCredentials = Depends(security)
+):
+
+
+---
+
+## 🔍 当前状态(2026-02-08)
+
+### ✅ 已完成的工作
+
+1. **前端401处理** - 完成
+   - 同时处理HTTP 401和业务错误码401
+   - 多重跳转保障机制
+   - 自动清除认证信息
+
+2. **后端Token验证和刷新逻辑** - 完成
+   - `verify_and_refresh_token()` 函数实现滑动过期
+   - 详细的token日志输出
+   - 刷新阈值设置为50%
+
+3. **认证依赖函数** - 完成
+   - `get_current_user_with_refresh` 已创建
+   - 包含详细的调试日志(`>>> [依赖函数]` 开头)
+   - 会将新token存储到 `request.state.new_token`
+
+4. **响应处理中间件** - 完成
+   - 检查 `request.state.new_token`
+   - 添加 `X-New-Token` 到响应头
+   - 包含详细的调试日志(`>>> [响应中间件]` 开头)
+
+5. **中间件注册** - 完成
+   - `TokenRefreshMiddleware` 已注册
+   - 响应处理中间件已注册
+   - 启动时会打印注册成功日志
+
+6. **视图函数更新** - 部分完成
+   - ✅ 用户列表接口已使用新依赖
+   - ✅ 角色列表接口已使用新依赖
+   - ⚠️  其他接口仍使用旧的 `verify_token`
+
+### ❌ 问题现象
+
+用户反馈:即使用户列表和角色列表接口已使用新依赖,token仍然没有刷新。
+
+### 🎯 下一步操作
+
+**关键:必须重启后台服务器!**
+
+代码修改后,如果没有重启服务器,修改不会生效。
+
+#### 步骤1: 运行诊断脚本
+
+```bash
+cd LQAdminPlatform
+python 诊断Token刷新问题.py
+```
+
+这个脚本会检查:
+- 所有关键文件是否存在
+- 配置是否正确
+- 依赖函数是否正确实现
+- 中间件是否正确注册
+- 视图函数是否使用新依赖
+
+#### 步骤2: 重启后台服务器
+
+```bash
+cd LQAdminPlatform
+# 按 Ctrl+C 停止当前服务器
+python src/main.py
+```
+
+**检查启动日志**,必须看到:
+```
+✅ Token刷新中间件已注册
+✅ Token响应处理中间件已注册
+```
+
+#### 步骤3: 测试Token刷新
+
+详细步骤请查看:`快速测试Token刷新.md`
+
+简要步骤:
+1. 登录系统(记录时间,如 18:00:00)
+2. 等待3分钟(超过2.5分钟阈值)
+3. 访问用户列表或角色列表
+4. 查看后台日志
+
+**预期日志**:
+```
+>>> [响应中间件] 处理请求: /api/v1/system/admin/users
+>>> [依赖函数] get_current_user_with_refresh 被调用
+Token验证信息 - 用户: admin
+  Token配置时长: 5 分钟
+  Token已使用时长: 3.00 分钟
+  Token刷新: 已使用时长超过阈值,生成新token
+>>> [依赖函数] Token已刷新,新token已存储到request.state
+>>> [响应中间件] 发现新token,添加到响应头
+```
+
+#### 步骤4: 检查响应头
+
+在浏览器开发者工具 -> Network -> 选择请求 -> Headers -> Response Headers
+
+应该看到:
+```
+X-New-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
+X-Token-Refreshed: true
+```
+
+#### 步骤5: 检查浏览器控制台
+
+应该看到:
+```
+Token已自动刷新
+```
+
+### 📚 相关文档
+
+1. **快速测试Token刷新.md** - 5分钟快速测试指南
+2. **调试Token刷新问题.md** - 详细的调试步骤和检查点
+3. **诊断Token刷新问题.py** - 自动诊断脚本
+4. **如何使用新的认证依赖.md** - 如何在其他接口中使用新依赖
+
+### 🐛 可能的问题
+
+1. **服务器没有重启** - 最常见的问题
+2. **日志级别不对** - 确保 `LOG_LEVEL=INFO`
+3. **等待时间不够** - 必须超过2.5分钟
+4. **访问了错误的接口** - 必须访问使用新依赖的接口
+5. **前端没有重启** - 前端代码修改后也需要重启
+
+### 💡 提示
+
+- 所有带 `>>>` 前缀的日志都是为了调试添加的,方便快速定位问题
+- 如果看不到这些日志,说明对应的代码没有执行
+- 日志会按顺序输出,可以看出执行流程
+
+
+
+
+
+# Token机制调整实现说明
+
+## 调整内容
+
+### 1. 前端调整(LQAdminFront)✅
+
+**文件**: `LQAdminFront/src/api/request.ts`
+
+**改动说明**:
+- 当接口返回 401 无效的访问令牌时,前端不再尝试刷新token
+- 直接清除本地认证信息并跳转到登录页面
+- 提示用户"无效的访问令牌,请重新登录"
+
+**测试方法**:
+1. 正常登录系统
+2. 手动修改浏览器 localStorage 中的 token 为无效值
+3. 刷新页面或访问任意需要认证的接口
+4. 验证是否自动跳转到登录页面并显示提示信息
+
+### 2. 后台调整(LQAdminPlatform)✅
+
+#### 2.1 Token滑动过期机制验证
+
+**文件**: `LQAdminPlatform/src/app/services/jwt_token.py`
+
+**改动说明**:
+- Token采用滑动过期机制:用户每次请求后台接口时,如果token已使用时长超过总时长的一半,则自动刷新token
+- 只有用户在token设置时间范围内都没有请求,才会导致token超时(需要重新登录)
+
+**配置参数**:
+- `ADMIN_TOKEN_EXPIRE_MINUTES`: Token总有效期(分钟),当前配置为 60 分钟
+- 刷新阈值:Token使用时长超过总时长的 50% 时自动刷新
+
+**测试方法**:
+1. 登录系统获取token
+2. 在30分钟内持续访问接口,观察token是否自动刷新
+3. 停止访问超过60分钟,再次访问应返回401需要重新登录
+4. 查看后台日志验证token信息是否正确打印
+
+#### 2.2 Token详细日志记录
+
+**文件**: 
+- `LQAdminPlatform/src/app/services/jwt_token.py`
+- `LQAdminPlatform/src/app/middleware/token_refresh_middleware.py`
+
+**改动说明**:
+用户每次访问后台接口时,日志会打印以下信息:
+
+```
+Token验证信息 - 用户: admin
+  Token配置时长: 60 分钟
+  Token创建时间: 2026-02-08 10:00:00 UTC
+  Token过期时间: 2026-02-08 11:00:00 UTC
+  Token已使用时长: 15.50 分钟 (930 秒)
+  Token剩余时长: 44.50 分钟 (2670 秒)
+  Token刷新: 未达到刷新阈值 (30.00 分钟),继续使用当前token
+```
+
+当token被刷新时:
+```
+Token刷新: 已使用时长超过阈值 (30.00 分钟),生成新token
+Token已刷新,用户: admin, 路径: /api/system/profile
+```
+
+**测试方法**:
+1. 启动后台服务
+2. 登录系统
+3. 访问任意需要认证的接口
+4. 查看后台日志文件(logs/lq-admin-app.log 或控制台输出)
+5. 验证是否打印了完整的token信息
+
+## 验证要点
+
+### Token滑动过期机制验证
+
+1. **正常使用场景**(应该不会超时):
+   - 用户持续操作,每隔10-20分钟访问一次接口
+   - Token应该自动刷新,用户无需重新登录
+   - 日志应显示token刷新记录
+
+2. **长时间不操作场景**(应该超时):
+   - 用户登录后60分钟内不进行任何操作
+   - 60分钟后访问接口应返回401
+   - 前端自动跳转到登录页
+
+3. **边界测试**:
+   - 在第29分钟访问:不应刷新
+   - 在第31分钟访问:应该刷新
+   - 在第59分钟访问:应该刷新且继续有效
+   - 在第61分钟访问:应该返回401
+
+### 日志验证
+
+检查日志中是否包含:
+- ✅ Token配置时长
+- ✅ Token创建时间
+- ✅ Token过期时间
+- ✅ Token已使用时长
+- ✅ Token剩余时长
+- ✅ Token刷新决策信息(是否刷新及原因)
+
+## 配置说明
+
+Token时长配置位于 `LQAdminPlatform/src/app/config/config.ini`:
+
+```ini
+[admin_app]
+# 后台管理Token配置
+ADMIN_TOKEN_EXPIRE_MINUTES=60  # Token总有效期(分钟)
+ADMIN_REFRESH_TOKEN_EXPIRE_HOURS=24  # Refresh Token有效期(小时)
+```
+
+可根据实际需求调整这些参数。
+
+## 注意事项
+
+1. Token刷新是自动的,前端会在响应头中接收新token(X-New-Token)
+2. 前端需要处理响应头中的新token并更新本地存储(如果有相关逻辑)
+3. 所有时间计算使用UTC时区,避免时区问题
+4. 日志级别需要设置为INFO或更低才能看到详细的token信息
+
+## 问题根源和修复 ✅
+
+### 问题发现
+从错误堆栈 `request.ts:65:27` 可以看出,错误发生在**成功响应拦截器**中,而不是401错误处理器中。
+
+**原因**: 后台返回的是 HTTP 200 + 业务错误码401,而不是 HTTP 401
+
+```
+HTTP/1.1 200 OK
+{
+  "code": 401,
+  "message": "无效的访问令牌"
+}
+```
+
+### 修复方案
+在成功响应拦截器中添加了对业务错误码401的检查:
+- 检测到 `code === 401` 时立即跳转
+- 清除所有存储
+- 显示错误提示
+- 阻止后续执行
+
+现在代码同时处理两种401情况:
+1. HTTP状态码401(中间件返回)
+2. HTTP 200 + 业务错误码401(业务逻辑返回)
+
+## 修改文件清单
+
+1. ✅ `LQAdminFront/src/api/request.ts` - 前端401处理逻辑 + Token自动更新(已增强调试日志和多重跳转保障)
+2. ✅ `LQAdminPlatform/src/app/services/jwt_token.py` - Token验证和刷新逻辑,添加详细日志
+3. ✅ `LQAdminPlatform/src/app/middleware/token_refresh_middleware.py` - 中间件日志增强
+
+## 前端401跳转问题修复
+
+### 问题
+前端调用接口返回401时没有自动跳转到登录页面。
+
+### 解决方案
+在 `LQAdminFront/src/api/request.ts` 中实施了以下增强:
+
+1. **详细的调试日志**: 每个步骤都输出日志,方便排查问题
+2. **多重跳转保障**: 
+   - 方法1: `router.push('/login')`
+   - 方法2: `router.replace('/login')` (如果push失败)
+   - 方法3: `window.location.href = '/login'` (最后的保障)
+3. **强制清除认证信息**: 直接操作localStorage确保token被清除
+
+### 测试步骤
+
+**重要!修改后必须重启前端开发服务器:**
+
+```bash
+# 停止当前服务器(Ctrl+C)
+cd LQAdminFront
+npm run dev
+```
+
+**清除浏览器缓存:**
+- 按F12打开开发者工具
+- 右键点击刷新按钮
+- 选择"清空缓存并硬性重新加载"
+
+**测试401跳转:**
+1. 正常登录系统
+2. 打开开发者工具(F12)-> Application -> Local Storage
+3. 修改 `sso_access_token` 的值为 `invalid_token_test`
+4. 刷新页面或点击任意菜单
+5. 观察控制台日志和页面跳转
+
+**预期结果:**
+- ✅ 控制台显示 `=== 401错误处理开始 ===` 等日志
+- ✅ 页面自动跳转到登录页
+- ✅ 显示提示:"无效的访问令牌,请重新登录"
+- ✅ localStorage中的token被清除
+
+### 排查指南
+
+详细的排查步骤请查看:`LQAdminFront/401跳转问题排查指南.md`
+
+## 前端Token自动更新机制
+
+前端响应拦截器会自动检查响应头中的 `X-New-Token`,如果存在则自动更新本地存储的token:
+
+```typescript
+// 检查响应头中是否有新的token(滑动过期机制)
+const newToken = response.headers['x-new-token']
+if (newToken) {
+  // 更新本地存储的token,保留原有的refresh token
+  const refreshToken = getRefreshToken()
+  saveToken(newToken, refreshToken || undefined)
+  console.log('Token已自动刷新')
+}
+```
+
+这样用户在正常使用过程中,token会自动刷新,无需手动处理。

+ 209 - 0
项目/test.md

@@ -0,0 +1,209 @@
+# Token机制调整测试说明
+
+## 本次调整说明
+
+### 1. 前端调整
+- LQAdminFront 前端调用接口返回401 无效的访问令牌,直接跳转到登录页面
+
+### 2. 后台调整
+- 2.1 验证token是否按预期处理,用户每次请求后台接口时,token的时长将重置,只有用户在token设置时间范围内都没有请求才作为token超时(需要重新登录)
+- 2.2 用户每次访问后台接口时,日志打印token配置时长、当前token的创建时间、使用时长、剩余时长、刷新token时长信息,用于验证token是否按预期处理
+
+## 问题根源和修复 ✅
+
+### 问题1: 前端401跳转
+**原因**: 后台返回的是 HTTP 200 + 业务错误码401,而不是 HTTP 401
+
+**解决方案**: 在成功响应拦截器中添加对业务错误码401的检查
+
+### 问题2: Token没有自动刷新 ⚠️ 关键问题!
+**原因**: **Token刷新中间件没有被注册!**
+
+查看 `LQAdminPlatform/src/app/server/app.py`,发现只注册了CORS中间件,但没有注册 `TokenRefreshMiddleware`。
+
+**影响**:
+- ❌ Token验证逻辑从未执行
+- ❌ Token刷新逻辑从未执行  
+- ❌ 日志从未打印
+- ❌ 新token从未生成
+
+**解决方案**: 在 `app.py` 中添加中间件注册:
+```python
+from app.middleware.token_refresh_middleware import TokenRefreshMiddleware
+
+app.add_middleware(
+    TokenRefreshMiddleware,
+    exclude_paths=[
+        "/auth/login",
+        "/auth/captcha",
+        "/auth/refresh",
+        "/docs",
+        "/openapi.json",
+        "/redoc",
+        "/favicon.ico",
+        "/health",
+        "/"
+    ]
+)
+```
+
+### 问题3: 数据库连接错误
+**原因**: 错误地使用了 `async with get_db_connection()`
+
+**解决方案**: 改用 `async for db in get_db()`
+
+## 修改文件清单
+
+### 前端(LQAdminFront)
+1. ✅ `src/api/request.ts` - 401处理 + Token自动更新
+
+### 后端(LQAdminPlatform)
+1. ✅ `src/app/services/jwt_token.py` - Token验证刷新 + 详细日志
+2. ✅ `src/app/middleware/token_refresh_middleware.py` - 中间件日志增强
+3. ✅ `src/app/server/app.py` - **注册Token刷新中间件**(关键修复)
+4. ✅ `src/app/services/snippet_service.py` - 数据库连接修复
+
+## 测试步骤
+
+### 1. 重启后台服务器 ⚠️ 必须!
+
+```bash
+cd LQAdminPlatform
+# Ctrl+C 停止
+python src/main.py
+```
+
+**启动时应该看到**:
+```
+✅ Token刷新中间件已注册
+```
+
+如果没有这条日志,说明中间件没有注册成功。
+
+### 2. 重启前端服务器
+
+```bash
+cd LQAdminFront
+# Ctrl+C 停止
+npm run dev
+```
+
+### 3. 清除浏览器缓存
+
+- F12 → 右键刷新按钮 → "清空缓存并硬性重新加载"
+- 或使用无痕模式(Ctrl+Shift+N)
+
+### 4. 测试Token滑动刷新(5分钟配置)
+
+#### 配置测试环境
+编辑 `LQAdminPlatform/src/app/config/config.ini`:
+```ini
+[admin_app]
+ADMIN_TOKEN_EXPIRE_MINUTES=5  # 测试用:5分钟
+```
+
+#### 测试时间表
+
+| 时间 | 操作 | 预期结果 |
+|------|------|----------|
+| 18:00:00 | 登录 | 获取token,有效期至18:05:00 |
+| 18:02:00 | 访问接口 | 正常访问,不刷新(40%) |
+| 18:02:30 | 访问接口 | 正常访问,不刷新(50%临界) |
+| 18:03:00 | 访问接口 | 正常访问,**刷新token**(60%),新有效期至18:08:00 |
+| 18:04:00 | 访问接口 | 正常访问,不刷新(使用新token,20%) |
+| 18:07:00 | 访问接口 | 正常访问,**刷新token**(使用新token,80%) |
+| 18:13:00 | 访问接口 | **401错误**,跳转登录(超过5分钟未访问) |
+
+#### 查看后台日志
+
+每次访问接口时,应该看到:
+```
+Token验证信息 - 用户: admin
+  Token配置时长: 5 分钟
+  Token创建时间: 2026-02-08 18:00:00 UTC
+  Token过期时间: 2026-02-08 18:05:00 UTC
+  Token已使用时长: 3.00 分钟 (180 秒)
+  Token剩余时长: 2.00 分钟 (120 秒)
+  Token刷新: 已使用时长超过阈值 (2.50 分钟),生成新token
+```
+
+当token刷新时:
+```
+Token已刷新,用户: admin, 路径: /api/v1/xxx
+```
+
+#### 查看浏览器控制台
+
+当token刷新时,应该看到:
+```
+Token已自动刷新
+```
+
+### 5. 测试401跳转
+
+在浏览器控制台执行:
+```javascript
+// 设置无效token
+localStorage.setItem('sso_access_token', 'invalid_token_test')
+
+// 刷新页面
+location.reload()
+```
+
+**预期结果**:
+- ✅ 控制台显示 `>>> 检测到业务错误码401`
+- ✅ 页面自动跳转到 `/login`
+- ✅ 显示错误提示
+- ✅ localStorage已清空
+
+## 验证成功标准
+
+### 后台
+- [x] 启动时看到 "Token刷新中间件已注册"
+- [x] 每次请求都打印token验证日志
+- [x] 超过50%时长后,token自动刷新
+- [x] 响应头包含 `X-New-Token`
+- [x] 完全过期后返回401
+
+### 前端
+- [x] 401响应自动跳转登录页
+- [x] 显示错误提示
+- [x] localStorage已清空
+- [x] 控制台显示 "Token已自动刷新"
+- [x] 可以重新登录
+
+## 常见问题
+
+### Q1: 为什么18:04:00访问后,18:05:10还是过期?
+
+**A**: 因为中间件没有注册,token从未刷新。修复后重启服务器即可。
+
+### Q2: 为什么没有打印日志?
+
+**A**: 因为中间件没有注册,验证逻辑从未执行。修复后重启服务器即可。
+
+### Q3: 如何确认中间件已注册?
+
+**A**: 启动服务器时,查看日志是否有:
+```
+✅ Token刷新中间件已注册
+```
+
+### Q4: 如何确认token已刷新?
+
+**A**: 
+1. 查看后台日志:`Token已刷新,用户: xxx`
+2. 查看浏览器控制台:`Token已自动刷新`
+3. 查看响应头:`X-New-Token` 存在
+
+## 相关文档
+
+- **Token中间件问题修复.md** - 详细的问题分析和修复说明
+- **Token机制调整完成报告.md** - 完整的技术文档
+- **数据库连接问题修复.md** - 数据库问题说明
+
+---
+
+**最后更新**: 2026-02-08
+**状态**: ✅ 已修复
+**关键修复**: 注册Token刷新中间件

+ 4 - 0
项目/项目追加开发说明.md

@@ -92,6 +92,10 @@
     - 
     - 
   - 所有列表界面的创建人、修改人使用表(t_sys_user username字段)进行显示
   - 所有列表界面的创建人、修改人使用表(t_sys_user username字段)进行显示
 
 
+- view 逻辑规范定义
+  - 获取当前用户请使用 函数参数 current_user: dict = Depends(get_current_user_with_refresh)
+  - 通过 current_user.get("sub") 用户ID
+
 - 系统异常处理规范
 - 系统异常处理规范
   - 所有的try catch 捕获后(except Exception as e:),通过 logger.exception("xxxx")打印异常堆栈信息便于问题定位分析,如: logger.exception("更新用户资料错误") 
   - 所有的try catch 捕获后(except Exception as e:),通过 logger.exception("xxxx")打印异常堆栈信息便于问题定位分析,如: logger.exception("更新用户资料错误") 
   - 
   -