35 KiB
35 KiB
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交互步骤):
- 进入 Settings → SLA 页面
- 点击"Add SLA Policy",填写名称、描述
- 设定三个时间阈值:first_response_time_threshold(首响)、next_response_time_threshold(下次响应)、resolution_time_threshold(解决),单位为秒
- 可勾选"only_during_business_hours",仅在工作时间内计算 SLA
- 保存后,可在 Inbox 设置中将 SLA 策略绑定到收件箱,新进入的对话自动应用该 SLA
- 对话列表中可查看 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 策略的迁移任务
- Sla::EvaluateAppliedSlaService — 核心 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)
- 功能描述:企业版审计日志,基于
auditedgem 记录账户内关键资源的变更操作(创建/更新/删除),包括变更内容、操作人、IP 地址等。管理员可在审计日志页面按时间倒序查看所有变更记录,用于合规审计和安全追溯。 - 用户操作流程(UI交互步骤):
- 账户需启用
audit_logsfeature flag - 进入账户设置 → Audit Logs 页面
- 可按时间倒序浏览所有变更记录(每页15条)
- 每条记录显示:操作类型(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/destroyauditable_type(string) — 被审计对象类型(Account/Inbox/User/AccountUser 等)auditable_id(bigint) — 被审计对象 IDaudited_changes(jsonb) — 变更内容associated_type(string) — 关联对象类型(通常是 Account)associated_id(bigint) — 关联对象 IDuser_id(bigint) — 操作人 IDusername(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层):
- 通过
auditedgem 的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: :accountEnterprise::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_logsfeature flag
- GoChat 实现要点:
- 审计日志需基于变更追踪机制(类似 audited gem),在核心模型上声明审计字段
- 需记录操作人、IP、变更内容、时间戳
- 分页查询接口,按时间倒序排列
- associated_type/associated_id 用于按账户维度聚合审计记录
- feature flag 控制审计功能的启用
功能3:自定义角色权限系统(CustomRole)
- 功能描述:企业版自定义角色系统,允许管理员在账户级创建自定义角色,定义细粒度权限列表,并将角色分配给 AccountUser。自定义角色扩展了默认的三级角色体系(administrator/agent/supervisor),使权限控制更灵活。
- 用户操作流程(UI交互步骤):
- 进入 Settings → Custom Roles 页面
- 点击"Add Role",填写角色名称、描述
- 从权限列表勾选所需权限
- 创建后可在 Agent Management 中将此角色分配给坐席
- 坐席权限将基于其 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交互步骤):
- 进入 Contacts → Companies 页面(需启用
companiesfeature flag) - 点击"Add Company",填写名称、域名、描述
- 可上传公司头像
- 如填入域名,系统自动获取 favicon
- 创建后可将联系人手动归属到该公司
- 系统也可根据联系人邮箱自动关联(业务邮箱检测 → 域名匹配 → 自动建公司)
- 在公司详情页可查看关联联系人列表、活动时间线
- 进入 Contacts → Companies 页面(需启用
- 涉及的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_atremove(contact:)— 移除联系人归属
- Contacts::CompanyAssociationService — 自动根据邮箱域名关联公司:
- 检测是否为业务邮箱 → 提取域名 → find_or_create_by(domain) → 自动归属
- Company model:
after_create_commit :fetch_favicon— 域名存在时异步获取 faviconrecord_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 需维护
- 需启用
companiesfeature flag
功能5:坐席容量策略(AgentCapacityPolicy + InboxCapacityLimit)
- 功能描述:企业版坐席容量策略,用于限制每个坐席在特定收件箱中的最大同时处理对话数。管理员创建容量策略,设定排除规则(如超过N小时的老对话不计入容量、特定标签对话不计入),并为策略关联收件箱及各自的对话上限。
- 用户操作流程(UI交互步骤):
- 进入 Settings → Agent Capacity Policies 页面
- 点击"Add Policy",填写策略名称、描述
- 设定排除规则:exclude_older_than_hours(超时老对话排除)、excluded_labels(特定标签对话排除)
- 为策略关联收件箱,并设定每个收件箱的 conversation_limit(对话上限)
- 将策略分配给坐席(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交互步骤):
- 呼出通话:在联系人详情页点击"Call",选择语音收件箱 → 系统发起 Twilio/WhatsApp 通话
- 呼入通话:客户拨打 Twilio 号码 → 系统创建 Call + 对话 → 坐席看到 ringing 状态
- 坐席接听:点击接听按钮 → 通话状态变为 in_progress → 坐席被自动分配到对话
- 通话结束:任一方挂断 → 状态变为 completed/no_answer/failed → 记录时长
- 录音/转录:通话完成后可查看录音和转录文本
- 涉及的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/failedprovider(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_atprovider_call_id(string, not null) — 提供商侧通话 ID(唯一约束)accepted_by_agent_id(bigint) — 接听坐席 IDaccount_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::OutboundCallBuilder — 呼出通话构建:
- 涉及的自动化/规则/事件:
- 通话创建时自动在对话中插入 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交互步骤):
- 管理员进入 Settings → SAML SSO 页面(需启用
samlfeature flag + 全局 ENABLE_SAML_SSO_LOGIN) - 填写 IdP 参数:SSO URL、Certificate(X509)、IdP Entity ID
- SP Entity ID 自动生成(基于 FRONTEND_URL + account_id)
- 可配置 role_mappings(SAML 属性 → Chatwoot 角色)
- 保存后,账户的登录页面出现 SAML 登录按钮
- 用户点击 SAML 登录 → 重定向到 IdP → 认证成功后回调 Chatwoot
- SamlUserBuilder 查找或创建用户,关联到账户
- 管理员进入 Settings → SAML SSO 页面(需启用
- 涉及的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 URLcertificate(text, not null) — X509 证书(需验证有效性)idp_entity_id(string, not null) — IdP Entity IDsp_entity_id(string) — SP Entity ID(自动生成)role_mappings(json) — SAML 角色映射配置account_id(bigint, not null, FK, 唯一)
- 关联:
AccountSamlSettings belongs_to :account;Accounthas_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_successhandle_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 转为 samlafter_destroy_commit :reset_account_users_provider— 删除后重置为 email
- SamlUserBuilder — SAML 认证后用户构建:
- 涉及的自动化/规则/事件:
- SAML 配置创建后,账户用户 provider 自动转为 'saml'
- SAML 配置删除后,用户 provider 重置为 'email'
- SP Entity ID 自动生成
- 证书有效性验证
- 涉及的权限/Policy:
AccountSamlSettingsPolicy:所有操作仅 administrator 可执行- 需启用
samlfeature 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)— 清理过期 ContactInboxremove_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 逐项控制启用,每个功能模块独立可配。核心设计模式:
- Concern 扩展:Enterprise::Concerns::Account 通过
included块注入has_many :sla_policies, :custom_roles, :agent_capacity_policies, :companies, :calls, has_one :saml_settings等关联 - Policy 红线:所有企业版功能的写操作(create/update/destroy)均需 administrator 权限
- 审计追踪:Enterprise::AuditLog 继承 Audited::Audit,全局配置审计类,核心模型通过 concern 声明审计范围
- Feature Flag 门控:每个企业版功能都有对应的 feature flag,controller 层 before_action 检查
- 定时调度:SLA 评估等周期性任务通过 SidekiqCron + TriggerScheduledItemsJob 链路触发
- 异步处理:SLA 评估、录音附件、favicon 获取等均通过 Sidekiq Job 异步执行