refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -3,7 +3,8 @@ from datetime import timedelta
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.cos_url_keys import avatar_url_for_api_response
|
||||
from app.core.db import utc_now
|
||||
from app.core.db import transactional, utc_now
|
||||
from app.core.errors import BadRequestError, NotFoundError
|
||||
from app.core.logging import get_logger
|
||||
from app.core.redis import redis_service
|
||||
from app.core.task_tracker import task_tracker
|
||||
@@ -50,16 +51,19 @@ class UserService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self._db = db
|
||||
|
||||
async def get_by_id(self, user_id: str) -> User | None:
|
||||
return await repo.get_user_by_id(user_id, self._db)
|
||||
|
||||
async def update_profile(
|
||||
self, user_id: str, body: UpdateUserProfileRequest
|
||||
) -> UserProfileResponse:
|
||||
user = await repo.get_user_by_id(user_id, self._db)
|
||||
if not user:
|
||||
raise ValueError("用户不存在")
|
||||
for field in ("birth_year", "birth_place", "grew_up_place", "occupation"):
|
||||
if field in body.model_fields_set:
|
||||
setattr(user, field, getattr(body, field))
|
||||
await self._db.commit()
|
||||
raise NotFoundError("用户不存在")
|
||||
async with transactional(self._db):
|
||||
for field in ("birth_year", "birth_place", "grew_up_place", "occupation"):
|
||||
if field in body.model_fields_set:
|
||||
setattr(user, field, getattr(body, field))
|
||||
await self._db.refresh(user)
|
||||
return _user_to_profile(user)
|
||||
|
||||
@@ -68,24 +72,23 @@ class UserService:
|
||||
) -> TestSubscriptionResponse:
|
||||
user = await repo.get_user_by_id(user_id, self._db)
|
||||
if not user:
|
||||
raise ValueError("用户不存在")
|
||||
raise NotFoundError("用户不存在")
|
||||
now = utc_now()
|
||||
if action == "activate":
|
||||
user.subscription_type = plan_id
|
||||
user.subscription_expires_at = now + timedelta(days=365)
|
||||
await self._db.commit()
|
||||
return TestSubscriptionResponse(
|
||||
success=True,
|
||||
message=f"已开启测试订阅:{plan_id}",
|
||||
subscription_type=plan_id,
|
||||
)
|
||||
user.subscription_type = "free"
|
||||
user.subscription_expires_at = None
|
||||
await self._db.commit()
|
||||
async with transactional(self._db):
|
||||
if action == "activate":
|
||||
user.subscription_type = plan_id
|
||||
user.subscription_expires_at = now + timedelta(days=365)
|
||||
subscription_type = plan_id
|
||||
message = f"已开启测试订阅:{plan_id}"
|
||||
else:
|
||||
user.subscription_type = "free"
|
||||
user.subscription_expires_at = None
|
||||
subscription_type = "free"
|
||||
message = "已关闭测试订阅,恢复免费体验版"
|
||||
return TestSubscriptionResponse(
|
||||
success=True,
|
||||
message="已关闭测试订阅,恢复免费体验版",
|
||||
subscription_type="free",
|
||||
message=message,
|
||||
subscription_type=subscription_type,
|
||||
)
|
||||
|
||||
async def purge_all_user_data(
|
||||
@@ -97,11 +100,11 @@ class UserService:
|
||||
) -> PurgeUserDataResponse:
|
||||
"""物理删除该用户业务数据(保留 users 行与登录字段);并清空出生年/出生地等档案字段;提交后再清 Redis 等。"""
|
||||
if confirmation != PURGE_USER_DATA_CONFIRMATION:
|
||||
raise ValueError("确认文案不正确,请按提示完整输入口令")
|
||||
raise BadRequestError("确认文案不正确,请按提示完整输入口令")
|
||||
|
||||
user = await repo.get_user_by_id(user_id, self._db)
|
||||
if not user:
|
||||
raise ValueError("用户不存在")
|
||||
raise NotFoundError("用户不存在")
|
||||
|
||||
logger.info("用户数据清空开始 user_id={}", user_id)
|
||||
|
||||
@@ -120,10 +123,10 @@ class UserService:
|
||||
len(story_ids),
|
||||
)
|
||||
|
||||
await repo.purge_user_related_rows(self._db, user_id)
|
||||
await repo.clear_user_demographics(self._db, user_id)
|
||||
await repo.clear_user_avatar_url(self._db, user_id)
|
||||
await self._db.commit()
|
||||
async with transactional(self._db):
|
||||
await repo.purge_user_related_rows(self._db, user_id)
|
||||
await repo.clear_user_demographics(self._db, user_id)
|
||||
await repo.clear_user_avatar_url(self._db, user_id)
|
||||
logger.info("用户数据 DB 行已删除、档案字段已清空并提交 user_id={}", user_id)
|
||||
|
||||
if object_storage and storage_keys:
|
||||
|
||||
Reference in New Issue
Block a user