diff --git a/zendao_tools/handle_bug.py b/zendao_tools/handle_bug.py index 7ca1960..4356836 100644 --- a/zendao_tools/handle_bug.py +++ b/zendao_tools/handle_bug.py @@ -1,15 +1,59 @@ import requests import hashlib +import json from bs4 import BeautifulSoup from datetime import datetime +import time -# 最简版本 - 获取所有产品的Bug -def get_all_bugs(): - """获取所有产品的Bug""" + +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) @@ -22,7 +66,9 @@ def get_all_bugs(): headers={'Content-Type': 'application/json'}) 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 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") 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 products = products_resp.json().get('products', []) @@ -41,38 +89,41 @@ def get_all_bugs(): all_bugs = [] - # 遍历每个产品获取Bug + # 遍历每个产品获取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...") - # 你的禅道实例没有开 REST bug 列表接口,只能爬取 HTML 页面 + + # 获取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: try: html = bugs_resp.text soup = BeautifulSoup(html, "html.parser") - # 找 bug 列表的主 table(尽量精确一点) + # 找 bug 列表的 table table = ( - soup.find("table", id="bugList") - or soup.find("table", class_="table") - or soup.find("table") + soup.find("table", id="bugList") + or soup.find("table", class_="table") + or soup.find("table") ) if not table: print(" ❌ 未找到 bug 列表 table") - # print(html[:500]) # 调试时可以打开 - return + continue - # 解析表头,建立「列名 -> 索引」映射 + # 解析表头 header_tr = table.find("tr") if not header_tr: print(" ❌ 未找到表头行") - return + continue header_map = {} for idx, th in enumerate(header_tr.find_all(["th", "td"])): @@ -92,7 +143,6 @@ def get_all_bugs(): elif "指派" in text: header_map["assignedTo"] = idx elif "创建" in text or "打开" in text: - # 如:创建日期 / 打开时间 header_map["openedDate"] = idx bugs = [] @@ -122,7 +172,7 @@ def get_all_bugs(): if "status" in header_map and header_map["status"] < len(tds): status = tds[header_map["status"]].get_text(strip=True) - # 严重程度(有的版本用颜色块 + title 提示) + # 严重程度 severity = "" if "severity" in header_map and header_map["severity"] < len(tds): sev_cell = tds[header_map["severity"]] @@ -148,7 +198,7 @@ def get_all_bugs(): opened_date = tds[header_map["openedDate"]].get_text(strip=True) bug = { - "id": int(bug_id) if str(bug_id).isdigit() else bug_id, + "id": bug_id, "title": title, "statusName": status, "severity": severity, @@ -164,53 +214,190 @@ def get_all_bugs(): except Exception as e: print(f" ❌ 解析失败: {e}") - # 调试用:打印部分 HTML 看实际结构 - # print(html[:800]) else: - print(f" ❌ 获取失败: {bugs_resp.status_code} - {bugs_resp.text[:100]}") + print(f" ❌ 获取失败: {bugs_resp.status_code}") - # 显示所有Bug + # 处理并发送统计信息 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() + today_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 = [ 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(f"📊 Bug统计") - print(f"{'-' * 80}") - print(f" 今日新增 Bug 数: {len(today_bugs)}") - print(f" 未关闭 Bug 数: {len(open_bugs)}") - print(f" 总 Bug 数: {total_bugs}") + print(message) 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个 - print( - f"{i}. [#{bug.get('id')}] {bug.get('title', '无标题')[:40]}{'...' if len(bug.get('title', '')) > 40 else ''}") - print(f" 产品: {bug.get('product_name')} | 状态: {bug.get('statusName', '未知')} | " - f"严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')} | " - f"创建时间: {bug.get('openedDate', '')}") - print(f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html") + 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() - # 获取所有Bug版本 - get_all_bugs() \ No newline at end of file + # 或者运行简化版本(只发速报) + # send_simple_report() \ No newline at end of file