feat: 租户管理体系建设 CRUD + 各业务模块接入 tenant_id

1. 新增 Tenant 模型(tenants 表),支持租户创建、重命名、删除
2. 新增 /api/tenants CRUD 蓝图,default 租户不可删除
3. 数据库初始化时自动创建默认租户记录
4. Dashboard 新增租户管理标签页(创建/编辑/删除租户)
5. 各业务模块写入数据时正确传递 tenant_id:
   - realtime_chat: create_session 和 _save_conversation 支持 tenant_id
   - dialogue_manager: _save_conversation 和 create_work_order 支持 tenant_id
   - conversation_history: save_conversation 支持 tenant_id
   - workorder_sync: sync_from_feishu 支持 tenant_id
   - websocket_server: create_session 传递 tenant_id
   - HTTP chat API: create_session 传递 tenant_id
   - feishu_sync API: 同步时传递 tenant_id
   - workorders API: 创建工单时传递 tenant_id
6. 网页对话入口添加租户选择器
7. 知识库搜索按租户隔离(realtime_chat 中 _search_knowledge 传递 tenant_id)
8. 初始化时自动加载租户列表填充选择器
This commit is contained in:
2026-04-02 09:33:16 +08:00
parent 7013e9db70
commit edb0616f7f
14 changed files with 465 additions and 15 deletions

View File

@@ -68,10 +68,35 @@ class DatabaseManager:
Base.metadata.create_all(bind=self.engine)
logger.info("数据库初始化成功")
# 确保默认租户存在
self._ensure_default_tenant()
except Exception as e:
logger.error(f"数据库初始化失败: {e}")
raise
def _ensure_default_tenant(self):
"""确保默认租户记录存在"""
try:
from .models import Tenant, DEFAULT_TENANT
session = self.SessionLocal()
try:
existing = session.query(Tenant).filter(Tenant.tenant_id == DEFAULT_TENANT).first()
if not existing:
session.add(Tenant(
tenant_id=DEFAULT_TENANT,
name="默认租户",
description="系统默认租户"
))
session.commit()
logger.info("默认租户已创建")
except Exception:
session.rollback()
finally:
session.close()
except Exception as e:
logger.warning(f"确保默认租户失败(不影响启动): {e}")
@contextmanager
def get_session(self) -> Generator[Session, None, None]:
"""获取数据库会话的上下文管理器"""

View File

@@ -9,6 +9,35 @@ Base = declarative_base()
# 默认租户ID单租户部署时使用
DEFAULT_TENANT = "default"
class Tenant(Base):
"""租户模型 — 管理多租户(市场)"""
__tablename__ = "tenants"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), unique=True, nullable=False) # 唯一标识,如 market_a
name = Column(String(100), nullable=False) # 显示名称,如 "市场A"
description = Column(Text, nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# 租户级配置:飞书 app 凭证、LLM 参数等JSON 格式)
config = Column(Text, nullable=True)
def to_dict(self):
import json
return {
'id': self.id,
'tenant_id': self.tenant_id,
'name': self.name,
'description': self.description,
'is_active': self.is_active,
'config': json.loads(self.config) if self.config else {},
'created_at': self.created_at.isoformat() if self.created_at else None,
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
}
class WorkOrder(Base):
"""工单模型"""
__tablename__ = "work_orders"