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

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
租户管理蓝图
处理租户 CRUD 的 API 路由
"""
import json
import logging
from flask import Blueprint, request, jsonify
from src.core.database import db_manager
from src.core.models import Tenant, DEFAULT_TENANT
logger = logging.getLogger(__name__)
tenants_bp = Blueprint('tenants', __name__, url_prefix='/api/tenants')
def _ensure_default_tenant():
"""确保 default 租户存在"""
try:
with db_manager.get_session() as session:
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()
except Exception as e:
logger.error(f"确保默认租户失败: {e}")
@tenants_bp.route('', methods=['GET'])
def list_tenants():
"""获取所有租户列表"""
try:
with db_manager.get_session() as session:
tenants = session.query(Tenant).order_by(Tenant.created_at).all()
return jsonify([t.to_dict() for t in tenants])
except Exception as e:
logger.error(f"获取租户列表失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('', methods=['POST'])
def create_tenant():
"""创建新租户"""
try:
data = request.get_json()
if not data or not data.get('tenant_id') or not data.get('name'):
return jsonify({"error": "tenant_id 和 name 为必填项"}), 400
tenant_id = data['tenant_id'].strip()
name = data['name'].strip()
if not tenant_id or not name:
return jsonify({"error": "tenant_id 和 name 不能为空"}), 400
with db_manager.get_session() as session:
existing = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if existing:
return jsonify({"error": f"租户 '{tenant_id}' 已存在"}), 409
tenant = Tenant(
tenant_id=tenant_id,
name=name,
description=data.get('description', ''),
config=json.dumps(data.get('config', {}))
)
session.add(tenant)
session.commit()
# 重新查询以获取完整数据
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
return jsonify({"success": True, "tenant": tenant.to_dict()}), 201
except Exception as e:
logger.error(f"创建租户失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('/<tenant_id>', methods=['PUT'])
def update_tenant(tenant_id):
"""更新租户信息(重命名等)"""
try:
data = request.get_json()
if not data:
return jsonify({"error": "请求体不能为空"}), 400
with db_manager.get_session() as session:
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if not tenant:
return jsonify({"error": f"租户 '{tenant_id}' 不存在"}), 404
if 'name' in data:
tenant.name = data['name'].strip()
if 'description' in data:
tenant.description = data['description']
if 'config' in data:
tenant.config = json.dumps(data['config'])
if 'is_active' in data:
tenant.is_active = data['is_active']
session.commit()
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
return jsonify({"success": True, "tenant": tenant.to_dict()})
except Exception as e:
logger.error(f"更新租户失败: {e}")
return jsonify({"error": str(e)}), 500
@tenants_bp.route('/<tenant_id>', methods=['DELETE'])
def delete_tenant(tenant_id):
"""删除租户(不允许删除 default"""
try:
if tenant_id == DEFAULT_TENANT:
return jsonify({"error": "不能删除默认租户"}), 403
with db_manager.get_session() as session:
tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first()
if not tenant:
return jsonify({"error": f"租户 '{tenant_id}' 不存在"}), 404
session.delete(tenant)
session.commit()
return jsonify({"success": True, "message": f"租户 '{tenant_id}' 已删除"})
except Exception as e:
logger.error(f"删除租户失败: {e}")
return jsonify({"error": str(e)}), 500