fix: preserve takeover and share token integrity

This commit is contained in:
stefanfeng
2026-08-19 14:23:43 +08:00
parent 4a2d788e85
commit 4ab7732da9
4 changed files with 15 additions and 4 deletions
+7 -1
View File
@@ -38,8 +38,9 @@ def init_db():
("authorizations", "takeover_enabled", "BOOLEAN DEFAULT 0"), ("authorizations", "takeover_enabled", "BOOLEAN DEFAULT 0"),
("authorizations", "takeover_mode", "VARCHAR DEFAULT 'immediate'"), ("authorizations", "takeover_mode", "VARCHAR DEFAULT 'immediate'"),
("authorizations", "takeover_delay_seconds", "INTEGER DEFAULT 30"), ("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): def _try_add_columns(*cols):
@@ -51,3 +52,8 @@ def _try_add_columns(*cols):
except Exception: except Exception:
# 列已存在(或全新库由 create_all 建好)则忽略 # 列已存在(或全新库由 create_all 建好)则忽略
pass pass
def _normalize_optional_unique_values():
with engine.begin() as conn:
conn.exec_driver_sql("UPDATE avatars SET share_token = NULL WHERE share_token = ''")
+2 -2
View File
@@ -20,7 +20,7 @@ class Avatar(Base):
photo_url = Column(String, default="") photo_url = Column(String, default="")
emoji = Column(String, default="🤖") emoji = Column(String, default="🤖")
status = Column(String, default="active") # active | inactive | training 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) token_balance = Column(Integer, default=0)
config = Column(JSON, default=dict) config = Column(JSON, default=dict)
created_at = Column(DateTime, server_default=func.now()) created_at = Column(DateTime, server_default=func.now())
@@ -36,7 +36,7 @@ class Avatar(Base):
"photoUrl": self.photo_url, "photoUrl": self.photo_url,
"emoji": self.emoji, "emoji": self.emoji,
"status": self.status, "status": self.status,
"shareToken": self.share_token, "shareToken": self.share_token or "",
"tokenBalance": self.token_balance, "tokenBalance": self.token_balance,
"config": self.config or {}, "config": self.config or {},
"createdAt": _iso(self.created_at), "createdAt": _iso(self.created_at),
@@ -133,4 +133,3 @@ def delete_avatar(avatar_id: str, db: Session = Depends(get_db)):
db.delete(a) db.delete(a)
db.commit() db.commit()
return ok({"success": True}) return ok({"success": True})
@@ -121,6 +121,12 @@ class ChatOrchestrationTests(unittest.TestCase):
self.assertNotIn("config", payload) self.assertNotIn("config", payload)
self.assertNotIn("ownerId", 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): def test_standard_answer_can_be_emitted_as_sse_chunks(self):
self.assertEqual(list(_iter_text_chunks("标准答案内容", size=2)), ["标准", "答案", "内容"]) self.assertEqual(list(_iter_text_chunks("标准答案内容", size=2)), ["标准", "答案", "内容"])