# M4 联系人管理 — Chatwoot 功能梳理文档 > 基于 Chatwoot 源码深度阅读 + CodeGraph 上下文梳理 > 生成日期:2026-05-22 > 参照仓库:chatwoot-reference --- ## 模块概述 本模块覆盖 Chatwoot 的联系人(Contact)全生命周期管理,包括联系人 CRUD、搜索、合并、ContactInbox(联系人-收件箱关联)、CustomAttribute(自定义属性)、CustomFilter(自定义筛选器)、Note(笔记)、Label(标签)以及企业版 Company(公司)功能。核心目标是构建完整的 CRM 体系,让客服团队可以对客户信息进行精细化管理、分类和检索。 --- ## 1. 联系人 CRUD + 状态管理 ### 联系人创建 - **功能描述**:联系人(Contact)是 Chatwoot CRM 的核心实体,代表一个客户/潜在客户。创建时可指定邮箱、电话、姓名等基本信息,并可同时关联到一个 Inbox(通过 ContactInbox)。联系人通过渠道消息自动创建,也可由坐席手动创建。 - **用户操作流程**: 1. 客户通过渠道(WhatsApp/Facebook/Telegram/WebWidget 等)发消息,系统自动创建联系人 + ContactInbox 2. 坐席可在 Contacts 页面手动创建联系人(`POST /api/v1/accounts/{account_id}/contacts`),可同时指定 `inbox_id` 和 `source_id` 3. CSV 批量导入联系人(`POST /api/v1/accounts/{account_id}/contacts/import`),仅管理员可操作 4. 创建后触发 `dispatch_create_event`(ActionCable)和 `ip_lookup`(如 feature flag 启用) - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts` — 创建联系人 - 请求参数:`{ name, identifier, email, phone_number, avatar, avatar_url, inbox_id, source_id, additional_attributes: {}, custom_attributes: {} }` - `POST /api/v1/accounts/{account_id}/contacts/import` — CSV 导入(管理员) - `POST /api/v1/accounts/{account_id}/contacts/export` — CSV 导出(管理员) - 响应:联系人对象 `{ id, name, email, phone_number, identifier, avatar_url, additional_attributes, custom_attributes, contact_type, blocked, ... }` - **涉及的数据模型**: - **Contact**(`contacts` 表)核心字段: - `id, name, email, phone_number, identifier, middle_name, last_name, country_code, location, contact_type, blocked` - `additional_attributes`(jsonb)— 存放 city, company_name, social_* 等扩展信息 - `custom_attributes`(jsonb)— 存放自定义属性键值对 - `account_id, company_id`(企业版), `last_activity_at, created_at, updated_at` - `contact_type` enum:`visitor(0), lead(1), customer(2)` - 索引: - `index_contacts_on_account_id_and_contact_type` — 按账户+类型查询 - `index_contacts_on_lower_email_account_id` — 邮箱大小写不敏感唯一 - `index_contacts_on_name_email_phone_number_identifier` — GIN 全文搜索 - `index_contacts_on_nonempty_fields` — 过滤有标识信息的联系人 - **涉及的业务逻辑**: - **ContactInboxBuilder**(`app/builders/contact_inbox_builder.rb`):创建联系人时若指定 inbox_id,自动创建 ContactInbox。根据渠道类型智能生成 source_id(Email→邮箱、WhatsApp→去掉+的电话、Twilio→带+的电话、WebWidget/API→UUID) - **Contacts::SyncAttributes**(`app/services/contacts/sync_attributes.rb`): - 从 `additional_attributes` 同步 `location` 和 `country_code` 到主字段 - 自动升级 `contact_type`:visitor → lead(当有 email/phone/social 信息时) - **Avatarable** concern:自动从 Gravatar 拉取头像(邮箱变更时异步执行) - `before_validation :prepare_contact_attributes` — 清理空属性 - `before_save :sync_contact_attributes` — 同步联系人属性 - `after_create_commit :dispatch_create_event, :ip_lookup` - `after_update_commit :dispatch_update_event` - `after_destroy_commit :dispatch_destroy_event` - **涉及的权限策略**: - **ContactPolicy**:`index?, active?, search?, filter?, show?, create?, update?, contactable_inboxes?, destroy_custom_attributes?, avatar?` → 所有坐席可操作;`import?, export?, destroy?` → 仅管理员 - **涉及的事件/通知**: - ActionCable:创建/更新/删除事件推送 - `CONTACT_MERGED` 事件(合并时触发) ### 联系人列表 + 排序 + 分页 - **功能描述**:支持按账户获取联系人列表,支持多字段排序和分页。默认只展示有标识信息的"resolved contacts"(CRM V2 模式下只展示 lead 类型)。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/contacts` — 分页列表(默认 15 条/页) - `GET /api/v1/accounts/{account_id}/contacts/active` — 最近活跃联系人 - 请求参数:`page, sort, include_contact_inboxes` - **排序支持(Sift gem)**: - `email, name, phone_number, last_activity_at, created_at, company_name, city, country` — 支持 asc/desc - **resolved_contacts 逻辑**: - 默认模式:`WHERE email <> '' OR phone_number <> '' OR identifier <> ''` - CRM V2 模式:`WHERE contact_type = 'lead'` - **标签过滤**:支持 `labels` 参数(ActsAsTaggableOn `tagged_with`,any: true) ### 联系人搜索 - **功能描述**:对联系人的 name/email/phone_number/identifier 进行 ILIKE 模糊搜索,返回分页结果。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/contacts/search?q=xxx` — 搜索 - 响应包含 `has_more` 标识(多取一条判断是否还有更多数据) ### 联系人更新 - **涉及的API端点**: - `PUT /api/v1/accounts/{account_id}/contacts/{id}` — 更新联系人 - 更新时 custom_attributes 和 additional_attributes 采用 merge(合并)策略而非覆盖 - 支持 `avatar_url` 参数,从 URL 拉取头像 ### 联系人删除 - **涉及的API端点**: - `DELETE /api/v1/accounts/{account_id}/contacts/{id}` — 删除联系人(仅管理员) - 删除前检查在线状态(OnlineStatusTracker),在线联系人不可删除 - 关联的 conversations, contact_inboxes, messages, notes 等通过 `dependent: :destroy_async` 异步级联删除 ### 联系人头像管理 - **涉及的API端点**: - `DELETE /api/v1/accounts/{account_id}/contacts/{id}/avatar` — 删除头像 --- ## 2. 联系人高级筛选(Filter) ### 筛选器运行 - **功能描述**:基于 `Contacts::FilterService`,支持多条件组合筛选联系人。筛选条件来自 `filter_keys.yml` 中的 contact 字段定义 + 自定义属性。支持标准属性、additional_attributes 和自定义属性三种类型。 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/filter` — 执行筛选 - 请求参数:`{ payload: { contacts: [ { attribute_key, filter_operator, values, attribute_type, data_type } ... ] } }` - **筛选字段(filter_keys.yml → contacts)**: | 字段 | attribute_type | data_type | filter_operators | |------|---------------|-----------|-----------------| | name | standard | text_case_insensitive | equal_to, not_equal_to, contains, does_not_contain | | phone_number | standard | text | equal_to, not_equal_to, contains, does_not_contain, starts_with | | email | standard | text_case_insensitive | equal_to, not_equal_to, contains, does_not_contain | | identifier | standard | text_case_insensitive | equal_to, not_equal_to | | country_code | additional_attributes | text_case_insensitive | equal_to, not_equal_to | | city | additional_attributes | text_case_insensitive | equal_to, not_equal_to, contains, does_not_contain | | company_name | additional_attributes | text_case_insensitive | equal_to, not_equal_to, contains, does_not_contain | | labels | standard | labels | equal_to, not_equal_to, is_present, is_not_present | | created_at | standard | date | is_greater_than, is_less_than, days_before | | last_activity_at | standard | date | is_greater_than, is_less_than, days_before | | blocked | standard | boolean | equal_to, not_equal_to | | *(自定义属性)* | custom_attribute | 对应类型 | 对应操作符 | - **涉及的业务逻辑**: - **Contacts::FilterService** 继承 **FilterService**: - `base_relation` → `account.contacts.resolved_contacts` - 支持 `phone_number` 特殊处理(自动加 +) - 支持 `country_code` 特殊处理(自动 downcase) - 支持 `crm_v2` feature flag - FilterService 核心逻辑:解析 filter payload → 逐条件构建 SQL → 组合 `query_string` + `filter_values` → 执行查询 - 自定义属性筛选通过 `Filters::CustomAttributeFilterHelper`,查询 `custom_attribute_definitions` 表获取属性定义,动态构建 JOIN + WHERE 条件 - **涉及的权限策略**: - ContactPolicy `filter?` → 所有坐席可用 --- ## 3. 联系人合并(Merge) ### 合并操作 - **功能描述**:将两个联系人合并为一个。base_contact 保留为主联系人,mergee_contact 的数据被合并后删除。合并操作在事务中执行,迁移所有关联数据。 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/actions/contact_merge` — 合并联系人 - 请求参数:`{ base_contact_id, mergee_contact_id }` - **涉及的数据模型 + 关键字段**:无独立数据模型,纯 Action 类 - **涉及的业务逻辑**: - **ContactMergeAction**(`app/actions/contact_merge_action.rb`): 1. `validate_contacts` — 校验两个联系人属于同一账户 2. `merge_conversations` — 将 mergee 的所有 Conversation 的 contact_id 改为 base 3. `merge_messages` — 将 mergee 作为 sender 的所有 Message 改为 base 4. `merge_contact_inboxes` — 将 mergee 的所有 ContactInbox 改为 base 5. `merge_contact_notes` — 将 mergee 的所有 Note 改为 base 6. `merge_and_remove_mergee_contact` — 合并属性策略: - 可合并字段:`identifier, name, email, phone_number, additional_attributes, custom_attributes` - 使用 `deep_merge`:base_contact 的值优先,mergee 的空值被跳过 - 合并后删除 mergee_contact 7. 触发 `CONTACT_MERGED` 事件(Dispatcher dispatch) - **涉及的权限策略**: - ContactMergesController 继承 BaseController,需要 `check_authorization` - **注意事项**: - 如果 base_contact.id == mergee_contact.id,直接返回 base_contact(防止重复合并) - 合并是事务性操作,失败则回滚 --- ## 4. ContactInbox(联系人-收件箱关联) ### ContactInbox 创建 - **功能描述**:ContactInbox 是联系人与收件箱的中间关联表,记录联系人在某个渠道的外部标识(source_id)。一个联系人可以关联多个 Inbox(多渠道联系)。 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/{contact_id}/contact_inboxes` — 创建 ContactInbox - 请求参数:`{ inbox_id, source_id }` - **涉及的数据模型**: - **ContactInbox**(`contact_inboxes` 表)核心字段: - `id, contact_id, inbox_id, source_id, hmac_verified, pubsub_token` - 索引:`inbox_id + source_id` UNIQUE(同一收件箱+source唯一) - `pubsub_token` UNIQUE(用于 ActionCable 推送) - **涉及的业务逻辑**: - **ContactInboxBuilder**(`app/builders/contact_inbox_builder.rb`): - 若已有相同 inbox_id + source_id 的 ContactInbox,直接返回(不重复创建) - 若未指定 source_id,根据渠道类型自动生成: - Email → 使用 `contact.email` - WhatsApp → 使用 `contact.phone_number`(去掉 +) - Twilio SMS → 根据渠道 medium 生成(SMS→带+的电话,WhatsApp→去掉+) - SMS → 使用 `contact.phone_number` - WebWidget/API → `SecureRandom.uuid` - 支持 `hmac_verified` 参数(Website 渠道 HMAC 校验标识) - `valid_source_id_format?` — 校验 source_id 格式(使用 RegexHelper) - `current_conversation` — 返回该 ContactInbox 的最新对话 - `webhook_data` — 包含 contact/inbox/account/current_conversation 的完整推送数据 - **ContactableInboxes(可用收件箱)**: - `GET /api/v1/accounts/{account_id}/contacts/{id}/contactable_inboxes` — 获取该联系人可以发消息的收件箱列表 - **Contacts::ContactableInboxesService**:遍历账户所有 Inbox,根据渠道类型和联系人信息判断可用性: - Email → 需要有 email - WhatsApp/SMS/Twilio → 需要有 phone_number - WebWidget → 需要已有 ContactInbox 且无活跃对话 - API → 直接可用(可用已有 source_id 或生成 UUID) ### ContactInbox 清理(Stale Cleanup) - **功能描述**:系统定期清理没有关联对话且创建时间过久的 ContactInbox 和无标识信息的联系人。 - **涉及的业务逻辑**: - `ContactInbox.stale_without_conversations(time_period)` — scope 查找无对话的旧 ContactInbox - `Contact.stale_without_conversations(time_period)` — scope 查找无标识、无对话的旧联系人 - `Internal::RemoveStaleContactInboxesJob/Service` — 定期清理 - `Internal::RemoveStaleContactsJob/Service` — 定期清理 --- ## 5. CustomAttributeDefinition(自定义属性定义) ### 自定义属性定义 CRUD - **功能描述**:自定义属性定义(CustomAttributeDefinition)是账户级别的配置,定义可用于 Contact/Conversation/Company 的扩展字段。每个定义指定属性键、显示名称、显示类型、所属模型等。定义后,对应实体的 `custom_attributes` jsonb 字段即可存储该键值对。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/custom_attribute_definitions` — 按模型类型列出定义 - `GET /api/v1/accounts/{account_id}/custom_attribute_definitions/{id}` — 获取单个定义 - `POST /api/v1/accounts/{account_id}/custom_attribute_definitions` — 创建定义 - `PUT /api/v1/accounts/{account_id}/custom_attribute_definitions/{id}` — 更新定义 - `DELETE /api/v1/accounts/{account_id}/custom_attribute_definitions/{id}` — 删除定义 - **涉及的数据模型**: - **CustomAttributeDefinition**(`custom_attribute_definitions` 表)核心字段: - `id, attribute_display_name, attribute_description, attribute_key, attribute_display_type, attribute_model, attribute_values(jsonb), default_value, regex_pattern, regex_cue, account_id` - `attribute_model` enum:`conversation_attribute(0), contact_attribute(1), company_attribute(2)` - `attribute_display_type` enum:`text(0), number(1), currency(2), percent(3), link(4), date(5), list(6), checkbox(7)` - 索引:`attribute_key + attribute_model + account_id` UNIQUE - **标准属性(不可覆盖)**: - Contact 标准属性:`name, email, phone_number, identifier, country_code, city, company_name, created_at, last_activity_at, referer, blocked` - Conversation 标准属性:`status, priority, assignee_id, inbox_id, team_id, display_id, campaign_id, labels, browser_language, country_code, referer, created_at, last_activity_at` - Company 标准属性:`name, domain, description, contacts_count, created_at, updated_at, last_activity_at` - **涉及的校验**: - `attribute_must_not_conflict` — attribute_key 不能与标准属性冲突(on :create) - `normalize_attribute_fields` — attribute_key 自动 downcase - `regex_pattern/regex_cue` — 企业版扩展,支持正则校验提示 - `attribute_values` — list 类型时存储可选值列表 - **涉及的业务逻辑**: - `after_update :update_widget_pre_chat_custom_fields` — 更新时同步到 Widget 的预聊天自定义字段(非 company_attribute) - `after_destroy :sync_widget_pre_chat_custom_fields` — 删除时同步 - 企业版 **Enterprise::Concerns::CustomAttributeDefinition**: - `after_destroy :cleanup_conversation_required_attributes` — 删除定义后清理 `account.conversation_required_attributes` - **涉及的权限策略**: - **CustomAttributeDefinitionPolicy**:`index?, show?` → 坐席+管理员;`create?, update?, destroy?` → 仅管理员 ### 联系人自定义属性操作 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/{id}/destroy_custom_attributes` — 删除指定的自定义属性键 - 请求参数:`{ custom_attributes: ["key1", "key2"] }` - 实际是排除(excluding)指定键,保留其余 - 更新联系人时通过 `custom_attributes: {}` 参数 merge 到已有属性 --- ## 6. CustomFilter(自定义筛选器) ### 自定义筛选器 CRUD - **功能描述**:自定义筛选器(CustomFilter)是用户级别的筛选条件保存机制,允许坐席保存常用筛选条件以便快速复用。每个筛选器属于一个用户+账户,可按类型区分(对话/联系人/报告)。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/custom_filters?filter_type=contact` — 列出用户的联系人筛选器 - `GET /api/v1/accounts/{account_id}/custom_filters/{id}` — 获取单个筛选器 - `POST /api/v1/accounts/{account_id}/custom_filters` — 创建筛选器 - `PUT /api/v1/accounts/{account_id}/custom_filters/{id}` — 更新筛选器 - `DELETE /api/v1/accounts/{account_id}/custom_filters/{id}` — 删除筛选器 - **涉及的数据模型**: - **CustomFilter**(`custom_filters` 表)核心字段: - `id, name, filter_type, query(jsonb), account_id, user_id` - `filter_type` enum:`conversation(0), contact(1), report(2)` - **涉及的校验**: - `validate_number_of_filters` — 每用户每账户最多 `Limits::MAX_CUSTOM_FILTERS_PER_USER` 个筛选器 - **创建筛选器请求**: - `{ custom_filter: { name: "VIP Contacts", filter_type: "contact", query: { contacts: [{ attribute_key: "email", filter_operator: "contains", values: ["@vip.com"] }] } } }` --- ## 7. Note(笔记) ### 笔记 CRUD - **功能描述**:笔记(Note)是联系人级别的内部备忘信息,供坐席记录与客户相关的备注。笔记按创建时间倒序排列。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/contacts/{contact_id}/notes` — 列出笔记(倒序) - `GET /api/v1/accounts/{account_id}/contacts/{contact_id}/notes/{id}` — 获取单条笔记 - `POST /api/v1/accounts/{account_id}/contacts/{contact_id}/notes` — 创建笔记 - `PUT /api/v1/accounts/{contact_id}/accounts/{account_id}/contacts/{contact_id}/notes/{id}` — 更新笔记 - `DELETE /api/v1/accounts/{account_id}/contacts/{contact_id}/notes/{id}` — 删除笔记 - **涉及的数据模型**: - **Note**(`notes` 表)核心字段: - `id, content(text, not null), account_id, contact_id, user_id, created_at, updated_at` - `user_id` optional — 可匿名笔记 - **涉及的业务逻辑**: - `before_validation :ensure_account_id` — 自动从 contact 取 account_id - `scope :latest` → `order(created_at: :desc)` - 创建时自动注入 `user_id: Current.user.id` - 删除联系人时级联删除(`dependent: :destroy_async`) - 合并联系人时笔记迁移到 base_contact --- ## 8. Label(标签) ### 标签 CRUD - **功能描述**:标签(Label)是账户级别的分类标记,可用于联系人和对话。标签通过 ActsAsTaggableOn 实现多对多关联。标签有颜色、描述、是否在侧边栏显示等属性。 - **涉及的API端点**(账户级别标签管理): - `GET /api/v1/accounts/{account_id}/labels` — 列出标签 - `GET /api/v1/accounts/{account_id}/labels/{id}` — 获取标签 - `POST /api/v1/accounts/{account_id}/labels` — 创建标签 - `PUT /api/v1/accounts/{account_id}/labels/{id}` — 更新标签 - `DELETE /api/v1/accounts/{account_id}/labels/{id}` — 删除标签 - **涉及的数据模型**: - **Label**(`labels` 表)核心字段: - `id, title, color(default "#1f93ff"), description, show_on_sidebar, account_id` - 索引:`title + account_id` UNIQUE - **涉及的校验**: - title:presence, format(UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE),uniqueness(scope: account_id) - `before_validation` → title 自动 downcase - **涉及的业务逻辑**: - `acts_as_taggable_on :labels`(通过 Labelable concern) - `Labelable` concern 方法: - `update_labels(labels)` — 设置标签列表(覆盖) - `add_labels(new_labels)` — 添加标签(追加) - `after_update_commit :update_associated_models` — 标题变更时触发 `Labels::UpdateJob`,异步更新所有使用旧标签名的 taggings - 删除标签时触发 `Labels::RemoveAssociationsJob`,清理所有 taggings - `default_scope { order(:title) }` — 默认按标题排序 ### 联系人标签操作 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/{contact_id}/labels` — 设置/更新联系人标签 - `GET /api/v1/accounts/{account_id}/contacts/{contact_id}/labels` — 获取联系人标签列表 - 请求参数:`{ labels: ["vip", "enterprise"] }` - 使用 LabelConcern:`model.update_labels(permitted_params[:labels])` ### 联系人批量标签操作 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/bulk_actions` — 批量操作(type=Contact) - 请求参数:`{ type: "Contact", ids: [...], labels: { add: ["vip"] } }` - **Contacts::BulkAssignLabelsService**:批量给多个联系人添加标签 --- ## 9. Company(企业版) ### 公司 CRUD - **功能描述**:公司(Company)是企业版功能,代表联系人所属的企业组织。联系人通过 email 域名自动关联到公司,也可手动关联。Company 拥有自己的 custom_attributes、avatar、description 等。 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/companies` — 公司列表(分页 25 条/页) - `GET /api/v1/accounts/{account_id}/companies/search?q=xxx` — 搜索公司(name/domain) - `GET /api/v1/accounts/{account_id}/companies/{id}` — 获取公司详情 - `POST /api/v1/accounts/{account_id}/companies` — 创建公司 - `PUT /api/v1/accounts/{account_id}/companies/{id}` — 更新公司 - `DELETE /api/v1/accounts/{account_id}/companies/{id}` — 删除公司(仅管理员) - `POST /api/v1/accounts/{account_id}/companies/{id}/destroy_custom_attributes` — 删除自定义属性 - `DELETE /api/v1/accounts/{account_id}/companies/{id}/avatar` — 删除公司头像 - 需 `ensure_companies_enabled!` → feature flag `companies` 必须启用 - **涉及的数据模型**: - **Company**(`companies` 表)核心字段: - `id, name(not null, length limit), domain(unique scope: account_id, 格式校验), description(text, length limit), additional_attributes(jsonb), custom_attributes(jsonb), contacts_count, last_activity_at, account_id` - 索引:`account_id + domain` UNIQUE WHERE domain IS NOT NULL - **涉及的业务逻辑**: - **Company** include Avatarable → 支持 avatar + Gravatar - `before_validation :prepare_jsonb_attributes` — 清理 jsonb - `after_create_commit :fetch_favicon` — domain 存在时异步拉取 favicon - `has_many :contacts, dependent: :nullify` — 删除公司时联系人 company_id 设为 NULL - scope:`ordered_by_name`, `search_by_name_or_domain` ### 公司-联系人关联 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/companies/{company_id}/contacts` — 公司下的联系人列表 - `GET /api/v1/accounts/{account_id}/companies/{company_id}/contacts/search?q=xxx` — 在公司下搜索联系人 - `POST /api/v1/accounts/{account_id}/companies/{company_id}/contacts` — 将联系人关联到公司 - `DELETE /api/v1/accounts/{account_id}/companies/{company_id}/contacts/{contact_id}` — 从公司中移除联系人 - **自动关联逻辑**: - **Enterprise::Concerns::Contact**: - `after_commit :associate_company_from_email, on: [:create, :update], if: :should_associate_company?` - 条件:`email.present? && company_id.nil? && saved_change_to_email? && saved_change_to_email.first.nil?` - **Contacts::CompanyAssociationService**: - 检查邮箱是否为商业邮箱(`Companies::BusinessEmailDetectorService`) - 提取域名 → `find_or_create_by!(account, domain)` 创建或查找公司 - 公司名称从 `additional_attributes.company_name` 或域名推导 - 关联后更新 `contact.company_id` + `Company.contacts_count`(counter_cache) - `after_update_commit :record_company_activity` — 联系人活跃时更新公司 `last_activity_at` --- ## 10. 批量操作 ### 批量删除联系人 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/bulk_actions` — `{ type: "Contact", action_name: "delete", ids: [...] }` - 权限检查:删除操作需 ContactPolicy `destroy?`(仅管理员) - **涉及的业务逻辑**: - **Contacts::BulkActionService** → 调度到 `Contacts::BulkActionJob` - `delete_requested?` → `Contacts::BulkDeleteService` → `find_each(&:destroy!)` - **涉及的数据模型**:无独立模型 --- ## 11. 导入/导出 ### CSV 导入 - **功能描述**:支持通过 CSV 文件批量导入联系人。仅管理员可操作。 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/import` — 导入 CSV - 如企业版未启用联系人导入功能,返回错误 ### CSV 导出 - **功能描述**:支持将联系人导出为 CSV 文件,异步执行完成后通过邮件发送给管理员。 - **涉及的API端点**: - `POST /api/v1/accounts/{account_id}/contacts/export` — 导出 - **涉及的业务逻辑**: - **Account::ContactsExportJob**: - 异步生成 CSV,支持自定义列名 - Labels 列特殊处理(preload + join taggings) - 生成后通过 `AdministratorNotifications::MailService` 发送邮件通知 --- ## 12. 联系人对话查看 ### 获取联系人的对话 - **涉及的API端点**: - `GET /api/v1/accounts/{account_id}/contacts/{contact_id}/conversations` — 获取联系人的所有对话 ### 获取联系人的附件 - **涉及的API端点**: - `GET /api/v1/accounts/{contact_id}/accounts/{account_id}/contacts/{contact_id}/attachments` — 获取联系人相关附件 --- ## 13. 关系图谱总结 ``` Account ├── Contact (belongs_to, N:1) │ ├── ContactInbox (has_many, N:N through contact_inboxes) │ │ ├── Inbox (belongs_to) │ │ ├── Conversation (has_many) │ │ └── Pubsubable (pubsub_token) │ ├── Conversation (has_many) │ ├── Message (has_many, as: sender) │ ├── Note (has_many) │ ├── CsatSurveyResponse (has_many) │ ├── Labels (acts_as_taggable_on :labels) │ ├── Avatar (has_one_attached) │ ├── Company (belongs_to, 企业版, counter_cache) │ ├── custom_attributes (jsonb) │ └── additional_attributes (jsonb) ├── Company (企业版, has_many contacts) ├── CustomAttributeDefinition (has_many) ├── CustomFilter (has_many) └── Label (has_many) ``` --- ## 14. 跨模块依赖 | 依赖模块 | 依赖内容 | |----------|---------| | M1 账户与用户 | Contact/Company/Label/CustomAttributeDefinition 均属于 Account;ContactPolicy/CustomAttributeDefinitionPolicy 依赖 AccountUser 角色 | | M2 收件箱与渠道 | ContactInbox 连接 Contact ↔ Inbox;ContactInboxBuilder 根据渠道类型生成 source_id;ContactableInboxesService 查询可用渠道 | | M3 对话与消息 | Contact has_many Conversations;Contact 是 Message 的 polymorphic sender;合并时迁移 Conversation 和 Message | | M5 团队与分配 | Contact 不直接关联 Team(通过 Conversation 间接关联) | | M7 通知与事件 | CONTACT_MERGED 事件经 Dispatcher dispatch;contact.create/update/destroy 事件推送 ActionCable | --- ## 15. GoChat 实现建议 ### P0(核心必备) 1. **Contact CRUD**:联系人创建/读取/更新/删除,含 email/phone/name/identifier 基本字段 + additional_attributes/custom_attributes jsonb 2. **ContactInbox**:联系人-收件箱关联,含 source_id,创建时按渠道类型智能生成 3. **Contact 搜索**:ILIKE 模糊搜索(name/email/phone/identifier) 4. **Contact 状态类型**:visitor/lead/customer 三级进阶,自动升级逻辑 5. **Note CRUD**:联系人笔记,含 content + user_id + timestamps ### P1(重要增强) 6. **Label 系统**:账户级标签 CRUD + 联系人/对话标签关联(可使用简化版 taggable) 7. **Contact 合并**:base + mergee 合并操作,迁移所有关联数据 8. **Contact 筛选**:多条件组合筛选(标准属性 + 自定义属性) 9. **CustomAttributeDefinition**:自定义属性定义 CRUD(contact/conversation/company 三类) 10. **CustomFilter**:用户级筛选条件保存和复用 11. **批量操作**:批量删除 + 批量标签赋值 12. **ContactableInboxes**:查询联系人可用的收件箱列表 ### P2(企业版扩展) 13. **Company**:公司 CRUD + 自动关联(email 域名检测) 14. **Company-Contact**:公司下的联系人管理 + 移除关联 15. **Stale Cleanup**:定期清理无对话/无标识的 ContactInbox 和 Contact 16. **Contact 导入/导出**:CSV 批量导入 + 异步导出邮件通知 17. **regex_pattern/regex_cue**:自定义属性正则校验