Files
wecom_it_smart_desk/backend/app/models/agent.py
T

159 lines
5.0 KiB
Python
Raw Normal View History

# =============================================================================
# 企微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"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容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"<Agent(id={self.id}, name={self.name}, "
f"status={self.status}, load={self.current_load}/{self.max_load})>"
)