From f7fc0dd45399363f82111a64811c01dd21bd9994 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:04:13 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/checker/comparator.py | 9 ++++-- .gitea/checker/controller_ast_parser.py | 30 +++++++++++++++++++ .gitea/checker/notifier.py | 4 +-- .../controller/CultureClockInController.java | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/.gitea/checker/comparator.py b/.gitea/checker/comparator.py index b5faeaa..a38d8da 100644 --- a/.gitea/checker/comparator.py +++ b/.gitea/checker/comparator.py @@ -31,6 +31,7 @@ class ParameterChange: required: Optional[bool] = None old_required: Optional[bool] = None detail: Optional[str] = None + description: Optional[str] = None def to_markdown_line(self, *, plain: bool = False) -> str: """ @@ -46,7 +47,8 @@ class ParameterChange: if req_required else '可选' ) - return f'> `{self.param_type}` **{self.param_name}** · {tag}' + desc = f" {self.description}" if self.description else "" + return f'> `{self.param_type}` **{self.param_name}** · {tag}{desc}' if self.change_type == ChangeType.REMOVED: return ( @@ -59,9 +61,10 @@ class ParameterChange: if req_required else '可选' ) + desc = f" {self.description}" if self.description else "" return ( f'【新增】 ' - f'`{self.param_type}` **{self.param_name}** · {tag}' + f'`{self.param_type}` **{self.param_name}** · {tag}{desc}' ) if self.change_type == ChangeType.RENAMED: return ( @@ -209,6 +212,7 @@ def compare_parameters( param_name=param.name, param_type=param.type, required=param.required, + description=param.description, ) ) @@ -365,6 +369,7 @@ def compare_endpoints( param_name=p.name, param_type=p.type, required=p.required, + description=p.description, ) for p in ep.parameters ], diff --git a/.gitea/checker/controller_ast_parser.py b/.gitea/checker/controller_ast_parser.py index ba76cc1..47d46f4 100644 --- a/.gitea/checker/controller_ast_parser.py +++ b/.gitea/checker/controller_ast_parser.py @@ -233,6 +233,27 @@ def _param_required(param: FormalParameter) -> bool: return not _has_ann(param, "Nullable") +def _param_description(param: FormalParameter) -> Optional[str]: + """提取参数描述,优先 Swagger 注解,其次 @RequestParam 的 value。""" + # 1. Swagger @ApiParam + ann = _find_ann(param, "ApiParam") + if ann: + desc = _ann_string(ann, "value", "name") + if desc: + return desc + + # 2. Swagger @ApiImplicitParam(方法级注解,较少见,暂不实现) + + # 3. Fallback: @RequestParam 的 value 作为描述提示 + ann = _find_ann(param, "RequestParam") + if ann: + val = _ann_string(ann, "value", "name") + if val and val != param.name: + return val + + return None + + class ControllerAstParser: """ 基于 javalang 的 Controller 解析器。 @@ -325,12 +346,14 @@ class ControllerAstParser: if _has_ann(param, "RequestBody"): return self._expand_dto(type_name, "body") + desc = _param_description(param) return [ ApiParameter( name=name, type=type_name, required=_param_required(param), source=_param_source(param), + description=desc, ) ] @@ -361,12 +384,19 @@ class ControllerAstParser: if "static" in (field.modifiers or []): continue for decl in field.declarators: + # 尝试提取 @ApiModelProperty 的描述 + desc = None + api_model_ann = _find_ann(field, "ApiModelProperty") + if api_model_ann: + desc = _ann_string(api_model_ann, "value") + fields.append( ApiParameter( name=decl.name, type=_type_to_str(field.type), required=not _has_ann(field, "Nullable"), source=source, + description=desc, ) ) diff --git a/.gitea/checker/notifier.py b/.gitea/checker/notifier.py index 3f3ad33..178843e 100644 --- a/.gitea/checker/notifier.py +++ b/.gitea/checker/notifier.py @@ -327,8 +327,8 @@ def send_parameter_change_notification( if changed_reports: # 构建参数变更通知(只包含参数变更报告) parts: List[str] = [] - parts.append("# API参数变更通知") - parts.append(f"- **修改人:** {push_user if push_user.startswith('@') else '@' + push_user}") + parts.append("# 【API参数变更通知】") + parts.append(f"- **修改人:** {push_user}") parts.append(f"- **修改时间:** {push_time}") parts.append("") for report in changed_reports: diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 75a7395..32b3952 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -132,7 +132,7 @@ public class CultureClockInController { * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") - public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) Boolean cursorDate, + public ActionResult getRecordList(@RequestParam(value = "cursorDate1", required = false) Boolean cursorDate, @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); From 0e118e7bc8c4dc591ecffb5560b1a8ae1c22c239 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:15:09 +0800 Subject: [PATCH 2/7] =?UTF-8?q?@ApiParam=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ftb/test/controller/CultureClockInController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 32b3952..3cd7f3d 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -132,7 +132,11 @@ public class CultureClockInController { * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") - public ActionResult getRecordList(@RequestParam(value = "cursorDate1", required = false) Boolean cursorDate, +// public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, +// @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { + @ApiParam(value = "游标日期(yyyy-MM-dd),首次请求不传", required = false) + @ApiParam(value = "返回有数据的天数", required = false) + public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); From 1d05aec86cd98eb7047df392a9616513d018a926 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:16:17 +0800 Subject: [PATCH 3/7] =?UTF-8?q?@ApiParam=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ftb/test/controller/CultureClockInController.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 3cd7f3d..5f53f89 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -134,10 +134,8 @@ public class CultureClockInController { @GetMapping(value = "/dynamic1") // public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, // @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { - @ApiParam(value = "游标日期(yyyy-MM-dd),首次请求不传", required = false) - @ApiParam(value = "返回有数据的天数", required = false) - public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, - @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { + public ActionResult getRecordList(@ApiParam(value = "游标日期(yyyy-MM-dd),首次请求不传", required = false) @RequestParam(value = "cursorDate1", required = false) String cursorDate, + @ApiParam(value = "返回有数据的天数", required = false) @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); RecordListVo recordList = cultureClockInService.getRecordList(cursorDate, limitNum); From dbcc1a3490638e5d9114a9052023066c55ead9dc Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:20:01 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/checker/controller_ast_parser.py | 49 ++++++++++++++++++- .../controller/CultureClockInController.java | 20 +++++--- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/.gitea/checker/controller_ast_parser.py b/.gitea/checker/controller_ast_parser.py index 47d46f4..2f0e2e5 100644 --- a/.gitea/checker/controller_ast_parser.py +++ b/.gitea/checker/controller_ast_parser.py @@ -254,6 +254,42 @@ def _param_description(param: FormalParameter) -> Optional[str]: return None +def _parse_javadoc_params(source_code: str) -> Dict[str, Dict[str, str]]: + """ + 使用正则从 Java 源代码中提取方法上的 Javadoc @param 描述。 + + 返回结构: + { method_name: { param_name: description } } + """ + javadoc_map: Dict[str, Dict[str, str]] = {} + + # 匹配 Javadoc 块 + 其后的方法声明 + # 简单启发式:Javadoc 后面第一个标识符 + ( 视为方法名 + pattern = re.compile( + r'/\*\*(?P.*?)\*/\s*(?:public|private|protected|static|\s)*' + r'[\w<>\[\],\.\s]+\s+(?P\w+)\s*\(', + re.DOTALL | re.MULTILINE + ) + + for match in pattern.finditer(source_code): + javadoc_block = match.group('javadoc') + method_name = match.group('method') + + param_descs: Dict[str, str] = {} + # 提取 @param 行 + param_pattern = re.compile(r'@param\s+(\w+)\s+([^\n@]+)', re.IGNORECASE) + for p_match in param_pattern.finditer(javadoc_block): + p_name = p_match.group(1) + p_desc = p_match.group(2).strip() + if p_desc: + param_descs[p_name] = p_desc + + if param_descs: + javadoc_map[method_name] = param_descs + + return javadoc_map + + class ControllerAstParser: """ 基于 javalang 的 Controller 解析器。 @@ -268,6 +304,7 @@ class ControllerAstParser: self.repo_root = repo_root self.source_dir = source_dir self._dto_cache: Dict[str, List[ApiParameter]] = {} + self._javadoc_map: Dict[str, Dict[str, str]] = {} # method_name -> param_name -> description def parse_file_content(self, source: str, repo_relative_path: str) -> List[ApiEndpoint]: """ @@ -284,6 +321,9 @@ class ControllerAstParser: print(f"[警告] 解析失败 {repo_relative_path}: {exc}") return endpoints + # 解析当前文件的 Javadoc @param(用于参数描述回退) + self._javadoc_map = _parse_javadoc_params(source) + for type_decl in tree.types or []: if not isinstance(type_decl, ClassDeclaration): continue @@ -323,7 +363,7 @@ class ControllerAstParser: method_path = _ann_string(ann, "value", "path") params = [] for p in method.parameters or []: - params.extend(self._extract_param(p)) + params.extend(self._extract_param(p, method_name=method.name)) return ApiEndpoint( http_method=_http_method(ann_name, ann), @@ -335,7 +375,7 @@ class ControllerAstParser: ) return None - def _extract_param(self, param: FormalParameter) -> List[ApiParameter]: + def _extract_param(self, param: FormalParameter, method_name: Optional[str] = None) -> List[ApiParameter]: """提取方法参数,@RequestBody 展开 DTO 字段;忽略框架注入参数。""" type_name = _type_to_str(param.type) name = _param_name(param) @@ -347,6 +387,11 @@ class ControllerAstParser: return self._expand_dto(type_name, "body") desc = _param_description(param) + + # Javadoc @param 回退(如果注解中没有描述) + if not desc and method_name and method_name in self._javadoc_map: + desc = self._javadoc_map[method_name].get(name) + return [ ApiParameter( name=name, diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 5f53f89..8af14f9 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -42,8 +42,9 @@ public class CultureClockInController { /** * 打卡分享 - 获取随机图片 + * * @param lastCombo 上次组合 - * @param response HttpServletResponse + * @param response HttpServletResponse */ @PostMapping(value = "/random-preview1") public void getRandomPicPreview(@RequestParam(value = "lastCombo1", required = false) String lastCombo1, HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -66,8 +67,9 @@ public class CultureClockInController { /** * 打卡分享 - 获取随机图片[base64] + * * @param lastCombo 上次组合 - * @param response HttpServletResponse + * @param response HttpServletResponse */ // @GetMapping(value = "/random-preview/base641") @PutMapping(value = "/random-preview/base64") @@ -89,8 +91,9 @@ public class CultureClockInController { /** * 打卡分享 - 获取随机图片[base64] + * * @param lastCombo 上次组合 - * @param response HttpServletResponse + * @param response HttpServletResponse */ @DeleteMapping(value = "/random-preview/delete") public ActionResult getRandomPicPreviewBase64(@RequestParam(value = "lastCombo", required = false) String lastCombo, HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -112,6 +115,7 @@ public class CultureClockInController { /** * 打卡分享 - 打卡 + * * @param currentCombo 当前组合 * @return jnpf.base.ActionResult */ @@ -127,15 +131,14 @@ public class CultureClockInController { /** * 打卡动态 - 打卡记录列表 + * * @param cursorDate 游标日期(yyyy-MM-dd)[首次不传] - * @param limitNum 返回有数据的天数 + * @param limitNum 返回有数据的天数 * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") -// public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, -// @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { - public ActionResult getRecordList(@ApiParam(value = "游标日期(yyyy-MM-dd),首次请求不传", required = false) @RequestParam(value = "cursorDate1", required = false) String cursorDate, - @ApiParam(value = "返回有数据的天数", required = false) @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { + public ActionResult getRecordList(@RequestParam(value = "cursorDate1", required = false) String cursorDate, + @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); RecordListVo recordList = cultureClockInService.getRecordList(cursorDate, limitNum); @@ -144,6 +147,7 @@ public class CultureClockInController { /** * 打卡日历 + * * @param year 年 * @return jnpf.base.ActionResult */ From be32bff0bb3fcc0a5004bc1408ec3f0af16a2da4 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:21:16 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ftb/test/controller/CultureClockInController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 8af14f9..8f2caf9 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -137,7 +137,7 @@ public class CultureClockInController { * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") - public ActionResult getRecordList(@RequestParam(value = "cursorDate1", required = false) String cursorDate, + public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate1, @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); From 145f11402989e7ac2826dc8e864088bd091b7ab7 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:22:08 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/ftb/test/controller/CultureClockInController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 8f2caf9..48877ae 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -137,7 +137,7 @@ public class CultureClockInController { * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") - public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate1, + public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30)); From f62a33b2c00ff4b36996c2c3592ccfd81afa7666 Mon Sep 17 00:00:00 2001 From: dongzi Date: Fri, 5 Jun 2026 15:27:22 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E5=AD=97=E6=AE=B5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/checker/controller_ast_parser.py | 20 ++++++++++++------- .../controller/CultureClockInController.java | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.gitea/checker/controller_ast_parser.py b/.gitea/checker/controller_ast_parser.py index 2f0e2e5..20e33fe 100644 --- a/.gitea/checker/controller_ast_parser.py +++ b/.gitea/checker/controller_ast_parser.py @@ -257,17 +257,19 @@ def _param_description(param: FormalParameter) -> Optional[str]: def _parse_javadoc_params(source_code: str) -> Dict[str, Dict[str, str]]: """ 使用正则从 Java 源代码中提取方法上的 Javadoc @param 描述。 + 支持方法声明前存在注解(@GetMapping、@ApiOperation 等)的情况。 返回结构: { method_name: { param_name: description } } """ javadoc_map: Dict[str, Dict[str, str]] = {} - # 匹配 Javadoc 块 + 其后的方法声明 - # 简单启发式:Javadoc 后面第一个标识符 + ( 视为方法名 + # 改进正则:允许 Javadoc 后存在零或多个注解,再匹配方法声明 pattern = re.compile( - r'/\*\*(?P.*?)\*/\s*(?:public|private|protected|static|\s)*' - r'[\w<>\[\],\.\s]+\s+(?P\w+)\s*\(', + r'/\*\*(?P.*?)\*/\s*' + r'(?:@\w+(?:\s*\([^)]*\))?\s*)*' # 跳过注解(支持 @GetMapping(...)) + r'(?:public|private|protected|static|final|\s)*' + r'[\w<>\[\],\.\s]+?\s+(?P\w+)\s*\(', re.DOTALL | re.MULTILINE ) @@ -276,11 +278,15 @@ def _parse_javadoc_params(source_code: str) -> Dict[str, Dict[str, str]]: method_name = match.group('method') param_descs: Dict[str, str] = {} - # 提取 @param 行 - param_pattern = re.compile(r'@param\s+(\w+)\s+([^\n@]+)', re.IGNORECASE) + # 提取 @param,支持多行描述(直到下一个 @ 或结束) + param_pattern = re.compile( + r'@param\s+(\w+)\s+((?:(?!\n\s*@).)+)', + re.IGNORECASE | re.DOTALL + ) for p_match in param_pattern.finditer(javadoc_block): p_name = p_match.group(1) - p_desc = p_match.group(2).strip() + # 合并多行空白为单行 + p_desc = ' '.join(p_match.group(2).split()) if p_desc: param_descs[p_name] = p_desc diff --git a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java index 48877ae..8af14f9 100644 --- a/ftb/src/main/java/ftb/test/controller/CultureClockInController.java +++ b/ftb/src/main/java/ftb/test/controller/CultureClockInController.java @@ -137,7 +137,7 @@ public class CultureClockInController { * @return jnpf.base.ActionResult */ @GetMapping(value = "/dynamic1") - public ActionResult getRecordList(@RequestParam(value = "cursorDate", required = false) String cursorDate, + public ActionResult getRecordList(@RequestParam(value = "cursorDate1", required = false) String cursorDate, @RequestParam(value = "limitNum", required = false, defaultValue = "10") Integer limitNum) { limitNum = Math.max(10, Math.min(limitNum, 30));