Files
assist/src/web/blueprints/tenants.py
Jeason 683b64ed62 fix: 工单详情AI建议区域UI统一为Bootstrap 5风格
- 去掉自定义 ai-suggestion-section/generate-ai-btn 等样式
- 改用 Bootstrap card + badge + btn 组件
- 相似度和审批状态用 badge bg-success/warning/danger
- 按钮用 btn btn-sm 标准样式
- 租户管理飞书配置简化为只保留群绑定(去掉独立凭证字段)
- 未绑定群的消息日志增加提示
2026-04-02 15:21:00 +08:00

176 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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
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}")
logger.warning(f"⚠️ 飞书群 {chat_id} 未绑定任何租户,使用默认租户。请在租户管理页面将此 chat_id 绑定到对应租户。")
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 {}