Files
AI-Check-Test/.gitea/checker/notifier.py
dongzi 1b19e8366e
All checks were successful
API接口参数变更检测 / api-param-check (push) Successful in 16s
py修改
2026-06-04 09:53:36 +08:00

229 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
企业微信 Markdown 通知模块。
支持加粗、颜色info/comment/warning新增接口与变更接口使用不同展示模板。
"""
import json
import re
from typing import List, Optional
import requests
from comparator import EndpointChangeReport
# 企微 Markdown 单条上限 4096 字符,留余量
MAX_MD_LENGTH = 3800
def truncate_text(text: str, max_length: int = MAX_MD_LENGTH) -> str:
"""截断超长消息。"""
if len(text) <= max_length:
return text
return text[:max_length] + "\n\n<font color=\"comment\">... 消息过长,已截断</font>"
def _format_endpoint_block(report: EndpointChangeReport) -> str:
"""
格式化单个接口块。
新增接口:只列参数,不出现「参数变更」字样。
变更接口:用颜色区分增删改。
删除接口:整接口标红。
"""
uri_line = f"**{report.http_method}** `{report.uri}`"
if report.is_removed_endpoint:
return (
f"### <font color=\"warning\">【已删除接口】</font>\n"
f"{uri_line}\n"
f"<font color=\"comment\">该接口已被移除</font>"
)
if report.is_new_endpoint:
lines = [
f"### <font color=\"info\">【新增接口】</font> {uri_line}",
]
if report.parameter_changes:
for change in report.parameter_changes:
lines.append(change.to_markdown_line(plain=True))
else:
lines.append('<font color="comment">无入参</font>')
return "\n".join(lines)
# 已有接口的参数变更
lines = [f"### {uri_line}"]
if report.parameter_changes:
for change in report.parameter_changes:
lines.append(change.to_markdown_line(plain=False))
else:
lines.append('<font color="comment">(无参数变化)</font>')
return "\n".join(lines)
def build_markdown_notification(
reports: List[EndpointChangeReport],
push_user: str,
push_time: str,
llm_summary: Optional[str] = None,
) -> str:
"""
构建完整 Markdown 通知正文。
:param reports: AST 变更报告
:param push_user: 推送人
:param push_time: 推送时间
:param llm_summary: LLM 兼容性摘要(可选,简短)
:return: Markdown 文本
"""
parts = [
"## <font color=\"warning\">API 接口参数变更通知</font>",
f"**修改人:** {push_user}",
f"**修改时间:** {push_time}",
"",
]
# 新增 Controller全部为新接口与变更接口分组
new_reports = [r for r in reports if r.is_new_endpoint]
changed_reports = [r for r in reports if not r.is_new_endpoint and not r.is_removed_endpoint]
removed_reports = [r for r in reports if r.is_removed_endpoint]
if new_reports:
controllers = sorted({r.controller_class for r in new_reports})
parts.append(f"### <font color=\"info\">新增 Controller</font> `{', '.join(controllers)}`")
parts.append("")
for report in new_reports:
parts.append(_format_endpoint_block(report))
parts.append("")
if changed_reports:
parts.append("### <font color=\"warning\">接口参数变更</font>")
parts.append("")
for report in changed_reports:
parts.append(_format_endpoint_block(report))
parts.append("")
if removed_reports:
parts.append("### <font color=\"warning\">已删除接口</font>")
parts.append("")
for report in removed_reports:
parts.append(_format_endpoint_block(report))
parts.append("")
if llm_summary:
cleaned = llm_summary.strip()
# 去掉 LLM 可能输出的「排除框架注入」类说明
cleaned = re.sub(
r"排除Spring MVC框架自动注入的[^]+",
"",
cleaned,
)
cleaned = re.sub(
r"排除Spring MVC框架自动注入的[`\w/]+[`\w/、/]*[。\.]?",
"",
cleaned,
)
if cleaned:
parts.append("---")
parts.append("### <font color=\"comment\">兼容性提示</font>")
parts.append(cleaned)
return "\n".join(parts).strip()
def _split_markdown(text: str, max_len: int) -> List[str]:
"""按 ### 标题块拆分超长 Markdown。"""
if len(text) <= max_len:
return [text]
lines = text.split("\n")
chunks: List[str] = []
current: List[str] = []
for line in lines:
if line.startswith("### ") and current and len("\n".join(current)) > 200:
chunks.append("\n".join(current))
current = [line]
else:
current.append(line)
if len("\n".join(current)) >= max_len:
chunks.append("\n".join(current))
current = []
if current:
if chunks and len("\n".join(current)) < 200:
chunks[-1] = chunks[-1] + "\n" + "\n".join(current)
else:
chunks.append("\n".join(current))
return chunks or [truncate_text(text)]
def _post_wecom_markdown(webhook_url: str, content: str) -> bool:
"""发送企微 Markdown 消息。"""
if not webhook_url or "YOUR_WECOM_KEY" in webhook_url:
print("[警告] 未配置有效的企业微信 Webhook URL。")
print("--- 通知预览 ---")
print(content[:1000])
return False
payload = {
"msgtype": "markdown",
"markdown": {"content": truncate_text(content)},
}
try:
resp = requests.post(
webhook_url,
headers={"Content-Type": "application/json"},
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
timeout=10,
)
if resp.status_code == 200 and resp.json().get("errcode", 0) == 0:
return True
print(f"[错误] 企微返回异常: {resp.status_code} {resp.text}")
return False
except requests.RequestException as exc:
print(f"[错误] 发送企微消息失败: {exc}")
return False
def send_parameter_change_notification(
webhook_url: str,
reports: List[EndpointChangeReport],
push_user: str,
push_time: str,
llm_review: Optional[str] = None,
mentioned_users: Optional[List[str]] = None,
) -> int:
"""
发送 Markdown 格式的接口参数变更通知。
:param webhook_url: 企微 Webhook
:param reports: 变更报告
:param push_user: 推送人
:param push_time: 推送时间
:param llm_review: LLM 兼容性摘要
:param mentioned_users: @ 成员Markdown 暂不支持,保留参数)
:return: 成功发送条数
"""
if not reports and not llm_review:
print("无接口参数变更,不发送到企业微信")
return 0
full_md = build_markdown_notification(reports, push_user, push_time, llm_review)
segments = _split_markdown(full_md, MAX_MD_LENGTH)
sent = 0
for i, segment in enumerate(segments):
if i > 0:
segment = (
f"<font color=\"comment\">(续 {i + 1}/{len(segments)}</font>\n\n" + segment
)
if _post_wecom_markdown(webhook_url, segment):
sent += 1
print(f"{sent} 条通知已发送到企业微信")
if sent > 0:
print(f"总共发送 {sent} 条通知到企业微信")
return sent