# -*- 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('/', 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('/', 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 def resolve_tenant_by_chat_id(chat_id: str) -> str: """ 根据飞书 chat_id 查找对应的 tenant_id。 遍历所有租户的 config.feishu.chat_groups,匹配则返回该 tenant_id。 未匹配时返回 DEFAULT_TENANT。 """ try: with db_manager.get_session() as session: tenants = session.query(Tenant).filter(Tenant.is_active == True).all() for t in tenants: if not t.config: continue try: cfg = json.loads(t.config) except (json.JSONDecodeError, TypeError): continue feishu_cfg = cfg.get('feishu', {}) chat_groups = feishu_cfg.get('chat_groups', []) if chat_id in chat_groups: logger.info(f"飞书群 {chat_id} 匹配到租户 {t.tenant_id}") return t.tenant_id except Exception as e: logger.error(f"解析飞书群租户映射失败: {e}") return DEFAULT_TENANT def get_tenant_feishu_config(tenant_id: str) -> dict: """ 获取租户的飞书配置。 返回 {'app_id': ..., 'app_secret': ..., 'chat_groups': [...]} 或空字典。 """ try: with db_manager.get_session() as session: tenant = session.query(Tenant).filter(Tenant.tenant_id == tenant_id).first() if tenant and tenant.config: cfg = json.loads(tenant.config) return cfg.get('feishu', {}) except Exception as e: logger.error(f"获取租户飞书配置失败: {e}") return {}