From 4ab7732da9f0686d723928b2e283c76a240ef28f Mon Sep 17 00:00:00 2001 From: stefanfeng Date: Wed, 19 Aug 2026 14:23:43 +0800 Subject: [PATCH] fix: preserve takeover and share token integrity --- digital-avatar-app/backend/database.py | 8 +++++++- digital-avatar-app/backend/models.py | 4 ++-- digital-avatar-app/backend/routers/avatars.py | 1 - .../backend/tests/test_chat_orchestration.py | 6 ++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/digital-avatar-app/backend/database.py b/digital-avatar-app/backend/database.py index 2cdd03d..8e1f7b3 100644 --- a/digital-avatar-app/backend/database.py +++ b/digital-avatar-app/backend/database.py @@ -38,8 +38,9 @@ def init_db(): ("authorizations", "takeover_enabled", "BOOLEAN DEFAULT 0"), ("authorizations", "takeover_mode", "VARCHAR DEFAULT 'immediate'"), ("authorizations", "takeover_delay_seconds", "INTEGER DEFAULT 30"), - ("avatars", "share_token", "VARCHAR DEFAULT ''"), + ("avatars", "share_token", "VARCHAR DEFAULT NULL"), ) + _normalize_optional_unique_values() def _try_add_columns(*cols): @@ -51,3 +52,8 @@ def _try_add_columns(*cols): except Exception: # 列已存在(或全新库由 create_all 建好)则忽略 pass + + +def _normalize_optional_unique_values(): + with engine.begin() as conn: + conn.exec_driver_sql("UPDATE avatars SET share_token = NULL WHERE share_token = ''") diff --git a/digital-avatar-app/backend/models.py b/digital-avatar-app/backend/models.py index dd76d16..72e6a44 100644 --- a/digital-avatar-app/backend/models.py +++ b/digital-avatar-app/backend/models.py @@ -20,7 +20,7 @@ class Avatar(Base): photo_url = Column(String, default="") emoji = Column(String, default="🤖") status = Column(String, default="active") # active | inactive | training - share_token = Column(String, default="", unique=True, index=True) # 对外分享使用的不可猜测令牌 + share_token = Column(String, nullable=True, default=None, unique=True, index=True) # 对外分享使用的不可猜测令牌 token_balance = Column(Integer, default=0) config = Column(JSON, default=dict) created_at = Column(DateTime, server_default=func.now()) @@ -36,7 +36,7 @@ class Avatar(Base): "photoUrl": self.photo_url, "emoji": self.emoji, "status": self.status, - "shareToken": self.share_token, + "shareToken": self.share_token or "", "tokenBalance": self.token_balance, "config": self.config or {}, "createdAt": _iso(self.created_at), diff --git a/digital-avatar-app/backend/routers/avatars.py b/digital-avatar-app/backend/routers/avatars.py index 415aabf..57f4e12 100644 --- a/digital-avatar-app/backend/routers/avatars.py +++ b/digital-avatar-app/backend/routers/avatars.py @@ -133,4 +133,3 @@ def delete_avatar(avatar_id: str, db: Session = Depends(get_db)): db.delete(a) db.commit() return ok({"success": True}) - diff --git a/digital-avatar-app/backend/tests/test_chat_orchestration.py b/digital-avatar-app/backend/tests/test_chat_orchestration.py index eea26a8..1c4aa33 100644 --- a/digital-avatar-app/backend/tests/test_chat_orchestration.py +++ b/digital-avatar-app/backend/tests/test_chat_orchestration.py @@ -121,6 +121,12 @@ class ChatOrchestrationTests(unittest.TestCase): self.assertNotIn("config", payload) self.assertNotIn("ownerId", payload) + def test_unshared_avatars_do_not_reuse_a_unique_share_token(self): + first = Avatar(name="first") + second = Avatar(name="second") + self.assertIsNone(first.share_token) + self.assertIsNone(second.share_token) + def test_standard_answer_can_be_emitted_as_sse_chunks(self): self.assertEqual(list(_iter_text_chunks("标准答案内容", size=2)), ["标准", "答案", "内容"])