配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
import uuid
|
||
|
||
from fastapi import APIRouter, Depends, status
|
||
|
||
from app.core.config import settings
|
||
from app.core.cos_url_keys import avatar_url_for_api_response
|
||
from app.core.dependencies import get_object_storage
|
||
from app.core.deps_types import CurrentUserDep
|
||
from app.core.errors import BadRequestError, NotFoundError
|
||
from app.core.logging import get_logger
|
||
from app.core.openapi import error_responses
|
||
from app.features.user.deps import get_user_service
|
||
from app.features.user.schemas import (
|
||
FeedbackResponse,
|
||
PurgeUserDataRequest,
|
||
PurgeUserDataResponse,
|
||
SubmitFeedbackRequest,
|
||
TestSubscriptionRequest,
|
||
TestSubscriptionResponse,
|
||
UpdateUserProfileRequest,
|
||
UserProfileResponse,
|
||
)
|
||
from app.features.user.service import UserService
|
||
from app.features.user.service import _coerce_language as _coerce_language_token
|
||
from app.ports.storage import ObjectStorage
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
router = APIRouter(
|
||
prefix="/api/user",
|
||
tags=["user"],
|
||
responses=error_responses(401, 403, 404),
|
||
)
|
||
|
||
feedback_router = APIRouter(
|
||
prefix="/api/feedback",
|
||
tags=["feedback"],
|
||
responses=error_responses(401, 403, 404),
|
||
)
|
||
|
||
|
||
@router.get("/profile", response_model=UserProfileResponse)
|
||
async def get_user_profile(
|
||
current_user: CurrentUserDep,
|
||
):
|
||
return UserProfileResponse(
|
||
id=current_user.id,
|
||
phone=current_user.phone,
|
||
email=current_user.email,
|
||
nickname=current_user.nickname,
|
||
avatar_url=avatar_url_for_api_response(current_user.avatar_url),
|
||
subscription_type=current_user.subscription_type,
|
||
created_at=current_user.created_at.isoformat(),
|
||
birth_year=current_user.birth_year,
|
||
birth_place=current_user.birth_place,
|
||
grew_up_place=current_user.grew_up_place,
|
||
occupation=current_user.occupation,
|
||
language_preference=_coerce_language_token(
|
||
getattr(current_user, "language_preference", "zh")
|
||
),
|
||
)
|
||
|
||
|
||
@router.put("/profile", response_model=UserProfileResponse)
|
||
async def update_user_profile(
|
||
body: UpdateUserProfileRequest,
|
||
current_user: CurrentUserDep,
|
||
service: UserService = Depends(get_user_service),
|
||
):
|
||
logger.info(
|
||
"更新用户档案 user_id={} fields={}",
|
||
current_user.id,
|
||
sorted(body.model_fields_set),
|
||
)
|
||
return await service.update_profile(current_user.id, body)
|
||
|
||
|
||
@router.post("/data/purge", response_model=PurgeUserDataResponse)
|
||
async def purge_user_data(
|
||
body: PurgeUserDataRequest,
|
||
current_user: CurrentUserDep,
|
||
service: UserService = Depends(get_user_service),
|
||
object_storage: ObjectStorage = Depends(get_object_storage),
|
||
):
|
||
"""
|
||
永久删除当前账号下的业务数据:对话与片段、记忆层、故事与插图意图、书籍与章节(含图片任务行)、
|
||
回忆录状态、订单记录、刷新令牌;并清理会话 Redis 历史、任务追踪与相关分布式锁 key;
|
||
对 memory_sources / memoir_images / 关联 Asset 中记录的 storage_key 尽力删除对象存储对象。
|
||
保留 users 表中的账号与登录字段(手机号、密码等),并清空出生年/出生地/成长地/职业等档案字段。
|
||
口令见请求体 schema 说明。
|
||
"""
|
||
return await service.purge_all_user_data(
|
||
current_user.id,
|
||
confirmation=body.confirmation,
|
||
object_storage=object_storage,
|
||
)
|
||
|
||
|
||
@router.post("/test-subscription", response_model=TestSubscriptionResponse)
|
||
async def test_subscription(
|
||
body: TestSubscriptionRequest,
|
||
current_user: CurrentUserDep,
|
||
service: UserService = Depends(get_user_service),
|
||
):
|
||
if not settings.enable_test_subscription:
|
||
raise NotFoundError("测试订阅功能未开放")
|
||
if body.action == "activate" and body.plan_id not in ("pro", "pro_plus"):
|
||
raise BadRequestError("plan_id 仅支持 pro 或 pro_plus")
|
||
return await service.toggle_test_subscription(
|
||
current_user.id, body.action, body.plan_id
|
||
)
|
||
|
||
|
||
@feedback_router.post(
|
||
"", response_model=FeedbackResponse, status_code=status.HTTP_201_CREATED
|
||
)
|
||
async def submit_feedback(
|
||
request: SubmitFeedbackRequest,
|
||
current_user: CurrentUserDep,
|
||
):
|
||
"""提交用户反馈。用户可通过此接口提交反馈意见或联系客服。"""
|
||
feedback_id = str(uuid.uuid4())
|
||
logger.info(
|
||
"用户反馈已提交 feedback_id={} user_id={} content_len={} has_contact={}",
|
||
feedback_id,
|
||
current_user.id,
|
||
len(request.content or ""),
|
||
bool(request.contact and str(request.contact).strip()),
|
||
)
|
||
logger.debug(
|
||
"用户反馈详情: feedback_id={} user_id={} content={} contact={}",
|
||
feedback_id,
|
||
current_user.id,
|
||
request.content,
|
||
request.contact,
|
||
)
|
||
return FeedbackResponse(
|
||
id=feedback_id,
|
||
message="反馈已提交,我们会尽快处理",
|
||
)
|