# M12 平台与集成 — Chatwoot 功能梳理文档 > 基于 Chatwoot 源码深度阅读 + CodeGraph 上下文梳理 > 生成日期:2026-05-22 > 参照仓库:chatwoot-reference > 模块覆盖:PlatformApp · AgentBot · DashboardApp · DataImport · Webhook · AccessToken认证 · InstallationConfig --- ## 目录 1. [PlatformApp(平台应用 / Partner API)](#1-platformapp平台应用--partner-api) 2. [PlatformAppPermissible(权限授权)](#2-platformapppermissible权限授权) 3. [Platform API 路由体系](#3-platform-api-路由体系) 4. [AccessToken 与 AccessTokenable(API令牌认证)](#4-accesstoken-与-accesstokenableapi令牌认证) 5. [AccessTokenAuthHelper(令牌鉴权中间件)](#5-accesstokenauthhelper令牌鉴权中间件) 6. [AgentBot(智能机器人)](#6-agentbot智能机器人) 7. [AgentBotInbox(Bot-Inbox绑定)](#7-agentbotinboxbot-inbox绑定) 8. [AgentBotListener(Bot事件监听)](#8-agentbotlistenerbot事件监听) 9. [AgentBots::WebhookJob(Bot Webhook投递)](#9-agentbotswebhookjobbot-webhook投递) 10. [DashboardApp(仪表盘扩展应用)](#10-dashboardapp仪表盘扩展应用) 11. [DataImport(数据导入)](#11-dataimport数据导入) 12. [DataImportJob + ContactManager(导入处理流程)](#12-dataimportjob--contactmanager导入处理流程) 13. [Webhook(账户级Webhook)](#13-webhook账户级webhook) 14. [WebhookSecretable(签名密钥机制)](#14-webhooksecretable签名密钥机制) 15. [Webhooks::Trigger(Webhook投递引擎)](#15-webhookstriggerwebhook投递引擎) 16. [InstallationConfig(平台级配置)](#16-installationconfig平台级配置) 17. [GlobalConfig + GlobalConfigService(配置缓存层)](#17-globalconfig--globalconfigservice配置缓存层) --- ## 1. PlatformApp(平台应用 / Partner API) ### 功能描述 PlatformApp 是 Chatwoot 为第三方合作伙伴(Partner)提供的平台级 API 认证主体。每个 PlatformApp 拥有一个 AccessToken,通过该令牌可调用 Platform API(独立于普通 Account API),实现对用户、账户、AgentBot、AccountUser 等资源的 CRUD 管理。PlatformApp 与被操作资源之间通过 `PlatformAppPermissible` 多态关联授权——只有被授权的资源才能被该 PlatformApp 访问/修改。 ### 用户操作流程 1. Super Admin 在后台创建 PlatformApp,系统自动生成 AccessToken 2. 第三方系统持有该 AccessToken,通过 `api_access_token` Header 调用 Platform API 3. PlatformController 验证令牌 → 解析出 PlatformApp → 校验 Permissible 授权范围 4. 通过授权校验后,第三方可操作被授权的 Account / User / AgentBot 等资源 5. 第三方可通过 `POST /platform/api/v1/users/:id/token` 获取用户的 SSO 登录令牌 ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | Super Admin CRUD | `/super_admin/platform_apps` | 管理后台管理 PlatformApp | | GET | `/platform/api/v1/users` | 列出授权用户(需 Permissible) | | POST | `/platform/api/v1/users` | 创建用户并自动授权 | | GET | `/platform/api/v1/users/:id` | 查看(需 Permissible) | | PATCH | `/platform/api/v1/users/:id` | 更新(需 Permissible) | | DELETE | `/platform/api/v1/users/:id` | 删除(需 Permissible) | | GET | `/platform/api/v1/users/:id/login` | 获取用户 SSO 登录 URL | | POST | `/platform/api/v1/users/:id/token` | 获取用户 API Token | | GET | `/platform/api/v1/accounts` | 列出授权账户 | | POST | `/platform/api/v1/accounts` | 创建账户并授权 | | GET | `/platform/api/v1/accounts/:id` | 查看授权账户 | | PATCH | `/platform/api/v1/accounts/:id` | 更新授权账户(含 features/limits) | | DELETE | `/platform/api/v1/accounts/:id` | 删除授权账户 | | GET | `/platform/api/v1/accounts/:account_id/account_users` | 列出账户成员 | | POST | `/platform/api/v1/accounts/:account_id/account_users` | 添加账户成员 | | DELETE | `/platform/api/v1/accounts/:account_id/account_users` | 移除账户成员 | | GET | `/platform/api/v1/agent_bots` | 列出授权 AgentBot | | POST | `/platform/api/v1/agent_bots` | 创建 AgentBot 并授权 | | GET | `/platform/api/v1/agent_bots/:id` | 查看授权 AgentBot | | PATCH | `/platform/api/v1/agent_bots/:id` | 更新授权 AgentBot | | DELETE | `/platform/api/v1/agent_bots/:id` | 删除授权 AgentBot | | DELETE | `/platform/api/v1/agent_bots/:id/avatar` | 删除 AgentBot 头像 | | POST | `/platform/api/v1/accounts/:account_id/email_channel_migrations` | 执行邮箱渠道迁移 | ### 涉及的数据模型+关键字段 - **PlatformApp**(`platform_apps` 表): - `id` (bigint, PK) - `name` (string, NOT NULL) — 应用名称 - `created_at`, `updated_at` (datetime) - 关联:`has_many :platform_app_permissibles, dependent: :destroy_async` - Concern:`include AccessTokenable`(创建时自动生成 AccessToken) - 验证:`validates :name, presence: true` ### 涉及的业务逻辑 - **PlatformApp 创建时**:`AccessTokenable` concern 的 `after_create :create_access_token` 自动创建 AccessToken - **PlatformController**(`app/controllers/platform_controller.rb`):全局鉴权基类 - `ensure_access_token` — 从 Header 中解析 `api_access_token` - `set_platform_app` — 从 AccessToken owner 解析出 PlatformApp,否则返回 401 - `validate_platform_app_permissible` — 校验当前资源是否在 Permissible 授权范围内 - **各子 Controller**:继承 PlatformController,在 create 操作时自动创建 Permissible 授权关系 --- ## 2. PlatformAppPermissible(权限授权) ### 功能描述 PlatformAppPermissible 是 PlatformApp 与被授权资源之间的多态授权桥梁。每个 Permissible 记录代表"某 PlatformApp 可以操作某资源"。资源类型可以是 Account、User、AgentBot 等。创建用户/账户/AgentBot 时,子控制器会自动为 PlatformApp 添加 Permissible 记录。 ### 涉及的数据模型+关键字段 - **PlatformAppPermissible**(`platform_app_permissibles` 表): - `id` (bigint, PK) - `platform_app_id` (bigint, NOT NULL) — 所属 PlatformApp - `permissible_id` (bigint, NOT NULL) — 被授权资源 ID - `permissible_type` (string, NOT NULL) — 被授权资源类型(Account / User / AgentBot 等) - `created_at`, `updated_at` (datetime) - 索引: - `index_platform_app_permissibles_on_permissibles` — (permissible_type, permissible_id) - `index_platform_app_permissibles_on_platform_app_id` — (platform_app_id) - `unique_permissibles_index` — (platform_app_id, permissible_id, permissible_type) UNIQUE - 关联:`belongs_to :platform_app`, `belongs_to :permissible, polymorphic: true` - 验证:`validates :platform_app, presence: true`; uniqueness scoped by `[permissible_id, permissible_type]` ### 涉及的业务逻辑 - **授权校验**:`PlatformController#validate_platform_app_permissible` 查询 `platform_app_permissibles.find_by(permissible: @resource)`,不在授权范围内返回 401 - **自动授权**: - `UsersController#create` → `find_or_create_by(permissible: @resource)` - `AccountsController#create` → `find_or_create_by(permissible: @resource)` - `AgentBotsController#create` → `find_or_create_by(permissible: @resource)` - **查询授权资源**: - `AccountsController#index` → 查询 `where(permissible_type: 'Account')` 并 includes permissible - `AgentBotsController#index` → 查询 `where(permissible_type: 'AgentBot')` --- ## 3. Platform API 路由体系 ### 功能描述 Platform API 是 Chatwoot 提供给合作伙伴的独立 API 套件,使用 AccessToken(而非 User session)鉴权,路由独立于 Account API 体系。所有 Platform API 控制器继承 `PlatformController`,统一走 AccessToken + Permissible 双重校验。 ### 路由结构 ``` namespace :platform, defaults: { format: 'json' } do namespace :api do namespace :v1 do resources :users, only: [:create, :show, :update, :destroy] member do get :login # SSO 登录链接 post :token # 获取用户 API token end resources :agent_bots, only: [:index, :create, :show, :update, :destroy] member do delete :avatar # 删除头像 end resources :accounts, only: [:index, :create, :show, :update, :destroy] resources :account_users, only: [:index, :create] collection do delete :destroy # 移除成员 end resources :email_channel_migrations, only: [:create] end end end ``` ### 控制器矩阵 | Controller | 基类 | 特殊 before_action | |------|------|------| | `Platform::Api::V1::UsersController` | PlatformController | `set_resource` + `validate_platform_app_permissible`(含 login/token) | | `Platform::Api::V1::AccountsController` | PlatformController | 标准 set_resource + validate | | `Platform::Api::V1::AgentBotsController` | PlatformController | 标准 set_resource + validate | | `Platform::Api::V1::AccountUsersController` | PlatformController | 手动 set_resource + validate | | `Platform::Api::V1::EmailChannelMigrationsController` | PlatformController | validate_account_permissible + validate_feature_flag + validate_params | --- ## 4. AccessToken 与 AccessTokenable(API令牌认证) ### 功能描述 AccessToken 是 Chatwoot 中多态令牌模型,服务于两类场景:① PlatformApp 的 Partner API 鉴权;② AgentBot 的 Bot API 鉴权。AccessToken 通过 `AccessTokenable` concern 自动创建——任何包含此 concern 的模型在创建后自动生成一条 AccessToken 记录。Token 通过 `has_secure_token` 生成,唯一且不可预测。 ### 涉及的数据模型+关键字段 - **AccessToken**(`access_tokens` 表): - `id` (bigint, PK) - `owner_type` (string) — 令牌所属者类型(PlatformApp / AgentBot) - `owner_id` (bigint) — 令牌所属者 ID - `token` (string, UNIQUE) — 令牌值(`has_secure_token` 自动生成) - `created_at`, `updated_at` (datetime) - 索引:`index_access_tokens_on_owner_type_and_owner_id` — (owner_type, owner_id);`index_access_tokens_on_token` — UNIQUE - 关联:`belongs_to :owner, polymorphic: true` ### AccessTokenable Concern ```ruby module AccessTokenable extend ActiveSupport::Concern included do has_one :access_token, as: :owner, dependent: :destroy_async after_create :create_access_token end def create_access_token AccessToken.create!(owner: self) end end ``` - 自动在模型创建后生成一条 AccessToken - `dependent: :destroy_async` — 父模型删除时异步删除令牌 - 包含此 concern 的模型:**PlatformApp**、**AgentBot** ### Token 再生成 - `AgentBotsController#reset_access_token` — 调用 `@agent_bot.access_token.regenerate_token` - `has_secure_token` 提供了 `regenerate_token` 方法 --- ## 5. AccessTokenAuthHelper(令牌鉴权中间件) ### 功能描述 AccessTokenAuthHelper 是用于 Account API(非 Platform API)场景的 AccessToken 鉴权 concern。它允许 AgentBot 通过 AccessToken 代替用户 session 调用特定 Account API 端点。但 Bot 仅能访问有限的端点集合(BOT_ACCESSIBLE_ENDPOINTS),防止越权。 ### 核心逻辑 - **ensure_access_token** — 从 Header 解析 `api_access_token`,查找对应 AccessToken 记录 - **authenticate_access_token!** — 验证令牌存在,设置 `@resource = @access_token.owner`,如 owner 是 User 则设置 `Current.user` - **validate_bot_access_token!** — 如果 owner 是 AgentBot,则检查当前端点是否在 `BOT_ACCESSIBLE_ENDPOINTS` 白名单内 - **BOT_ACCESSIBLE_ENDPOINTS 白名单**: - `api/v1/accounts/conversations` → `toggle_status, toggle_typing_status, toggle_priority, create, update, custom_attributes` - `api/v1/accounts/conversations/messages` → `create` - `api/v1/accounts/conversations/assignments` → `create` - **allowed_current_user_type?** — 仅 User 和 AgentBot 可设置为 Current.user --- ## 6. AgentBot(智能机器人) ### 功能描述 AgentBot 是 Chatwoot 的智能机器人模型,支持 Webhook 类型(将对话事件推送到外部 URL)。AgentBot 可绑定到 Inbox(通过 AgentBotInbox),也可被直接分配为会话的 assignee(`assignee_agent_bot_id`)。Bot 创建时自动获得 AccessToken 和 Secret(用于 Webhook 签名),支持头像管理。Bot 分为账户级(`account_id` 有值)和全局级(`account_id = nil`)。 ### 用户操作流程 1. 管理员在 Agent Bot 设置页创建 AgentBot,填写名称、描述、outgoing_url 2. 系统自动生成 AccessToken + Secret 3. 管理员将 Bot 绑定到 Inbox(AgentBotInbox),Bot 开始监听该 Inbox 的对话事件 4. 事件触发 → AgentBotListener 收集该 Inbox 的 Bot → 通过 AgentBots::WebhookJob 将 payload 推送到 outgoing_url 5. 外部 Bot 服务处理后,通过 AccessToken 鉴权调用 Account API 回写消息/更新会话状态 6. 管理员也可将 Bot 直接设置为会话的 assignee ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/v1/accounts/:account_id/agent_bots` | 列出 accessible Bot(含全局 + 本账户) | | POST | `/api/v1/accounts/:account_id/agent_bots` | 创建账户级 Bot | | GET | `/api/v1/accounts/:account_id/agent_bots/:id` | 查看某 Bot | | PATCH | `/api/v1/accounts/:account_id/agent_bots/:id` | 更新 Bot | | DELETE | `/api/v1/accounts/:account_id/agent_bots/:id` | 删除 Bot | | DELETE | `/api/v1/accounts/:account_id/agent_bots/:id/avatar` | 删除头像 | | POST | `/api/v1/accounts/:account_id/agent_bots/:id/reset_access_token` | 重置 AccessToken | | POST | `/api/v1/accounts/:account_id/agent_bots/:id/reset_secret` | 重置 Webhook Secret | ### 涉及的数据模型+关键字段 - **AgentBot**(`agent_bots` 表): - `id` (bigint, PK) - `name` (string) — Bot 名称 - `description` (string) — Bot 描述 - `outgoing_url` (string) — Webhook 推送 URL - `bot_type` (integer, default: webhook) — Bot 类型枚举(目前仅 webhook: 0) - `bot_config` (jsonb) — Bot 自定义配置 - `secret` (string) — Webhook 签名密钥(`has_secure_token` + 可选加密) - `account_id` (bigint, nullable) — 所属账户(nil = 全局 Bot) - 关联: - `has_many :agent_bot_inboxes, dependent: :destroy_async` - `has_many :inboxes, through: :agent_bot_inboxes` - `has_many :messages, as: :sender, dependent: :nullify` - `has_many :platform_app_permissibles, as: :permissible, dependent: :destroy` - `has_many :assigned_conversations`(foreign_key: `assignee_agent_bot_id`) - `belongs_to :account, optional: true` - Concern: - `AccessTokenable` — 自动创建 AccessToken - `Avatarable` — 支持头像 - `WebhookSecretable` — 自动生成 Secret(可加密) - Scope:`accessible_to(account)` → `where(account_id: [nil, account.id])` - 方法: - `push_event_data(inbox)` → `{ id, name, avatar_url, type: 'agent_bot' }` ### 涉及的业务逻辑 - **AgentBotPolicy** — index/show 需 administrator 或 agent;create/update/destroy/reset_access_token/reset_secret/avatar 仅 administrator - **AgentBotPresenter** — `access_token` 方法:仅当 `Current.account.id == account_id` 时返回 token(跨账户不暴露令牌) - **AvatarFromUrlJob** — 支持 `avatar_url` 参数远程拉取头像 --- ## 7. AgentBotInbox(Bot-Inbox绑定) ### 功能描述 AgentBotInbox 是 AgentBot 与 Inbox 之间的绑定关系模型。每个绑定记录表示"某 Bot 监听某 Inbox"。支持 active/inactive 状态控制,允许管理员在不删除绑定的情况下暂停 Bot 对某 Inbox 的监听。`account_id` 由 Inbox 自动继承。 ### 涉及的数据模型+关键字段 - **AgentBotInbox**(`agent_bot_inboxes` 表): - `id` (bigint, PK) - `agent_bot_id` (integer, NOT NULL) - `inbox_id` (integer, NOT NULL) - `account_id` (integer) — 从 inbox.account_id 自动填充 - `status` (integer, default: active) — 状态枚举:active(0), inactive(1) - 关联:`belongs_to :inbox`, `belongs_to :agent_bot`, `belongs_to :account` - `before_validation :ensure_account_id` — 自动从 inbox 继承 account_id --- ## 8. AgentBotListener(Bot事件监听) ### 功能描述 AgentBotListener 是事件驱动的 Bot Webhook 推送监听器,继承 BaseListener。监听对话和消息相关事件,将 payload 推送给与 Inbox 绑定的 Bot。Bot 来源分两种:① Inbox 绑定的活跃 Bot(`AgentBotInbox.active`);② 会话的 assignee Bot(`conversation.assignee_agent_bot`)。 ### 监听的事件 | 事件 | 推送内容 | |------|------| | `conversation_resolved` | `conversation.webhook_data.merge(event: 'conversation_resolved')` | | `conversation_opened` | `conversation.webhook_data.merge(event: 'conversation_opened')` | | `conversation_status_changed` | webhook_data + changed_attributes | | `conversation_updated` | webhook_data + changed_attributes | | `message_created` | `message.webhook_data.merge(event: 'message_created')` | | `message_updated` | `message.webhook_data.merge(event: 'message_updated')` | | `webwidget_triggered` | inbox 数据 + contact 信息 | ### 涉及的业务逻辑 - **agent_bots_for(inbox, conversation)** — 收集活跃 Bot 列表: - 会话 assignee Bot(`conversation.assignee_agent_bot`) - Inbox 绑定的活跃 Bot(`inbox.agent_bot`,仅当 `agent_bot_inbox.active?`) - 去重合并 - **process_webhook_bot_event** — 如果 Bot 的 `outgoing_url` 不为空,通过 `AgentBots::WebhookJob.perform_later` 异步推送 - 推送参数:`(outgoing_url, payload, :agent_bot_webhook, secret: agent_bot.secret, delivery_id: SecureRandom.uuid)` --- ## 9. AgentBots::WebhookJob(Bot Webhook投递) ### 功能描述 AgentBots::WebhookJob 是 AgentBot 专用的 Webhook 推送 Job,继承 WebhookJob,队列优先级为 `high`。支持 `RetryableError`(HTTP 429/500)的3次重试,每次间隔 3秒。重试失败后调用 `Webhooks::Trigger#handle_failure` 处理。 ### 核心逻辑 - `perform(url, payload, webhook_type = :agent_bot_webhook, secret:, delivery_id:)` → 调用 `Webhooks::Trigger.execute` - 重试策略:`retry_on Webhooks::Trigger::RetryableError, wait: 3.seconds, attempts: 3` - 失败日志:`Rails.logger.warn("[AgentBots::WebhookJob] attempt #{executions} failed")` --- ## 10. DashboardApp(仪表盘扩展应用) ### 功能描述 DashboardApp 是 Chatwoot 仪表盘侧边栏的 iframe 扩展应用模型。每个 DashboardApp 由账户内用户创建,包含一个 `content` jsonb 字段,存储一个数组——数组中的每个元素定义一个 iframe 面板(type: 'frame', url: 指定 iframe URL)。DashboardApp 用于第三方将自建界面嵌入 Chatwoot 仪表盘侧边栏。 ### 用户操作流程 1. 管理员在设置页创建 DashboardApp,填写 title 和 iframe URL 列表 2. 系统验证 content 格式(必须是数组,每个元素包含 type:'frame' + 合法 http/https URL) 3. DashboardApp 创建后关联到创建者和所属账户 4. 仪表盘侧边栏渲染时加载该账户的所有 DashboardApp,每个 app 以 iframe 嵌入 ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/v1/accounts/:account_id/dashboard_apps` | 列出账户所有 DashboardApp | | GET | `/api/v1/accounts/:account_id/dashboard_apps/:id` | 查看单个 | | POST | `/api/v1/accounts/:account_id/dashboard_apps` | 创建(自动绑定 user_id) | | PATCH | `/api/v1/accounts/:account_id/dashboard_apps/:id` | 更新 | | DELETE | `/api/v1/accounts/:account_id/dashboard_apps/:id` | 删除 | ### 涉及的数据模型+关键字段 - **DashboardApp**(`dashboard_apps` 表): - `id` (bigint, PK) - `title` (string, NOT NULL) — 应用标题 - `content` (jsonb) — iframe 配置数组,格式:`[{ type: 'frame', url: 'https://...' }]` - `account_id` (bigint, NOT NULL) — 所属账户 - `user_id` (bigint) — 创建者 - 关联:`belongs_to :user`, `belongs_to :account` ### Content 验证规则 - 必须是数组(`is_a?(Array)`),非空(`minItems: 1`) - 每个元素必须包含 `type`(仅允许 `'frame'`)和 `url`(必须 http/https URI) - 使用 JSONSchemer 校验 schema - 空白/非数组数据自动重置为 `[]` --- ## 11. DataImport(数据导入) ### 功能描述 DataImport 是 Chatwoot 的批量数据导入模型,目前仅支持 contacts 导入。用户上传 CSV 文件后,系统创建 DataImport 记录并自动触发 DataImportJob 异步处理。DataImport 跟踪导入状态(pending → processing → completed/failed)、已处理记录数、错误信息,并可附加 `import_file`(源 CSV)和 `failed_records`(失败记录 CSV)。 ### 用户操作流程 1. 管理员在联系人页面点击"导入",上传 CSV 文件 2. 系统创建 DataImport 记录(`data_type: 'contacts'`),附上 `import_file` 3. `after_create_commit` 触发 `DataImportJob.set(wait: 1.minute).perform_later`(等待文件上传到云存储) 4. Job 解析 CSV → 查找/创建联系人(支持 identifier/email/phone_number 去重合并) 5. 处理完成后更新 `processed_records` / `total_records` / `status` 6. 失败记录保存为 `failed_records` CSV 附件 7. 发送导入完成/失败通知邮件给管理员 ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/v1/accounts/:account_id/contacts/import` | 上传 CSV 导入联系人 | ### 涉及的数据模型+关键字段 - **DataImport**(`data_imports` 表): - `id` (bigint, PK) - `data_type` (string, NOT NULL) — 导入类型(目前仅 `'contacts'`) - `status` (integer, default: pending) — 状态枚举:pending(0), processing(1), completed(2), failed(3) - `total_records` (integer) — 总记录数 - `processed_records` (integer) — 成功处理记录数 - `processing_errors` (text) — 处理错误描述 - `account_id` (bigint, NOT NULL) - 附件:`has_one_attached :import_file`,`has_one_attached :failed_records` - 关联:`belongs_to :account` - 验证:`data_type` inclusion `{ in: ['contacts'] }` - 回调:`after_create_commit :process_data_import` → 延迟1分钟触发 DataImportJob --- ## 12. DataImportJob + ContactManager(导入处理流程) ### 功能描述 DataImportJob 是异步 CSV 导入处理器,使用 `DataImport::ContactManager` 执行联系人查找/去重/创建。流程:解析CSV → 查找已有联系人(按 identifier → email → phone_number 优先级) → 合并属性 → 批量创建 → 标签关联 → 失败记录导出 → 邮件通知。 ### 核心逻辑 - **DataImportJob**(`app/jobs/data_import_job.rb`): - 队列:`low` - 重试:`retry_on ActiveStorage::FileNotFoundError, wait: 1.minute, attempts: 3` - 流程: 1. 更新 `status: :processing` 2. 解析 CSV → `ContactManager.build_contact` 逐行处理 3. 每行提取 labels,校验是否在账户已批准标签内 4. 不合法标签行 → 加入 `rejected_contacts` 5. 合法行 → 构建联系人属性(name, email, phone_number, identifier, custom_attributes 等) 6. 批量导入联系人(`Contact.import`,batch_size: 1000) 7. 批量应用标签(`ActsAsTaggableOn::Tagging.import`) 8. 保存失败记录 CSV 为 `failed_records` 附件 9. 发送 `AdministratorNotificationsMailer.contact_import_complete` 或 `contact_import_failed` - **DataImport::ContactManager**(`app/services/data_import/contact_manager.rb`): - 查找优先级:`identifier → email → phone_number` - 合并策略: - `update_contact_with_merged_attributes` — 去重时合并 identifier/name/phone_number/email/custom_attributes - `format_phone_number` — 自动加 `+` 前缀 - `from_email` — 使用 Contact scope 查询邮箱 - `find_or_initialize_contact` — 如找不到则初始化新联系人 --- ## 13. Webhook(账户级Webhook) ### 功能描述 Webhook 是 Chatwoot 的账户级事件推送模型。管理员可在账户或 Inbox 级别创建 Webhook,订阅特定事件(conversation_status_changed, message_created 等),当事件发生时由 WebhookListener 触发 WebhookJob 投递 payload 到指定 URL。Webhook 支持 account_type(账户级)和 inbox_type(Inbox级)两种类型,每个 Webhook 包含 `secret` 用于 HMAC 签名验证。 ### 用户操作流程 1. 管理员在设置页创建 Webhook,填写 URL、名称、订阅事件列表 2. 系统自动生成 Secret,验证 URL 格式和订阅事件合法性 3. 事件发生时 → WebhookListener 触发 → WebhookJob 异步推送 4. 推送使用 HMAC-SHA256 签名(`x-chatwoot-signature` Header) ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/v1/accounts/:account_id/webhooks` | 列出账户 Webhook | | POST | `/api/v1/accounts/:account_id/webhooks` | 创建 Webhook | | PATCH | `/api/v1/accounts/:account_id/webhooks/:id` | 更新 | | DELETE | `/api/v1/accounts/:account_id/webhooks/:id` | 删除 | ### 涉及的数据模型+关键字段 - **Webhook**(`webhooks` 表): - `id` (bigint, PK) - `name` (string) — Webhook 名称 - `url` (text) — 推送 URL(account_id + url 联合唯一) - `secret` (string) — HMAC 签名密钥(WebhookSecretable) - `subscriptions` (jsonb) — 订阅事件数组 - `webhook_type` (integer, default: account_type) — 类型枚举:account_type(0), inbox_type(1) - `account_id` (integer) — 所属账户 - `inbox_id` (integer, nullable) — 所属 Inbox(仅 inbox_type) - 关联:`belongs_to :account`, `belongs_to :inbox, optional: true` - Concern:`WebhookSecretable` - 验证:`url` 格式(http/https URI regex);`subscriptions` 必须是数组且所有事件在 `ALLOWED_WEBHOOK_EVENTS` 内 ### ALLOWED_WEBHOOK_EVENTS ``` conversation_status_changed, conversation_updated, conversation_created, contact_created, contact_updated, message_created, message_updated, webwidget_triggered, inbox_created, inbox_updated, conversation_typing_on, conversation_typing_off ``` --- ## 14. WebhookSecretable(签名密钥机制) ### 功能描述 WebhookSecretable 是为 Webhook 和 AgentBot 提供签名密钥的 concern。使用 `has_secure_token :secret` 自动生成密钥,并支持加密存储(`encrypts :secret`,依赖 `Chatwoot.encryption_configured?`)。提供 `reset_secret!` 方法用于密钥轮换。 ### 核心逻辑 ```ruby module WebhookSecretable extend ActiveSupport::Concern included do has_secure_token :secret encrypts :secret if Chatwoot.encryption_configured? end def reset_secret! regenerate_secret reload end end ``` - 包含此 concern 的模型:**Webhook**、**AgentBot** - 签名使用:Webhooks::Trigger 中 `OpenSSL::HMAC.hexdigest('SHA256', secret, body)` → 生成 `x-chatwoot-signature` Header --- ## 15. Webhooks::Trigger(Webhook投递引擎) ### 功能描述 Webhooks::Trigger 是 Chatwoot 所有 Webhook 推送的核心执行引擎。负责构建 HTTP 请求、设置签名 Header、执行请求、处理失败和重试。支持三种 webhook_type:`account_webhook`、`inbox_webhook`、`agent_bot_webhook`。 ### 核心逻辑 - **请求构建**: - `SafeFetch.fetch(url, method: :post, body: payload.to_json, headers: request_headers)` - Headers:`Content-Type: application/json`, `Accept: application/json` - `X-Chatwoot-Delivery` — delivery_id(UUID) - `x-chatwoot-signature` — HMAC-SHA256 签名(使用 secret + body) - **超时**:`webhook_timeout`(根据 webhook_type 区分:agent_bot 默认更长) - **重试机制**: - `RetryableError` — AgentBot 场景下 HTTP 429/500 触发重试 - AgentBots::WebhookJob:`retry_on RetryableError, wait: 3.seconds, attempts: 3` - 失败处理:`handle_failure(error)` → 日志记录 - **三种 Job**: - `WebhookJob`(queue: medium)— account/inbox webhook - `AgentBots::WebhookJob`(queue: high)— agent_bot webhook,支持重试 - 两者均调用 `Webhooks::Trigger.execute` --- ## 16. InstallationConfig(平台级配置) ### 功能描述 InstallationConfig 是 Chatwoot 的平台级全局配置模型,存储在数据库中,通过 GlobalConfig 缓存层提供高效读取。每个配置项由 `name`(唯一键)和 `serialized_value`(jsonb)组成。配置项分两类:`locked`(不可通过 API 修改)和 `editable`(可通过 Super Admin API 修改)。部分配置修改后需要重启应用(RESTART_REQUIRED_CONFIG_KEYS)。 ### 关键配置项 | 配置名 | 说明 | 类型 | |------|------|------| | `ENABLE_ACCOUNT_SIGNUP` | 是否允许自助注册 | boolean | | `ENABLE_SAML_SSO_LOGIN` | 是否启用 SAML SSO | boolean(修改前校验是否有 SAML 用户) | | `CAPTAIN_OPEN_AI_API_KEY` | Captain AI LLM 密钥 | string(需重启) | | `CAPTAIN_OPEN_AI_ENDPOINT` | Captain AI LLM Endpoint | string(需重启) | | `CAPTAIN_OPEN_AI_MODEL` | Captain AI LLM 模型名 | string(需重启) | | `LANGFUSE_BASE_URL` / `PUBLIC_KEY` / `SECRET_KEY` | Langfuse 可观测配置 | string(需重启) | | `OTEL_PROVIDER` | OpenTelemetry Provider | string(需重启) | | `DISPLAY_MANIFEST` | 是否展示 app manifest | boolean | ### 涉及的数据模型+关键字段 - **InstallationConfig**(`installation_configs` 表): - `id` (bigint, PK) - `name` (string, NOT NULL, UNIQUE) — 配置键名 - `serialized_value` (jsonb, NOT NULL) — 配置值(serialize YAML,默认 `{}`) - `locked` (boolean, default: TRUE, NOT NULL) — 是否锁定(锁定项不可通过 API 编辑) - `created_at`, `updated_at` (datetime) - 索引:`index_installation_configs_on_name` (UNIQUE),`index_installation_configs_on_name_and_created_at` (UNIQUE) - Scope:`editable` → `where(locked: false)` - `default_scope` → `order(created_at: :desc)` - `before_validation :set_lock` — 新建时默认锁定 - `after_commit :clear_cache` — 修改后清除 GlobalConfig Redis 缓存 - 验证:`validates :name, presence: true`; `saml_sso_users_check` — 关闭 SAML SSO 前检查是否有 SAML 用户 ### 涉及的API端点 | 方法 | 路径 | 说明 | |------|------|------| | Super Admin CRUD | `/super_admin/installation_configs` | 管理后台管理 InstallationConfig(全量 CRUD) | ### 涉及的业务逻辑 - **value accessor** — `InstallationConfig#value` 读 `serialized_value[:value]`;`InstallationConfig#value=` 写 `{ value: ... }` - **GlobalConfig 缓存** — 通过 Redis $alfred 缓存,1天过期,fallback 到数据库 - **GlobalConfigService.load(key, default)** — 先查 GlobalConfig 缓存,再 fallback ENV,最后创建 InstallationConfig 记录 - **配置修改需要重启的标记** — `RESTART_REQUIRED_CONFIG_KEYS` 数组 --- ## 17. GlobalConfig + GlobalConfigService(配置缓存层) ### 功能描述 GlobalConfig 是 InstallationConfig 的 Redis 缓存读取层,通过 `$alfred`(Redis 连接)缓存配置值,默认1天过期。当 InstallationConfig 修改时,`after_commit :clear_cache` 自动清除缓存,确保读取到最新值。GlobalConfigService 提供便捷方法,支持 ENV fallback 和自动创建 InstallationConfig。 ### GlobalConfig 核心逻辑 - **缓存键**:`V1:GLOBAL_CONFIG:{config_key}` - **读取流程**: 1. 查 Redis 缓存 → 如有直接返回 2. 缓存不存在 → 查 InstallationConfig DB → 写入 Redis(1天过期) - **类型转换**:通过 ConfigLoader 加载 general_configs 元数据,对 boolean 类型做 `ActiveModel::Type::Boolean.new.cast` - **clear_cache**:扫描 `$alfred.keys("V1:GLOBAL_CONFIG:*")` 并逐个 expire(0) ### GlobalConfigService 核心逻辑 - **load(key, default)**: 1. `GlobalConfig.get(key)` → 如有值返回 2. fallback `ENV.fetch(key) { default }` 3. `InstallationConfig.where(name: key).first_or_create(value: env_value, locked: false)` 4. `GlobalConfig.clear_cache` → 下次读取走 DB - **account_signup_enabled?** — `load('ENABLE_ACCOUNT_SIGNUP', 'false')` 转 boolean --- ## 跨模块交互关系 ``` PlatformApp ──1:1──→ AccessToken (认证) PlatformApp ──1:N──→ PlatformAppPermissible (授权) ↕ polymorphic ├── Account ├── User └── AgentBot AgentBot ──1:1──→ AccessToken (Bot API认证) AgentBot ──1:1──→ Secret (Webhook签名) AgentBot ──1:N──→ AgentBotInbox → Inbox (监听绑定) AgentBot ──1:N──→ PlatformAppPermissible (Partner授权) AgentBot ──1:N──→ AssignedConversations (assignee) AgentBotListener ──→ AgentBots::WebhookJob → Webhooks::Trigger Webhook ──1:1──→ Secret (HMAC签名) WebhookListener ──→ WebhookJob → Webhooks::Trigger DashboardApp ──1:N──→ Account DashboardApp ──1:1──→ User (创建者) DataImport ──1:1──→ Account DataImport ──1:1──→ ImportFile (ActiveStorage) DataImport ──1:1──→ FailedRecords (ActiveStorage) DataImportJob ──→ DataImport::ContactManager → Contact InstallationConfig ──→ GlobalConfig (Redis缓存) ──→ GlobalConfigService (ENV fallback) ``` --- ## GoChat 实现建议 | 模块 | 优先级 | 实现要点 | |------|--------|----------| | AccessToken + AccessTokenable | P0 | 多态令牌,支持 PlatformApp 和 AgentBot 两种 owner;`has_secure_token` 生成唯一 token | | PlatformApp + Permissible | P1 | Partner API 认证主体;多态授权矩阵;建议 Go 中用 interface + 泛型实现 polymorphic | | PlatformController 鉴权 | P1 | Token → PlatformApp 解析 → Permissible 校验;建议中间件链实现 | | AgentBot + AgentBotInbox | P1 | Bot 模型核心;AccessToken + Secret 双令牌;account_id nullable 支持全局 Bot | | AgentBotListener | P2 | 事件驱动推送;建议 EventBus + Handler 模式 | | AgentBots::WebhookJob | P2 | 异步推送 + 重试;建议消息队列 + 指数退避 | | Webhook + WebhookSecretable | P1 | HMAC-SHA256 签名验证;WebhookJob 异步推送;account/inbox 两种 scope | | Webhooks::Trigger | P2 | 统一推送引擎;签名 + 超时 + 重试 + 错误处理 | | DashboardApp | P3 | iframe 扩展;content schema 校验;前端侧边栏 iframe 渲染 | | DataImport | P2 | CSV 异步导入;联系人去重合并;标签批量关联;进度追踪 | | InstallationConfig | P1 | 平台级配置 DB 存储;Redis 缓存层;ENV fallback;locked/editable 分类 | | GlobalConfig | P2 | Redis 缓存 + DB fallback;类型转换;after_commit 清缓存 |