# -*- coding: utf-8 -*- # bruno-sync 全量对账(票 04 t9):源码 tags 钥匙 vs 文档仓文件夹钥匙 双向差集 # 钥匙 = (METHOD, url, tag全路径/文档文件夹相对路径) # 修正版:注解区扫描(@Operation 可在 mapping 之前或之后),覆盖四种盲区: # @PreAuthorize 嵌套括号 / 裸字符串 tags / 无括号 @PostMapping / @Operation 在 @PostMapping 之后 import os, sys, io, re sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') REPO = r'D:\code\crm-backend-matt' DOCS = r'D:\code\crm-api-docs' MODS = ['crm-auth','crm-file','crm-lead','crm-opportunity','crm-rule','crm-preference','crm-dict'] BT = chr(96) src = {} for m in MODS: base = os.path.join(REPO, m, 'src', 'main', 'java') for dp, dns, fns in os.walk(base): for fn in sorted(fns): if not fn.endswith('Controller.java'): continue txt = open(os.path.join(dp, fn), encoding='utf-8').read() mt = re.search(r'@RequestMapping\(\s*"([^"]+)"', txt) cls_prefix = mt.group(1) if mt else '' ct = re.search(r'@Tag\((?:[^()]*?)name\s*=\s*"([^"]+)"', txt) or re.search(r'@Tag\(\s*"([^"]+)"', txt) cls_tag = ct.group(1) if ct else '' for mm in re.finditer(r'@(Get|Post|Put|Delete|Patch)Mapping(?:\(([^)]*)\))?', txt): verb, args = mm.group(1), mm.group(2) or '' pm = re.search(r'"([^"]*)"', args) path = pm.group(1) if pm else '' wstart = max(0, mm.start() - 800) pa = re.search(r'public\s', txt[mm.end():mm.end()+600]) bend = mm.end() + (pa.start() if pa else 600) region = txt[wstart:bend] gs = re.findall(r'@Operation\((?:[^()]|\([^()]*\))*\)', region) g = gs[-1] if gs else '' mtags = [] if g: t = re.search(r'tags\s*=\s*(\{[^}]*\}|"[^"]+")', g, re.S) if t: mtags = re.findall(r'"([^"]+)"', t.group(1)) tags = mtags if mtags else ([cls_tag] if cls_tag else []) url = cls_prefix + path for t in tags: src.setdefault((verb.upper(), url, t), []).append(fn) doc = {} for dp, dns, fns in os.walk(DOCS): dns[:] = [d for d in dns if d.lower() != 'environments'] rel = os.path.relpath(dp, DOCS).replace('\\', '/') tag = '' if rel == '.' else rel for fn in sorted(fns): if not fn.endswith('.bru') or fn in ('folder.bru', 'collection.bru'): continue txt = open(os.path.join(dp, fn), encoding='utf-8').read() mv = re.search(r'^[ \t]*(get|post|put|delete|patch)[ \t]*\{', txt, re.M | re.I) if not mv: continue verb = mv.group(1).upper() ul = re.search(r'url:[^\n]*', txt) if not ul: continue u = ul.group(0).split(':', 1)[1].strip() if u.startswith(BT) and u.endswith(BT): u = u[1:-1] u = re.sub(r'\{\{[^}]*\}\}', '', u).strip() u = re.sub(r'^https?://[^/\s]+', '', u).strip() u = re.sub(r'^/crm-api(?=/api/)', '', u).strip() u = u.split('?', 1)[0].strip() doc.setdefault((verb, u, tag), []).append(fn) miss_doc = sorted(set(src) - set(doc)) miss_src = sorted(set(doc) - set(src)) print('src keys:', len(src), '; doc keys:', len(doc)) print('--- doc-has-src-missing', len(miss_doc), '---') for k in miss_doc: print(' | '.join(k), ' <=', ','.join(src[k])) print('--- src-has-doc-missing', len(miss_src), '---') for k in miss_src: print(' | '.join(k), ' <=', ','.join(doc[k]))