清理: - 删除 34 份过时文档(gap reports/QA临时报告/验收报告/阶段性文档) - 删除 docs/.hermes/skills 第三方 skills 副本(16 文件) - 删除 skills-lock.json 目录归集: - 根目录仅保留 README.md 索引 - product/ — 产品与架构设计(PRD + ARCHITECTURE + P2设计文档 + AI/企业路线图) - tracking/ — Chatwoot parity 开发跟踪 - requirements/ — M01-M12 模块需求 - plans/ — 历史实现计划 - parity/ — 路由 parity 与前端契约 - qa/ — QA 报告与测试计划 - ops/ — 运维部署 命名规范: - 全小写 kebab-case,禁止全大写文件名 - product/tracking/ops 用 NN- 序号前缀 - requirements 用 MNN- 两位零填充模块号 - plans/qa 用 YYYY-MM-DD- 日期前缀 - requirements M1-M9 零填充为 M01-M09(修复字典序) 同步更新: - backend/cmd/route_parity/main.go 路径默认值 - backend/scripts/parity_frontend_smoke.sh 报告路径 - 所有 docs 内部交叉引用 - .gitignore 排除编译产物 (backend/gochat, backend/route_parity) - 新增迁移 000052/000053 - 前端 WS 相关修改
29 KiB
29 KiB
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)。联系人通过渠道消息自动创建,也可由坐席手动创建。
- 用户操作流程:
- 客户通过渠道(WhatsApp/Facebook/Telegram/WebWidget 等)发消息,系统自动创建联系人 + ContactInbox
- 坐席可在 Contacts 页面手动创建联系人(
POST /api/v1/accounts/{account_id}/contacts),可同时指定inbox_id和source_id - CSV 批量导入联系人(
POST /api/v1/accounts/{account_id}/contacts/import),仅管理员可操作 - 创建后触发
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, blockedadditional_attributes(jsonb)— 存放 city, company_name, social_* 等扩展信息custom_attributes(jsonb)— 存放自定义属性键值对account_id, company_id(企业版),last_activity_at, created_at, updated_at
contact_typeenum: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— 过滤有标识信息的联系人
- Contact(
- 涉及的业务逻辑:
- 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_lookupafter_update_commit :dispatch_update_eventafter_destroy_commit :dispatch_destroy_event
- ContactInboxBuilder(
- 涉及的权限策略:
- ContactPolicy:
index?, active?, search?, filter?, show?, create?, update?, contactable_inboxes?, destroy_custom_attributes?, avatar?→ 所有坐席可操作;import?, export?, destroy?→ 仅管理员
- ContactPolicy:
- 涉及的事件/通知:
- 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参数(ActsAsTaggableOntagged_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_v2feature flag
- FilterService 核心逻辑:解析 filter payload → 逐条件构建 SQL → 组合
query_string+filter_values→ 执行查询 - 自定义属性筛选通过
Filters::CustomAttributeFilterHelper,查询custom_attribute_definitions表获取属性定义,动态构建 JOIN + WHERE 条件
- Contacts::FilterService 继承 FilterService:
- 涉及的权限策略:
- ContactPolicy
filter?→ 所有坐席可用
- ContactPolicy
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):validate_contacts— 校验两个联系人属于同一账户merge_conversations— 将 mergee 的所有 Conversation 的 contact_id 改为 basemerge_messages— 将 mergee 作为 sender 的所有 Message 改为 basemerge_contact_inboxes— 将 mergee 的所有 ContactInbox 改为 basemerge_contact_notes— 将 mergee 的所有 Note 改为 basemerge_and_remove_mergee_contact— 合并属性策略:- 可合并字段:
identifier, name, email, phone_number, additional_attributes, custom_attributes - 使用
deep_merge:base_contact 的值优先,mergee 的空值被跳过 - 合并后删除 mergee_contact
- 可合并字段:
- 触发
CONTACT_MERGED事件(Dispatcher dispatch)
- ContactMergeAction(
- 涉及的权限策略:
- ContactMergesController 继承 BaseController,需要
check_authorization
- ContactMergesController 继承 BaseController,需要
- 注意事项:
- 如果 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_idUNIQUE(同一收件箱+source唯一) pubsub_tokenUNIQUE(用于 ActionCable 推送)
- ContactInbox(
- 涉及的业务逻辑:
- 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
- Email → 使用
- 支持
hmac_verified参数(Website 渠道 HMAC 校验标识)
valid_source_id_format?— 校验 source_id 格式(使用 RegexHelper)current_conversation— 返回该 ContactInbox 的最新对话webhook_data— 包含 contact/inbox/account/current_conversation 的完整推送数据
- ContactInboxBuilder(
- 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 查找无对话的旧 ContactInboxContact.stale_without_conversations(time_period)— scope 查找无标识、无对话的旧联系人Internal::RemoveStaleContactInboxesJob/Service— 定期清理Internal::RemoveStaleContactsJob/Service— 定期清理
5. CustomAttributeDefinition(自定义属性定义)
自定义属性定义 CRUD
- 功能描述:自定义属性定义(CustomAttributeDefinition)是账户级别的配置,定义可用于 Contact/Conversation/Company 的扩展字段。每个定义指定属性键、显示名称、显示类型、所属模型等。定义后,对应实体的
custom_attributesjsonb 字段即可存储该键值对。 - 涉及的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_idattribute_modelenum:conversation_attribute(0), contact_attribute(1), company_attribute(2)attribute_display_typeenum:text(0), number(1), currency(2), percent(3), link(4), date(5), list(6), checkbox(7)- 索引:
attribute_key + attribute_model + account_idUNIQUE
- CustomAttributeDefinition(
- 标准属性(不可覆盖):
- 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
- Contact 标准属性:
- 涉及的校验:
attribute_must_not_conflict— attribute_key 不能与标准属性冲突(on :create)normalize_attribute_fields— attribute_key 自动 downcaseregex_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?→ 仅管理员
- CustomAttributeDefinitionPolicy:
联系人自定义属性操作
- 涉及的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_idfilter_typeenum:conversation(0), contact(1), report(2)
- CustomFilter(
- 涉及的校验:
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_atuser_idoptional — 可匿名笔记
- Note(
- 涉及的业务逻辑:
before_validation :ensure_account_id— 自动从 contact 取 account_idscope :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_idUNIQUE
- Label(
- 涉及的校验:
- title:presence, format(UNICODE_CHARACTER_NUMBER_HYPHEN_UNDERSCORE),uniqueness(scope: account_id)
before_validation→ title 自动 downcase
- 涉及的业务逻辑:
acts_as_taggable_on :labels(通过 Labelable concern)Labelableconcern 方法: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 flagcompanies必须启用
- 涉及的数据模型:
- 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 + domainUNIQUE WHERE domain IS NOT NULL
- Company(
- 涉及的业务逻辑:
- Company include Avatarable → 支持 avatar + Gravatar
before_validation :prepare_jsonb_attributes— 清理 jsonbafter_create_commit :fetch_favicon— domain 存在时异步拉取 faviconhas_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
- 检查邮箱是否为商业邮箱(
- Enterprise::Concerns::Contact:
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!)
- Contacts::BulkActionService → 调度到
- 涉及的数据模型:无独立模型
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发送邮件通知
- Account::ContactsExportJob:
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(核心必备)
- Contact CRUD:联系人创建/读取/更新/删除,含 email/phone/name/identifier 基本字段 + additional_attributes/custom_attributes jsonb
- ContactInbox:联系人-收件箱关联,含 source_id,创建时按渠道类型智能生成
- Contact 搜索:ILIKE 模糊搜索(name/email/phone/identifier)
- Contact 状态类型:visitor/lead/customer 三级进阶,自动升级逻辑
- Note CRUD:联系人笔记,含 content + user_id + timestamps
P1(重要增强)
- Label 系统:账户级标签 CRUD + 联系人/对话标签关联(可使用简化版 taggable)
- Contact 合并:base + mergee 合并操作,迁移所有关联数据
- Contact 筛选:多条件组合筛选(标准属性 + 自定义属性)
- CustomAttributeDefinition:自定义属性定义 CRUD(contact/conversation/company 三类)
- CustomFilter:用户级筛选条件保存和复用
- 批量操作:批量删除 + 批量标签赋值
- ContactableInboxes:查询联系人可用的收件箱列表
P2(企业版扩展)
- Company:公司 CRUD + 自动关联(email 域名检测)
- Company-Contact:公司下的联系人管理 + 移除关联
- Stale Cleanup:定期清理无对话/无标识的 ContactInbox 和 Contact
- Contact 导入/导出:CSV 批量导入 + 异步导出邮件通知
- regex_pattern/regex_cue:自定义属性正则校验