chore: initial baseline with P0-safety .gitignore

This commit is contained in:
Simon
2026-06-14 16:49:18 +08:00
commit 63262292d7
510 changed files with 146008 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# =============================================================================
# 企微IT智能服务台 — 模型包初始化
# =============================================================================
# 说明:导出所有模型类,方便 Alembic 和其他模块统一导入
# 注意:即使某些模型在当前文件未直接使用,也必须导入
# 否则 Alembic 无法检测到这些模型,不会生成对应的迁移脚本
# =============================================================================
from app.models.conversation import Conversation
from app.models.message import Message
from app.models.agent import Agent
from app.models.quick_reply_template import QuickReplyTemplate
from app.models.system_config import SystemConfig
from app.models.funny_phrase import FunnyPhrase
from app.models.approval_link import ApprovalLink
from app.models.software_download import SoftwareDownload
from app.models.agent_note import AgentNote
from app.models.employee import Employee
from app.models.todo_item import TodoItem
from app.models.troubleshooting_template import TroubleshootingTemplate
from app.models.config_change_log import ConfigChangeLog
from app.models.role import Role
from app.models.user_role import UserRole
from app.models.role_mapping_rule import RoleMappingRule
# 所有模型类的列表,方便遍历
__all__ = [
"Conversation",
"Message",
"Agent",
"QuickReplyTemplate",
"SystemConfig",
"FunnyPhrase",
"ApprovalLink",
"SoftwareDownload",
"AgentNote",
"Employee",
"TodoItem",
"TroubleshootingTemplate",
"ConfigChangeLog",
"Role",
"UserRole",
"RoleMappingRule",
]
+146
View File
@@ -0,0 +1,146 @@
# =============================================================================
# 企微IT智能服务台 — 坐席模型
# =============================================================================
# 说明:对应数据库 agents 表,存储坐席(IT服务人员)信息
# 坐席状态:online(在线)/offline(离线)/busy(忙碌)
# =============================================================================
import uuid
from datetime import datetime
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=是)",
)
def __repr__(self) -> str:
"""坐席对象的字符串表示,方便调试。"""
return (
f"<Agent(id={self.id}, name={self.name}, "
f"status={self.status}, load={self.current_load}/{self.max_load})>"
)
+100
View File
@@ -0,0 +1,100 @@
# =============================================================================
# 企微IT智能服务台 — 坐席备注模型
# =============================================================================
# 说明:对应数据库 agent_notes 表,存储坐席对会话的备注
# 用途:坐席可以记录处理过程中的关键信息,方便后续跟进
# 一个会话可以有多条备注(不同坐席或同一坐席多次记录)
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Index, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class AgentNote(Base):
"""坐席备注模型 — 对应 agent_notes 表。
记录坐席在处理会话时添加的备注信息。
一个会话可以有多条备注。
Attributes:
id: 备注唯一标识(UUID,数据库自动生成)
conversation_id: 所属会话ID(外键,关联 conversations 表)
agent_id: 坐席ID
content: 备注内容
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "agent_notes"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 所属会话ID(外键,关联 conversations 表)
# ON DELETE CASCADE:删除会话时自动删除该会话的所有备注
conversation_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("conversations.id", ondelete="CASCADE"),
nullable=False,
comment="所属会话ID",
)
# 坐席ID(记录是哪个坐席写的备注)
agent_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="坐席ID",
)
# 备注内容(坐席自由输入的文本)
content: Mapped[str] = mapped_column(
Text,
nullable=False,
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按会话ID查询(如获取某会话的所有备注)
Index("idx_an_conversation", "conversation_id"),
)
def __repr__(self) -> str:
"""备注对象的字符串表示,方便调试。"""
return (
f"<AgentNote(id={self.id}, conv={self.conversation_id}, "
f"agent={self.agent_id})>"
)
+104
View File
@@ -0,0 +1,104 @@
# =============================================================================
# 企微IT智能服务台 — 审批流程链接模型
# =============================================================================
# 说明:对应数据库 approval_links 表,存储审批流程的外部链接
# 分类:IT/HR/行政/财务
# 在H5用户端右侧AI助手面板中展示,方便员工快速访问审批页面
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Index, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class ApprovalLink(Base):
"""审批流程链接模型 — 对应 approval_links 表。
存储公司各类审批流程的外部链接,
在H5用户端AI助手面板中按分类展示。
Attributes:
id: 链接唯一标识(UUID,数据库自动生成)
category: 分类(IT/HR/行政/财务)
title: 审批名称
url: 审批链接
sort_order: 排序权重
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "approval_links"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 分类(按部门分类,方便在H5面板中折叠展示)
category: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="分类:IT/HR/行政/财务",
)
# 审批名称(展示给用户看的标题)
title: Mapped[str] = mapped_column(
String(128),
nullable=False,
comment="审批名称",
)
# 审批链接(点击后跳转到对应的审批页面)
url: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="审批链接",
)
# 排序权重(同一分类内排序,数值越小越靠前)
sort_order: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按分类查询(如获取所有"IT"分类的审批链接)
Index("idx_al_category", "category"),
)
def __repr__(self) -> str:
"""链接对象的字符串表示,方便调试。"""
return f"<ApprovalLink(id={self.id}, category={self.category}, title={self.title})>"
+95
View File
@@ -0,0 +1,95 @@
# =============================================================================
# 企微IT智能服务台 — 配置变更日志模型
# =============================================================================
# 说明:对应数据库 config_change_logs 表,记录每次配置项的变更历史
# 包含变更前后的值、操作人和时间,用于配置审计和回滚
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Index, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class ConfigChangeLog(Base):
"""配置变更日志模型 — 对应 config_change_logs 表。
记录每次配置项的变更历史,包含变更前后的值、操作人和时间。
Attributes:
id: 日志唯一标识(UUID)
config_key: 变更的配置键
old_value: 变更前的值
new_value: 变更后的值
changed_by: 变更操作人(agent_id
changed_at: 变更时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "config_change_logs"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 配置键(关联 system_configs 表的 config_key
config_key: Mapped[str] = mapped_column(
String(128),
nullable=False,
comment="配置键",
)
# 变更前的值(空字符串表示新增配置项)
old_value: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="变更前的值",
)
# 变更后的值
new_value: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="变更后的值",
)
# 变更操作人(关联 agents 表的 id)
changed_by: Mapped[str] = mapped_column(
String(36),
nullable=False,
comment="变更操作人 agent_id",
)
# 变更时间
changed_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="变更时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按配置键查询(如查询某配置项的所有变更历史)
Index("idx_ccl_config_key", "config_key"),
# 按变更时间查询(如查询最近的变更记录)
Index("idx_ccl_changed_at", "changed_at"),
)
def __repr__(self) -> str:
"""变更日志对象的字符串表示,方便调试。"""
return f"<ConfigChangeLog(key={self.config_key}, by={self.changed_by})>"
+292
View File
@@ -0,0 +1,292 @@
# =============================================================================
# 企微IT智能服务台 — 会话模型
# =============================================================================
# 说明:对应数据库 conversations 表,存储所有会话信息
# 核心概念:每个员工的每次咨询对应一个会话(Conversation
# 会话状态流转:ai_handling → queued → serving → resolved
# =============================================================================
import uuid
from datetime import datetime
from typing import Any, Dict, Optional
from sqlalchemy import Boolean, DateTime, Index, Integer, JSON, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class Conversation(Base):
"""会话模型 — 对应 conversations 表。
每个员工的一次完整咨询过程对应一个会话记录。
包含员工信息、会话状态、紧急度评分、标签等核心数据。
Attributes:
id: 会话唯一标识(UUID,数据库自动生成)
employee_id: 企微员工UserID(关联企微通讯录)
employee_name: 员工姓名(冗余存储,减少关联查询)
department: 员工部门
position: 员工岗位
level: 员工等级(用于 VIP 判断)
status: 会话状态(ai_handling/queued/serving/resolved
is_vip: VIP标记(基于企微通讯录规则自动匹配)
is_pinned: 置顶标记(坐席手动操作)
is_todo: 代办标记(坐席手动操作)
urgency_score: 紧急度评分(1-5,数值越大越紧急)
tags: 标签集合(JSONB,存储举手/需介入/情绪等标记)
assigned_agent_id: 分配的坐席ID
last_message_at: 最后消息时间(用于会话排序)
last_message_summary: 最后消息摘要(会话列表预览用)
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "conversations"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 企业微信企业ID(US-7: 区分主企业和下游企业员工)
# 默认值为主企业 corp_id,下游企业员工使用下游企业 corp_id
corp_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="",
comment="企业微信企业ID(主企业或下游企业)",
)
# 企微员工UserIDNOT NULL,配合 corp_id 唯一标识员工)
employee_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="企微员工UserID",
)
# 员工姓名(冗余存储,避免每次查询都要关联企微API)
employee_name: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="员工姓名",
)
# 部门
department: Mapped[str] = mapped_column(
String(256),
nullable=False,
default="",
comment="部门",
)
# 岗位
position: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="岗位",
)
# 等级(用于 VIP 判断:总监及以上为 VIP)
level: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="",
comment="等级",
)
# 会话状态(CHECK 约束:只能取四种值)
# ai_handling: AI处理中(第二步启用)
# queued: 排队中,等待坐席接入
# serving: 服务中,坐席正在处理
# resolved: 已结单
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="queued",
comment="会话状态: ai_handling/queued/serving/resolved",
)
# VIP标记(基于企微通讯录API规则自动匹配)
is_vip: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="VIP标记",
)
# 置顶标记(坐席手动操作,置顶的会话在列表中优先显示)
is_pinned: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="置顶标记",
)
# 代办标记(坐席手动操作,标记需要后续跟进的会话)
is_todo: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="代办标记",
)
# 紧急度评分(1-5,数值越大越紧急)
# 计算公式:基础分 + 情绪加成 + VIP加成 + 重复追问加成
urgency_score: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=1,
comment="紧急度1-5",
)
# 标签集合(JSON 格式,存储结构化标记数据,兼容所有数据库)
# 示例:{"hand_raise": true, "emotion": "angry", "need_intervene": true}
tags: Mapped[Dict[str, Any]] = mapped_column(
JSON,
nullable=False,
default=dict,
comment="标签集合",
)
# 分配的坐席ID(可为空,表示尚未分配坐席)
assigned_agent_id: Mapped[Optional[str]] = mapped_column(
String(64),
nullable=True,
comment="分配的坐席ID",
)
# 协作坐席ID列表(JSON 数组,存储所有被邀请来协作的坐席ID)
# 和 assigned_agent_id 的区别:
# - assigned_agent_id:会话的「主责」坐席(接单人),只有他才能结单/转接
# - collaborating_agent_ids:被邀请来协助的坐席,可以查看和回复,但不能结单
# 设计决策:用 JSON 而非关联表,因为协作人数少(1-3人),JSON 查询足够
collaborating_agent_ids: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="协作坐席ID列表",
)
# 被邀请参与会话的非坐席人员列表(JSON 数组,存储员工ID或部门ID)
# 和 collaborating_agent_ids 的区别:
# - collaborating_agent_ids:坐席 → 坐席协作(摇人)
# - participants:坐席 → 任意员工/部门(邀请功能 P0-09~P0-11
# 格式:[ {"id": "employee_id", "name": "姓名", "department": "部门", "type": "employee"},
# {"id": "dept_id", "name": "部门名", "type": "department"} ]
# 设计决策:存储完整信息,减少企微API调用
participants: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="被邀请参与会话的人员列表(邀请功能)",
)
# AI 实质性回复计数(排除打招呼/呼叫人工的引导回复)
# 当计数 >= 3 时,前端显示「呼叫坐席」按钮
ai_substantive_reply_count: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="AI实质性回复计数(满3次可呼叫坐席)",
)
# 影响范围(受影响人数,0=未评估,数值越大影响范围越广)
impact_scope: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="影响范围",
)
# 阻断性标记(问题是否阻断员工正常工作流程)
is_blocking: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="阻断性标记",
)
# 情绪状态(normal: 正常, worried: 担忧, angry: 愤怒, urgent: 紧急)
emotion_state: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="normal",
comment="情绪状态",
)
# Dify 会话ID(用于多轮对话上下文保持)
# Dify 侧通过此 ID 关联同一员工的多轮对话
dify_conversation_id: Mapped[Optional[str]] = mapped_column(
String(128),
nullable=True,
default=None,
comment="Dify会话ID(多轮对话上下文)",
)
# 最后消息时间(用于会话列表按最新消息排序)
last_message_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
comment="最后消息时间",
)
# 最后消息摘要(会话列表预览用,截取消息前256字符)
last_message_summary: Mapped[str] = mapped_column(
String(256),
nullable=False,
default="",
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按状态查询(如查询所有排队中的会话)
Index("idx_conversations_status", "status"),
# 按员工ID查询(如查询某个员工的所有会话)
Index("idx_conversations_employee_id", "employee_id"),
# US-7: 按企业ID查询(如查询某企业所有会话)
Index("idx_conversations_corp_id", "corp_id"),
# 按坐席ID查询(如查询某个坐席正在服务的所有会话)
Index("idx_conversations_assigned_agent", "assigned_agent_id"),
# 按紧急度倒序查询(紧急度高的排前面)
Index("idx_conversations_urgency_score", "urgency_score"),
# 按最后消息时间倒序查询(最新消息的排前面)
Index("idx_conversations_last_message_at", "last_message_at"),
)
def __repr__(self) -> str:
"""会话对象的字符串表示,方便调试。"""
return (
f"<Conversation(id={self.id}, employee={self.employee_name}, "
f"status={self.status}, urgency={self.urgency_score})>"
)
+192
View File
@@ -0,0 +1,192 @@
# =============================================================================
# 企微IT智能服务台 — 员工模型
# =============================================================================
# 说明:对应数据库 employees 表,存储通过 OAuth2 认证的员工信息
# US-7 扩展:增加 corp_id 字段支持上下游互联企业场景
# 主企业(亿企赢总部): corp_id = 主企业 corp_id
# 下游企业(亿企赢): corp_id = 下游企业 corp_id
# 复合唯一键 (corp_id, employee_id) 确保跨企业员工标识唯一
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Index, JSON, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class Employee(Base):
"""员工模型 — 对应 employees 表。
通过 OAuth2 认证后记录员工信息,支持跨企业场景(US-7)。
与 Conversation.employee_id 通过 (corp_id, employee_id) 关联。
Attributes:
id: 主键(UUID,数据库自动生成)
corp_id: 企业微信企业ID(主企业或下游企业)
employee_id: 员工UserID(企业内唯一)
name: 员工姓名
department: 部门(JSON数组字符串)
position: 岗位
mobile: 手机号
email: 邮箱
avatar: 头像URL
status: 激活状态(1=已激活, 2=已禁用, 4=未激活)
last_login_at: 最后登录时间
created_at: 创建时间
updated_at: 更新时间
"""
# 表名
__tablename__ = "employees"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
comment="员工记录唯一标识",
)
# 企业微信企业ID(主企业或下游企业)
# US-7: 用于区分不同企业的员工,格式如 "wwa8c87970b2011f41"
corp_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="企业微信企业ID",
)
# 员工UserID(企业内唯一)
# 注意:不同企业的 userid 可能重复,需配合 corp_id 使用
employee_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="企微员工UserID(企业内唯一)",
)
# 员工姓名
name: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="员工姓名",
)
# 部门(JSON数组字符串,如 "[1, 2, 3]"
department: Mapped[str] = mapped_column(
String(512),
nullable=False,
default="",
comment="部门ID列表(JSON数组)",
)
# 岗位
position: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="岗位",
)
# 手机号
mobile: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="",
comment="手机号",
)
# 邮箱
email: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="邮箱",
)
# 头像URL
avatar: Mapped[str] = mapped_column(
String(512),
nullable=False,
default="",
comment="头像URL",
)
# 激活状态(企微通讯录返回: 1=已激活, 2=已禁用, 4=未激活)
status: Mapped[int] = mapped_column(
default=1,
comment="激活状态: 1=已激活, 2=已禁用, 4=未激活",
)
# IT技能等级(7级: bronze/silver/gold/platinum/diamond/star/king
it_level: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="silver",
comment="IT技能等级",
)
# 等级来源(system: 系统自动评定, manual: 坐席手动调整, assessment: 评估结果)
it_level_source: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="system",
comment="等级来源",
)
# 坐席备注(JSON 格式,存储坐席对员工的备注信息)
notes: Mapped[dict] = mapped_column(
JSON,
nullable=False,
default=dict,
comment="坐席备注",
)
# 最后登录时间
last_login_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=True,
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引和约束定义
# --------------------------------------------------------------------------
__table_args__ = (
# 复合唯一约束:同一企业内 employee_id 唯一
UniqueConstraint("corp_id", "employee_id", name="uq_employee_corp"),
# 按 corp_id 查询(查询某企业所有员工)
Index("idx_employees_corp_id", "corp_id"),
# 按 employee_id 查询
Index("idx_employees_employee_id", "employee_id"),
)
def __repr__(self) -> str:
"""员工对象的字符串表示。"""
return (
f"<Employee(corp_id={self.corp_id}, employee_id={self.employee_id}, "
f"name={self.name})>"
)
+120
View File
@@ -0,0 +1,120 @@
# =============================================================================
# 企微IT智能服务台 — 趣味话术模型
# =============================================================================
# 说明:对应数据库 funny_phrases 表,存储各场景的趣味话术
# 场景:shake(摇人)/keyword(关键词)/waiting(等待)/connected(接入)/timeout(超时)/vip
# 话术在用户端H5中显示,给等待过程增添趣味性
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Index, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class FunnyPhrase(Base):
"""趣味话术模型 — 对应 funny_phrases 表。
按触发场景存储趣味话术,在用户等待过程中显示。
支持后台动态修改,无需发版。
Attributes:
id: 话术唯一标识(UUID,数据库自动生成)
scene: 触发场景(shake/keyword/waiting/connected/timeout/vip
content: 话术内容
tone: 语气标签(亲切/稍正式/安抚/明确交接/降级安抚/正式)
sort_order: 排序权重
is_active: 是否启用
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "funny_phrases"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 触发场景
# shake: 点击摇人按钮时
# keyword: 关键词触发转人工时
# waiting: 排队等待时(30秒无人接单)
# connected: 坐席接入时
# timeout: 等待超时时(2分钟)
# vip: VIP员工时
scene: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="触发场景: shake/keyword/waiting/connected/timeout/vip",
)
# 话术内容
content: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="话术内容",
)
# 语气标签(方便管理员理解话术风格)
tone: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="亲切",
comment="语气标签",
)
# 排序权重(同一场景下有多条话术时,按此排序)
sort_order: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="排序权重",
)
# 是否启用(False 的话术不会被使用)
is_active: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按场景查询(如获取所有"摇人"场景的话术)
Index("idx_fp_scene", "scene"),
)
def __repr__(self) -> str:
"""话术对象的字符串表示,方便调试。"""
return f"<FunnyPhrase(id={self.id}, scene={self.scene}, content={self.content[:20]})>"
+252
View File
@@ -0,0 +1,252 @@
# =============================================================================
# 企微IT智能服务台 — 消息模型
# =============================================================================
# 说明:对应数据库 messages 表,存储会话中的所有消息
# 消息来源:员工(employee)、坐席(agent)、AI(ai)、系统(system)
# 消息类型:文本(text)、图片(image)、文件(file)、语音(voice)、系统提示(system)
# =============================================================================
import uuid
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class Message(Base):
"""消息模型 — 对应 messages 表。
每条消息都属于一个会话(Conversation),记录对话中的每一条信息。
包含发送者信息、消息内容、消息类型等。
Attributes:
id: 消息唯一标识(UUID,数据库自动生成)
conversation_id: 所属会话ID(外键,关联 conversations 表)
sender_type: 发送者类型(employee/agent/ai/system
sender_id: 发送者ID
sender_name: 发送者姓名(冗余存储,减少关联查询)
content: 消息内容(文本消息为文字,媒体消息为描述文字或URL)
msg_type: 消息类型(text/image/file/voice/system
media_id: 企微媒体文件ID(图片/语音/视频消息,3天有效)
media_url: 本地存储的媒体文件URL(下载后保存到服务器)
file_name: 文件名(文件消息用)
file_size: 文件大小(字节)
extra_data: 扩展元数据(JSON,如图片尺寸、语音格式等)
ai_suggestion: 是否为AI建议(坐席端展示用)
status: 消息状态(sending/sent/delivered/read
recallable_until: 可撤回截止时间(创建时间+2分钟)
is_read: 是否已读
created_at: 创建时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "messages"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 所属会话ID(外键,关联 conversations 表)
# ON DELETE CASCADE:删除会话时自动删除该会话的所有消息
conversation_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("conversations.id", ondelete="CASCADE"),
nullable=False,
comment="所属会话ID",
)
# 发送者类型(CHECK 约束:只能取四种值)
# employee: 员工发送的消息
# agent: 坐席发送的消息
# ai: AI生成的消息(第二步启用)
# system: 系统消息(如"坐席已接入"等通知)
sender_type: Mapped[str] = mapped_column(
String(20),
nullable=False,
comment="发送者类型: employee/agent/ai/system",
)
# 发送者ID
# 员工消息时为企微UserID,坐席消息时为坐席user_id
sender_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="发送者ID",
)
# 发送者姓名(冗余存储,避免每次查消息都要关联用户表)
sender_name: Mapped[str] = mapped_column(
String(128),
nullable=False,
default="",
comment="发送者姓名",
)
# 消息内容
# 文本消息时为文本内容,图片/文件消息时为媒体URL或描述文字
content: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
comment="消息内容",
)
# 消息类型(CHECK 约束)
# text: 文本消息
# image: 图片消息
# file: 文件消息
# voice: 语音消息
# system: 系统消息
msg_type: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="text",
comment="消息类型: text/image/file/voice/system",
)
# 引用回复:指向被回复的消息ID(M1 新增)
# 为 None 时表示普通消息,非 None 时表示对某条消息的回复
# 前端根据此字段显示引用内容(被回复消息的摘要)
reply_to_id: Mapped[Optional[str]] = mapped_column(
String(36),
nullable=True,
default=None,
comment="引用回复:被回复的消息ID",
)
# 企微媒体文件ID(图片/语音/视频消息携带)
# 注意:MediaId 仅3天有效,收到消息后应尽快下载保存到本地
# 下载接口:GET https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=TOKEN&media_id=MEDIA_ID
media_id: Mapped[Optional[str]] = mapped_column(
String(256),
nullable=True,
default=None,
comment="企微媒体文件ID3天有效)",
)
# 本地存储的媒体文件URL(下载后保存到服务器/NAS的访问路径)
# 格式示例:/media/2026/06/03/abc123.jpg
media_url: Mapped[Optional[str]] = mapped_column(
String(512),
nullable=True,
default=None,
comment="本地存储的媒体文件URL",
)
# 文件名(文件消息携带,或下载后自定义的文件名)
file_name: Mapped[Optional[str]] = mapped_column(
String(256),
nullable=True,
default=None,
comment="文件名",
)
# 文件大小(字节,文件消息携带)
file_size: Mapped[Optional[int]] = mapped_column(
Integer,
nullable=True,
default=None,
comment="文件大小(字节)",
)
# 扩展元数据(JSON格式,存储各消息类型的额外信息)
# 示例:
# 图片消息: {"pic_url": "https://...", "width": 1920, "height": 1080}
# 语音消息: {"format": "amr", "duration_seconds": 15}
# 视频消息: {"thumb_media_id": "...", "duration_seconds": 30}
# 位置消息: {"location_x": 23.134, "location_y": 113.358, "label": "杭州市"}
extra_data: Mapped[Optional[Dict[str, Any]]] = mapped_column(
JSON,
nullable=True,
default=None,
comment="扩展元数据(JSON",
)
# 是否为AI建议(坐席端展示用)
# True: 此消息为AI建议的回复,坐席可选择采纳/编辑/忽略
# False: 正常消息
ai_suggestion: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="是否为AI建议",
)
# 消息状态(新增:sending/sent/delivered/read
# sending: 发送中
# sent: 已发送
# delivered: 已送达
# read: 已读
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="sent",
comment="消息状态: sending/sent/delivered/read",
)
# 可撤回截止时间(创时间+2分钟)
# 用于判断消息是否在可撤回时间窗口内
recallable_until: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
default=None,
comment="可撤回截止时间",
)
# 是否已读
# 用于统计未读消息数(坐席端显示红点)
is_read: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="是否已读",
)
# 坐席对 AI 建议的操作行为
# 仅当 ai_suggestion=True 时有意义
# accepted: 坐席直接采纳了AI建议
# edited: 坐席编辑后采纳了AI建议
# ignored: 坐席忽略了AI建议
suggestion_action: Mapped[Optional[str]] = mapped_column(
String(20),
nullable=True,
default=None,
comment="AI建议操作行为: accepted/edited/ignored",
)
# 创建时间
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="创建时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按会话ID查询(如查询某会话的所有消息)
Index("idx_messages_conversation_id", "conversation_id"),
# 按创建时间查询(如按时间排序消息)
Index("idx_messages_created_at", "created_at"),
# 复合索引:按会话+时间查询(最常见的查询:获取某会话的消息列表)
Index("idx_messages_conversation_created", "conversation_id", "created_at"),
)
def __repr__(self) -> str:
"""消息对象的字符串表示,方便调试。"""
return (
f"<Message(id={self.id}, conv={self.conversation_id}, "
f"from={self.sender_type}, type={self.msg_type})>"
)
+145
View File
@@ -0,0 +1,145 @@
# =============================================================================
# 企微IT智能服务台 — 快速回复模板模型
# =============================================================================
# 说明:对应数据库 quick_reply_templates 表,存储坐席常用回复模板
# 分类:账号/网络/软件/硬件/通用
# 支持变量替换:如 {employee_name} 会被替换为实际员工姓名
# =============================================================================
import uuid
from datetime import datetime
from typing import Any, Dict, List
from sqlalchemy import DateTime, Index, Integer, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class QuickReplyTemplate(Base):
"""快速回复模板模型 — 对应 quick_reply_templates 表。
坐席常用回复模板,按分类组织,支持变量替换。
变量如 {employee_name} 在使用时会被替换为实际值。
Attributes:
id: 模板唯一标识(UUID,数据库自动生成)
category: 分类(账号/网络/软件/硬件/通用)
title: 模板标题(简短描述,方便坐席快速识别)
content: 模板内容(支持 {employee_name} 等变量)
variables: 可用变量列表(JSONB,如 ["employee_name","department"]
sort_order: 排序权重(数值越小越靠前)
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "quick_reply_templates"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 分类(用于按类别组织模板,在坐席端折叠展示)
category: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="通用",
comment="分类:账号/网络/软件/硬件/通用",
)
# 模板标题(简短描述,方便坐席快速识别)
title: Mapped[str] = mapped_column(
String(128),
nullable=False,
comment="模板标题",
)
# 模板内容(支持变量替换)
# 示例:"您好{employee_name},您的密码重置链接已发送"
content: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="模板内容,支持变量如 {employee_name}",
)
# 可用变量列表(JSON 格式,存储模板中可替换的变量名,兼容所有数据库)
# 示例:["employee_name", "department"]
variables: Mapped[List[str]] = mapped_column(
JSON,
nullable=False,
default=list,
comment="可用变量列表",
)
# 排序权重(数值越小越靠前,同一分类内排序)
sort_order: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="排序权重",
)
# 状态(draft=草稿/pending_review=待审核/approved=已通过/rejected=已驳回)
# 审核流转:draft → pending_review → approved / rejected
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="approved",
comment="状态:draft/pending_review/approved/rejected",
)
# 版本号(每次审核通过后 +1
version: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=1,
comment="版本号,每次审核通过后 +1",
)
# 提交人 agent_id(提交审核的坐席ID,可为空表示系统创建)
submitted_by: Mapped[str] = mapped_column(
String(36),
nullable=True,
default=None,
comment="提交人 agent_id",
)
# 创建时间
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按分类查询(如获取所有"账号"分类的模板)
Index("idx_qr_category", "category"),
)
def __repr__(self) -> str:
"""模板对象的字符串表示,方便调试。"""
return (
f"<QuickReplyTemplate(id={self.id}, category={self.category}, "
f"title={self.title})>"
)
+91
View File
@@ -0,0 +1,91 @@
# =============================================================================
# 角色模型 — roles 表
# =============================================================================
# 说明:定义系统角色(user/agent/admin),支持 RBAC 权限控制
# =============================================================================
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Boolean, DateTime, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class Role(Base):
"""角色模型 — 对应 roles 表。
预置三个角色:
- user: 所有在职员工默认角色(is_default=True
- agent: IT坐席,通过企微标签或eHR字段映射
- admin: 管理员,通过管理后台手动绑定
"""
__tablename__ = "roles"
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 角色标识:user/agent/admin(唯一)
name: Mapped[str] = mapped_column(
String(50),
unique=True,
nullable=False,
comment="角色标识:user/agent/admin",
)
# 显示名称:用户/坐席/管理员
display_name: Mapped[str] = mapped_column(
String(100),
nullable=False,
comment="显示名称:用户/坐席/管理员",
)
# 角色描述
description: Mapped[Optional[str]] = mapped_column(
Text,
nullable=True,
comment="角色描述",
)
# 权限列表(JSON数组)
permissions: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="权限列表(JSON数组)",
)
# 是否默认角色(user=True
is_default: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
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="更新时间",
)
def __repr__(self) -> str:
return f"<Role(id={self.id}, name={self.name}, display_name={self.display_name})>"
+89
View File
@@ -0,0 +1,89 @@
# =============================================================================
# 角色映射规则模型 — role_mapping_rules 表
# =============================================================================
# 说明:定义角色自动映射规则,支持企微标签和eHR字段两种来源
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import String, Boolean, DateTime, Integer, ForeignKey, Index
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class RoleMappingRule(Base):
"""角色映射规则模型 — 对应 role_mapping_rules 表。
定义自动映射规则,当用户满足条件时自动获得对应角色:
- wecom_tag: 企微标签匹配(如标签包含"IT坐席"
- ehr_position: eHR岗位关键词匹配(如岗位包含"IT支持"
"""
__tablename__ = "role_mapping_rules"
__table_args__ = (
# 按 role_id 查询优化
Index("idx_role_mapping_rules_role_id", "role_id"),
# 按 source_type 查询优化
Index("idx_role_mapping_rules_source_type", "source_type"),
)
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 角色 ID(外键)
role_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("roles.id", ondelete="CASCADE"),
nullable=False,
comment="目标角色 ID",
)
# 来源类型:wecom_tag/ehr_position
source_type: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="来源类型:wecom_tag/ehr_position",
)
# 来源值:标签名/岗位关键词
source_value: Mapped[str] = mapped_column(
String(200),
nullable=False,
comment="来源值:标签名/岗位关键词",
)
# 优先级(数值越大优先级越高)
priority: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="优先级(数值越大优先级越高)",
)
# 是否启用
is_active: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
comment="是否启用",
)
# 创建时间
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="创建时间",
)
def __repr__(self) -> str:
return (
f"<RoleMappingRule(id={self.id}, role_id={self.role_id}, "
f"source_type={self.source_type}, source_value={self.source_value})>"
)
+125
View File
@@ -0,0 +1,125 @@
# =============================================================================
# 企微IT智能服务台 — 软件下载入口模型
# =============================================================================
# 说明:对应数据库 software_downloads 表,存储软件下载链接
# 分类:办公/开发/安全/工具
# 在H5用户端右侧AI助手面板中展示,方便员工下载常用软件
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Index, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class SoftwareDownload(Base):
"""软件下载入口模型 — 对应 software_downloads 表。
存储公司常用软件的下载链接,
在H5用户端AI助手面板中按分类展示。
Attributes:
id: 下载入口唯一标识(UUID,数据库自动生成)
category: 分类(办公/开发/安全/工具)
name: 软件名称
version: 版本号
platform: 平台(Windows/Mac/Linux/全平台)
download_url: 下载链接
sort_order: 排序权重
created_at: 创建时间
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "software_downloads"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 分类(按用途分类,方便在H5面板中折叠展示)
category: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="分类:办公/开发/安全/工具",
)
# 软件名称
name: Mapped[str] = mapped_column(
String(128),
nullable=False,
comment="软件名称",
)
# 版本号(如 "12.1"、"最新版"
version: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="",
comment="版本号",
)
# 支持平台(如 "Windows/Mac"、"全平台"
platform: Mapped[str] = mapped_column(
String(32),
nullable=False,
default="",
comment="平台: Windows/Mac/Linux/全平台",
)
# 下载链接
download_url: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="下载链接",
)
# 排序权重(同一分类内排序,数值越小越靠前)
sort_order: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
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="更新时间",
)
# --------------------------------------------------------------------------
# 索引定义(和架构文档 DDL 严格一致)
# --------------------------------------------------------------------------
__table_args__ = (
# 按分类查询(如获取所有"办公"分类的软件)
Index("idx_sd_category", "category"),
)
def __repr__(self) -> str:
"""下载入口对象的字符串表示,方便调试。"""
return (
f"<SoftwareDownload(id={self.id}, category={self.category}, "
f"name={self.name}, version={self.version})>"
)
+83
View File
@@ -0,0 +1,83 @@
# =============================================================================
# 企微IT智能服务台 — 系统配置模型
# =============================================================================
# 说明:对应数据库 system_configs 表,存储系统级配置项
# 包括:关键词列表、评分阈值、话术模板等
# 优势:配置存在数据库中,支持后台动态修改,无需重启服务
# =============================================================================
import uuid
from datetime import datetime
from sqlalchemy import DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class SystemConfig(Base):
"""系统配置模型 — 对应 system_configs 表。
将可动态修改的配置项存储在数据库中,
支持后台修改后立即生效,无需重启服务。
Attributes:
id: 配置唯一标识(UUID,数据库自动生成)
config_key: 配置键(唯一,如 "hand_raise_keywords"
config_value: 配置值(JSON 字符串或纯文本)
description: 配置说明
updated_at: 更新时间
"""
# 表名(必须和架构文档 DDL 一致)
__tablename__ = "system_configs"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUIDPython端生成(兼容PostgreSQL和SQLite
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 配置键(唯一,用于查找配置项)
# 示例:hand_raise_keywords, emotion_keywords_angry, polling_interval_seconds
config_key: Mapped[str] = mapped_column(
String(128),
unique=True,
nullable=False,
comment="配置键",
)
# 配置值(存储 JSON 字符串或纯文本)
# JSON 示例:'["转人工","人工","人工服务"]'
# 纯文本示例:'3'(表示阈值)
config_value: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="配置值(JSON字符串或纯文本)",
)
# 配置说明(方便管理员理解配置用途)
description: Mapped[str] = mapped_column(
String(256),
nullable=False,
default="",
comment="配置说明",
)
# 更新时间(配置修改时自动更新)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
onupdate=datetime.now,
comment="更新时间",
)
def __repr__(self) -> str:
"""配置对象的字符串表示,方便调试。"""
return f"<SystemConfig(key={self.config_key}, value={self.config_value})>"
+128
View File
@@ -0,0 +1,128 @@
# =============================================================================
# 企微IT智能服务台 — 待办事项模型
# =============================================================================
# 说明:对应数据库 todo_items 表,存储坐席的待办事项
# 待办类型:ticket(工单)/approval(审批)/device(设备) 等
# =============================================================================
import uuid
from datetime import datetime
from typing import Any, Dict, Optional
from sqlalchemy import Boolean, DateTime, Integer, JSON, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class TodoItem(Base):
"""待办事项模型 — 对应 todo_items 表。
存储坐席需要跟进的各类待办事项,包括工单、审批、设备处理等。
Attributes:
id: 待办唯一标识(UUID,数据库自动生成)
type: 待办类型(ticket/approval/device
title: 待办标题
priority: 优先级(urgent/high/normal
description: 详细描述(JSON,存储结构化数据)
status: 状态(pending/processing/resolved
assigned_agent_id: 分配的坐席ID(可为空,表示未分配)
corp_id: 企业微信企业ID
created_at: 创建时间
updated_at: 更新时间
"""
# 表名
__tablename__ = "todo_items"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
comment="待办唯一标识",
)
# 待办类型
type: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="ticket",
comment="待办类型: ticket/approval/device",
)
# 待办标题
title: Mapped[str] = mapped_column(
String(256),
nullable=False,
default="",
comment="待办标题",
)
# 优先级
priority: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="normal",
comment="优先级: urgent/high/normal",
)
# 详细描述(JSON 格式,存储结构化数据)
description: Mapped[Dict[str, Any]] = mapped_column(
JSON,
nullable=False,
default=dict,
comment="详细描述",
)
# 状态
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="pending",
comment="状态: pending/processing/resolved",
)
# 分配的坐席ID(可为空,表示未分配)
assigned_agent_id: Mapped[Optional[str]] = mapped_column(
String(64),
nullable=True,
comment="分配的坐席ID",
)
# 企业微信企业ID
corp_id: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="",
comment="企业微信企业ID",
)
# 创建时间
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="更新时间",
)
def __repr__(self) -> str:
"""待办事项对象的字符串表示。"""
return (
f"<TodoItem(id={self.id}, type={self.type}, "
f"title={self.title}, status={self.status})>"
)
@@ -0,0 +1,114 @@
# =============================================================================
# 企微IT智能服务台 — 排障模板模型
# =============================================================================
# 说明:对应数据库 troubleshooting_templates 表,存储常见问题的排障模板
# 包含排障步骤路径和流程图定义
# =============================================================================
import uuid
from datetime import datetime
from typing import Any, Dict, Optional
from sqlalchemy import Boolean, DateTime, JSON, String
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class TroubleshootingTemplate(Base):
"""排障模板模型 — 对应 troubleshooting_templates 表。
存储常见 IT 问题的标准化排障模板,包括步骤路径和流程图。
分类:vpn/email/system/account 等。
Attributes:
id: 模板唯一标识(UUID,数据库自动生成)
name: 模板名称
category: 分类(vpn/email/system/account
path_steps: 排障步骤路径(JSON,存储步骤序列)
flowchart: 流程图定义(JSON,存储节点和连线)
is_active: 是否启用
created_at: 创建时间
updated_at: 更新时间
"""
# 表名
__tablename__ = "troubleshooting_templates"
# --------------------------------------------------------------------------
# 字段定义
# --------------------------------------------------------------------------
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
comment="模板唯一标识",
)
# 模板名称
name: Mapped[str] = mapped_column(
String(256),
nullable=False,
default="",
comment="模板名称",
)
# 分类
category: Mapped[str] = mapped_column(
String(20),
nullable=False,
default="system",
comment="分类: vpn/email/system/account",
)
# 排障步骤路径(JSON 格式)
# 示例:[{"step": 1, "title": "检查VPN连接状态", "action": "..."}, ...]
path_steps: Mapped[list] = mapped_column(
JSON,
nullable=False,
default=list,
comment="排障步骤路径",
)
# 流程图定义(JSON 格式)
# 示例:{"nodes": [...], "edges": [...]}
flowchart: Mapped[Dict[str, Any]] = mapped_column(
JSON,
nullable=False,
default=dict,
comment="流程图定义",
)
# 是否启用
is_active: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
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="更新时间",
)
def __repr__(self) -> str:
"""排障模板对象的字符串表示。"""
return (
f"<TroubleshootingTemplate(id={self.id}, name={self.name}, "
f"category={self.category}, is_active={self.is_active})>"
)
+89
View File
@@ -0,0 +1,89 @@
# =============================================================================
# 用户角色关联模型 — user_roles 表
# =============================================================================
# 说明:用户与角色的多对多关联表,记录角色来源和分配信息
# =============================================================================
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class UserRole(Base):
"""用户角色关联模型 — 对应 user_roles 表。
记录用户拥有的角色,支持以下来源:
- auto: 系统自动分配(所有员工默认 user 角色)
- tag: 企微标签映射
- ehr: eHR 字段映射
- manual: 管理后台手动分配
"""
__tablename__ = "user_roles"
__table_args__ = (
# 同一用户同一角色只能有一条记录
UniqueConstraint("employee_id", "role_id", name="uq_user_role"),
# 按 employee_id 查询优化
Index("idx_user_roles_employee_id", "employee_id"),
# 按 role_id 查询优化
Index("idx_user_roles_role_id", "role_id"),
)
# 主键:UUID
id: Mapped[str] = mapped_column(
String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
# 企微 UserID
employee_id: Mapped[str] = mapped_column(
String(100),
nullable=False,
comment="企微 UserID",
)
# 角色 ID(外键)
role_id: Mapped[str] = mapped_column(
String(36),
ForeignKey("roles.id", ondelete="CASCADE"),
nullable=False,
comment="角色 ID",
)
# 角色来源:auto/tag/ehr/manual
source: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="角色来源:auto/tag/ehr/manual",
)
# 分配者(手动分配时记录操作人)
assigned_by: Mapped[Optional[str]] = mapped_column(
String(100),
nullable=True,
comment="分配者(手动分配时记录操作人)",
)
# 分配时间
assigned_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=datetime.now,
comment="分配时间",
)
# 过期时间(可选,用于临时角色)
expires_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True),
nullable=True,
comment="过期时间(可选,用于临时角色)",
)
def __repr__(self) -> str:
return f"<UserRole(employee_id={self.employee_id}, role_id={self.role_id}, source={self.source})>"