#!/usr/bin/env python3 """Precise comparison of GoChat routes vs Chatwoot routes. Normalizes parameter names (:account_id → :id, etc.) for fair comparison. """ import re import sys def normalize(path): """Normalize route params to generic :id for comparison.""" # Replace all :xxx_id variants with :id path = re.sub(r':[a-zA-Z_]+_id', ':id', path) # Replace :website_token, :thread_id, :slug, :identifier, etc. with :id path = re.sub(r':(website_token|thread_id|slug|identifier|channel_type|fb_id|locale|category_slug|article_slug|portal_id|user_id)', ':id', path) # Remove trailing slashes path = path.rstrip('/') return path def load_routes(filepath): routes = {} with open(filepath) as f: for line in f: line = line.strip() if not line: continue parts = line.split(' ', 1) if len(parts) == 2: method, path = parts norm = normalize(path) routes[(method, norm)] = (method, path) return routes def main(): gochat = load_routes('/tmp/gochat_routes_all.txt') chatwoot = load_routes('/tmp/chatwoot_routes_all.txt') gochat_keys = set(gochat.keys()) chatwoot_keys = set(chatwoot.keys()) matched = gochat_keys & chatwoot_keys missing_in_gochat = chatwoot_keys - gochat_keys extra_in_gochat = gochat_keys - chatwoot_keys print("=" * 80) print("GoChat vs Chatwoot 路由对比报告") print("=" * 80) print(f"\nGoChat 路由总数: {len(gochat_keys)}") print(f"Chatwoot API路由总数: {len(chatwoot_keys)}") print(f"匹配路由数: {len(matched)}") print(f"GoChat缺失路由数: {len(missing_in_gochat)}") print(f"GoChat多余路由数: {len(extra_in_gochat)}") print(f"覆盖率: {len(matched)/len(chatwoot_keys)*100:.1f}%") # Group missing routes by resource prefix print(f"\n{'=' * 80}") print("GoChat缺失路由 (按资源分组)") print("=" * 80) by_resource = {} for key in sorted(missing_in_gochat): method, norm_path = key orig_method, orig_path = chatwoot[key] # Extract resource name m = re.match(r'/api/v1(/platform/api/v1)?(/public)?(/[a-zA-Z_]+(?:_scope)?/?)?(/[a-zA-Z_]+)', norm_path) if m: resource = m.group(0) else: resource = norm_path.split('/:id')[0] if '/:id' in norm_path else norm_path by_resource.setdefault(resource, []).append((method, orig_path, norm_path)) for resource in sorted(by_resource.keys()): routes = by_resource[resource] print(f"\n [{resource}] — 缺失{len(routes)}条路由:") for method, orig_path, norm_path in sorted(routes): print(f" {method} {orig_path}") # Group extra routes print(f"\n{'=' * 80}") print("GoChat多余路由 (Chatwoot中不存在)") print("=" * 80) by_resource_extra = {} for key in sorted(extra_in_gochat): method, norm_path = key orig_method, orig_path = gochat[key] # Extract top-level prefix parts = norm_path.split('/') if len(parts) >= 4: resource = '/' + '/'.join(parts[:4]) else: resource = norm_path by_resource_extra.setdefault(resource, []).append((method, orig_path)) for resource in sorted(by_resource_extra.keys()): routes = by_resource_extra[resource] print(f"\n [{resource}] — 多余{len(routes)}条路由:") for method, orig_path in sorted(routes): print(f" {method} {orig_path}") if __name__ == '__main__': main()