V4 验收报告 — Companies模块 (2026-06-02 更新版)
验收标准
- 接口路径一致
- 请求参数一致
- 响应格式一致
- 错误处理与状态码一致
- 业务逻辑一致
1. 接口路径对比
| # |
Chatwoot端点 |
GoChat端点 |
状态 |
| 1 |
GET /api/v1/accounts/:account_id/companies |
GET /api/v1/accounts/:id/companies |
✅ 兼容 (path param命名差异不影响功能) |
| 2 |
POST /api/v1/accounts/:account_id/companies |
POST /api/v1/accounts/:id/companies |
✅ 兼容 |
| 3 |
GET /api/v1/accounts/:account_id/companies/search |
GET /api/v1/accounts/:id/companies/search |
✅ 兼容 |
| 4 |
GET /api/v1/accounts/:account_id/companies/:id |
GET /api/v1/accounts/:id/companies/:company_id |
⚠️ path param名不同 — 功能兼容 |
| 5 |
PATCH/PUT /api/v1/accounts/:account_id/companies/:id |
PUT /api/v1/accounts/:id/companies/:company_id |
⚠️ Chatwoot支持PATCH, GoChat仅PUT |
| 6 |
DELETE /api/v1/accounts/:account_id/companies/:id |
DELETE /api/v1/accounts/:id/companies/:company_id |
⚠️ path param命名差异 |
| 7 |
POST /companies/:id/destroy_custom_attributes |
— |
❌ 缺失 |
| 8 |
DELETE /companies/:id/avatar |
— |
❌ 缺失 |
| 9 |
GET /companies/:company_id/contacts |
GET /companies/:company_id/contacts |
✅ 兼容 |
| 10 |
GET /companies/:company_id/contacts/search |
— |
❌ 缺失 |
| 11 |
POST /companies/:company_id/contacts |
POST /companies/:company_id/contacts/:contact_id |
⚠️ 路径差异 — Chatwoot用body传contact_id, GoChat用URL param |
| 12 |
DELETE /companies/:company_id/contacts/:id |
DELETE /companies/:company_id/contacts/:contact_id |
✅ 兼容 (path param命名差异) |
| 13 |
GET /companies/:company_id/conversations |
GET /companies/:company_id/conversations |
✅ 兼容 |
| 14 |
GET /companies/:company_id/notes |
GET /companies/:company_id/notes |
✅ 兼容 |
GoChat额外端点 (Chatwoot不存在):
| # |
GoChat端点 |
说明 |
| 15 |
POST /companies/:company_id/notes |
Chatwoot notes只有:index,无:create — GoChat扩展 |
| 16 |
DELETE /companies/:company_id/notes/:note_id |
Chatwoot无delete note端点 — GoChat扩展 |
端点汇总:
- ✅ 兼容: 7个
- ⚠️ 路径差异但功能兼容: 4个 (path param命名, PATCH vs PUT)
- ❌ 缺失: 3个 (destroy_custom_attributes, avatar delete, contacts/search)
- ⚠️ 新增路径差异: 1个 (AddContact用URL param而非body)
结论: 接口路径 ≈80%一致, 缺失3个端点, 1个端点路径结构不同
2. 请求参数对比
Create Company
| 字段 |
Chatwoot |
GoChat |
状态 |
| name |
✅ (required, permit) |
✅ (required, validate:min=1) |
✅ 兼容 |
| domain |
✅ (permit) |
✅ (json:domain) |
✅ 兼容 |
| description |
✅ (permit) |
✅ (json:description) |
✅ 兼容 |
| avatar |
✅ (permit — file upload) |
❌ 缺失 |
|
| additional_attributes |
✅ (permit: {}) |
❌ 缺失 |
|
| custom_attributes |
✅ (permit: {}) |
✅ (json.RawMessage) |
✅ 兼容 |
Update Company
| 字段 |
Chatwoot |
GoChat |
状态 |
| name |
✅ |
✅ |
✅ 兼容 |
| domain |
✅ |
✅ |
✅ 兼容 |
| description |
✅ |
✅ |
✅ 兼容 |
| avatar |
✅ |
❌ 缺失 |
|
| custom_attributes |
✅ (merge逻辑) |
⚠️ 全量替换 |
❌ 行为不兼容 |
| additional_attributes |
✅ |
❌ 缺失 |
|
Search
| 参数 |
Chatwoot |
GoChat |
状态 |
| q (query string) |
✅ (required — 422 if blank) |
✅ (optional — 空query回退到list) |
⚠️ 行为差异 |
| page |
✅ |
✅ |
✅ 兼容 |
| sort |
✅ (Sift gem: name/domain/created_at/last_activity_at/contacts_count) |
✅ (name/domain/created_at/last_activity_at) |
⚠️ GoChat缺少contacts_count排序 |
List (index)
| 参数 |
Chatwoot |
GoChat |
状态 |
| page |
✅ |
✅ |
✅ 兼容 |
| sort |
✅ |
✅ (同上支持) |
⚠️ 缺contacts_count排序 |
| per_page |
— (Chatwoot固定25) |
✅ (动态page_size) |
⚠️ Chatwoot固定25 |
Notes (create)
| 参数 |
Chatwoot |
GoChat |
状态 |
| content |
— (Chatwoot无create) |
✅ (required) |
GoChat扩展功能 |
结论: 请求参数 ≈75%一致, 缺失avatar/additional_attributes, custom_attributes更新行为不兼容
3. 响应格式对比
GoChat当前响应envelope (APIResponse struct):
Chatwoot list/search envelope:
响应envelope差异:
| 属性 |
Chatwoot |
GoChat |
状态 |
| 顶层key |
payload (数组) / payload (单对象) |
data |
⚠️ GoChat用统一data而非payload |
| total_count |
✅ |
✅ (total_count in meta) |
✅ 兼容 |
| page |
✅ |
✅ |
✅ 兼容 |
| success字段 |
❌ (Chatwoot无) |
✅ |
⚠️ GoChat额外字段 |
| per_page |
❌ (Chatwoot不输出) |
✅ |
⚠️ GoChat额外字段 |
说明: GoChat现在使用统一的APIResponse{success, data, meta} envelope。与Chatwoot的payload key不同,但使用total_count而非count。这是一个系统性设计选择(全项目统一),而非Companies模块独有。
Company单对象字段对比:
| 字段 |
Chatwoot |
GoChat |
状态 |
| id |
✅ (integer) |
✅ (integer) |
✅ 兼容 |
| name |
✅ |
✅ |
✅ 兼容 |
| domain |
✅ |
✅ |
✅ 兼容 |
| description |
✅ |
✅ |
✅ 兼容 |
| custom_attributes |
✅ (jsonb) |
✅ (jsonb) |
✅ 兼容 |
| contacts_count |
✅ (counter cache列) |
❌ 缺失 |
❌ |
| avatar_url |
✅ |
❌ 缺失 |
❌ |
| additional_attributes |
✅ (schema有但view不渲染) |
❌ 缺失字段 |
⚠️ |
| account_id |
❌ (Chatwoot不输出) |
✅ |
⚠️ GoChat额外暴露 |
| website_url |
❌ (Chatwoot无此字段) |
✅ |
⚠️ GoChat额外字段 |
| favicon_url |
❌ (Chatwoot用avatar_url) |
✅ |
⚠️ GoChat额外字段 |
| last_activity_at |
✅ (Unix timestamp integer) |
⚠️ (RFC3339 string) |
❌ 格式不兼容 |
| created_at |
✅ (Unix timestamp integer) |
⚠️ (RFC3339 string) |
❌ 格式不兼容 |
| updated_at |
✅ (Unix timestamp integer) |
⚠️ (RFC3339 string) |
❌ 格式不兼容 |
嵌套资源envelope:
| 资源 |
Chatwoot envelope |
GoChat envelope |
状态 |
| contacts |
payload[] + meta{total_count} |
data[] + meta{total_count,page,per_page} |
⚠️ key不同 |
| conversations |
payload[] (无分页meta) |
data[] + meta{total_count,page,per_page} |
⚠️ key不同; GoChat多分页 |
| notes |
payload[] (无分页meta) |
data[] + meta{total_count,page,per_page} |
⚠️ key不同; GoChat多分页 |
结论: 响应格式 ≈60%一致 — envelope key(data vs payload)、时间戳格式(RFC3339 vs Unix)、缺失字段(contacts_count/avatar_url)是三大不兼容项
4. 错误处理与状态码对比
| 场景 |
Chatwoot |
GoChat |
状态 |
| Create成功 |
200 (implicit) |
201 |
⚠️ GoChat用201更符合REST,但与Chatwoot不一致 |
| Create验证失败 |
— (Rails异常) |
400 (VALIDATION_ERROR) |
⚠️ 行为差异 |
| Update成功 |
200 |
200 |
✅ 兼容 |
| Delete成功 |
200 空body |
204 No Content |
⚠️ Chatwoot返回200空body, GoChat返回204 |
| Search空query |
422 {error:...} |
回退到list (200) |
❌ 不兼容 |
| Company不存在 |
404 |
404 (handleServiceError now detects "not found") |
✅ 已修复 |
| 权限不足 |
403 (Pundit) |
— |
❌ 缺失 — GoChat无权限检查 |
| Companies未启用 |
403 (feature flag) |
— |
❌ 缺失 |
结论: 错误处理 ≈50%一致 — 404已修复是好消息, 但Search空query、权限检查、Delete状态码仍不兼容
5. 业务逻辑对比
| 功能 |
Chatwoot |
GoChat |
状态 |
| 排序(Sift) |
✅ name/domain/created_at/last_activity_at/contacts_count |
✅ name/domain/created_at/last_activity_at |
⚠️ 缺contacts_count排序 |
| 分页 |
✅ 固定25/page |
✅ 动态per_page |
⚠️ 默认值不同 |
| 搜索ILIKE |
✅ name/domain |
✅ name/domain/description |
⚠️ GoChat多搜description |
| Contacts搜索 |
✅ name/email/phone/identifier |
❌ 缺失端点 |
❌ |
| Conversations分页 |
❌ (限20, 无分页) |
✅ (有分页) |
⚠️ 行为差异 |
| Notes分页 |
❌ (限20, 无分页) |
✅ (有分页) |
⚠️ 行为差异 |
| Domain唯一性 |
✅ (scoped to account_id) |
❌ 缺失验证 |
❌ |
| Domain格式验证 |
✅ (regex) |
❌ 缺失 |
❌ |
| Custom attributes更新 |
✅ merge逻辑 |
❌ 全量替换 |
❌ 行为不兼容 |
| Avatar处理 |
✅ (ActiveStorage + purge) |
❌ 缺失整个avatar功能 |
❌ |
| Favicon自动获取 |
✅ (AvatarFromFaviconJob) |
❌ 缺失 |
⚠️ |
| Contact关联模式 |
✅ 1:N (company_id FK) |
⚠️ M:N (company_contacts关联表) |
❌ 架构不兼容 |
| Last activity rollup |
✅ (5min interval) |
❌ 缺失 |
❌ |
| Contacts count cache |
✅ (contacts_count列) |
❌ 缺失 |
❌ |
结论: 业务逻辑 ≈40%一致 — Contact关联架构、custom_attributes merge、domain验证、avatar、last activity是关键缺失
6. 与旧报告(首次验收)对比 — 已修复项
| 项 |
旧报告状态 |
当前状态 |
变化 |
| AddContact端点 |
❌ 缺失 |
✅ 已实现 (POST /:company_id/contacts/:contact_id) |
✅ 修复 |
| RemoveContact端点 |
❌ 缺失 |
✅ 已实现 (DELETE /:company_id/contacts/:contact_id) |
✅ 修复 |
| Company不存在返回500 |
❌ 返回500 |
✅ 返回404 (handleServiceError检测"not found") |
✅ 修复 |
| 响应envelope key |
❌ companies/contacts/notes/conversations |
✅ 统一data (APIResponse) |
⚠️ 改善了结构性,但key名仍是data而非payload |
| total_count key |
❌ count |
✅ total_count (APIResponse.MetaBody) |
✅ 修复 |
| Delete返回200+JSON |
❌ 200 + {"message":"company deleted"} |
✅ 204 No Content |
⚠️ 改善了但Chatwoot是200空body而非204 |
| 排序支持 |
❌ 固定created_at DESC |
✅ name/domain/created_at/last_activity_at |
✅ 修复 |
7. 验收结论
验收结果: ❌ 不合格
5项验收标准评分:
- 接口路径一致: ≈80% — 缺3个端点, 1个路径差异
- 请求参数一致: ≈75% — 缺avatar/additional_attributes, custom_attributes行为不同
- 响应格式一致: ≈60% — envelope key(data vs payload)、时间戳格式(RFC3339 vs Unix)、缺失字段
- 错误处理与状态码一致: ≈50% — Search空query、权限检查、Delete状态码
- 业务逻辑一致: ≈40% — Contact关联架构、custom_attributes merge、domain验证
综合一致度: ≈61% (5项加权平均)
高优先级不兼容项(影响客户端集成):
- 响应envelope key —
data vs payload (全项目统一设计,改动影响全局)
- 时间戳格式 — RFC3339 string vs Unix integer (created_at/updated_at/last_activity_at)
- Contact关联架构 — M:N vs Chatwoot的1:N (数据模型差异,影响所有嵌套查询)
- Custom attributes更新 — 全量替换 vs merge (破坏渐进更新语义)
- Search空query行为 — 回退到list vs 422 (客户端依赖422做验证)
- 缺失contacts/search端点 — 无法搜索公司关联联系人
- 缺失destroy_custom_attributes端点 — 无法删除指定custom_attributes键
- 缺失avatar功能 — 无上传/删除头像
- 缺失权限检查 — 无Pundit式权限控制
中优先级缺失:
- Domain唯一性/格式验证缺失
- contacts_count counter cache缺失
- last_activity_at rollup缺失
- additional_attributes字段缺失
低优先级差异(可容忍):
- 分页默认值不同(25 vs 动态)
- Conversations/Notes分页(Chatwoot限20 vs GoChat分页)
- GoChat额外字段(website_url/favicon_url/account_id)
- Delete返回204而非200空body
- Create返回201而非200
建议下一步:
- 优先修复(影响客户端): 时间戳格式、envelope key、custom_attributes merge、Search空query行为
- 其次修复(功能完善): contacts/search端点、destroy_custom_attributes、domain验证、avatar功能
- 架构决策: Contact关联模式是否统一到Chatwoot的1:N (需要CTO评估)
- 全项目层面: envelope key(data vs payload)是统一设计,需要与CTO确认是否全局切换