341 lines
10 KiB
Python
341 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
日志和历史记录API
|
||
"""
|
||
|
||
from typing import List, Optional
|
||
from datetime import datetime, timedelta
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel
|
||
from sqlalchemy.orm import Session, joinedload
|
||
from sqlalchemy import desc
|
||
|
||
from app.db import get_db
|
||
from app.core.security import get_current_user
|
||
from app.models.logs import PltProcessRecord, UserActionLog, PltPiece
|
||
from app.models.user import User
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
# ==================== 数据模型 ====================
|
||
|
||
class ActionLogCreate(BaseModel):
|
||
"""创建操作日志"""
|
||
session_id: Optional[str] = None
|
||
action_type: str
|
||
action_params: Optional[dict] = None
|
||
page: Optional[str] = None
|
||
result: str = "success"
|
||
error_message: Optional[str] = None
|
||
duration_ms: Optional[int] = None
|
||
|
||
|
||
class ActionLogResponse(BaseModel):
|
||
"""操作日志响应"""
|
||
id: int
|
||
action_type: str
|
||
action_params: Optional[dict]
|
||
page: Optional[str]
|
||
result: str
|
||
error_message: Optional[str]
|
||
duration_ms: Optional[int]
|
||
created_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class PltPieceCreate(BaseModel):
|
||
"""裁片信息"""
|
||
group_id: int
|
||
size: str
|
||
image_url: Optional[str] = None
|
||
width_px: int = 0
|
||
height_px: int = 0
|
||
width_cm: float = 0
|
||
height_cm: float = 0
|
||
left_cm: float = 0
|
||
top_cm: float = 0
|
||
center_x_cm: float = 0
|
||
center_y_cm: float = 0
|
||
|
||
|
||
class PltPieceResponse(BaseModel):
|
||
"""裁片响应"""
|
||
id: int
|
||
group_id: int
|
||
size: str
|
||
image_url: Optional[str]
|
||
width_px: int
|
||
height_px: int
|
||
width_cm: float
|
||
height_cm: float
|
||
left_cm: float
|
||
top_cm: float
|
||
center_x_cm: float
|
||
center_y_cm: float
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class PltRecordCreate(BaseModel):
|
||
"""创建PLT处理记录"""
|
||
filename: Optional[str] = None
|
||
file_size: Optional[int] = None
|
||
rotated_filename: Optional[str] = None
|
||
size_labels: Optional[List[str]] = None
|
||
dpi: int = 150
|
||
rotation: int = 0
|
||
total_groups: int = 0
|
||
total_pieces: int = 0
|
||
process_time_ms: Optional[int] = None
|
||
status: str = "success"
|
||
error_message: Optional[str] = None
|
||
pieces: Optional[List[PltPieceCreate]] = None # 裁片详情
|
||
|
||
|
||
class PltRecordResponse(BaseModel):
|
||
"""PLT处理记录响应"""
|
||
id: int
|
||
filename: Optional[str]
|
||
file_size: Optional[int]
|
||
rotated_filename: Optional[str]
|
||
size_labels: Optional[List[str]]
|
||
dpi: int
|
||
rotation: int
|
||
total_groups: int
|
||
total_pieces: int
|
||
process_time_ms: Optional[int]
|
||
status: str
|
||
created_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
class PltRecordDetailResponse(PltRecordResponse):
|
||
"""PLT处理记录详情响应(包含裁片)"""
|
||
pieces: List[PltPieceResponse] = []
|
||
|
||
|
||
class SessionLogsResponse(BaseModel):
|
||
"""会话日志响应(给AI用)"""
|
||
user_id: int
|
||
username: str
|
||
current_page: Optional[str]
|
||
recent_actions: List[ActionLogResponse]
|
||
recent_plt_records: List[PltRecordResponse]
|
||
error_count: int
|
||
|
||
|
||
# ==================== API端点 ====================
|
||
|
||
@router.post("/logs/action", response_model=ActionLogResponse)
|
||
async def create_action_log(
|
||
log_data: ActionLogCreate,
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""记录用户操作日志"""
|
||
# 获取用户ID
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
# 创建日志
|
||
log = UserActionLog(
|
||
user_id=user.id,
|
||
session_id=log_data.session_id,
|
||
action_type=log_data.action_type,
|
||
action_params=log_data.action_params,
|
||
page=log_data.page,
|
||
result=log_data.result,
|
||
error_message=log_data.error_message,
|
||
duration_ms=log_data.duration_ms
|
||
)
|
||
db.add(log)
|
||
db.commit()
|
||
db.refresh(log)
|
||
|
||
return log
|
||
|
||
|
||
@router.post("/plt/record", response_model=PltRecordResponse)
|
||
async def create_plt_record(
|
||
record_data: PltRecordCreate,
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""记录PLT处理记录(包含裁片详情)"""
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
record = PltProcessRecord(
|
||
user_id=user.id,
|
||
filename=record_data.filename,
|
||
file_size=record_data.file_size,
|
||
rotated_filename=record_data.rotated_filename,
|
||
size_labels=record_data.size_labels,
|
||
dpi=record_data.dpi,
|
||
rotation=record_data.rotation,
|
||
total_groups=record_data.total_groups,
|
||
total_pieces=record_data.total_pieces,
|
||
process_time_ms=record_data.process_time_ms,
|
||
status=record_data.status,
|
||
error_message=record_data.error_message
|
||
)
|
||
db.add(record)
|
||
db.flush() # 获取 record.id
|
||
|
||
# 保存裁片详情
|
||
if record_data.pieces:
|
||
for piece_data in record_data.pieces:
|
||
piece = PltPiece(
|
||
record_id=record.id,
|
||
group_id=piece_data.group_id,
|
||
size=piece_data.size,
|
||
image_url=piece_data.image_url,
|
||
width_px=piece_data.width_px,
|
||
height_px=piece_data.height_px,
|
||
width_cm=piece_data.width_cm,
|
||
height_cm=piece_data.height_cm,
|
||
left_cm=piece_data.left_cm,
|
||
top_cm=piece_data.top_cm,
|
||
center_x_cm=piece_data.center_x_cm,
|
||
center_y_cm=piece_data.center_y_cm
|
||
)
|
||
db.add(piece)
|
||
|
||
db.commit()
|
||
db.refresh(record)
|
||
|
||
return record
|
||
|
||
|
||
@router.get("/plt/history/{record_id}", response_model=PltRecordDetailResponse)
|
||
async def get_plt_record_detail(
|
||
record_id: int,
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取PLT处理记录详情(包含裁片)"""
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
record = db.query(PltProcessRecord)\
|
||
.options(joinedload(PltProcessRecord.pieces))\
|
||
.filter(PltProcessRecord.id == record_id)\
|
||
.filter(PltProcessRecord.user_id == user.id)\
|
||
.first()
|
||
|
||
if not record:
|
||
raise HTTPException(status_code=404, detail="记录不存在")
|
||
|
||
return record
|
||
|
||
|
||
@router.get("/plt/history", response_model=List[PltRecordResponse])
|
||
async def get_plt_history(
|
||
limit: int = Query(20, ge=1, le=100),
|
||
days: int = Query(7, ge=1, le=30), # 保留天数,默认7天
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取用户的PLT处理历史(默认保留7天)"""
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
# 只返回指定天数内的记录
|
||
since = datetime.now() - timedelta(days=days)
|
||
|
||
records = db.query(PltProcessRecord)\
|
||
.filter(PltProcessRecord.user_id == user.id)\
|
||
.filter(PltProcessRecord.created_at >= since)\
|
||
.order_by(desc(PltProcessRecord.created_at))\
|
||
.limit(limit)\
|
||
.all()
|
||
|
||
return records
|
||
|
||
|
||
@router.get("/logs/recent", response_model=List[ActionLogResponse])
|
||
async def get_recent_logs(
|
||
limit: int = Query(50, ge=1, le=200),
|
||
action_type: Optional[str] = None,
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取用户最近的操作日志"""
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
query = db.query(UserActionLog).filter(UserActionLog.user_id == user.id)
|
||
|
||
if action_type:
|
||
query = query.filter(UserActionLog.action_type.like(f"{action_type}%"))
|
||
|
||
logs = query.order_by(desc(UserActionLog.created_at)).limit(limit).all()
|
||
|
||
return logs
|
||
|
||
|
||
@router.get("/logs/session", response_model=SessionLogsResponse)
|
||
async def get_session_context(
|
||
hours: int = Query(24, ge=1, le=168),
|
||
current_username: str = Depends(get_current_user),
|
||
db: Session = Depends(get_db)
|
||
):
|
||
"""获取会话上下文(给AI助手用)"""
|
||
user = db.query(User).filter(User.username == current_username).first()
|
||
if not user:
|
||
raise HTTPException(status_code=404, detail="用户不存在")
|
||
|
||
since = datetime.now() - timedelta(hours=hours)
|
||
|
||
# 最近的操作日志
|
||
recent_actions = db.query(UserActionLog)\
|
||
.filter(UserActionLog.user_id == user.id)\
|
||
.filter(UserActionLog.created_at >= since)\
|
||
.order_by(desc(UserActionLog.created_at))\
|
||
.limit(50)\
|
||
.all()
|
||
|
||
# 最近的PLT处理记录
|
||
recent_plt = db.query(PltProcessRecord)\
|
||
.filter(PltProcessRecord.user_id == user.id)\
|
||
.filter(PltProcessRecord.created_at >= since)\
|
||
.order_by(desc(PltProcessRecord.created_at))\
|
||
.limit(10)\
|
||
.all()
|
||
|
||
# 错误数量
|
||
error_count = db.query(UserActionLog)\
|
||
.filter(UserActionLog.user_id == user.id)\
|
||
.filter(UserActionLog.created_at >= since)\
|
||
.filter(UserActionLog.result == "error")\
|
||
.count()
|
||
|
||
# 当前页面(最近一次进入页面的记录)
|
||
last_page_action = db.query(UserActionLog)\
|
||
.filter(UserActionLog.user_id == user.id)\
|
||
.filter(UserActionLog.action_type == "page.enter")\
|
||
.order_by(desc(UserActionLog.created_at))\
|
||
.first()
|
||
|
||
current_page = last_page_action.action_params.get("page") if last_page_action and last_page_action.action_params else None
|
||
|
||
return SessionLogsResponse(
|
||
user_id=user.id,
|
||
username=current_username,
|
||
current_page=current_page,
|
||
recent_actions=recent_actions,
|
||
recent_plt_records=recent_plt,
|
||
error_count=error_count
|
||
)
|