Files
gochat/scripts/route_gap_analysis.py
T
2026-06-04 15:44:48 +08:00

221 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""
从GoChat router.go提取所有已注册的HTTP路由(带完整group路径),
与Chatwoot 473条路由做对比,输出差距报告。
"""
import re
# ============ Step 1: 提取GoChat已注册路由 ============
with open('/home/yanghao05/Workspace/gochat/internal/router/router.go') as f:
text = f.read()
lines = text.split('\n')
gochat_routes = [] # [(method, path)]
# 跟踪group嵌套路径前缀
group_stack = [] # stack of (prefix, is_named_param)
def normalize_go_path(raw_path):
"""将Go路由路径转为Chatwoot风格的路径以便对比"""
# :param → :param_name (保持原样)
return raw_path
i = 0
while i < len(lines):
line = lines[i].strip()
# Skip comments and empty lines
if line.startswith('//') or line == '' or line.startswith('/*') or line.startswith('*'):
i += 1
continue
# Match Group declarations: r.Group("/path") or engine.Group("/path")
group_match = re.match(r'(\w+)\.Group\("([^"]+)"\)', line)
if group_match:
prefix = group_match.group(2)
group_stack.append(prefix)
i += 1
continue
# Match route registration: .GET("/path", handler) etc.
route_match = re.match(r'\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\("([^"]+)"\s*,', line)
if route_match:
method = route_match.group(1)
path = route_match.group(2)
full_path = ''.join(group_stack) + path
gochat_routes.append((method, full_path))
i += 1
continue
# Detect closing braces that pop group stack
# Count { and } to track nesting
open_braces = line.count('{')
close_braces = line.count('}')
if close_braces > 0 and group_stack:
# Each } pops one group
for _ in range(close_braces - open_braces):
if group_stack:
group_stack.pop()
i += 1
print(f"GoChat registered routes: {len(gochat_routes)}")
# ============ Step 2: 加载Chatwoot路由清单 ============
# 从manifest文件导入
import sys
sys.path.insert(0, '/home/yanghao05/Workspace/gochat/scripts')
from chatwoot_routes_manifest import ALL_CHATWOOT, CHATWOOT_V1_NON_ACCOUNT, CHATWOOT_ACCOUNT_SCOPED, CHATWOOT_V2, CHATWOOT_INTEGRATIONS, CHATWOOT_WIDGET, CHATWOOT_PUBLIC, CHATWOOT_PLATFORM, CHATWOOT_ENTERPRISE, CHATWOOT_WEBHOOKS, CHATWOOT_WS, CHATWOOT_HEALTH
print(f"Chatwoot total routes: {len(ALL_CHATWOOT)}")
# ============ Step 3: 路径规范化对比 ============
def normalize_for_compare(method, path):
"""规范化路径以便对比: 去掉版本号差异,统一参数命名"""
# GoChat可能用 :accountId 而 Chatwoot用 :account_id
# 统一为 :xxx 格式
p = re.sub(r':(\w+)', ':param', path)
# 去掉尾部斜杠
p = p.rstrip('/')
return f"{method} {p}"
# 构建GoChat路由集合(规范化后)
gochat_set = set()
gochat_original = {} # norm_key → original route
for method, path in gochat_routes:
norm = normalize_for_compare(method, path)
gochat_set.add(norm)
gochat_original[norm] = (method, path)
# 构建Chatwoot路由集合(规范化后)
chatwoot_set = set()
chatwoot_original = {} # norm_key → original route
for method, path, desc in ALL_CHATWOOT:
norm = normalize_for_compare(method, path)
chatwoot_set.add(norm)
chatwoot_original[norm] = (method, path, desc)
# ============ Step 4: 差距分析 ============
missing = chatwoot_set - gochat_set
covered = chatwoot_set & gochat_set
extra_go = gochat_set - chatwoot_set
print(f"\n=== 差距概览 ===")
print(f"Chatwoot路由: {len(chatwoot_set)}")
print(f"GoChat路由: {len(gochat_set)}")
print(f"已覆盖: {len(covered)}")
print(f"缺失: {len(missing)}")
print(f"GoChat额外路由: {len(extra_go)}")
print(f"覆盖率: {len(covered)/len(chatwoot_set)*100:.1f}%")
# ============ Step 5: 分类缺失路由 ============
categories = {
'V1非Account级': [],
'V1 Account级-核心': [],
'V1 Account级-联系人': [],
'V1 Account级-会话': [],
'V1 Account级-收件箱': [],
'V1 Account级-通知': [],
'V1 Account级-团队/角色/策略': [],
'V1 Account级-集成': [],
'V1 Account级-帮助中心': [],
'V1 Account级-Captain/AI': [],
'V1 Account级-其他': [],
'V2报表': [],
'Widget': [],
'Public API': [],
'Platform API': [],
'Enterprise API': [],
'Webhooks': [],
'WebSocket/Health': [],
}
def classify_route(method, path, desc):
"""将路由分类到对应类别"""
if path.startswith('/auth') or path.startswith('/api/v1/profile') or path.startswith('/api/v1/notification_subscriptions') or path.startswith('/api/v1/auth'):
return 'V1非Account级'
elif path.startswith('/api/v2/'):
return 'V2报表'
elif path.startswith('/widget/'):
return 'Widget'
elif path.startswith('/public/'):
return 'Public API'
elif path.startswith('/platform/'):
return 'Platform API'
elif path.startswith('/enterprise/'):
return 'Enterprise API'
elif path.startswith('/webhooks/') or path.startswith('/webhooks'):
return 'Webhooks'
elif path.startswith('/ws') or path.startswith('/cable') or path.startswith('/health') or path.startswith('/hc/'):
return 'WebSocket/Health'
elif path.startswith('/api/v1/integrations/webhooks'):
return 'V1非Account级'
elif path.startswith('/api/v1/accounts/:param'):
# 按资源子分类
sub = path.replace('/api/v1/accounts/:param/', '')
if sub.startswith('contacts') or sub.startswith('contact_inboxes'):
return 'V1 Account级-联系人'
elif sub.startswith('conversations') or sub.startswith('unread_counts'):
return 'V1 Account级-会话'
elif sub.startswith('inboxes') or sub.startswith('inbox_members') or sub.startswith('inbox_limits'):
return 'V1 Account级-收件箱'
elif sub.startswith('notifications'):
return 'V1 Account级-通知'
elif sub.startswith('teams') or sub.startswith('custom_roles') or sub.startswith('assignment_policies') or sub.startswith('sla_policies') or sub.startswith('agent_capacity_policies'):
return 'V1 Account级-团队/角色/策略'
elif sub.startswith('integrations') or sub.startswith('webhooks') or sub.startswith('twitter') or sub.startswith('microsoft') or sub.startswith('google') or sub.startswith('instagram') or sub.startswith('tiktok') or sub.startswith('notion') or sub.startswith('whatsapp/authorization'):
return 'V1 Account级-集成'
elif sub.startswith('portals') or sub.startswith('categories') or sub.startswith('articles'):
return 'V1 Account级-帮助中心'
elif sub.startswith('captain'):
return 'V1 Account级-Captain/AI'
else:
return 'V1 Account级-核心'
return 'V1 Account级-其他'
for norm_key in sorted(missing):
method, path, desc = chatwoot_original[norm_key]
cat = classify_route(method, path, desc)
categories[cat].append((method, path, desc))
print(f"\n=== 缺失路由分类统计 ===")
for cat, routes in sorted(categories.items()):
if routes:
print(f"\n--- {cat} ({len(routes)}条缺失) ---")
for method, path, desc in routes:
print(f" {method} {path}{desc}")
# ============ Step 6: 输出GoChat额外路由 ============
print(f"\n=== GoChat额外路由(Chatwoot无对应) ===")
for norm_key in sorted(extra_go):
method, path = gochat_original[norm_key]
print(f" {method} {path}")
# ============ Step 7: 保存差距报告到文件 ============
report_lines = []
report_lines.append("# GoChat vs Chatwoot 路由差距报告")
report_lines.append(f"\n生成时间: {__import__('datetime').datetime.now().isoformat()}")
report_lines.append(f"\n## 概览")
report_lines.append(f"- Chatwoot路由总数: {len(chatwoot_set)}")
report_lines.append(f"- GoChat路由总数: {len(gochat_set)}")
report_lines.append(f"- 已覆盖: {len(covered)} ({len(covered)/len(chatwoot_set)*100:.1f}%)")
report_lines.append(f"- 缺失: {len(missing)}")
report_lines.append(f"- GoChat额外: {len(extra_go)}")
report_lines.append(f"\n## 缺失路由清单")
for cat, routes in sorted(categories.items()):
if routes:
report_lines.append(f"\n### {cat} ({len(routes)}条)")
for method, path, desc in routes:
report_lines.append(f"- `{method} {path}` → {desc}")
report_lines.append(f"\n## GoChat额外路由")
for norm_key in sorted(extra_go):
method, path = gochat_original[norm_key]
report_lines.append(f"- `{method} {path}`")
with open('/home/yanghao05/Workspace/gochat/ROUTE_GAP_REPORT.md', 'w') as f:
f.write('\n'.join(report_lines))
print(f"\n差距报告已保存到 ROUTE_GAP_REPORT.md")