520 lines
35 KiB
Markdown
520 lines
35 KiB
Markdown
# M11: 企业版功能(Enterprise Features)
|
||
|
||
## 模块概述
|
||
|
||
本模块覆盖 Chatwoot 企业版专属功能,包括:SLA 策略与应用(SlaPolicy + AppliedSla + SlaEvent)、审计日志(AuditLog)、自定义角色权限系统(CustomRole)、组织/公司管理(Company)、坐席容量策略(AgentCapacityPolicy + InboxCapacityLimit)、语音通话(Call + Voice 系列 Service)、SAML SSO 单点登录(AccountSamlSettings)、SidekiqCron 定时任务调度。这些功能在社区版基础上增强了合规、安全、权限精细化、组织管理和语音通信能力。
|
||
|
||
---
|
||
|
||
### 功能1:SLA 策略管理(SlaPolicy CRUD + 应用到对话)
|
||
|
||
- **功能描述**:账户级 SLA(Service 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 策略,自动创建 AppliedSla(conversation callback)
|
||
- SlaEvent 创建后自动发送通知(`after_create_commit :create_notifications`)
|
||
- 对话 resolve 后,EvaluateAppliedSlaService 判断 SLA 是否最终达标
|
||
- **涉及的权限/Policy**:
|
||
- `SlaPolicyPolicy`:index/show → administrator || agent;create/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)、IP(remote_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层)**:
|
||
- 无独立 service,CRUD 在 controller 完成
|
||
- 权限合并逻辑在 `Enterprise::AccountUser#permissions` 中
|
||
- 删除 CustomRole 时,关联的 AccountUser 的 custom_role_id 设为 NULL(nullify),回归默认角色权限
|
||
- **涉及的自动化/规则/事件**:
|
||
- 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_at(5分钟间隔去重)
|
||
- `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层)**:
|
||
- 无独立 service,CRUD 在 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 Token(JWT)+ 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 实现要点**:
|
||
- 通话模型需支持多 provider(Twilio/WhatsApp)和方向(呼入/呼出)
|
||
- 状态机:ringing → in_progress → completed/no_answer/failed
|
||
- Conference 模式:坐席和客户通过会议桥连接
|
||
- 录音附件化处理需异步
|
||
- 通话消息(voice_call content_type)与对话系统集成
|
||
- Twilio Access Token 生成(JWT + VoiceGrant)
|
||
- 需 feature flag 控制 voice 通话功能启用
|
||
|
||
---
|
||
|
||
### 功能7:SAML 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、Certificate(X509)、IdP Entity ID
|
||
3. SP Entity ID 自动生成(基于 FRONTEND_URL + account_id)
|
||
4. 可配置 role_mappings(SAML 属性 → 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 ID(FRONTEND_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 角色
|
||
|
||
---
|
||
|
||
### 功能8:SidekiqCron 定时任务调度
|
||
|
||
- **功能描述**: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 关联 Conversation;Call 关联 Conversation |
|
||
| M4-Contact | Company 关联 Contact(belongs_to);Call 关联 Contact;CompanyAssociationService 自动关联 |
|
||
| M5-Team/Assignment | AgentCapacityPolicy 与自动分配系统集成;Call 接听时 auto_assign_conversation |
|
||
| M2-Inbox | SlaPolicy 绑定到 Inbox;InboxCapacityLimit 关联 Inbox;Call 关联 Inbox;Voice 需 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 flag,controller 层 before_action 检查
|
||
5. **定时调度**:SLA 评估等周期性任务通过 SidekiqCron + TriggerScheduledItemsJob 链路触发
|
||
6. **异步处理**:SLA 评估、录音附件、favicon 获取等均通过 Sidekiq Job 异步执行 |