feat: AI套图分层方案 + Gemini集成 - 4种图案类型处理 + 正片叠底 + 宽高比 + 模型选择
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
340
Server/app/api/v1/logs.py
Normal file
340
Server/app/api/v1/logs.py
Normal file
@@ -0,0 +1,340 @@
|
||||
# -*- 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
|
||||
)
|
||||
Reference in New Issue
Block a user