diff --git a/.gitea/checker/comparator.py b/.gitea/checker/comparator.py index d447c67..b5faeaa 100644 --- a/.gitea/checker/comparator.py +++ b/.gitea/checker/comparator.py @@ -215,11 +215,6 @@ def compare_parameters( return changes -def _method_identity(ep: ApiEndpoint) -> Tuple[str, str]: - """生成方法的唯一标识:(source_file, method_name)。""" - return (ep.source_file or "", ep.method_name) - - def compare_endpoints( old_endpoints: Dict[str, ApiEndpoint], new_endpoints: Dict[str, ApiEndpoint], @@ -227,142 +222,170 @@ def compare_endpoints( """ 对比新旧两个版本的全部 Controller 端点,生成变更报告列表。 - 匹配策略(方案 1): - - 使用 (source_file, method_name) 作为「同一个 Java 方法」的唯一标识。 - - 只要同一个 Java 方法被修改,就分别判断「请求方式是否变」和「路径是否变」。 - - 支持「同时改请求方式 + 路径」时生成两条独立报告。 - - 支持「同时改请求方式/路径 + 参数」时生成多条独立报告。 + 支持以下变更类型检测: + - HTTP 方法变更(GET → POST 等) + - URI 路径变更(路径重命名) + - 新增 / 删除接口 + - 参数变更 """ reports: List[EndpointChangeReport] = [] - # 1. 构建基于方法标识的映射 - old_by_identity: Dict[Tuple[str, str], List[ApiEndpoint]] = {} - new_by_identity: Dict[Tuple[str, str], List[ApiEndpoint]] = {} + old_keys = set(old_endpoints.keys()) + new_keys = set(new_endpoints.keys()) - for ep in old_endpoints.values(): - identity = _method_identity(ep) - old_by_identity.setdefault(identity, []).append(ep) + removed_keys = old_keys - new_keys + added_keys = new_keys - old_keys + common_keys = old_keys & new_keys - for ep in new_endpoints.values(): - identity = _method_identity(ep) - new_by_identity.setdefault(identity, []).append(ep) + # 收集未匹配的 removed / added + unmatched_removed: List[Tuple[str, ApiEndpoint]] = [] + unmatched_added: List[Tuple[str, ApiEndpoint]] = [] - all_identities = set(old_by_identity.keys()) | set(new_by_identity.keys()) + for key in removed_keys: + unmatched_removed.append((key, old_endpoints[key])) + for key in added_keys: + unmatched_added.append((key, new_endpoints[key])) - for identity in all_identities: - old_list = old_by_identity.get(identity, []) - new_list = new_by_identity.get(identity, []) + matched_removed: Set[str] = set() + matched_added: Set[str] = set() - # 简单场景:同一个方法在旧版和新版各出现一次 - if len(old_list) == 1 and len(new_list) == 1: - old_ep = old_list[0] - new_ep = new_list[0] - - # 判断请求方式是否改变 - if old_ep.http_method != new_ep.http_method: + # 1. HTTP 方法变更检测(uri + controller 相同,但 method 不同) + # 放宽匹配条件:只要同一个 Controller 的同一个 URI 请求方式改变,就识别为「修改请求方式」 + # 不再要求 method_name 相同(允许方法重命名场景) + # 如果同时有参数变更,生成两条独立报告(方法变更 + 参数变更),互不干扰 + for r_key, r_ep in unmatched_removed: + for a_key, a_ep in unmatched_added: + if a_key in matched_added: + continue + if ( + r_ep.uri == a_ep.uri + and r_ep.controller_class == a_ep.controller_class + and r_ep.http_method != a_ep.http_method + ): + # 先生成纯方法变更报告 reports.append( EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, + uri=a_ep.uri, + http_method=a_ep.http_method, + controller_class=a_ep.controller_class, + method_name=a_ep.method_name, + source_file=a_ep.source_file, is_method_changed=True, - old_http_method=old_ep.http_method, + old_http_method=r_ep.http_method, ) ) - # 如果同时有参数变更,额外生成参数报告 - param_changes = compare_parameters(old_ep.parameters, new_ep.parameters) + # 再检测参数变更,如果有则额外生成参数报告 + param_changes = compare_parameters(r_ep.parameters, a_ep.parameters) if param_changes: reports.append( EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, + uri=a_ep.uri, + http_method=a_ep.http_method, + controller_class=a_ep.controller_class, + method_name=a_ep.method_name, + source_file=a_ep.source_file, parameter_changes=param_changes, ) ) + matched_removed.add(r_key) + matched_added.add(a_key) + break - # 判断路径是否改变 - if old_ep.uri != new_ep.uri: + # 2. URI 路径变更检测(method + controller + method_name 相同,但 uri 不同) + # 如果同时有参数变更,生成两条独立报告(路径变更 + 参数变更),互不干扰 + for r_key, r_ep in unmatched_removed: + if r_key in matched_removed: + continue + for a_key, a_ep in unmatched_added: + if a_key in matched_added: + continue + if ( + r_ep.http_method == a_ep.http_method + and r_ep.controller_class == a_ep.controller_class + and r_ep.method_name == a_ep.method_name + and r_ep.uri != a_ep.uri + ): + # 先生成纯路径变更报告 reports.append( EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, + uri=a_ep.uri, + http_method=a_ep.http_method, + controller_class=a_ep.controller_class, + method_name=a_ep.method_name, + source_file=a_ep.source_file, is_renamed_endpoint=True, - old_uri=old_ep.uri, + old_uri=r_ep.uri, ) ) - # 如果同时有参数变更,额外生成参数报告(避免重复) - if old_ep.http_method == new_ep.http_method: - param_changes = compare_parameters(old_ep.parameters, new_ep.parameters) - if param_changes: - reports.append( - EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, - parameter_changes=param_changes, - ) - ) - - # 如果请求方式和路径都没变,只检测参数变更 - if old_ep.http_method == new_ep.http_method and old_ep.uri == new_ep.uri: - param_changes = compare_parameters(old_ep.parameters, new_ep.parameters) + # 再检测参数变更,如果有则额外生成参数报告 + param_changes = compare_parameters(r_ep.parameters, a_ep.parameters) if param_changes: reports.append( EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, + uri=a_ep.uri, + http_method=a_ep.http_method, + controller_class=a_ep.controller_class, + method_name=a_ep.method_name, + source_file=a_ep.source_file, parameter_changes=param_changes, ) ) + matched_removed.add(r_key) + matched_added.add(a_key) + break - elif len(old_list) == 0 and len(new_list) > 0: - # 新增接口 - for new_ep in new_list: - reports.append( - EndpointChangeReport( - uri=new_ep.uri, - http_method=new_ep.http_method, - controller_class=new_ep.controller_class, - method_name=new_ep.method_name, - source_file=new_ep.source_file, - is_new_endpoint=True, - parameter_changes=[ - ParameterChange( - change_type=ChangeType.ADDED, - param_name=p.name, - param_type=p.type, - required=p.required, - ) - for p in new_ep.parameters - ], - ) + # 3. 剩余未匹配的 removed → 删除接口 + for key, ep in unmatched_removed: + if key not in matched_removed: + reports.append( + EndpointChangeReport( + uri=ep.uri, + http_method=ep.http_method, + controller_class=ep.controller_class, + method_name=ep.method_name, + source_file=ep.source_file, + is_removed_endpoint=True, ) + ) - elif len(old_list) > 0 and len(new_list) == 0: - # 删除接口 - for old_ep in old_list: - reports.append( - EndpointChangeReport( - uri=old_ep.uri, - http_method=old_ep.http_method, - controller_class=old_ep.controller_class, - method_name=old_ep.method_name, - source_file=old_ep.source_file, - is_removed_endpoint=True, - ) + # 4. 剩余未匹配的 added → 新增接口 + for key, ep in unmatched_added: + if key not in matched_added: + reports.append( + EndpointChangeReport( + uri=ep.uri, + http_method=ep.http_method, + controller_class=ep.controller_class, + method_name=ep.method_name, + source_file=ep.source_file, + is_new_endpoint=True, + parameter_changes=[ + ParameterChange( + change_type=ChangeType.ADDED, + param_name=p.name, + param_type=p.type, + required=p.required, + ) + for p in ep.parameters + ], ) + ) + + # 5. 共同 URI:对比参数变更 + for key in common_keys: + old_ep = old_endpoints[key] + new_ep = new_endpoints[key] + param_changes = compare_parameters(old_ep.parameters, new_ep.parameters) + if param_changes: + reports.append( + EndpointChangeReport( + uri=new_ep.uri, + http_method=new_ep.http_method, + controller_class=new_ep.controller_class, + method_name=new_ep.method_name, + source_file=new_ep.source_file, + parameter_changes=param_changes, + ) + ) return [r for r in reports if r.has_changes]