# ============================================================================= # 企微IT智能服务台 — 坐席模型 # ============================================================================= # 说明:对应数据库 agents 表,存储坐席(IT服务人员)信息 # 坐席状态:online(在线)/offline(离线)/busy(忙碌) # ============================================================================= import uuid from datetime import datetime from typing import Optional from sqlalchemy import DateTime, Integer, JSON, String from sqlalchemy.orm import Mapped, mapped_column from app.database import Base class Agent(Base): """坐席模型 — 对应 agents 表。 记录坐席的基本信息和状态,用于消息分配和负载管理。 Attributes: id: 坐席唯一标识(UUID,数据库自动生成) user_id: 企微用户ID(唯一,关联企微通讯录) name: 坐席姓名 status: 坐席状态(online/offline/busy) current_load: 当前服务会话数 max_load: 最大同时服务数(默认5) created_at: 创建时间 updated_at: 更新时间 """ # 表名(必须和架构文档 DDL 一致) __tablename__ = "agents" # -------------------------------------------------------------------------- # 字段定义 # -------------------------------------------------------------------------- # 主键:UUID,Python端生成(兼容PostgreSQL和SQLite) id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()), ) # 企微用户ID(唯一,用于关联企微通讯录和登录认证) user_id: Mapped[str] = mapped_column( String(64), unique=True, nullable=False, comment="企微用户ID(唯一)", ) # 坐席姓名 name: Mapped[str] = mapped_column( String(128), nullable=False, comment="坐席姓名", ) # 坐席状态(CHECK 约束:只能取三种值) # online: 在线,可以接收新的会话分配 # offline: 离线,不接收任何会话 # busy: 忙碌,不接收新会话但继续处理已有的 status: Mapped[str] = mapped_column( String(20), nullable=False, default="offline", comment="坐席状态: online/offline/busy", ) # 当前服务会话数(分配新会话时 +1,结单时 -1) current_load: Mapped[int] = mapped_column( Integer, nullable=False, default=0, comment="当前服务会话数", ) # 最大同时服务数(坐席同时处理的会话数上限) # 默认5个,可根据坐席能力调整 max_load: Mapped[int] = mapped_column( Integer, nullable=False, default=5, comment="最大同时服务数", ) # 创建时间 created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now, comment="创建时间", ) # 更新时间 updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now, onupdate=datetime.now, comment="更新时间", ) # 角色(admin=组长, agent=坐席) # 管理后台需要 admin 角色才能访问,坐席端无限制 role: Mapped[str] = mapped_column( String(20), nullable=False, default="agent", comment="角色:admin=组长, agent=坐席", ) # 技能标签列表(JSON 数组,存储坐席的技能分类) # 可选值:电脑/软件/外设/网络/安全/资产/其他 skill_tags: Mapped[list] = mapped_column( JSON, nullable=False, default=list, comment="技能标签列表(电脑/软件/外设/网络/安全/资产/其他)", ) # OTP密钥(用于TOTP动态码验证,为空表示未绑定) otp_secret: Mapped[str] = mapped_column( String(32), nullable=True, default=None, comment="OTP密钥(Base32编码)", ) # OTP是否启用(admin角色强制启用) otp_enabled: Mapped[bool] = mapped_column( Integer, nullable=False, default=0, comment="OTP是否启用(0=否, 1=是)", ) # 本地密码哈希(可选,用于本地密码认证) # 使用 bcrypt 加密存储,不存储明文密码 # 当企微验证不可用时,可作为备用认证方式 # P1 修复: Mapped[Optional[str]] 解决严格模式下 None 赋值报错 password_hash: Mapped[Optional[str]] = mapped_column( String(128), nullable=True, default=None, comment="本地密码哈希(bcrypt)", ) def __repr__(self) -> str: """坐席对象的字符串表示,方便调试。""" return ( f"" )