""" 用户反馈API端点 """ from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field, field_validator from typing import List, Optional from datetime import datetime from pathlib import Path import json import logging from app.config.settings import settings from app.config.constants import FEEDBACKS_FILE router = APIRouter() logger = logging.getLogger(__name__) class FeedbackCreate(BaseModel): """反馈创建模型""" username: str = Field(..., min_length=1, max_length=50, description="用户名") content: str = Field(..., min_length=1, max_length=1000, description="反馈内容") @field_validator("username") @classmethod def validate_username(cls, v): v = v.strip() if not v: raise ValueError("用户名不能为空") return v @field_validator("content") @classmethod def validate_content(cls, v): v = v.strip() if not v: raise ValueError("反馈内容不能为空") return v class FeedbackResponse(BaseModel): """反馈响应模型""" id: str username: str content: str timestamp: str def _get_feedbacks_file_path() -> Path: """获取反馈文件路径""" data_dir = settings.DATA_DIR data_dir.mkdir(parents=True, exist_ok=True) return data_dir / FEEDBACKS_FILE # 确保反馈文件存在且格式正确 def _initialize_feedback_file(): """初始化反馈文件""" feedbacks_file = _get_feedbacks_file_path() if not feedbacks_file.exists() or feedbacks_file.stat().st_size == 0: try: with open(feedbacks_file, "w", encoding="utf-8") as f: json.dump([], f, ensure_ascii=False) logger.info("反馈文件初始化完成") except Exception as e: logger.error(f"初始化反馈文件失败: {e}") # 应用启动时初始化 _initialize_feedback_file() def _load_feedbacks() -> List[FeedbackResponse]: """加载反馈记录""" feedbacks_file = _get_feedbacks_file_path() if not feedbacks_file.exists(): return [] # 检查文件大小,避免空文件解析错误 if feedbacks_file.stat().st_size == 0: return [] try: with open(feedbacks_file, "r", encoding="utf-8") as f: content = f.read().strip() if not content: return [] data = json.loads(content) return [FeedbackResponse(**item) for item in data] except json.JSONDecodeError as e: logger.error(f"JSON解析失败: {e}") return [] except Exception as e: logger.error(f"加载反馈记录失败: {e}") return [] def _save_feedbacks(feedbacks: List[FeedbackResponse]) -> bool: """保存反馈记录""" feedbacks_file = _get_feedbacks_file_path() try: with open(feedbacks_file, "w", encoding="utf-8") as f: json.dump([fb.dict() for fb in feedbacks], f, ensure_ascii=False, indent=2) f.flush() return True except Exception as e: logger.error(f"保存反馈记录失败: {e}") return False @router.get("/", response_model=List[FeedbackResponse]) async def get_feedbacks(): """获取所有反馈记录""" feedbacks = _load_feedbacks() return sorted(feedbacks, key=lambda x: x.timestamp, reverse=True) @router.post("/", response_model=FeedbackResponse) async def create_feedback(feedback: FeedbackCreate): """创建新反馈""" try: feedbacks = _load_feedbacks() new_feedback = FeedbackResponse( id=datetime.now().strftime("%Y%m%d%H%M%S"), username=feedback.username, content=feedback.content, timestamp=datetime.now().strftime("%Y-%m-%d %H:%M"), ) feedbacks.append(new_feedback) if _save_feedbacks(feedbacks): logger.info(f"新反馈已提交: {feedback.username}") return new_feedback else: raise HTTPException(status_code=500, detail="保存反馈失败") except HTTPException: raise except Exception as e: logger.error(f"创建反馈失败: {e}") raise HTTPException(status_code=500, detail=f"创建反馈失败: {str(e)}") @router.delete("/{feedback_id}") async def delete_feedback(feedback_id: str): """删除反馈记录""" try: feedbacks = _load_feedbacks() original_count = len(feedbacks) feedbacks = [fb for fb in feedbacks if fb.id != feedback_id] if len(feedbacks) == original_count: raise HTTPException( status_code=404, detail=f"反馈记录 {feedback_id} 不存在" ) if _save_feedbacks(feedbacks): return {"success": True, "message": "反馈记录已删除"} else: raise HTTPException(status_code=500, detail="保存失败") except HTTPException: raise except Exception as e: logger.error(f"删除反馈失败: {e}") raise HTTPException(status_code=500, detail=f"删除反馈失败: {str(e)}")