public.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. """公开 API:附件上传、反馈/建言提交、热线/二维码读取(无需登录)。"""
  2. from typing import Annotated, Optional
  3. from fastapi import APIRouter, Depends, File, Form, UploadFile
  4. from sqlalchemy.orm import Session
  5. from app.core import storage
  6. from app.core.database import get_db
  7. from app.core.response import AppHTTPError, success_response
  8. from app.core.storage import StorageError
  9. from app.core.storage import _normalize_folder
  10. from app.core.upload_validate import validate_upload
  11. from app.crud import attachment as attachment_crud
  12. from app.schemas.business import FeedbackCreateBody, SuggestionCreateBody
  13. from app.services import config_service, feedback_service, suggestion_service
  14. router = APIRouter(prefix="/public", tags=["public"])
  15. PUBLIC_UPLOAD_FOLDERS = frozenset({"feedback", "suggestion"})
  16. @router.post("/attachment/upload")
  17. async def upload_attachment(
  18. db: Annotated[Session, Depends(get_db)],
  19. file: UploadFile = File(...),
  20. folder: Optional[str] = Form(""),
  21. ):
  22. if not file or not file.filename:
  23. raise AppHTTPError(400, "请选择文件")
  24. try:
  25. folder_norm = _normalize_folder(folder or "")
  26. except StorageError as exc:
  27. raise AppHTTPError(400, exc.message) from exc
  28. if folder_norm not in PUBLIC_UPLOAD_FOLDERS:
  29. raise AppHTTPError(400, "不允许的上传目录")
  30. content = await file.read()
  31. validate_upload(file.filename, content)
  32. try:
  33. stored = storage.upload_file(content, file.filename, folder_norm)
  34. except StorageError as exc:
  35. raise AppHTTPError(400, exc.message) from exc
  36. row = attachment_crud.create_attachment(
  37. db,
  38. file_name=file.filename,
  39. object_key=stored["objectKey"],
  40. url=stored["url"],
  41. size=len(content),
  42. )
  43. return success_response(
  44. {
  45. "id": row.id,
  46. "url": row.url,
  47. "fileName": row.file_name,
  48. "size": row.size,
  49. }
  50. )
  51. @router.get("/hotline")
  52. def get_hotline(db: Annotated[Session, Depends(get_db)]):
  53. return success_response(config_service.get_hotline_payload(db))
  54. @router.get("/qrcode")
  55. def get_qrcode(db: Annotated[Session, Depends(get_db)]):
  56. return success_response(config_service.get_qrcode_payload(db))
  57. @router.post("/feedback")
  58. def submit_feedback(
  59. body: FeedbackCreateBody,
  60. db: Annotated[Session, Depends(get_db)],
  61. ):
  62. data = feedback_service.submit_feedback(
  63. db,
  64. fb_type=body.fb_type,
  65. content=body.content,
  66. target_name=body.target_name,
  67. target_dept=body.target_dept,
  68. target_post=body.target_post,
  69. reporter_name=body.reporter_name,
  70. reporter_unit=body.reporter_unit,
  71. reporter_post=body.reporter_post,
  72. reporter_phone=body.reporter_phone,
  73. attachment_urls=body.attachment_urls,
  74. )
  75. return success_response(data)
  76. @router.post("/suggestion")
  77. def submit_suggestion(
  78. body: SuggestionCreateBody,
  79. db: Annotated[Session, Depends(get_db)],
  80. ):
  81. data = suggestion_service.submit_suggestion(
  82. db,
  83. content=body.content,
  84. contact_name=body.contact_name,
  85. contact_unit=body.contact_unit,
  86. contact_post=body.contact_post,
  87. contact_phone=body.contact_phone,
  88. attachment_urls=body.attachment_urls,
  89. )
  90. return success_response(data)