From b8769d8d54986afc4f874ce43da5f8b781bd6706 Mon Sep 17 00:00:00 2001 From: wsb1224 Date: Tue, 9 Jun 2026 20:44:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20token=20=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4=EF=BC=9A=E7=BB=9F=E4=B8=80=E6=89=A3?= =?UTF-8?q?=E8=B4=B9=E6=9D=A5=E6=BA=90=E5=B9=B6=E6=94=AF=E6=8C=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - chat_service: 文字聊天 ChatLog 改用独立会话保存,避免路由会话 生命周期干扰导致记录丢失 - token_service: 分类查询改用 mode 字段精确匹配替代 conversation_id 模糊匹配;总消耗直接读钱包数据与后台管理一致 - admin_router: 用户列表和详情新增文字/语音/通话分类 token 消耗 Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/api/admin_router.py | 34 ++++++++++++++++++++++++++++--- backend/services/chat_service.py | 20 ++++++++++-------- backend/services/token_service.py | 19 ++++++++++------- 3 files changed, 55 insertions(+), 18 deletions(-) diff --git a/backend/api/admin_router.py b/backend/api/admin_router.py index 9c5ec14..006b4f8 100644 --- a/backend/api/admin_router.py +++ b/backend/api/admin_router.py @@ -82,11 +82,22 @@ async def list_users( for user in users: wallet_result = await db.execute(select(UserWallet).where(UserWallet.user_id == user.id)) wallet = wallet_result.scalar_one_or_none() - + + # 查询该用户各模式的 token 消耗 + mode_tokens_result = await db.execute( + select( + ChatLog.mode, + func.coalesce(func.sum(ChatLog.tokens_input), 0) + + func.coalesce(func.sum(ChatLog.tokens_output), 0).label("tokens") + ).where(ChatLog.user_id == user.id) + .group_by(ChatLog.mode) + ) + mode_tokens = {row.mode or "text_chat": int(row.tokens or 0) for row in mode_tokens_result.fetchall()} + # 将状态从整数转换为字符串格式 # status: 1 或非零 = active, 0 或 null = disabled status_str = 'active' if (user.status and user.status != 0) else 'disabled' - + user_dict = { "id": user.id, "phone": user.phone, @@ -97,6 +108,9 @@ async def list_users( "daily_quota": wallet.daily_quota if wallet else 0, "extra_quota": wallet.extra_quota if wallet else 0, "total_consumed": wallet.total_consumed if wallet else 0, + "text_chat_tokens": mode_tokens.get("text_chat", 0), + "voice_chat_tokens": mode_tokens.get("voice_chat", 0), + "realtime_call_tokens": mode_tokens.get("voice_realtime_call", 0), "created_at": user.created_at.isoformat() if user.created_at else None } user_list.append(user_dict) @@ -129,6 +143,17 @@ async def get_user_detail( wallet_result = await db.execute(select(UserWallet).where(UserWallet.user_id == user_id)) wallet = wallet_result.scalar_one_or_none() + # 查询该用户各模式的 token 消耗 + mode_tokens_result = await db.execute( + select( + ChatLog.mode, + func.coalesce(func.sum(ChatLog.tokens_input), 0) + + func.coalesce(func.sum(ChatLog.tokens_output), 0).label("tokens") + ).where(ChatLog.user_id == user_id) + .group_by(ChatLog.mode) + ) + mode_tokens = {row.mode or "text_chat": int(row.tokens or 0) for row in mode_tokens_result.fetchall()} + return { "success": True, "data": { @@ -141,7 +166,10 @@ async def get_user_detail( "wallet": { "daily_quota": wallet.daily_quota if wallet else 0, "extra_quota": wallet.extra_quota if wallet else 0, - "total_consumed": wallet.total_consumed if wallet else 0 + "total_consumed": wallet.total_consumed if wallet else 0, + "text_chat_tokens": mode_tokens.get("text_chat", 0), + "voice_chat_tokens": mode_tokens.get("voice_chat", 0), + "realtime_call_tokens": mode_tokens.get("voice_realtime_call", 0) } }, "message": "User details retrieved successfully" diff --git a/backend/services/chat_service.py b/backend/services/chat_service.py index 54603a2..1f4b642 100644 --- a/backend/services/chat_service.py +++ b/backend/services/chat_service.py @@ -15,6 +15,7 @@ import asyncio from models.database import ChatLog, Pet, Background, BackgroundPetConfig, UserWallet from services.doubao_service import doubao_service from config.settings import get_chat_context_rounds +from utils.database import AsyncSessionLocal logger = logging.getLogger(__name__) @@ -307,15 +308,18 @@ async def send_message_stream(db_session: AsyncSession, user_id: int, message: s duration_ms=duration_ms, mode="text_chat" ) - db_session.add(chat_log) - await db_session.commit() - # 查询用户实际剩余token - wallet_result = await db_session.execute( - sa_select(UserWallet).where(UserWallet.user_id == user_id) - ) - wallet = wallet_result.scalar_one_or_none() - remaining_tokens = (wallet.available_tokens or 0) if wallet else 0 + # 使用独立会话保存记录,避免受路由 get_db() 生命周期影响 + async with AsyncSessionLocal() as save_session: + save_session.add(chat_log) + await save_session.commit() + + # 查询用户实际剩余token + wallet_result = await save_session.execute( + sa_select(UserWallet).where(UserWallet.user_id == user_id) + ) + wallet = wallet_result.scalar_one_or_none() + remaining_tokens = (wallet.available_tokens or 0) if wallet else 0 yield { "type": "stats", diff --git a/backend/services/token_service.py b/backend/services/token_service.py index ce37e76..4fa4d58 100644 --- a/backend/services/token_service.py +++ b/backend/services/token_service.py @@ -485,14 +485,21 @@ class TokenService: func.coalesce(func.sum(ChatLog.tokens_output), 0) ).where( *base_conditions, - ChatLog.conversation_id.like("voice-%") + ChatLog.mode == "voice_chat" ) realtime_query = select( func.coalesce(func.sum(ChatLog.tokens_input), 0) + func.coalesce(func.sum(ChatLog.tokens_output), 0) ).where( *base_conditions, - ChatLog.conversation_id.like("realtime-%") + ChatLog.mode == "voice_realtime_call" + ) + text_query = select( + func.coalesce(func.sum(ChatLog.tokens_input), 0) + + func.coalesce(func.sum(ChatLog.tokens_output), 0) + ).where( + *base_conditions, + ChatLog.mode == "text_chat" ) total_input = (await self.db_session.execute(total_input_query)).scalar() or 0 @@ -500,16 +507,14 @@ class TokenService: total_chats = (await self.db_session.execute(total_chats_query)).scalar() or 0 voice_tokens = (await self.db_session.execute(voice_query)).scalar() or 0 realtime_tokens = (await self.db_session.execute(realtime_query)).scalar() or 0 + text_chat_tokens = (await self.db_session.execute(text_query)).scalar() or 0 quota_info = await self._check_user_quota(user_id) wallet_consumed = quota_info.get("total_consumed", 0) or 0 chatlog_total = total_input + total_output - total_tokens = max(chatlog_total, wallet_consumed) - if total_tokens <= 0: - total_tokens = chatlog_total or wallet_consumed or 0 - - text_chat_tokens = max(0, chatlog_total - voice_tokens - realtime_tokens) + # 直接使用钱包累计消耗,与后台管理一致 + total_tokens = wallet_consumed return { "user_id": user_id,