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:
Sully
2026-05-22 13:44:50 +08:00
committed by GitHub
parent f09ae248f9
commit 53e0065e3e
298 changed files with 15247 additions and 4344 deletions

View File

@@ -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: