Files
wecom_it_smart_desk/backend/app/models/agent.py
T
Simon 78f60c6857 feat(v0.7.1): P0 修复 + 企微 SSO + RBAC 细粒度 + audit_log
P0 修复:
- /api/ready import 错误 (_get_engine + settings.create_redis_client)
- 删 agent.otp_secret/otp_enabled 双字段 (migration 026)
- 重建 021_rbac migration (IF NOT EXISTS 兼容)

P1 新增:
- 企微 SSO (auth_wecom_sso.py, useWeChatWorkSSO composable, PortalSelect UA 检测)
- RBAC 5 角色 × 4 资源 × 4 操作 × 3 范围 (rbac_service + seed_rbac + require_permission)
- audit_log 模型 + migration 027 + 服务 + API
- 管理后台 RBAC 权限矩阵 UI (PermissionsMatrix.vue)

质量:
- pytest 405 passed / 33 pre-existing failed / 4 xfailed (v0.7.1 引入失败 = 0)
- conftest GBK patch 强制 UTF-8 读 .env
- .gitignore 排除 *.b64 (含 admin token 凭据)
- DEPLOY-v0.7.1.md 7 步 runbook + 4 坑 + 回滚预案
2026-06-22 17:38:47 +08:00

186 lines
6.4 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.
# =============================================================================
# 企微IT智能服务台 — 坐席模型
# =============================================================================
# 说明:对应数据库 agents 表,存储坐席(IT服务人员)信息
# 坐席状态:online(在线)/offline(离线)/busy(忙碌)
# =============================================================================
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, DateTime, Integer, JSON, String, text
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="技能标签列表(电脑/软件/外设/网络/安全/资产/其他)",
)
# v0.7.1: 删除 otp_secret / otp_enabled 字段
# 原因: 与下方 mfa_secret / mfa_enabled 完全重复(都是 TOTP secret)
# 旧 OTP 字段只用于高危操作前的二次验证,mfa 字段已涵盖该用途
# 迁移策略: alembic 010 改为 DROP COLUMN otp_secret, otp_enabled
# 本地密码哈希(可选,用于本地密码认证)
# 使用 bcrypt 加密存储,不存储明文密码
# 当企微验证不可用时,可作为备用认证方式
# P1 修复: Mapped[Optional[str]] 解决严格模式下 None 赋值报错
password_hash: Mapped[Optional[str]] = mapped_column(
String(128),
nullable=True,
default=None,
comment="本地密码哈希(bcrypt",
)
# --------------------------------------------------------------------------
# MFA 二次认证字段(Phase 2.1 task #17
# --------------------------------------------------------------------------
# 说明:MFA TOTP 独立于早期 OTP 字段,采用全新字段名以便区分演进阶段。
# - mfa_secret: TOTP 共享密钥(base32),绑定时生成,首次验证前不算启用
# - mfa_enabled: 是否启用(仅当 bind/confirm 验证成功后置 true)
# - mfa_bound_at: 首次绑定完成时间(用于审计 + 回收策略)
# - mfa_last_verified_at: 最近一次 verify 成功时间(用于安全审计)
# --------------------------------------------------------------------------
mfa_secret: Mapped[Optional[str]] = mapped_column(
String(32),
nullable=True,
default=None,
comment="MFA TOTP 共享密钥(base32,绑定时生成)",
)
mfa_enabled: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
server_default=text("false"),
comment="MFA 是否启用(False/True)",
)
mfa_bound_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
comment="MFA 首次绑定完成时间",
)
mfa_last_verified_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
comment="MFA 最近一次验证成功时间",
)
def __repr__(self) -> str:
"""坐席对象的字符串表示,方便调试。"""
return (
f"<Agent(id={self.id}, name={self.name}, "
f"status={self.status}, load={self.current_load}/{self.max_load})>"
)