perf: paginate admin audit lists
This commit is contained in:
@@ -21,6 +21,7 @@ from app.schemas.knowledge import AttentionUpdateRequest
|
||||
from app.services.admin_service import OperationLogService
|
||||
from app.models.logs import LogRetentionPolicy
|
||||
from app.services.maintenance_service import MaintenanceService
|
||||
from app.api.pagination import page_result
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -48,8 +49,9 @@ def retrieval_logs(
|
||||
query = query.where(KnowledgeRetrievalLog.status == statusValue)
|
||||
if attentionCreated is not None:
|
||||
query = query.where(KnowledgeRetrievalLog.attention_created == (1 if attentionCreated else 0))
|
||||
query = query.offset((page - 1) * pageSize).limit(pageSize)
|
||||
return api_success([_retrieval_dict(item, detail=False) for item in db.scalars(query).all()])
|
||||
total = db.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
|
||||
rows = db.scalars(query.offset((page - 1) * pageSize).limit(pageSize)).all()
|
||||
return api_success(page_result([_retrieval_dict(item, detail=False) for item in rows], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.post("/retrieval-log/cleanup/estimate")
|
||||
@@ -133,15 +135,19 @@ def retrieval_detail(
|
||||
def attention_list(
|
||||
priority: str = Query(default=""),
|
||||
statusValue: str = Query(default="", alias="status"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(HumanAttentionRecord).order_by(HumanAttentionRecord.id.desc()).limit(300)
|
||||
query = select(HumanAttentionRecord)
|
||||
if priority:
|
||||
query = query.where(HumanAttentionRecord.priority == priority)
|
||||
if statusValue:
|
||||
query = query.where(HumanAttentionRecord.status == statusValue)
|
||||
return api_success([_attention_dict(item) for item in db.scalars(query).all()])
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery())) or 0
|
||||
rows = db.scalars(query.order_by(HumanAttentionRecord.id.desc()).offset((page - 1) * pageSize).limit(pageSize)).all()
|
||||
return api_success(page_result([_attention_dict(item) for item in rows], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.put("/attention/{attention_id}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -26,13 +26,42 @@ from app.schemas.admin import KnowledgeSaveRequest
|
||||
from app.services.admin_service import OperationLogService
|
||||
from app.services.feishu_service import FeishuKnowledgeService
|
||||
from app.services.knowledge_catalog_cache_service import KnowledgeCatalogCacheService
|
||||
from app.api.pagination import page_result
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/knowledge/list")
|
||||
def list_knowledge(db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)) -> dict:
|
||||
items = db.scalars(select(Knowledge).order_by(Knowledge.id.desc())).all()
|
||||
def list_knowledge(
|
||||
keyword: str = Query(default=""),
|
||||
knowledgeType: str = Query(default=""),
|
||||
openStatus: int | None = Query(default=None),
|
||||
sourceStatus: str = Query(default=""),
|
||||
pending: bool | None = Query(default=None),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(Knowledge)
|
||||
if keyword:
|
||||
query = query.where((Knowledge.name.like(f"%{keyword}%")) | (Knowledge.source_title.like(f"%{keyword}%")))
|
||||
if knowledgeType:
|
||||
query = query.where(Knowledge.knowledge_type == knowledgeType)
|
||||
if openStatus is not None:
|
||||
query = query.where(Knowledge.status == openStatus)
|
||||
if sourceStatus:
|
||||
query = query.where(Knowledge.source_status == sourceStatus)
|
||||
if pending is not None:
|
||||
query = query.where(Knowledge.pending_version_id.is_not(None) if pending else Knowledge.pending_version_id.is_(None))
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery())) or 0
|
||||
items = db.scalars(query.order_by(Knowledge.id.desc()).offset((page - 1) * pageSize).limit(pageSize)).all()
|
||||
return api_success(page_result([_knowledge_dict(item) for item in items], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.get("/knowledge/options")
|
||||
def knowledge_options(db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)) -> dict:
|
||||
items = db.scalars(select(Knowledge).where(Knowledge.lifecycle_status == "active").order_by(Knowledge.id.desc())).all()
|
||||
return api_success([_knowledge_dict(item) for item in items])
|
||||
|
||||
|
||||
|
||||
@@ -367,11 +367,11 @@ def migration_summary(
|
||||
db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)
|
||||
) -> dict:
|
||||
_require_super_admin(current_admin)
|
||||
rows = db.scalars(select(Knowledge).order_by(Knowledge.id)).all()
|
||||
counts: dict[str, int] = {}
|
||||
for item in rows:
|
||||
counts[item.migration_status] = counts.get(item.migration_status, 0) + 1
|
||||
return api_success({"total": len(rows), "counts": counts, "items": [_knowledge_detail(item) for item in rows]})
|
||||
rows = db.execute(
|
||||
select(Knowledge.migration_status, func.count(Knowledge.id)).group_by(Knowledge.migration_status)
|
||||
).all()
|
||||
counts = {migration_status: count for migration_status, count in rows}
|
||||
return api_success({"total": sum(counts.values()), "counts": counts})
|
||||
|
||||
|
||||
@router.post("/knowledge-migration/run")
|
||||
|
||||
@@ -8,8 +8,8 @@ from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy import exists, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import exists, func, or_, select
|
||||
from sqlalchemy.orm import Session, load_only
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.dependencies import get_current_admin
|
||||
@@ -18,6 +18,7 @@ from app.models.admin import Admin
|
||||
from app.models.chat import ChatMessage, ChatSession
|
||||
from app.models.logs import AiRequestLog, OperationLog
|
||||
from app.models.user import User
|
||||
from app.api.pagination import page_result
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -29,15 +30,15 @@ def chat_list(
|
||||
status: str = Query(default=""),
|
||||
dateFrom: datetime | None = Query(default=None),
|
||||
dateTo: datetime | None = Query(default=None),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
rows = db.execute(
|
||||
_chat_query(keyword=keyword, user_id=userId, status=status, date_from=dateFrom, date_to=dateTo)
|
||||
.order_by(ChatSession.updated_at.desc())
|
||||
.limit(200)
|
||||
).all()
|
||||
return api_success([_chat_row_dict(session, user) for session, user in rows])
|
||||
query = _chat_query(keyword=keyword, user_id=userId, status=status, date_from=dateFrom, date_to=dateTo)
|
||||
total = db.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
|
||||
rows = db.execute(query.order_by(ChatSession.updated_at.desc()).offset((page - 1) * pageSize).limit(pageSize)).all()
|
||||
return api_success(page_result([_chat_row_dict(session, user) for session, user in rows], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.get("/chat/export")
|
||||
@@ -118,14 +119,20 @@ def chat_detail(
|
||||
@router.get("/log/list")
|
||||
def operation_logs(
|
||||
module: str = Query(default=""),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(OperationLog).order_by(OperationLog.created_at.desc()).limit(200)
|
||||
query = select(OperationLog)
|
||||
if module:
|
||||
query = query.where(OperationLog.module == module)
|
||||
logs = db.scalars(query).all()
|
||||
return api_success(
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery())) or 0
|
||||
logs = db.scalars(
|
||||
query.options(load_only(OperationLog.id, OperationLog.admin_id, OperationLog.module, OperationLog.action, OperationLog.target_id, OperationLog.result, OperationLog.created_at))
|
||||
.order_by(OperationLog.created_at.desc()).offset((page - 1) * pageSize).limit(pageSize)
|
||||
).all()
|
||||
return api_success(page_result(
|
||||
[
|
||||
{
|
||||
"id": item.id,
|
||||
@@ -137,8 +144,8 @@ def operation_logs(
|
||||
"createdAt": item.created_at,
|
||||
}
|
||||
for item in logs
|
||||
]
|
||||
)
|
||||
], total=total, page=page, page_size=pageSize
|
||||
))
|
||||
|
||||
|
||||
@router.get("/ai-log/list")
|
||||
@@ -146,18 +153,28 @@ def ai_logs(
|
||||
sessionId: int | None = Query(default=None),
|
||||
userId: int | None = Query(default=None),
|
||||
status: str = Query(default=""),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
query = select(AiRequestLog).order_by(AiRequestLog.created_at.desc()).limit(200)
|
||||
query = select(AiRequestLog)
|
||||
if sessionId is not None:
|
||||
query = query.where(AiRequestLog.session_id == sessionId)
|
||||
if userId is not None:
|
||||
query = query.where(AiRequestLog.user_id == userId)
|
||||
if status:
|
||||
query = query.where(AiRequestLog.status == status)
|
||||
logs = db.scalars(query).all()
|
||||
return api_success([_ai_log_dict(item) for item in logs])
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery())) or 0
|
||||
logs = db.scalars(
|
||||
query.options(load_only(
|
||||
AiRequestLog.id, AiRequestLog.session_id, AiRequestLog.message_id, AiRequestLog.user_id,
|
||||
AiRequestLog.model_name, AiRequestLog.knowledge_ids, AiRequestLog.retrieve_count,
|
||||
AiRequestLog.input_token, AiRequestLog.output_token, AiRequestLog.total_token,
|
||||
AiRequestLog.cost_ms, AiRequestLog.status, AiRequestLog.error_message, AiRequestLog.created_at,
|
||||
)).order_by(AiRequestLog.created_at.desc()).offset((page - 1) * pageSize).limit(pageSize)
|
||||
).all()
|
||||
return api_success(page_result([_ai_log_dict(item, include_chunks=False) for item in logs], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.get("/ai-log/{log_id}")
|
||||
@@ -241,7 +258,7 @@ def _message_dict(message: ChatMessage) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _ai_log_dict(log: AiRequestLog, *, include_prompt: bool = False) -> dict:
|
||||
def _ai_log_dict(log: AiRequestLog, *, include_prompt: bool = False, include_chunks: bool = True) -> dict:
|
||||
data = {
|
||||
"id": log.id,
|
||||
"sessionId": log.session_id,
|
||||
@@ -250,7 +267,7 @@ def _ai_log_dict(log: AiRequestLog, *, include_prompt: bool = False) -> dict:
|
||||
"modelName": log.model_name,
|
||||
"knowledgeIds": log.knowledge_ids,
|
||||
"retrieveCount": log.retrieve_count,
|
||||
"retrievedChunks": _parse_retrieved_chunks(log.retrieved_chunks),
|
||||
"retrievedChunks": _parse_retrieved_chunks(log.retrieved_chunks) if include_chunks else [],
|
||||
"inputToken": log.input_token,
|
||||
"outputToken": log.output_token,
|
||||
"totalToken": log.total_token,
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile,
|
||||
from fastapi.responses import StreamingResponse
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
@@ -19,6 +19,7 @@ from app.models.ai_config import SystemConfig
|
||||
from app.models.user import User
|
||||
from app.schemas.admin import AdminUserCreateRequest, AdminUserImportItem, AdminUserImportRequest, AdminUserUpdateRequest
|
||||
from app.services.admin_service import OperationLogService
|
||||
from app.api.pagination import page_result
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -28,6 +29,8 @@ STUDENT_TEMPLATE_HEADERS = ["手机号", "姓名", "昵称", "每日聊天额度
|
||||
@router.get("/user/list")
|
||||
def list_users(
|
||||
keyword: str = Query(default=""),
|
||||
page: int = Query(default=1, ge=1),
|
||||
pageSize: int = Query(default=20, ge=10, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: Admin = Depends(get_current_admin),
|
||||
) -> dict:
|
||||
@@ -35,8 +38,9 @@ def list_users(
|
||||
if keyword:
|
||||
like = f"%{keyword}%"
|
||||
query = query.where((User.phone.like(like)) | (User.name.like(like)))
|
||||
users = db.scalars(query.limit(200)).all()
|
||||
return api_success([_user_dict(user) for user in users])
|
||||
total = db.scalar(select(func.count()).select_from(query.order_by(None).subquery())) or 0
|
||||
users = db.scalars(query.offset((page - 1) * pageSize).limit(pageSize)).all()
|
||||
return api_success(page_result([_user_dict(user) for user in users], total=total, page=page, page_size=pageSize))
|
||||
|
||||
|
||||
@router.post("/user")
|
||||
|
||||
7
ai_knowledge_base_v2/apps/backend/app/api/pagination.py
Normal file
7
ai_knowledge_base_v2/apps/backend/app/api/pagination.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def page_result(items: list[Any], *, total: int, page: int, page_size: int) -> dict[str, Any]:
|
||||
return {"items": items, "total": total, "page": page, "pageSize": page_size}
|
||||
Reference in New Issue
Block a user