Files
zzyy-cs/zendao_tools/handle_bug.py
2025-12-04 19:54:07 +08:00

406 lines
15 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.
import requests
import hashlib
import json
from bs4 import BeautifulSoup
from datetime import datetime
import time
def send_to_feishu(webhook_url, content, keyword="bug"):
"""
发送消息到飞书机器人
Args:
webhook_url: 飞书机器人的webhook地址
content: 要发送的消息内容
keyword: 关键词,需要包含在消息中
"""
try:
# 构建消息体
message = {
"msg_type": "text",
"content": {
"text": f"{keyword}\n{content}"
}
}
# 发送请求
headers = {'Content-Type': 'application/json'}
response = requests.post(webhook_url,
data=json.dumps(message),
headers=headers,
timeout=10)
if response.status_code == 200:
print(f"✅ 消息发送成功到飞书")
return True
else:
print(f"❌ 飞书消息发送失败: {response.status_code}")
print(f"响应: {response.text}")
return False
except Exception as e:
print(f"❌ 发送飞书消息异常: {e}")
return False
def get_all_bugs_and_send():
"""获取所有产品的Bug并发送到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug列表")
print("=" * 50)
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败: {login_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取所有产品
products_resp = session.get(f"{base_url}/api.php/v1/products")
if products_resp.status_code != 200:
error_msg = f"❌ 获取产品列表失败: {products_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
products = products_resp.json().get('products', [])
print(f"📦 共 {len(products)} 个产品")
all_bugs = []
# 遍历每个产品获取Bug只获取产品ID为2的
for product in products:
product_id = product.get('id')
product_name = product.get('name')
# 只获取产品ID为2的
if product_id != 2:
continue
print(f"\n获取产品 '{product_name}' 的Bug...")
# 获取HTML页面
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 100})
if bugs_resp.status_code == 200:
try:
html = bugs_resp.text
soup = BeautifulSoup(html, "html.parser")
# 找 bug 列表的 table
table = (
soup.find("table", id="bugList")
or soup.find("table", class_="table")
or soup.find("table")
)
if not table:
print(" ❌ 未找到 bug 列表 table")
continue
# 解析表头
header_tr = table.find("tr")
if not header_tr:
print(" ❌ 未找到表头行")
continue
header_map = {}
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
text = th.get_text(strip=True)
if not text:
continue
if "ID" == text or text == "编号":
header_map["id"] = idx
elif "标题" in text:
header_map["title"] = idx
elif "状态" in text:
header_map["status"] = idx
elif "严重" in text:
header_map["severity"] = idx
elif "优先" in text or "优先级" in text:
header_map["pri"] = idx
elif "指派" in text:
header_map["assignedTo"] = idx
elif "创建" in text or "打开" in text:
header_map["openedDate"] = idx
bugs = []
# 遍历表体行
for tr in table.find_all("tr")[1:]:
tds = tr.find_all("td")
if not tds:
continue
# id
bug_id = tr.get("data-id")
if not bug_id and "id" in header_map and header_map["id"] < len(tds):
bug_id = tds[header_map["id"]].get_text(strip=True)
if not bug_id:
continue
# 标题
title = ""
if "title" in header_map and header_map["title"] < len(tds):
cell = tds[header_map["title"]]
link = cell.find("a")
title = (link or cell).get_text(strip=True)
# 状态
status = ""
if "status" in header_map and header_map["status"] < len(tds):
status = tds[header_map["status"]].get_text(strip=True)
# 严重程度
severity = ""
if "severity" in header_map and header_map["severity"] < len(tds):
sev_cell = tds[header_map["severity"]]
severity = sev_cell.get_text(strip=True)
if not severity:
sev_span = sev_cell.find("span")
if sev_span and sev_span.get("title"):
severity = sev_span.get("title").strip()
# 优先级
pri = ""
if "pri" in header_map and header_map["pri"] < len(tds):
pri = tds[header_map["pri"]].get_text(strip=True)
# 指派给
assigned_to = ""
if "assignedTo" in header_map and header_map["assignedTo"] < len(tds):
assigned_to = tds[header_map["assignedTo"]].get_text(strip=True)
# 创建/打开日期
opened_date = ""
if "openedDate" in header_map and header_map["openedDate"] < len(tds):
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
bug = {
"id": bug_id,
"title": title,
"statusName": status,
"severity": severity,
"pri": pri,
"assignedToName": assigned_to,
"openedDate": opened_date,
"product_name": product_name,
}
bugs.append(bug)
print(f" ✅ 解析出 {len(bugs)} 个Bug")
all_bugs.extend(bugs)
except Exception as e:
print(f" ❌ 解析失败: {e}")
else:
print(f" ❌ 获取失败: {bugs_resp.status_code}")
# 处理并发送统计信息
if all_bugs:
# 计算统计数据
today_str = datetime.today().strftime("%Y-%m-%d")
today_md_str = datetime.today().strftime("%m-%d")
total_bugs = len(all_bugs)
# 今日新增Bug
today_bugs = [
b for b in all_bugs
if (
today_str in str(b.get("openedDate", "")).strip()
or today_md_str in str(b.get("openedDate", "")).strip()
)
]
# 未关闭Bug状态不包含"已关闭"、"已解决"等)
open_bugs = [
b for b in all_bugs
if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"])
and not any(kw in str(b.get("statusName", "")) for kw in ["已关闭", "已解决", "已完成", "已取消"])
]
# 按指派人员统计
assigned_stats = {}
for bug in all_bugs:
assigned = bug.get("assignedToName", "未指派")
assigned_stats[assigned] = assigned_stats.get(assigned, 0) + 1
# 按状态统计
status_stats = {}
for bug in all_bugs:
status = bug.get("statusName", "未知")
status_stats[status] = status_stats.get(status, 0) + 1
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug统计报告\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 Bug统计概览:\n"
message += f" • 总Bug数: {total_bugs}\n"
message += f" • 今日新增: {len(today_bugs)}\n"
message += f" • 未关闭Bug: {len(open_bugs)}\n"
message += f"\n📋 状态分布:\n"
# 添加状态统计
for status, count in sorted(status_stats.items()):
message += f"{status}: {count}\n"
message += f"\n👥 指派人员统计:\n"
# 添加指派统计前5名
sorted_assigned = sorted(assigned_stats.items(), key=lambda x: x[1], reverse=True)[:5]
for person, count in sorted_assigned:
message += f"{person}: {count}\n"
# 今日新增Bug详情
if today_bugs:
message += f"\n🆕 今日新增Bug详情 ({len(today_bugs)}个):\n"
today_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
for bug in today_bugs[:10]: # 只显示前10个
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
# 未关闭Bug详情前5个
if open_bugs:
open_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
message += f"\n⚠️ 最新未关闭Bug (前5个):\n"
for bug in open_bugs[:5]:
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
message += f"\n🔗 禅道地址: {base_url}"
# 打印到控制台
print(f"\n{'=' * 80}")
print(message)
print(f"{'=' * 80}")
# 发送到飞书
print("\n发送消息到飞书...")
# 如果没有未打开的bug则不发送飞书
if open_bugs != 0:
success = send_to_feishu(feishu_webhook, message, keyword)
else:
success = ""
if success:
print(f"✅ Bug统计报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
else:
message = f"📭 禅道Bug统计报告\n"
message += f"⏰ 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"✅ 当前没有Bug数据\n"
message += f"🔗 禅道地址: {base_url}"
print(message)
send_to_feishu(feishu_webhook, message, keyword)
def send_simple_report():
"""发送简化的Bug统计报告到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug统计")
print("=" * 50)
try:
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取产品ID=2的Bug页面
bug_url = f"{base_url}/bug-browse-2.html"
response = session.get(bug_url)
if response.status_code != 200:
error_msg = f"❌ 无法获取Bug页面"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
# 简单统计Bug数量
import re
bug_matches = re.findall(r'bug-view-(\d+)\.html', response.text)
total_bugs = len(set(bug_matches)) # 去重
# 构建简单消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug速报\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 当前总Bug数: {total_bugs}\n"
message += f"🔗 查看详情: {bug_url}\n"
message += f"\n💡 提示: 详细统计请查看禅道系统"
print(f"发现 {total_bugs} 个Bug")
# 发送到飞书
success = send_to_feishu(feishu_webhook, message, keyword)
if success:
print(f"✅ 简版Bug报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
except Exception as e:
error_msg = f"❌ 获取Bug统计异常: {str(e)}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
if __name__ == "__main__":
# 运行完整版本(带详细统计)
get_all_bugs_and_send()
# 或者运行简化版本(只发速报)
# send_simple_report()