"""飞书多应用 open_id 管理 — 存储/查询各应用下用户的 open_id,union_id 跨应用关联""" from __future__ import annotations import logging from typing import Optional, Tuple from sqlalchemy.orm import Session from app.core.config import settings logger = logging.getLogger(__name__) def get_app_id_for_agent(agent_id: str) -> str: """根据 agent_id 返回对应的飞书应用 app_id。默认返回主飞书应用(苹果)。""" if settings.LINGXI_AGENT_ID and agent_id == settings.LINGXI_AGENT_ID: return settings.LINGXI_APP_ID or "" if settings.ORANGE_AGENT_ID and agent_id == settings.ORANGE_AGENT_ID: return settings.ORANGE_APP_ID or "" if settings.SUYAO_AGENT_ID and agent_id == settings.SUYAO_AGENT_ID: return settings.SUYAO_APP_ID or "" if settings.TIANTIAN_AGENT_ID and agent_id == settings.TIANTIAN_AGENT_ID: return settings.TIANTIAN_APP_ID or "" return settings.FEISHU_APP_ID or "" def save_open_id( db: Session, app_id: str, open_id: str, union_id: Optional[str] = None, user_id: Optional[str] = None, ) -> None: """保存或更新 (app_id, open_id) 记录。 如果已有同 app+open_id 的记录,更新 union_id 和 user_id; 否则新建记录。user_id 可以为空(首次捕获时可能未知)。 """ from app.models.user_feishu_open_id import UserFeishuOpenId try: existing = ( db.query(UserFeishuOpenId) .filter( UserFeishuOpenId.app_id == app_id, UserFeishuOpenId.open_id == open_id, ) .first() ) if existing: changed = False if union_id and existing.union_id != union_id: existing.union_id = union_id changed = True if user_id and existing.user_id != user_id: existing.user_id = user_id changed = True if changed: db.commit() else: record = UserFeishuOpenId( user_id=user_id, app_id=app_id, open_id=open_id, union_id=union_id, ) db.add(record) db.commit() except Exception as e: logger.warning("保存飞书 open_id 失败: %s", e) db.rollback() def find_user_id_by_open_id(db: Session, app_id: str, open_id: str) -> Optional[str]: """通过 (app_id, open_id) 查找已关联的 user_id。""" from app.models.user_feishu_open_id import UserFeishuOpenId try: record = ( db.query(UserFeishuOpenId) .filter( UserFeishuOpenId.app_id == app_id, UserFeishuOpenId.open_id == open_id, ) .first() ) return record.user_id if record else None except Exception as e: logger.warning("查询 user_id 失败: %s", e) return None def find_user_id_by_union_id(db: Session, union_id: str) -> Optional[str]: """通过 union_id 查找已关联的 user_id(跨应用识别)。""" from app.models.user_feishu_open_id import UserFeishuOpenId try: record = ( db.query(UserFeishuOpenId) .filter( UserFeishuOpenId.union_id == union_id, UserFeishuOpenId.user_id.isnot(None), ) .first() ) return record.user_id if record else None except Exception as e: logger.warning("通过 union_id 查询 user_id 失败: %s", e) return None def get_open_id_for_app(db: Session, user_id: str, app_id: str) -> Optional[str]: """查询用户在某飞书应用下的 open_id。""" from app.models.user_feishu_open_id import UserFeishuOpenId try: record = ( db.query(UserFeishuOpenId) .filter( UserFeishuOpenId.user_id == user_id, UserFeishuOpenId.app_id == app_id, ) .first() ) return record.open_id if record else None except Exception as e: logger.warning("查询飞书 open_id 失败: %s", e) return None def link_open_id_to_user( db: Session, app_id: str, open_id: str, user_id: str, union_id: Optional[str] = None, ) -> None: """将 (app_id, open_id) 关联到平台用户。已有记录则更新,否则创建。""" save_open_id(db, app_id=app_id, open_id=open_id, union_id=union_id, user_id=user_id) def resolve_user_and_save( db: Session, app_id: str, open_id: str, union_id: Optional[str] = None, ) -> Optional[str]: """WS 消息处理入口:自动解析用户身份并保存 open_id。 查找策略(按优先级): 1. 已有 (app_id, open_id) 记录中的 user_id 2. 通过 union_id 在 user_feishu_open_ids 中查找 3. 通过 open_id 匹配 User.feishu_open_id(苹果旧数据兼容) 4. 通过 union_id 匹配 User.feishu_union_id 找到用户后自动 link,否则只保存 (app_id, open_id, union_id)。 Returns: 找到的 user_id,未找到返回 None """ from app.models.user import User # 1. 已有记录 existing_user_id = find_user_id_by_open_id(db, app_id, open_id) if existing_user_id: # 补充 union_id if union_id: save_open_id(db, app_id=app_id, open_id=open_id, union_id=union_id, user_id=existing_user_id) return existing_user_id # 2. 通过 union_id 查找 if union_id: user_id_by_union = find_user_id_by_union_id(db, union_id) if user_id_by_union: link_open_id_to_user(db, app_id=app_id, open_id=open_id, user_id=user_id_by_union, union_id=union_id) return user_id_by_union # 3. 兼容旧 User.feishu_open_id(苹果的 open_id) user = db.query(User).filter(User.feishu_open_id == open_id).first() if user: link_open_id_to_user(db, app_id=app_id, open_id=open_id, user_id=user.id, union_id=union_id) return user.id # 4. 有 union_id 但前面都没匹配到:尝试通过飞书 API 查找苹果 open_id 的 union_id if union_id: from app.services.feishu_app_service import lookup_union_id_by_open_id users_with_apple = db.query(User).filter(User.feishu_open_id.isnot(None)).all() for u in users_with_apple: apple_union_id = lookup_union_id_by_open_id(u.feishu_open_id) if apple_union_id and apple_union_id == union_id: # 回填苹果记录的 union_id from app.models.user_feishu_open_id import UserFeishuOpenId apple_record = ( db.query(UserFeishuOpenId) .filter( UserFeishuOpenId.user_id == u.id, UserFeishuOpenId.app_id == (settings.FEISHU_APP_ID or ""), ) .first() ) if apple_record: apple_record.union_id = union_id db.commit() # 关联当前记录 link_open_id_to_user(db, app_id=app_id, open_id=open_id, user_id=u.id, union_id=union_id) return u.id # 5. 未找到,只保存记录(user_id 为空) save_open_id(db, app_id=app_id, open_id=open_id, union_id=union_id) return None