Compare commits

...

4 Commits

Author SHA1 Message Date
xiaoxia d669b7c736 feat(ci): rebase到最新develop,解决与#326物理分层的冲突
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m32s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m27s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m42s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m21s
2026-07-14 18:12:13 +08:00
xiaoxia c7c410642c feat(ci): 通知卡片化+Frontend Lint缓存+全Job耗时统计
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m42s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m51s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m51s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m17s
2026-07-14 17:40:35 +08:00
xiaoxia 44f94f1c66 feat(ci): 通知卡片化+Frontend Lint缓存+全Job耗时统计 2026-07-14 17:40:06 +08:00
xiaoxia 8713c47133 feat(ci): 统一飞书卡片通知脚本 - 支持开始/成功/失败三种状态 2026-07-14 17:39:56 +08:00
2 changed files with 900 additions and 1616 deletions
+722 -1616
View File
File diff suppressed because one or more lines are too long
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
统一CI通知脚本 - 发送飞书卡片通知
支持三种模式: start / success / failure
包含: PR链接、耗时、失败阶段、分支、提交者、Run链接
用法:
NOTIFY_MODE=start JOB_NAME="xxx" python3 scripts/ci_notify.py
NOTIFY_MODE=success JOB_NAME="xxx" JOB_DURATION="2m30s" python3 scripts/ci_notify.py
NOTIFY_MODE=failure JOB_NAME="xxx" FAILED_STEP="xxx" JOB_DURATION="2m30s" python3 scripts/ci_notify.py
环境变量:
CI_NOTIFY_WEBHOOK - 飞书webhook地址 (必填)
NOTIFY_MODE - 通知模式: start / success / failure (必填)
JOB_NAME - Job名称 (必填)
JOB_DURATION - 耗时,如"2m30s" (成功/失败时建议传)
FAILED_STEP - 失败的步骤名 (失败时建议传)
GITHUB_REF_NAME - 分支名
GITHUB_SHA - commit SHA
GITHUB_ACTOR - 提交者
GITHUB_RUN_ID - Run ID
GITHUB_REPOSITORY - 仓库路径
GITHUB_EVENT_NAME - 事件类型 (pull_request / push / ...)
GITHUB_PR_NUMBER - PR编号 (PR事件时)
GITHUB_PR_TITLE - PR标题 (PR事件时)
"""
import json
import os
import sys
import urllib.request
def get_env(name, default=""):
return os.environ.get(name, default)
def format_duration(seconds_str):
"""将秒数格式化为易读形式"""
try:
seconds = int(float(seconds_str))
mins = seconds // 60
secs = seconds % 60
if mins > 0:
return f"{mins}m{secs}s"
return f"{secs}s"
except (ValueError, TypeError):
return seconds_str or "未知"
def main() -> int:
webhook = get_env("CI_NOTIFY_WEBHOOK")
if not webhook:
print("未配置 CI_NOTIFY_WEBHOOK,跳过通知")
print("如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK")
return 0
mode = get_env("NOTIFY_MODE", "failure").lower()
job_name = get_env("JOB_NAME", "Unknown Job")
duration = get_env("JOB_DURATION")
if not duration:
duration_sec = get_env("JOB_DURATION_SECONDS")
duration = format_duration(duration_sec) if duration_sec else "计算中..."
failed_step = get_env("FAILED_STEP", "")
branch = get_env("GITHUB_REF_NAME", "unknown")
commit = get_env("GITHUB_SHA", "unknown")[:8]
actor = get_env("GITHUB_ACTOR", "unknown")
run_id = get_env("GITHUB_RUN_ID", "unknown")
repo = get_env("GITHUB_REPOSITORY", "unknown")
event_name = get_env("GITHUB_EVENT_NAME", "")
pr_number = get_env("GITHUB_PR_NUMBER", "")
pr_title = get_env("GITHUB_PR_TITLE", "")
run_url = f"https://git.xiaoxiajianji.com/{repo}/actions/runs/{run_id}"
# 根据模式设置标题、状态、颜色
if mode == "start":
title = "🔄 CI 任务开始"
status = "blue"
button_text = "查看进度"
button_type = "primary"
elif mode == "success":
title = f"{job_name} 成功"
status = "green"
button_text = "查看详情"
button_type = "primary"
else: # failure
title = f"{job_name} 失败"
status = "red"
button_text = "查看失败日志"
button_type = "danger"
# 构建卡片内容
content_lines = []
content_lines.append(f"**任务**: {job_name}")
if mode != "start":
content_lines.append(f"**耗时**: {duration}")
if mode == "failure" and failed_step:
content_lines.append(f"**失败阶段**: {failed_step}")
# PR信息
if event_name == "pull_request" and pr_number:
pr_url = f"https://git.xiaoxiajianji.com/{repo}/pulls/{pr_number}"
pr_display = f"#{pr_number}"
if pr_title:
pr_display += f" {pr_title}"
content_lines.append(f"**PR**: [{pr_display}]({pr_url})")
elif event_name == "push":
content_lines.append(f"**分支**: {branch}")
content_lines.append(f"**提交**: `{commit}`")
content_lines.append(f"**提交者**: {actor}")
content_lines.append(f"**Run ID**: {run_id}")
payload = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": title,
},
"status": status,
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "\n".join(content_lines),
},
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"tag": "plain_text", "content": button_text},
"url": run_url,
"type": button_type,
}
],
},
],
},
}
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
webhook,
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as resp:
resp_body = resp.read().decode("utf-8")
print(f"通知已发送 ({mode})")
# 飞书返回code=0表示成功
try:
result = json.loads(resp_body)
if result.get("code", 0) != 0:
print(f"飞书返回错误: {result.get('msg', resp_body)}", file=sys.stderr)
return 1
except json.JSONDecodeError:
pass
except Exception as e:
print(f"通知发送失败: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())