chore: resolve WIP after merging internal/development
- .gitignore: keep api/uploads ignore and copyright_source_listing.pdf path - auth: keep COS avatar upload URL; delete prior COS object when applying preset - i18n: regenerate resources.ts (includes profile tapAwayToClose) - Avatar/COS tests and personal-info remain from prior local work Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,11 @@ from fastapi.responses import FileResponse
|
||||
from PIL import Image
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.cos_url_keys import (
|
||||
avatar_url_for_api_response,
|
||||
best_effort_delete_cos_object_for_url,
|
||||
extract_cos_object_key_if_owned,
|
||||
)
|
||||
from app.core.dependencies import get_current_user
|
||||
from app.core.logging import get_logger
|
||||
from app.features.auth.deps import get_auth_service
|
||||
@@ -46,7 +51,6 @@ router = APIRouter(
|
||||
)
|
||||
|
||||
AVATAR_DIR = Path("uploads/avatars")
|
||||
AVATAR_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────
|
||||
|
||||
@@ -75,7 +79,7 @@ def _user_response(user: User) -> UserResponse:
|
||||
phone=user.phone,
|
||||
email=user.email,
|
||||
nickname=user.nickname,
|
||||
avatar_url=user.avatar_url,
|
||||
avatar_url=avatar_url_for_api_response(user.avatar_url),
|
||||
subscription_type=user.subscription_type,
|
||||
created_at=user.created_at.isoformat(),
|
||||
language_preference=lang,
|
||||
@@ -278,9 +282,17 @@ async def upload_avatar(
|
||||
len(file_content),
|
||||
)
|
||||
|
||||
try:
|
||||
AVATAR_DIR.mkdir(parents=True, exist_ok=True)
|
||||
if not (
|
||||
(settings.tencent_cos_secret_id or "").strip()
|
||||
and (settings.tencent_cos_secret_key or "").strip()
|
||||
and (settings.tencent_cos_bucket or "").strip()
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="头像存储服务未配置,请稍后再试",
|
||||
)
|
||||
|
||||
try:
|
||||
image_bytes = io.BytesIO(file_content)
|
||||
image_bytes.seek(0)
|
||||
|
||||
@@ -326,14 +338,28 @@ async def upload_avatar(
|
||||
if size > 512:
|
||||
image = image.resize((512, 512), Image.Resampling.LANCZOS)
|
||||
|
||||
file_extension = "jpg"
|
||||
filename = f"{current_user.id}.{file_extension}"
|
||||
file_path = AVATAR_DIR / filename
|
||||
jpeg_buffer = io.BytesIO()
|
||||
image.save(jpeg_buffer, format="JPEG", quality=85, optimize=True)
|
||||
jpeg_bytes = jpeg_buffer.getvalue()
|
||||
|
||||
image.save(file_path, "JPEG", quality=85, optimize=True)
|
||||
cos_key = f"avatars/{current_user.id}.jpg"
|
||||
old_url = current_user.avatar_url
|
||||
old_key = extract_cos_object_key_if_owned(old_url) if old_url else None
|
||||
if old_key and old_key != cos_key:
|
||||
best_effort_delete_cos_object_for_url(old_url)
|
||||
|
||||
from app.core.dependencies import get_object_storage
|
||||
|
||||
storage = get_object_storage()
|
||||
try:
|
||||
avatar_url = storage.upload(cos_key, jpeg_bytes, "image/jpeg")
|
||||
except Exception as exc:
|
||||
logger.exception("COS 头像上传失败: {}", exc)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="头像存储暂时不可用,请稍后再试",
|
||||
) from exc
|
||||
|
||||
# 路径固定为 {user_id}.jpg,客户端会缓存;每次写入新文件后 bump URL 以绕过缓存。
|
||||
avatar_url = f"/api/auth/avatars/{filename}?v={time.time_ns()}"
|
||||
user = await service.update_avatar_url(current_user.id, avatar_url)
|
||||
return _user_response(user)
|
||||
except HTTPException:
|
||||
@@ -381,6 +407,8 @@ async def set_avatar_preset(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="预设头像不可用",
|
||||
)
|
||||
best_effort_delete_cos_object_for_url(current_user.avatar_url)
|
||||
|
||||
avatar_url = f"{avatar_url_for_preset_filename(filename)}?v={time.time_ns()}"
|
||||
try:
|
||||
user = await service.update_avatar_url(current_user.id, avatar_url)
|
||||
@@ -410,6 +438,7 @@ async def get_avatar_preset(filename: str):
|
||||
responses={404: {"description": "头像不存在"}},
|
||||
)
|
||||
async def get_avatar(filename: str):
|
||||
AVATAR_DIR.mkdir(parents=True, exist_ok=True)
|
||||
file_path = safe_avatar_upload_path(filename, AVATAR_DIR)
|
||||
if file_path is None or not file_path.exists():
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user