a-cloud-all/.devops/scripts/dingtalk.py

255 lines
6.4 KiB
Python
Raw Normal View History

2026-01-27 14:17:44 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
钉钉 Webhook 通知模块
用于发送构建和部署通知到钉钉群
"""
import time
import hmac
import hashlib
import base64
import urllib.parse
import urllib.request
import json
from datetime import datetime
class DingTalkNotifier:
"""钉钉通知器"""
def __init__(self, access_token, secret=None):
"""
初始化钉钉通知器
Args:
access_token: 钉钉机器人的 access_token
secret: 钉钉机器人的加签密钥可选
"""
self.access_token = access_token
self.secret = secret
self.base_url = "https://oapi.dingtalk.com/robot/send"
def _generate_sign(self, timestamp):
"""
生成钉钉加签
Args:
timestamp: 当前时间戳毫秒
Returns:
签名字符串
"""
if not self.secret:
return None
string_to_sign = f'{timestamp}\n{self.secret}'
string_to_sign_enc = string_to_sign.encode('utf-8')
secret_enc = self.secret.encode('utf-8')
hmac_code = hmac.new(
secret_enc,
string_to_sign_enc,
digestmod=hashlib.sha256
).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
return sign
def _build_url(self):
"""
构建完整的 webhook URL
Returns:
完整的 URL 字符串
"""
url = f"{self.base_url}?access_token={self.access_token}"
if self.secret:
timestamp = str(round(time.time() * 1000))
sign = self._generate_sign(timestamp)
url += f"&timestamp={timestamp}&sign={sign}"
return url
def send_text(self, content, at_mobiles=None, at_all=False):
"""
发送文本消息
Args:
content: 消息内容
at_mobiles: @的手机号列表
at_all: 是否@所有人
Returns:
发送结果 (True/False)
"""
data = {
"msgtype": "text",
"text": {
"content": content
},
"at": {
"atMobiles": at_mobiles or [],
"isAtAll": at_all
}
}
return self._send_request(data)
def send_build_success(self, repo_name, branch, commit_hash, duration):
"""
发送构建成功通知
Args:
repo_name: 仓库名称
branch: 分支名称
commit_hash: 提交哈希
duration: 构建耗时
Returns:
发送结果 (True/False)
"""
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
title = f"✅ 构建成功 - {repo_name}"
text = f"""### ✅ 构建成功
**仓库**: {repo_name}
**分支**: {branch}
**提交**: {commit_hash[:8]}
**耗时**: {duration:.1f}
**时间**: {now}
---
构建和部署已完成
"""
return self.send_markdown(title, text)
def send_build_failure(self, repo_name, branch, commit_hash, error_msg, at_all=False):
"""
发送构建失败通知
Args:
repo_name: 仓库名称
branch: 分支名称
commit_hash: 提交哈希
error_msg: 错误信息
at_all: 是否@所有人
Returns:
发送结果 (True/False)
"""
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
title = f"❌ 构建失败 - {repo_name}"
# 限制错误信息长度
error_display = error_msg[:500] if len(error_msg) > 500 else error_msg
text = f"""### ❌ 构建失败
**仓库**: {repo_name}
**分支**: {branch}
**提交**: {commit_hash[:8]}
**时间**: {now}
**错误信息**:
```
{error_display}
```
---
请检查日志并修复问题
"""
return self.send_markdown(title, text, at_all=at_all)
2026-01-27 14:39:51 +08:00
def send_build_start(self, repo_name, branch, commit_hash, commit_message=None, server_ip=None):
2026-01-27 14:17:44 +08:00
"""
发送构建开始通知
Args:
repo_name: 仓库名称
branch: 分支名称
commit_hash: 提交哈希
2026-01-27 14:39:51 +08:00
commit_message: 提交消息可选
server_ip: 服务器IP可选
2026-01-27 14:17:44 +08:00
Returns:
发送结果 (True/False)
"""
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
title = f"🚀 构建开始 - {repo_name}"
2026-01-27 14:39:51 +08:00
# 构建消息内容
2026-01-27 14:17:44 +08:00
text = f"""### 🚀 构建开始
**仓库**: {repo_name}
**分支**: {branch}
2026-01-27 14:39:51 +08:00
**提交**: {commit_hash[:8]}"""
if commit_message:
text += f"\n**消息**: {commit_message}"
if server_ip:
text += f"\n**服务器**: {server_ip}"
text += f"""
2026-01-27 14:17:44 +08:00
**时间**: {now}
---
构建任务已启动请稍候...
"""
return self.send_markdown(title, text)
def _send_request(self, data):
"""
发送 HTTP 请求到钉钉 webhook
Args:
data: 请求数据字典
Returns:
发送结果 (True/False)
"""
try:
url = self._build_url()
headers = {'Content-Type': 'application/json'}
json_data = json.dumps(data).encode('utf-8')
req = urllib.request.Request(url, data=json_data, headers=headers)
response = urllib.request.urlopen(req, timeout=10)
result = json.loads(response.read().decode('utf-8'))
if result.get('errcode') == 0:
return True
else:
print(f"钉钉通知发送失败: {result.get('errmsg')}")
return False
except Exception as e:
print(f"钉钉通知发送异常: {e}")
return False
def send_markdown(self, title, text, at_mobiles=None, at_all=False):
"""
发送 Markdown 消息
Args:
title: 消息标题
text: Markdown 格式的消息内容
at_mobiles: @的手机号列表
at_all: 是否@所有人
Returns:
发送结果 (True/False)
"""
data = {
"msgtype": "markdown",
"markdown": {
"title": title,
"text": text
},
"at": {
"atMobiles": at_mobiles or [],
"isAtAll": at_all
}
}
return self._send_request(data)