新增头像接口:POST /profiles/me/avatar 上传、DELETE /profiles/me/avatar 删除

This commit is contained in:
2026-07-26 02:14:22 +08:00
parent 49df3f067a
commit 8149505f1b
3 changed files with 154 additions and 1 deletions
+36
View File
@@ -12,6 +12,7 @@ from app.config import settings
logger = logging.getLogger(__name__)
ALLOWED_FORMATS = {"JPEG": "jpg", "PNG": "png", "WEBP": "webp"}
AVATAR_MAX_EDGE = 512
def _process_image(raw: bytes) -> tuple[bytes, str, bytes]:
@@ -105,3 +106,38 @@ async def delete_files(paths: list[str]) -> None:
logger.exception("删除图片失败:%s", relative_path)
await asyncio.to_thread(remove)
async def save_avatar(file: UploadFile, user_id: uuid.UUID) -> str:
raw = await file.read(settings.max_upload_bytes + 1)
if not raw:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="图片不能为空")
if len(raw) > settings.max_upload_bytes:
raise HTTPException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="图片文件过大")
def process() -> str:
Image.MAX_IMAGE_PIXELS = settings.max_image_pixels
try:
with Image.open(io.BytesIO(raw)) as source:
if source.format not in ALLOWED_FORMATS:
raise ValueError("仅支持 JPEG、PNG 和 WebP 图片")
source = source.convert("RGB")
source.thumbnail(
(AVATAR_MAX_EDGE, AVATAR_MAX_EDGE),
Image.Resampling.LANCZOS,
)
relative_path = Path("avatars") / f"{user_id}.jpg"
target = settings.upload_dir / relative_path
target.parent.mkdir(parents=True, exist_ok=True)
source.save(target, format="JPEG", quality=85, optimize=True)
return relative_path.as_posix()
except (UnidentifiedImageError, OSError, Image.DecompressionBombError, ValueError) as exc:
raise ValueError(str(exc) or "图片内容无效") from exc
try:
return await asyncio.to_thread(process)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"图片无法处理:{exc}",
) from exc