Files
gochat/docs/requirements/M11-enterprise-features.md
T
2026-06-04 15:44:48 +08:00

520 lines
35 KiB
Markdown
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.
# M11: 企业版功能(Enterprise Features
## 模块概述
本模块覆盖 Chatwoot 企业版专属功能,包括:SLA 策略与应用(SlaPolicy + AppliedSla + SlaEvent)、审计日志(AuditLog)、自定义角色权限系统(CustomRole)、组织/公司管理(Company)、坐席容量策略(AgentCapacityPolicy + InboxCapacityLimit)、语音通话(Call + Voice 系列 Service)、SAML SSO 单点登录(AccountSamlSettings)、SidekiqCron 定时任务调度。这些功能在社区版基础上增强了合规、安全、权限精细化、组织管理和语音通信能力。
---
### 功能1SLA 策略管理(SlaPolicy CRUD + 应用到对话)
- **功能描述**:账户级 SLAService Level Agreement)策略管理。管理员可创建 SLA 策略,设定首响时间(FRT)、下次响应时间(NRT)、解决时间(RT)阈值,并将策略绑定到对话。系统通过定时 Job 定期评估 SLA 是否达标/违规,并自动生成 SlaEvent 通知。
- **用户操作流程(UI交互步骤)**:
1. 进入 Settings → SLA 页面
2. 点击"Add SLA Policy",填写名称、描述
3. 设定三个时间阈值:first_response_time_threshold(首响)、next_response_time_threshold(下次响应)、resolution_time_threshold(解决),单位为秒
4. 可勾选"only_during_business_hours",仅在工作时间内计算 SLA
5. 保存后,可在 Inbox 设置中将 SLA 策略绑定到收件箱,新进入的对话自动应用该 SLA
6. 对话列表中可查看 SLA 状态标识(active/hit/missed/active_with_misses
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/sla_policies` — 获取 SLA 策略列表
- `POST /api/v1/accounts/{account_id}/sla_policies` — 创建 SLA 策略
- 请求:`{ sla_policy: { name: "Standard SLA", description: "...", first_response_time_threshold: 300, next_response_time_threshold: 600, resolution_time_threshold: 86400, only_during_business_hours: false } }`
- 响应:策略对象 `{ id, name, description, first_response_time_threshold, next_response_time_threshold, resolution_time_threshold, only_during_business_hours, account_id }`
- `GET /api/v1/accounts/{account_id}/sla_policies/{id}` — 获取单个 SLA 策略
- `PUT /api/v1/accounts/{account_id}/sla_policies/{id}` — 更新 SLA 策略
- `DELETE /api/v1/accounts/{account_id}/sla_policies/{id}` — 删除 SLA 策略(异步通过 DeleteObjectJob
- **涉及的数据模型 + 关键字段**:
- `SlaPolicy`(表 `sla_policies`):
- `id` (bigint, PK)
- `name` (string, not null) — 策略名称
- `description` (string) — 策略描述
- `first_response_time_threshold` (float) — 首响时间阈值(秒)
- `next_response_time_threshold` (float) — 下次响应时间阈值(秒)
- `resolution_time_threshold` (float) — 解决时间阈值(秒)
- `only_during_business_hours` (boolean, default false) — 仅在工作时间计算
- `account_id` (bigint, not null, FK)
- `AppliedSla`(表 `applied_slas`):
- `id` (bigint, PK)
- `sla_status` (integer, enum) — active(0)/hit(1)/missed(2)/active_with_misses(3)
- `account_id` (bigint, not null, FK)
- `sla_policy_id` (bigint, not null, FK)
- `conversation_id` (bigint, not null, FK)
- 唯一索引:`(account_id, sla_policy_id, conversation_id)`
- `SlaEvent`(表 `sla_events`):
- `id` (bigint, PK)
- `event_type` (integer, enum) — frt(0)/nrt(1)/rt(2)
- `meta` (jsonb) — 事件元数据
- `account_id`, `applied_sla_id`, `conversation_id`, `inbox_id`, `sla_policy_id` (bigint, FK)
- 关联:`SlaPolicy has_many :conversations, :applied_slas``AppliedSla has_many :sla_events``AppliedSla belongs_to :sla_policy, :conversation, :account`
- **涉及的业务逻辑(service层)**:
- **Sla::EvaluateAppliedSlaService** — 核心 SLA 评估逻辑:
- 依次检查 FRT/NRT/RT 三个阈值
- 首响检查:`conversation.created_at + sla_policy.first_response_time_threshold`,对比首条回复时间
- 下次响应检查:等待客户回复后,对比等待时间 + 阈值
- 解决时间检查:对话创建时间 + 阈值
- 达标时调用 `handle_hit_sla`,违规时调用 `handle_missed_sla` 生成 SlaEvent
- **Sla::TriggerSlasForAccountsJob** → **Sla::ProcessAccountAppliedSlasJob****Sla::ProcessAppliedSlaJob** — 定时评估链路,遍历所有 active/active_with_misses 状态的 AppliedSla
- **rake sla:apply_to_conversations** — 批量给已有对话应用 SLA 策略的迁移任务
- **涉及的自动化/规则/事件**
- 对话创建时,如 Inbox 绑定了 SLA 策略,自动创建 AppliedSlaconversation callback
- SlaEvent 创建后自动发送通知(`after_create_commit :create_notifications`
- 对话 resolve 后,EvaluateAppliedSlaService 判断 SLA 是否最终达标
- **涉及的权限/Policy**
- `SlaPolicyPolicy`index/show → administrator || agentcreate/update/destroy → administrator
- **GoChat 实现要点**
- SLA 三种阈值(FRT/NRT/RT)是核心概念,需要完整建模
- 定时评估 Job 需要设计好调度频率(Chatwoot 每5分钟通过 TriggerScheduledItemsJob 触发)
- `only_during_business_hours` 需要结合 M3 的 BusinessHours 计算
- AppliedSla 状态机:active → active_with_misses → hit/missed
---
### 功能2:审计日志(AuditLog
- **功能描述**:企业版审计日志,基于 `audited` gem 记录账户内关键资源的变更操作(创建/更新/删除),包括变更内容、操作人、IP 地址等。管理员可在审计日志页面按时间倒序查看所有变更记录,用于合规审计和安全追溯。
- **用户操作流程(UI交互步骤)**:
1. 账户需启用 `audit_logs` feature flag
2. 进入账户设置 → Audit Logs 页面
3. 可按时间倒序浏览所有变更记录(每页15条)
4. 每条记录显示:操作类型(action)、被审计对象(auditable_type + auditable_id)、变更内容(audited_changes)、操作人(username/email)、IPremote_address)、时间戳
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/audit_logs` — 获取审计日志(分页)
- 请求参数:`page`(页码)
- 响应:审计记录列表,含 `action, auditable_type, auditable_id, audited_changes, username, remote_address, request_uuid, created_at, associated_type, associated_id`
- **涉及的数据模型 + 关键字段**:
- `Enterprise::AuditLog`(继承 `Audited::Audit`,表 `audits`):
- `id` (bigint, PK)
- `action` (string) — 操作类型:create/update/destroy
- `auditable_type` (string) — 被审计对象类型(Account/Inbox/User/AccountUser 等)
- `auditable_id` (bigint) — 被审计对象 ID
- `audited_changes` (jsonb) — 变更内容
- `associated_type` (string) — 关联对象类型(通常是 Account)
- `associated_id` (bigint) — 关联对象 ID
- `user_id` (bigint) — 操作人 ID
- `username` (string) — 操作人邮箱(自动填充)
- `remote_address` (string) — IP 地址
- `request_uuid` (string) — 请求唯一标识
- `version` (integer) — 版本号
- `comment` (string)
- `user_type` (string)
- `created_at` (datetime)
- `Enterprise::AuditLog``after_save` 时自动补充 `associated_type/associated_id/username`
- **涉及的业务逻辑(service层)**:
- 通过 `audited` gem 的 `audited` 声明自动记录变更,无需独立 service
- 各模型通过 concern 声明审计范围:
- `Enterprise::Audit::Account``audited except: :updated_at, on: [:update]`
- `Enterprise::Audit::Inbox``audited associated_with: :account, on: [:create, :update]`
- `Enterprise::Audit::AccountUser``audited only: [:availability, :role, :account_id, :inviter_id, :user_id], on: [:create, :update], associated_with: :account`
- `Enterprise::Audit::User``audited only: [:availability, :display_name, :email, :name]`(手动记录登录/登出)
- `Enterprise::Audit::Conversation``audited only: [], on: [:destroy]`(仅记录删除)
- 其他:AutomationRule, Webhook, Macro, AgentBot, Team, TeamMember, InboxMember 等均有审计 concern
- 全局配置:`config/initializers/audited.rb``config.audit_class = 'Enterprise::AuditLog'`
- **涉及的自动化/规则/事件**
- 模型变更时自动写入 `audits` 表(audited gem callback
- `Enterprise::AuditLog.after_save :log_additional_information` — 自动补充关联信息和操作人邮箱
- **涉及的权限/Policy**
- `Api::V1::Accounts::AuditLogsController` — 仅 administrator 可访问(`before_action :check_admin_authorization?`
- 需要账户启用 `audit_logs` feature flag
- **GoChat 实现要点**
- 审计日志需基于变更追踪机制(类似 audited gem),在核心模型上声明审计字段
- 需记录操作人、IP、变更内容、时间戳
- 分页查询接口,按时间倒序排列
- associated_type/associated_id 用于按账户维度聚合审计记录
- feature flag 控制审计功能的启用
---
### 功能3:自定义角色权限系统(CustomRole)
- **功能描述**:企业版自定义角色系统,允许管理员在账户级创建自定义角色,定义细粒度权限列表,并将角色分配给 AccountUser。自定义角色扩展了默认的三级角色体系(administrator/agent/supervisor),使权限控制更灵活。
- **用户操作流程(UI交互步骤)**:
1. 进入 Settings → Custom Roles 页面
2. 点击"Add Role",填写角色名称、描述
3. 从权限列表勾选所需权限
4. 创建后可在 Agent Management 中将此角色分配给坐席
5. 坐席权限将基于其 CustomRole 的 permissions 列表生效
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/custom_roles` — 获取自定义角色列表
- `POST /api/v1/accounts/{account_id}/custom_roles` — 创建自定义角色
- 请求:`{ custom_role: { name: "Sales Agent", description: "...", permissions: ["conversation_manage", "contact_manage"] } }`
- 响应:角色对象 `{ id, name, description, permissions, account_id }`
- `GET /api/v1/accounts/{account_id}/custom_roles/{id}` — 获取单个角色
- `PUT /api/v1/accounts/{account_id}/custom_roles/{id}` — 更新角色
- `DELETE /api/v1/accounts/{account_id}/custom_roles/{id}` — 删除角色
- **涉及的数据模型 + 关键字段**:
- `CustomRole`(表 `custom_roles`):
- `id` (bigint, PK)
- `name` (string) — 角色名称
- `description` (string) — 角色描述
- `permissions` (text array, default []) — 权限列表
- `account_id` (bigint, not null, FK)
- 可用权限列表(`PERMISSIONS` 常量):
- `conversation_manage` — 管理所有对话
- `conversation_unassigned_manage` — 管理未分配对话并可认领
- `conversation_participating_manage` — 管理自己参与的对话
- `contact_manage` — 管理联系人
- `report_manage` — 管理报表
- `knowledge_base_manage` — 管理知识库
- 关联:`CustomRole has_many :account_users, dependent: :nullify`
- `Enterprise::AccountUser` 扩展:`permissions` 方法返回 `custom_role.permissions + ['custom_role']`(当有自定义角色时),否则返回默认角色权限
- **涉及的业务逻辑(service层)**:
- 无独立 serviceCRUD 在 controller 完成
- 权限合并逻辑在 `Enterprise::AccountUser#permissions`
- 删除 CustomRole 时,关联的 AccountUser 的 custom_role_id 设为 NULLnullify),回归默认角色权限
- **涉及的自动化/规则/事件**
- AccountUser 权限检查时,如存在 custom_role 则优先使用其 permissions 列表
- CustomRole 删除后,AccountUser 自动回退到默认角色
- **涉及的权限/Policy**
- `CustomRolePolicy`:所有操作(index/show/create/update/destroy)仅 administrator 可执行
- **GoChat 实现要点**
- 权限列表需设计为可扩展的枚举/常量体系
- AccountUser 需支持 custom_role_id 外键
- 权限合并逻辑:CustomRole.permissions + 默认角色权限
- 删除角色时注意 nullify 而非 restrict_with_error
- 当前6项权限可能需要扩展(如 automation_manage、inbox_manage 等)
---
### 功能4:组织/公司管理(Company)
- **功能描述**:企业版组织管理功能,允许账户创建"公司"(Company)实体,将联系人按组织分组。每个公司有名称、域名、描述、自定义属性,支持头像和 favicon 自动获取。联系人可归属于公司,系统可根据联系人邮箱域名自动关联公司。
- **用户操作流程(UI交互步骤)**:
1. 进入 Contacts → Companies 页面(需启用 `companies` feature flag
2. 点击"Add Company",填写名称、域名、描述
3. 可上传公司头像
4. 如填入域名,系统自动获取 favicon
5. 创建后可将联系人手动归属到该公司
6. 系统也可根据联系人邮箱自动关联(业务邮箱检测 → 域名匹配 → 自动建公司)
7. 在公司详情页可查看关联联系人列表、活动时间线
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/companies` — 获取公司列表(分页25条/页,支持排序/过滤)
- `GET /api/v1/accounts/{account_id}/companies/search?q=xxx` — 搜索公司(按名称/域名)
- `POST /api/v1/accounts/{account_id}/companies` — 创建公司
- 请求:`{ company: { name: "Acme Corp", domain: "acme.com", description: "...", additional_attributes: {}, custom_attributes: {} } }`
- 响应:公司对象 `{ id, name, domain, description, contacts_count, last_activity_at, additional_attributes, custom_attributes, account_id }`
- `GET /api/v1/accounts/{account_id}/companies/{id}` — 获取单个公司
- `PUT /api/v1/accounts/{account_id}/companies/{id}` — 更新公司(custom_attributes 为 merge 逻辑)
- `DELETE /api/v1/accounts/{account_id}/companies/{id}` — 删除公司(仅 administrator
- `DELETE /api/v1/accounts/{account_id}/companies/{id}/destroy_custom_attributes` — 删除指定自定义属性
- `POST /api/v1/accounts/{account_id}/companies/{id}/avatar` — 上传/更新头像
- **涉及的数据模型 + 关键字段**:
- `Company`(表 `companies`):
- `id` (bigint, PK)
- `name` (string, not null, 最大255字符) — 公司名称
- `domain` (string, 可选, 同账户下唯一) — 公司域名
- `description` (text, 最大限制) — 公司描述
- `contacts_count` (integer) — 关联联系人数(counter cache
- `last_activity_at` (datetime) — 最后活动时间
- `additional_attributes` (jsonb) — 附加属性
- `custom_attributes` (jsonb) — 自定义属性(merge 语义)
- `account_id` (bigint, not null, FK)
- 唯一索引:`(account_id, domain) WHERE domain IS NOT NULL`
- 关联:`Company has_many :contacts, dependent: :nullify``Company include Avatarable`
- **涉及的业务逻辑(service层)**:
- **Companies::BusinessEmailDetectorService** — 检测邮箱是否为业务邮箱(排除 disposable/free provider),用于自动关联判断
- **Companies::ContactMembershipService** — 公司-联系人关联管理:
- `assign(contact:)` — 将联系人归属公司,更新 company.last_activity_at
- `remove(contact:)` — 移除联系人归属
- **Contacts::CompanyAssociationService** — 自动根据邮箱域名关联公司:
- 检测是否为业务邮箱 → 提取域名 → find_or_create_by(domain) → 自动归属
- Company model
- `after_create_commit :fetch_favicon` — 域名存在时异步获取 favicon
- `record_activity_at!(timestamp)` — 滚动更新 last_activity_at5分钟间隔去重)
- `search_by_name_or_domain(query)` — 名称/域名搜索 scope
- **涉及的自动化/规则/事件**
- 创建公司时如有域名,自动获取 favicon
- 联系人创建/更新时,如邮箱为业务邮箱,自动关联对应公司
- last_activity_at 滚动更新(ACTIVITY_ROLLUP_INTERVAL = 5分钟)
- **涉及的权限/Policy**
- `CompanyPolicy`index/search/show/create/update → 所有账户用户;destroy → 仅 administrator
- **GoChat 实现要点**
- Company 是联系人分组维度,需与 Contact 模型建立 belongs_to 关系
- custom_attributes 需支持 merge 语义(更新时合并而非替换)
- domain 唯一约束(同账户下)需注意 NULL domain 不参与唯一检查
- 自动关联逻辑:邮箱域名 → 业务邮箱检测 → find_or_create Company → assign Contact
- contacts_count counter cache 需维护
- 需启用 `companies` feature flag
---
### 功能5:坐席容量策略(AgentCapacityPolicy + InboxCapacityLimit
- **功能描述**:企业版坐席容量策略,用于限制每个坐席在特定收件箱中的最大同时处理对话数。管理员创建容量策略,设定排除规则(如超过N小时的老对话不计入容量、特定标签对话不计入),并为策略关联收件箱及各自的对话上限。
- **用户操作流程(UI交互步骤)**:
1. 进入 Settings → Agent Capacity Policies 页面
2. 点击"Add Policy",填写策略名称、描述
3. 设定排除规则:exclude_older_than_hours(超时老对话排除)、excluded_labels(特定标签对话排除)
4. 为策略关联收件箱,并设定每个收件箱的 conversation_limit(对话上限)
5. 将策略分配给坐席(AccountUser.agent_capacity_policy_id
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/agent_capacity_policies` — 获取容量策略列表
- `POST /api/v1/accounts/{account_id}/agent_capacity_policies` — 创建容量策略
- 请求:`{ agent_capacity_policy: { name: "Standard Capacity", description: "...", exclusion_rules: { exclude_older_than_hours: 24, excluded_labels: ["spam"] } } }`
- 响应:策略对象 `{ id, name, description, exclusion_rules, account_id }`
- `GET /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 获取单个策略
- `PUT /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 更新策略
- `DELETE /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 删除策略
- **涉及的数据模型 + 关键字段**:
- `AgentCapacityPolicy`(表 `agent_capacity_policies`):
- `id` (bigint, PK)
- `name` (string, 最大255字符, not null) — 策略名称
- `description` (text) — 策略描述
- `exclusion_rules` (jsonb, not null) — 排除规则:
- `exclude_older_than_hours` — 超过N小时的活跃对话不计入容量
- `excluded_labels` — 带特定标签的对话不计入容量
- `account_id` (bigint, not null, FK)
- `InboxCapacityLimit`(表 `inbox_capacity_limits`):
- `id` (bigint, PK)
- `agent_capacity_policy_id` (bigint, not null, FK)
- `inbox_id` (bigint, not null, FK)
- `conversation_limit` (integer, not null, ≥ 0) — 该收件箱的对话上限
- 唯一索引:`(agent_capacity_policy_id, inbox_id)`
- 关联:
- `AgentCapacityPolicy has_many :inbox_capacity_limits, :inboxes (through), :account_users (dependent: :nullify)`
- `AccountUser` 增加 `agent_capacity_policy_id` 外键
- **涉及的业务逻辑(service层)**:
- 无独立 serviceCRUD 在 controller 完成
- 容量计算逻辑(在自动分配系统中使用):
- 遍历坐席的所有 inbox_capacity_limits
- 计算每个收件箱的活跃对话数(排除规则过滤)
- 判断是否达到上限
- **涉及的自动化/规则/事件**
- 删除策略时,关联的 AccountUser 的 agent_capacity_policy_id 被设为 NULL
- 容量策略在自动分配时作为坐席可分配判断依据
- **涉及的权限/Policy**
- `AgentCapacityPolicyPolicy`:所有操作仅 administrator 可执行
- **GoChat 实现要点**
- exclusion_rules 作为 jsonb 存储,灵活支持扩展
- InboxCapacityLimit 是策略和收件箱的中间表,需唯一约束
- 容量计算需排除规则过滤(超时对话 + 标签排除)
- 与自动分配系统(M5)深度集成
- AccountUser 需增加 agent_capacity_policy_id 字段
---
### 功能6:语音通话(Call + Voice Services
- **功能描述**:企业版语音通话功能,支持通过 Twilio 和 WhatsApp 两种通话提供商进行语音通话。支持呼入/呼出通话,基于 Twilio Conference 实现坐席接听/拒接/挂断,通话状态实时更新,支持录音和转录。呼出通话在对话中创建 voice_call 类型消息气泡。
- **用户操作流程(UI交互步骤)**:
1. 呼出通话:在联系人详情页点击"Call",选择语音收件箱 → 系统发起 Twilio/WhatsApp 通话
2. 呼入通话:客户拨打 Twilio 号码 → 系统创建 Call + 对话 → 坐席看到 ringing 状态
3. 坐席接听:点击接听按钮 → 通话状态变为 in_progress → 坐席被自动分配到对话
4. 通话结束:任一方挂断 → 状态变为 completed/no_answer/failed → 记录时长
5. 录音/转录:通话完成后可查看录音和转录文本
- **涉及的API端点 + 请求/响应格式**:
- `POST /api/v1/accounts/{account_id}/contacts/{contact_id}/calls` — 发起呼出通话
- 请求:`{ inbox_id: 1, conversation_id: 42 }`(可选关联现有对话)
- 响应:`{ conversation_id, inbox_id, call_sid, conference_sid }`
- `POST /api/v1/accounts/{account_id}/whatsapp_calls/initiate` — WhatsApp 呼出
- `GET /api/v1/accounts/{account_id}/whatsapp_calls/{id}` — 获取通话详情
- `POST /api/v1/accounts/{account_id}/whatsapp_calls/{id}/accept` — 接听
- `POST /api/v1/accounts/{account_id}/whatsapp_calls/{id}/reject` — 拒接
- `POST /api/v1/accounts/{account_id}/whatsapp_calls/{id}/terminate` — 挂断
- `POST /api/v1/accounts/{account_id}/whatsapp_calls/{id}/upload_recording` — 上传录音
- Twilio Webhook 端点:
- `POST /twilio/voice/{phone}/status` — 通话状态回调
- `POST /twilio/voice/{phone}/call_twiml` — TwiML 响应
- `POST /twilio/voice/{phone}/conference_status` — 会议状态回调
- `POST /twilio/voice/{phone}/recording_status` — 录音状态回调
- **涉及的数据模型 + 关键字段**:
- `Call`(表 `calls`):
- `id` (bigint, PK)
- `direction` (integer, enum) — incoming(0)/outgoing(1)
- `status` (string, default "ringing") — ringing/in_progress/completed/no_answer/failed
- `provider` (integer, enum) — twilio(0)/whatsapp(1)
- `duration_seconds` (integer) — 通话时长(秒)
- `end_reason` (string) — 结束原因
- `started_at` (datetime) — 开始时间
- `transcript` (text) — 转录文本
- `meta` (jsonb) — conference_sid, twilio_conference_sid, recording_sid, parent_call_sid, initiated_at, ended_at
- `provider_call_id` (string, not null) — 提供商侧通话 ID(唯一约束)
- `accepted_by_agent_id` (bigint) — 接听坐席 ID
- `account_id`, `contact_id`, `conversation_id`, `inbox_id`, `message_id` (bigint, FK)
- `recording` (ActiveStorage attachment) — 录音文件
- TERMINAL_STATUSES = completed/no_answer/failed
- 关联:`Call belongs_to :account, :inbox, :conversation, :contact, :message (optional), :accepted_by_agent (User, optional)`
- **涉及的业务逻辑(service层)**:
- **Voice::OutboundCallBuilder** — 呼出通话构建:
- 事务性创建 ContactInbox → Conversation → Call → Message(voice_call)
- 通过 `inbox.channel.initiate_call` 发起 Twilio 通话
- **Voice::InboundCallBuilder** — 呼入通话构建:
- 查找或创建 ContactInbox → Contact → Conversation → Call → Message
- 支持并发防重复(RecordNotUnique 处理)
- **Voice::StatusUpdateService** — 通话状态更新:
- Twilio 状态映射:queued/initiated/ringing → ringing; in-progress/answered → in_progress; completed → completed; busy/no-answer → no_answer; failed/canceled → failed
- **Voice::CallStatus::Manager** — 通话状态机管理:
- 处理状态更新,记录 started_at/duration_seconds
- 通话结束时更新 meta、时长
- 触发 message.touch 使前端实时更新
- **Voice::Conference::Manager** — 会议事件处理:
- conference-start → ringing; participant-join → in_progress + 自动分配坐席; participant-leave → 处理离开; conference-end → finalize
- 坐席接听时自动 claim_for_user + auto_assign_conversation
- **Voice::Provider::Twilio::ConferenceService** — Twilio 会议管理:
- ensure_conference_sid, mark_agent_joined, end_conference
- claim_call + assign_conversation(首次接听获胜)
- **Voice::Provider::Twilio::TokenService** — 生成 Twilio Access TokenJWT+ Voice Grant
- **Voice::CallMessageBuilder** — 创建/更新 voice_call 类型消息:
- content_type: 'voice_call', 包含 call_id/call_sid/call_source/call_direction/status
- **Voice::RecordingStatusService** — 处理 Twilio 录音回调,异步附件化录音文件
- **Whatsapp::CallService** — WhatsApp 通话操作(accept/reject/terminate
- **Whatsapp::IncomingCallService** — WhatsApp 呼入处理
- **Whatsapp::CallPermissionReplyService** — WhatsApp 通话权限回复
- **涉及的自动化/规则/事件**
- 通话创建时自动在对话中插入 voice_call 消息气泡
- 坐席接听时自动 claim_call + auto_assign(如对话无现有 assignee
- 通话结束时更新 duration_seconds、transcript
- 录音状态回调异步处理附件
- Twilio Conference 事件实时驱动状态更新
- **涉及的权限/Policy**
- 呼出通话:需 authorize contact (:show) + inbox (:show),且 inbox 需启用 voice
- WhatsApp 通话:需确保 calling_enabled + SDP offer + contact phone
- **GoChat 实现要点**
- 通话模型需支持多 providerTwilio/WhatsApp)和方向(呼入/呼出)
- 状态机:ringing → in_progress → completed/no_answer/failed
- Conference 模式:坐席和客户通过会议桥连接
- 录音附件化处理需异步
- 通话消息(voice_call content_type)与对话系统集成
- Twilio Access Token 生成(JWT + VoiceGrant
- 需 feature flag 控制 voice 通话功能启用
---
### 功能7SAML SSO 单点登录(AccountSamlSettings
- **功能描述**:企业版 SAML 2.0 单点登录,允许账户配置 SAML IdP 参数,用户通过企业身份提供商(如 Okta、Azure AD)登录 Chatwoot。支持角色映射(SAML 属性 → Chatwoot 角色),创建/更新 SAML 配置后自动将账户用户 provider 转为 saml。
- **用户操作流程(UI交互步骤)**:
1. 管理员进入 Settings → SAML SSO 页面(需启用 `saml` feature flag + 全局 ENABLE_SAML_SSO_LOGIN
2. 填写 IdP 参数:SSO URL、CertificateX509)、IdP Entity ID
3. SP Entity ID 自动生成(基于 FRONTEND_URL + account_id
4. 可配置 role_mappingsSAML 属性 → Chatwoot 角色)
5. 保存后,账户的登录页面出现 SAML 登录按钮
6. 用户点击 SAML 登录 → 重定向到 IdP → 认证成功后回调 Chatwoot
7. SamlUserBuilder 查找或创建用户,关联到账户
- **涉及的API端点 + 请求/响应格式**:
- `GET /api/v1/accounts/{account_id}/saml_settings` — 获取 SAML 配置
- `POST /api/v1/accounts/{account_id}/saml_settings` — 创建 SAML 配置
- 请求:`{ saml_settings: { sso_url: "https://idp.example.com/saml/sso", certificate: "X509 cert string", idp_entity_id: "idp_entity", role_mappings: { "admin" => "administrator" } } }`
- 响应:SAML 配置对象
- `PUT /api/v1/accounts/{account_id}/saml_settings` — 更新配置
- `DELETE /api/v1/accounts/{account_id}/saml_settings` — 删除配置(重置用户 provider 为 email
- **涉及的数据模型 + 关键字段**:
- `AccountSamlSettings`(表 `account_saml_settings`):
- `id` (bigint, PK)
- `sso_url` (string, not null) — IdP SSO URL
- `certificate` (text, not null) — X509 证书(需验证有效性)
- `idp_entity_id` (string, not null) — IdP Entity ID
- `sp_entity_id` (string) — SP Entity ID(自动生成)
- `role_mappings` (json) — SAML 角色映射配置
- `account_id` (bigint, not null, FK, 唯一)
- 关联:`AccountSamlSettings belongs_to :account`Account `has_one :saml_settings`
- **涉及的业务逻辑(service层)**:
- **SamlUserBuilder** — SAML 认证后用户构建:
- 查找已有用户(by email)→ 如用户属于账户则直接登录
- 不属于账户则抛出 AuthenticationFailed
- 新用户创建时设置 provider: 'saml', uid, 自动确认邮箱
- 角色映射:根据 SAML 属性 + role_mappings 配置分配 AccountUser 角色
- **Enterprise::DeviseOverrides::OmniauthCallbacksController** — SAML 认证回调处理:
- `redirect_callbacks` → SAML provider 时走 `omniauth_success`
- `handle_saml_auth` → 检查 SAML 是否启用 → SamlUserBuilder → 登录/错误处理
- 支持 mobile 和 web 两种 RelayState 处理
- AccountSamlSettings
- `certificate_must_be_valid_x509` — 验证证书格式
- `certificate_fingerprint` — 计算证书指纹
- `set_sp_entity_id` — 自动生成 SP Entity IDFRONTEND_URL/saml/sp/{account_id}
- `after_create_commit :update_account_users_provider` — 创建后将用户 provider 转为 saml
- `after_destroy_commit :reset_account_users_provider` — 删除后重置为 email
- **涉及的自动化/规则/事件**
- SAML 配置创建后,账户用户 provider 自动转为 'saml'
- SAML 配置删除后,用户 provider 重置为 'email'
- SP Entity ID 自动生成
- 证书有效性验证
- **涉及的权限/Policy**
- `AccountSamlSettingsPolicy`:所有操作仅 administrator 可执行
- 需启用 `saml` feature flag + 全局 ENABLE_SAML_SSO_LOGIN
- **GoChat 实现要点**
- 需集成 SAML 2.0 协议(可参考 ruby-saml 或类似库)
- X509 证书验证和指纹计算
- SP Entity ID 自动生成逻辑
- 用户查找/创建/角色映射需事务性处理
- Omniauth 回调流程适配
- 账户级 SAML 配置(每个账户独立 IdP)
- role_mappings 灵活映射 SAML 属性到 Chatwoot 角色
---
### 功能8SidekiqCron 定时任务调度
- **功能描述**Chatwoot 使用 sidekiq-cron gem 管理 Sidekiq 定时任务。通过 `config/schedule.yml` 定义任务计划,在服务启动时自动加载并持久化到 Redis。定时任务涵盖系统巡检、SLA 评估触发、IMAP 邮件收取、数据清理、报表生成等。
- **用户操作流程(UI交互步骤)**:
- 此功能为后台运维功能,无用户 UI 交互
- 管理员可在 Sidekiq Web UI/sidekiq/cron)查看和管理定时任务
- **涉及的数据模型 + 关键字段**:
- 无独立数据模型,任务定义存储在 `config/schedule.yml` 和 Redis 中
- **核心定时任务列表**
- `trigger_scheduled_items_job`*/5 * * * *(每5分钟)— 触发 SLA 评估等调度任务
- `trigger_hourly_scheduled_items_job` — 0 * * * *(每小时)— 低频定时任务触发
- `trigger_imap_email_inboxes_job`*/1 * * * *(每分钟)— IMAP 邮件收取
- `internal_check_new_versions_job` — 0 0 * * *(每日 00:00 UTC)— 版本检查 + 日级调度
- `remove_stale_contact_inboxes_job` — 30 22 * * *(每日 22:30 UTC)— 清理过期 ContactInbox
- `remove_stale_redis_keys_job` — 30 22 * * *(每日 22:30 UTC)— 清理过期 Redis Key
- SLA 相关:通过 `TriggerScheduledItemsJob``Sla::TriggerSlasForAccountsJob` 链路触发
- **涉及的业务逻辑**
- `config/initializers/sidekiq.rb` — Sidekiq 服务启动时:
- 加载 `config/schedule.yml`
- `Sidekiq::Cron::Job.load_from_hash!` — upsert 任务并清理已删除条目
- 显式清理 legacy 动态任务(如 bulk_auto_assignment_job
- `TriggerScheduledItemsJob` — 核心调度入口,每5分钟执行,触发 SLA 评估等子任务
- `Internal::TriggerDailyScheduledItemsJob` — 日级调度
- `Internal::TriggerHourlyScheduledItemsJob` — 小时级调度
- **涉及的自动化/规则/事件**
- 服务启动时自动加载 schedule.yml 到 Redis
- 部署时自动清理已删除的 schedule 条目
- 任务执行日志(Sidekiq logger
- **GoChat 实现要点**
- 如使用 Go 实现,需设计定时任务调度框架(可参考 cron 表达式 + 分布式锁)
- SLA 评估链路:定时触发 → 遍历账户 → 遍历 AppliedSla → EvaluateAppliedSlaService
- 任务需要幂等性(防止重复执行)
- 需考虑分布式环境下的任务竞争(Redis 锁 / DB 锁)
- 任务配置建议外部化(YAML / DB 配置),支持动态调整
- 清理类任务(stale data)需在低峰时段执行
---
## 模块间依赖关系
| 依赖模块 | 依赖说明 |
|---------|---------|
| M1-Account | SLA/CustomRole/Company/AgentCapacityPolicy/SAML 均属于 Account 级别;AuditLog 的 associated 关联 Account |
| M3-Conversation | SLA AppliedSla/SlaEvent 关联 ConversationCall 关联 Conversation |
| M4-Contact | Company 关联 Contactbelongs_to);Call 关联 ContactCompanyAssociationService 自动关联 |
| M5-Team/Assignment | AgentCapacityPolicy 与自动分配系统集成;Call 接听时 auto_assign_conversation |
| M2-Inbox | SlaPolicy 绑定到 InboxInboxCapacityLimit 关联 InboxCall 关联 InboxVoice 需 Inbox 启用 voice |
| M6-Automation | Audit 审计 AutomationRule 变更 |
| M8-Notification | SlaEvent 创建后发送通知 |
| BusinessHours | SLA only_during_business_hours 依赖工作时间计算 |
---
## Feature Flags 依赖
| Feature Flag | 所属功能 | 说明 |
|-------------|---------|-----|
| `audit_logs` | AuditLog | 控制审计日志功能启用 |
| `companies` | Company | 控制组织管理功能启用 |
| `saml` | SAML SSO | 控制 SAML 单点登录启用 |
| `voice`(隐含) | Call/Voice | 控制语音通话功能启用 |
| `sla`(隐含) | SLA | 控制 SLA 功能启用 |
---
## 企业版功能全景总结
Chatwoot 企业版功能通过 Feature Flags 逐项控制启用,每个功能模块独立可配。核心设计模式:
1. **Concern 扩展**Enterprise::Concerns::Account 通过 `included` 块注入 `has_many :sla_policies, :custom_roles, :agent_capacity_policies, :companies, :calls, has_one :saml_settings` 等关联
2. **Policy 红线**:所有企业版功能的写操作(create/update/destroy)均需 administrator 权限
3. **审计追踪**Enterprise::AuditLog 继承 Audited::Audit,全局配置审计类,核心模型通过 concern 声明审计范围
4. **Feature Flag 门控**:每个企业版功能都有对应的 feature flagcontroller 层 before_action 检查
5. **定时调度**:SLA 评估等周期性任务通过 SidekiqCron + TriggerScheduledItemsJob 链路触发
6. **异步处理**SLA 评估、录音附件、favicon 获取等均通过 Sidekiq Job 异步执行