"""帮助与反馈 endpoint。 路由前缀 `/api/v1/feedback`,需 Bearer 鉴权(反馈绑到登录用户,便于回访)。 POST / 提交反馈(multipart:content 必填;contact 可选(原型改版后客户端已不再采集);images 可选 ≤6 张) 截图复用 [app.core.media] 落盘到 /media/feedback/。 """ from __future__ import annotations import logging from fastapi import APIRouter, File, Form, HTTPException, UploadFile from app.api.deps import CurrentUser, DbSession from app.core import media from app.repositories import feedback as feedback_repo from app.schemas.feedback import FeedbackOut logger = logging.getLogger("shagua.feedback") router = APIRouter(prefix="/api/v1/feedback", tags=["feedback"]) _MAX_IMAGES = 6 _CONTENT_MAX = 200 _CONTACT_MAX = 128 @router.post("", response_model=FeedbackOut, summary="提交反馈") async def submit_feedback( user: CurrentUser, db: DbSession, content: str = Form(...), # 原型改版后客户端不再采集联系方式;保留字段以兼容旧端 + 后续可能复用,默认空串。 contact: str = Form(default=""), images: list[UploadFile] = File(default=[]), ) -> FeedbackOut: content = content.strip() contact = contact.strip() if not content: raise HTTPException(status_code=400, detail="反馈内容不能为空") if len(content) > _CONTENT_MAX: raise HTTPException(status_code=400, detail="反馈内容过长") if len(contact) > _CONTACT_MAX: raise HTTPException(status_code=400, detail="联系方式过长") files = [f for f in (images or []) if f is not None and f.filename] if len(files) > _MAX_IMAGES: raise HTTPException(status_code=400, detail=f"最多上传 {_MAX_IMAGES} 张图片") urls: list[str] = [] for f in files: data = await f.read() try: urls.append(media.save_feedback_image(user.id, data)) except media.MediaError as e: raise HTTPException(status_code=400, detail=str(e)) from e fb = feedback_repo.create_feedback( db, user_id=user.id, content=content, contact=contact, images=urls, ) logger.info("feedback id=%d user_id=%d images=%d", fb.id, user.id, len(urls)) return FeedbackOut.model_validate(fb)