tijiao
This commit is contained in:
@@ -1,15 +1,59 @@
|
|||||||
import requests
|
import requests
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
# 最简版本 - 获取所有产品的Bug
|
|
||||||
def get_all_bugs():
|
def send_to_feishu(webhook_url, content, keyword="bug"):
|
||||||
"""获取所有产品的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"
|
base_url = "http://39.170.26.156:8888"
|
||||||
username = "qiaoxinjiu"
|
username = "qiaoxinjiu"
|
||||||
password = "Qiao123456"
|
password = "Qiao123456"
|
||||||
|
|
||||||
|
# 飞书配置
|
||||||
|
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
|
||||||
|
keyword = "bug"
|
||||||
|
|
||||||
print("获取禅道Bug列表")
|
print("获取禅道Bug列表")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
|
|
||||||
@@ -22,7 +66,9 @@ def get_all_bugs():
|
|||||||
headers={'Content-Type': 'application/json'})
|
headers={'Content-Type': 'application/json'})
|
||||||
|
|
||||||
if login_resp.status_code not in [200, 201]:
|
if login_resp.status_code not in [200, 201]:
|
||||||
print("登录失败")
|
error_msg = f"❌ 禅道登录失败: {login_resp.status_code}"
|
||||||
|
print(error_msg)
|
||||||
|
send_to_feishu(feishu_webhook, error_msg, keyword)
|
||||||
return
|
return
|
||||||
|
|
||||||
token = login_resp.json().get('token')
|
token = login_resp.json().get('token')
|
||||||
@@ -33,7 +79,9 @@ def get_all_bugs():
|
|||||||
# 获取所有产品
|
# 获取所有产品
|
||||||
products_resp = session.get(f"{base_url}/api.php/v1/products")
|
products_resp = session.get(f"{base_url}/api.php/v1/products")
|
||||||
if products_resp.status_code != 200:
|
if products_resp.status_code != 200:
|
||||||
print("获取产品失败")
|
error_msg = f"❌ 获取产品列表失败: {products_resp.status_code}"
|
||||||
|
print(error_msg)
|
||||||
|
send_to_feishu(feishu_webhook, error_msg, keyword)
|
||||||
return
|
return
|
||||||
|
|
||||||
products = products_resp.json().get('products', [])
|
products = products_resp.json().get('products', [])
|
||||||
@@ -41,23 +89,27 @@ def get_all_bugs():
|
|||||||
|
|
||||||
all_bugs = []
|
all_bugs = []
|
||||||
|
|
||||||
# 遍历每个产品获取Bug
|
# 遍历每个产品获取Bug(只获取产品ID为2的)
|
||||||
for product in products:
|
for product in products:
|
||||||
product_id = product.get('id')
|
product_id = product.get('id')
|
||||||
product_name = product.get('name')
|
product_name = product.get('name')
|
||||||
|
|
||||||
|
# 只获取产品ID为2的
|
||||||
if product_id != 2:
|
if product_id != 2:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f"\n获取产品 '{product_name}' 的Bug...")
|
print(f"\n获取产品 '{product_name}' 的Bug...")
|
||||||
# 你的禅道实例没有开 REST bug 列表接口,只能爬取 HTML 页面
|
|
||||||
|
# 获取HTML页面
|
||||||
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
|
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
|
||||||
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 50})
|
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 100})
|
||||||
|
|
||||||
if bugs_resp.status_code == 200:
|
if bugs_resp.status_code == 200:
|
||||||
try:
|
try:
|
||||||
html = bugs_resp.text
|
html = bugs_resp.text
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
# 找 bug 列表的主 table(尽量精确一点)
|
# 找 bug 列表的 table
|
||||||
table = (
|
table = (
|
||||||
soup.find("table", id="bugList")
|
soup.find("table", id="bugList")
|
||||||
or soup.find("table", class_="table")
|
or soup.find("table", class_="table")
|
||||||
@@ -65,14 +117,13 @@ def get_all_bugs():
|
|||||||
)
|
)
|
||||||
if not table:
|
if not table:
|
||||||
print(" ❌ 未找到 bug 列表 table")
|
print(" ❌ 未找到 bug 列表 table")
|
||||||
# print(html[:500]) # 调试时可以打开
|
continue
|
||||||
return
|
|
||||||
|
|
||||||
# 解析表头,建立「列名 -> 索引」映射
|
# 解析表头
|
||||||
header_tr = table.find("tr")
|
header_tr = table.find("tr")
|
||||||
if not header_tr:
|
if not header_tr:
|
||||||
print(" ❌ 未找到表头行")
|
print(" ❌ 未找到表头行")
|
||||||
return
|
continue
|
||||||
|
|
||||||
header_map = {}
|
header_map = {}
|
||||||
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
|
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
|
||||||
@@ -92,7 +143,6 @@ def get_all_bugs():
|
|||||||
elif "指派" in text:
|
elif "指派" in text:
|
||||||
header_map["assignedTo"] = idx
|
header_map["assignedTo"] = idx
|
||||||
elif "创建" in text or "打开" in text:
|
elif "创建" in text or "打开" in text:
|
||||||
# 如:创建日期 / 打开时间
|
|
||||||
header_map["openedDate"] = idx
|
header_map["openedDate"] = idx
|
||||||
|
|
||||||
bugs = []
|
bugs = []
|
||||||
@@ -122,7 +172,7 @@ def get_all_bugs():
|
|||||||
if "status" in header_map and header_map["status"] < len(tds):
|
if "status" in header_map and header_map["status"] < len(tds):
|
||||||
status = tds[header_map["status"]].get_text(strip=True)
|
status = tds[header_map["status"]].get_text(strip=True)
|
||||||
|
|
||||||
# 严重程度(有的版本用颜色块 + title 提示)
|
# 严重程度
|
||||||
severity = ""
|
severity = ""
|
||||||
if "severity" in header_map and header_map["severity"] < len(tds):
|
if "severity" in header_map and header_map["severity"] < len(tds):
|
||||||
sev_cell = tds[header_map["severity"]]
|
sev_cell = tds[header_map["severity"]]
|
||||||
@@ -148,7 +198,7 @@ def get_all_bugs():
|
|||||||
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
|
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
|
||||||
|
|
||||||
bug = {
|
bug = {
|
||||||
"id": int(bug_id) if str(bug_id).isdigit() else bug_id,
|
"id": bug_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"statusName": status,
|
"statusName": status,
|
||||||
"severity": severity,
|
"severity": severity,
|
||||||
@@ -164,17 +214,18 @@ def get_all_bugs():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" ❌ 解析失败: {e}")
|
print(f" ❌ 解析失败: {e}")
|
||||||
# 调试用:打印部分 HTML 看实际结构
|
|
||||||
# print(html[:800])
|
|
||||||
else:
|
else:
|
||||||
print(f" ❌ 获取失败: {bugs_resp.status_code} - {bugs_resp.text[:100]}")
|
print(f" ❌ 获取失败: {bugs_resp.status_code}")
|
||||||
|
|
||||||
# 显示所有Bug
|
# 处理并发送统计信息
|
||||||
if all_bugs:
|
if all_bugs:
|
||||||
# 计算统计数据
|
# 计算统计数据
|
||||||
today_str = datetime.today().strftime("%Y-%m-%d")
|
today_str = datetime.today().strftime("%Y-%m-%d")
|
||||||
today_md_str = datetime.today().strftime("%m-%d")
|
today_md_str = datetime.today().strftime("%m-%d")
|
||||||
|
|
||||||
total_bugs = len(all_bugs)
|
total_bugs = len(all_bugs)
|
||||||
|
|
||||||
|
# 今日新增Bug
|
||||||
today_bugs = [
|
today_bugs = [
|
||||||
b for b in all_bugs
|
b for b in all_bugs
|
||||||
if (
|
if (
|
||||||
@@ -182,35 +233,171 @@ def get_all_bugs():
|
|||||||
or today_md_str in str(b.get("openedDate", "")).strip()
|
or today_md_str in str(b.get("openedDate", "")).strip()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
# 认为“未关闭”的状态:不在已关闭/已解决/已取消集合中
|
|
||||||
closed_keywords = {"已关闭", "已解决", "已完成", "已取消", "closed", "resolved", "done", "cancelled"}
|
# 未关闭Bug(状态不包含"已关闭"、"已解决"等)
|
||||||
open_bugs = [
|
open_bugs = [
|
||||||
b for b in all_bugs
|
b for b in all_bugs
|
||||||
if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"])
|
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 ["已关闭", "已解决", "已完成", "已取消"])
|
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(f"\n{'=' * 80}")
|
||||||
print(f"📊 Bug统计")
|
print(message)
|
||||||
print(f"{'-' * 80}")
|
|
||||||
print(f" 今日新增 Bug 数: {len(today_bugs)}")
|
|
||||||
print(f" 未关闭 Bug 数: {len(open_bugs)}")
|
|
||||||
print(f" 总 Bug 数: {total_bugs}")
|
|
||||||
print(f"{'=' * 80}")
|
print(f"{'=' * 80}")
|
||||||
|
|
||||||
# 按ID排序
|
# 发送到飞书
|
||||||
all_bugs.sort(key=lambda x: x.get('id', 0), reverse=True)
|
print("\n发送消息到飞书...")
|
||||||
|
success = send_to_feishu(feishu_webhook, message, keyword)
|
||||||
|
|
||||||
for i, bug in enumerate(all_bugs[:30], 1): # 只显示前30个
|
if success:
|
||||||
print(
|
print(f"✅ Bug统计报告已发送到飞书")
|
||||||
f"{i}. [#{bug.get('id')}] {bug.get('title', '无标题')[:40]}{'...' if len(bug.get('title', '')) > 40 else ''}")
|
else:
|
||||||
print(f" 产品: {bug.get('product_name')} | 状态: {bug.get('statusName', '未知')} | "
|
print(f"❌ 飞书消息发送失败")
|
||||||
f"严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')} | "
|
else:
|
||||||
f"创建时间: {bug.get('openedDate', '')}")
|
message = f"📭 禅道Bug统计报告\n"
|
||||||
print(f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html")
|
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__":
|
if __name__ == "__main__":
|
||||||
|
# 运行完整版本(带详细统计)
|
||||||
|
get_all_bugs_and_send()
|
||||||
|
|
||||||
# 获取所有Bug版本
|
# 或者运行简化版本(只发速报)
|
||||||
get_all_bugs()
|
# send_simple_report()
|
||||||
Reference in New Issue
Block a user