This commit is contained in:
zhang_heng 2025-07-16 02:57:20 +08:00
parent aef79cb5fa
commit e39f32dbf4
1 changed files with 286 additions and 629 deletions

View File

@ -136,30 +136,48 @@ async def get_project_id_from_webhook(data: dict) -> int:
"""
repo_url = None
# 从不同平台的webhook数据中提取仓库URL
if "repository" in data:
# GitHub/Gitee格式
if "html_url" in data["repository"]:
repo_url = data["repository"]["html_url"]
elif "url" in data["repository"]:
repo_url = data["repository"]["url"]
elif "clone_url" in data["repository"]:
repo_url = data["repository"]["clone_url"]
elif "project" in data:
# GitLink格式
if "html_url" in data["project"]:
repo_url = data["project"]["html_url"]
elif "git_url" in data["project"]:
repo_url = data["project"]["git_url"]
try:
# 从不同平台的webhook数据中提取仓库URL
if "repository" in data:
# GitHub/Gitee格式
if "html_url" in data["repository"]:
repo_url = data["repository"]["html_url"]
elif "url" in data["repository"]:
repo_url = data["repository"]["url"]
elif "clone_url" in data["repository"]:
repo_url = data["repository"]["clone_url"]
elif "project" in data:
# GitLink格式
project = data["project"]
if "html_url" in project:
repo_url = project["html_url"]
elif "git_url" in project:
repo_url = project["git_url"]
else:
# 如果没有直接的URL尝试构建URL
project_name = project.get("name")
project_owner = project.get("owner", {}).get("login") or project.get("owner", {}).get("name")
if project_name and project_owner:
repo_url = f"https://www.gitlink.org.cn/{project_owner}/{project_name}"
logger.info(f"🔍 从项目信息构建GitLink URL: {repo_url}")
if repo_url:
project_id = await find_project_by_repo_url(repo_url)
if project_id:
return project_id
if repo_url:
logger.info(f"🔍 尝试通过仓库URL匹配项目: {repo_url}")
project_id = await find_project_by_repo_url(repo_url)
if project_id:
logger.info(f"✅ 成功匹配到项目ID: {project_id}")
return project_id
else:
logger.warning(f"⚠️ 未找到匹配的项目: {repo_url}")
else:
logger.warning("⚠️ 无法从webhook数据中提取仓库URL")
# 如果找不到匹配的项目使用默认项目ID=1
logger.warning(f"🔄 使用默认项目ID=1仓库URL: {repo_url}")
return 1
# 如果找不到匹配的项目使用默认项目ID=1
logger.warning(f"🔄 使用默认项目ID=1这可能会导致同步失败")
return 1
except Exception as e:
logger.error(f"❌ 项目ID提取失败: {str(e)}")
return 1
async def get_sync_repo(project_id: int = 1):
"""获取同步仓库配置"""
@ -176,7 +194,7 @@ async def get_sync_repo(project_id: int = 1):
config.get("github", {}).get("owner")
)
async def verify_webhook_signature(request: Request, body: bytes, project_id: int = 1) -> None:
async def verify_webhook_signature(request: Request, body: bytes, source: str, project_id: int = 1) -> None:
"""验证webhook签名"""
config = await get_config(project_id)
@ -185,22 +203,22 @@ async def verify_webhook_signature(request: Request, body: bytes, project_id: in
github_signature = request.headers.get("X-Hub-Signature-256")
gitlink_signature = request.headers.get("X-Gitlink-Signature")
# 根据签名类型验证
if gitee_signature:
# 根据来源平台验证签名
if source == "Gitee":
gitee_webhook_secret = config.get("gitee", {}).get("webhook_secret")
if not gitee_webhook_secret:
logger.info("🔓 Gitee webhook签名验证跳过未配置secret")
return # 如果未配置secret跳过验证
return
if gitee_signature != gitee_webhook_secret:
logger.error("❌ Gitee webhook签名验证失败")
raise HTTPException(status_code=401, detail="Invalid Gitee webhook signature")
logger.info("✅ Gitee webhook签名验证成功")
elif github_signature:
elif source == "GitHub":
github_webhook_secret = config.get("github", {}).get("webhook_secret")
if not github_webhook_secret:
logger.info("🔓 GitHub webhook签名验证跳过未配置secret")
return # 如果未配置secret跳过验证
return
# GitHub使用HMAC-SHA256签名验证
expected_signature = hmac.new(
@ -215,476 +233,265 @@ async def verify_webhook_signature(request: Request, body: bytes, project_id: in
raise HTTPException(status_code=401, detail="Invalid GitHub webhook signature")
logger.info("✅ GitHub webhook签名验证成功")
elif gitlink_signature:
elif source == "GitLink":
gitlink_webhook_secret = config.get("gitlink", {}).get("webhook_secret")
if not gitlink_webhook_secret:
logger.info("🔓 GitLink webhook签名验证跳过未配置secret")
return # 如果未配置secret跳过验证
return
if gitlink_signature != gitlink_webhook_secret:
logger.error("❌ GitLink webhook签名验证失败")
raise HTTPException(status_code=401, detail="Invalid GitLink webhook signature")
logger.info("✅ GitLink webhook签名验证成功")
else:
logger.warning("⚠️ 未找到webhook签名头")
raise HTTPException(status_code=401, detail="Missing webhook signature")
logger.warning(f"⚠️ 未知的webhook来源: {source}")
raise HTTPException(status_code=401, detail="Unknown webhook source")
async def sync_issue_to_gitee(data: dict, token: str, owner: str, repo: str) -> dict:
"""同步issue到Gitee"""
try:
issue = data["issue"]
title = issue["subject"]
body = issue.get("description", "")
# 在Gitee上创建issue
url = f'https://gitee.com/api/v5/repos/{owner}/issues'
payload = {
"access_token": token,
"repo": repo,
"title": title,
"body": body
}
headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
logger.info(f"📤 向Gitee发送请求: POST {url}")
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
return {
"issue_number": result.get("number"),
"url": result.get("html_url")
}
except Exception as e:
logger.error(f"❌ Gitee同步失败: {str(e)}")
raise
async def sync_issue_to_github(data: dict, token: str, owner: str, repo: str) -> dict:
"""同步issue到GitHub"""
try:
issue = data["issue"]
title = issue["subject"]
body = issue.get("description", "")
# 在GitHub上创建issue
url = f"https://api.github.com/repos/{owner}/{repo}/issues"
payload = {
"title": title,
"body": body,
"labels": ["sync issue"]
}
headers = {
'Authorization': f'Bearer {token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
logger.info(f"📤 向GitHub发送请求: POST {url}")
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
return {
"issue_number": result.get("number"),
"url": result.get("html_url")
}
except Exception as e:
logger.error(f"❌ GitHub同步失败: {str(e)}")
raise
async def sync_issue_to_gitlink(data: dict, token: str, owner: str, repo: str) -> dict:
"""同步issue到GitLink"""
try:
# 从Gitee的webhook数据中提取issue信息
issue = data.get("issue", {})
title = issue.get("title", "")
body = issue.get("body", "")
# 构造GitLink API请求
url = f"https://www.gitlink.org.cn/api/v1/repos/{owner}/{repo}/issues"
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
payload = {
"subject": title,
"description": body,
"labels": ["sync issue"]
}
logger.info(f"📤 向GitLink发送请求: POST {url}")
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
return {
"issue_number": result.get("number"),
"url": result.get("html_url")
}
except Exception as e:
logger.error(f"❌ GitLink同步失败: {str(e)}")
raise
@WEBHOOK.post("/issue")
async def issue_webhook(request: Request):
"""处理issue相关的webhook事件 - 自动识别项目"""
start_time = datetime.now()
logger.info("🚀 =========================")
logger.info("🚀 收到Issue Webhook请求")
logger.info("🚀 =========================")
# 先读取请求体
body = await request.body()
# 解析JSON数据以获取项目信息
data = json.loads(body.decode('utf-8'))
# 检测webhook来源和事件信息
source = detect_webhook_source(data, dict(request.headers))
event_info = get_webhook_event_info(data, source)
logger.info(f"📡 来源平台: {event_info['source']}")
logger.info(f"📝 事件类型: {event_info['event_type']}")
logger.info(f"🎯 动作: {event_info['action']}")
logger.info(f"📁 仓库: {event_info['repository']}")
logger.info(f"👤 用户: {event_info['user']}")
logger.info(f"🏷️ 标题: {event_info['title']}")
logger.info(f"🔢 编号: #{event_info['number']}")
# 自动查找对应的项目ID
project_id = await get_project_id_from_webhook(data)
logger.info(f"🆔 使用项目ID: {project_id}")
# 验证签名
await verify_webhook_signature(request, body, project_id)
# 获取仓库配置
row = await get_sync_repo(project_id)
gitlink_token = row[0]
gitlink_repo = row[1]
gitlink_owner = row[2]
gitee_token = row[3]
gitee_repo = row[4]
gitee_owner = row[5]
github_token = row[6]
github_repo = row[7]
github_owner = row[8]
# 获取webhook数据
# data = await request.json() # This line is removed as per the edit hint
# 检测来源平台
if "repository" in data:
if data["repository"]["owner"]["avatar_url"] and "gitee" in data["repository"]["owner"]["avatar_url"]:
flag = "gitee"
else:
flag = "github"
else:
flag = "gitlink"
# 处理来自不同平台的issue事件
if flag == "gitee":
logger.info("🔄 开始处理Gitee Issue事件")
# 处理Gitee的issue事件
if data["issue"] and data["action"] == "open":
# 创建issue
title = data["issue"]["title"]
body = data["issue"]["body"]
gitee_issue_index = data["issue"]["number"]
try:
logger.info("🚀 =========================")
logger.info("🚀 收到Issue Webhook请求")
logger.info("🚀 =========================")
# 先读取请求体
body = await request.body()
# 解析JSON数据以获取项目信息
try:
data = json.loads(body.decode('utf-8'))
except json.JSONDecodeError as e:
logger.error(f"❌ JSON解析失败: {str(e)}")
return {"status": "error", "message": "Invalid JSON data"}
# 检测webhook来源和事件信息
source = detect_webhook_source(data, dict(request.headers))
event_info = get_webhook_event_info(data, source)
# 记录事件信息
logger.info(f"📡 来源平台: {event_info['source']}")
logger.info(f"📝 事件类型: {event_info['event_type']}")
logger.info(f"🎯 动作: {event_info['action']}")
logger.info(f"📁 仓库: {event_info['repository']}")
logger.info(f"👤 用户: {event_info['user']}")
logger.info(f"🏷️ 标题: {event_info['title']}")
logger.info(f"🔢 编号: #{event_info['number']}")
# 自动查找对应的项目ID
project_id = await get_project_id_from_webhook(data)
logger.info(f"🆔 使用项目ID: {project_id}")
if project_id == 1:
logger.warning("⚠️ 使用默认项目ID请检查项目配置")
# 验证签名
await verify_webhook_signature(request, body, event_info["source"], project_id)
# 获取仓库配置
row = await get_sync_repo(project_id)
gitlink_token, gitlink_repo, gitlink_owner = row[0:3]
gitee_token, gitee_repo, gitee_owner = row[3:6]
github_token, github_repo, github_owner = row[6:9]
# 验证配置完整性
if event_info["source"] == "GitLink":
if not all([gitlink_token, gitlink_repo, gitlink_owner]):
logger.error("❌ GitLink配置不完整")
return {"status": "error", "message": "GitLink configuration incomplete"}
elif event_info["source"] == "Gitee":
if not all([gitee_token, gitee_repo, gitee_owner]):
logger.error("❌ Gitee配置不完整")
return {"status": "error", "message": "Gitee configuration incomplete"}
elif event_info["source"] == "GitHub":
if not all([github_token, github_repo, github_owner]):
logger.error("❌ GitHub配置不完整")
return {"status": "error", "message": "GitHub configuration incomplete"}
# 处理issue事件
if event_info["event_type"] != "issue":
logger.warning(f"⚠️ 非issue事件: {event_info['event_type']}")
return {"status": "ignored", "message": "Not an issue event"}
logger.info(f"📝 创建Issue: #{gitee_issue_index} - {title}")
if event_info["action"] not in ["opened", "edited", "closed", "reopened"]:
logger.info(f" 忽略的issue动作: {event_info['action']}")
return {"status": "ignored", "message": f"Ignored issue action: {event_info['action']}"}
# 同步处理
sync_results = []
# 从GitLink同步到其他平台
if event_info["source"] == "GitLink" and event_info["action"] == "opened":
logger.info("🔄 开始处理GitLink Issue事件")
logger.info("📝 创建Issue: #{} - {}", event_info["number"], event_info["title"])
logger.info("🔄 开始同步到Gitee和GitHub...")
# 同步到Gitee
if all([gitee_token, gitee_repo, gitee_owner]):
try:
logger.info("📤 正在向Gitee创建Issue...")
gitee_result = await sync_issue_to_gitee(data, gitee_token, gitee_owner, gitee_repo)
sync_results.append({"platform": "Gitee", "status": "success", "result": gitee_result})
logger.info("✅ Gitee Issue创建成功")
except Exception as e:
logger.error(f"❌ Gitee同步失败: {str(e)}")
sync_results.append({"platform": "Gitee", "status": "error", "error": str(e)})
# 同步到GitHub
if all([github_token, github_repo, github_owner]):
try:
logger.info("📤 正在向GitHub创建Issue...")
github_result = await sync_issue_to_github(data, github_token, github_owner, github_repo)
sync_results.append({"platform": "GitHub", "status": "success", "result": github_result})
logger.info("✅ GitHub Issue创建成功")
except Exception as e:
logger.error(f"❌ GitHub同步失败: {str(e)}")
sync_results.append({"platform": "GitHub", "status": "error", "error": str(e)})
# 从Gitee同步到其他平台
elif event_info["source"] == "Gitee" and event_info["action"] in ["open", "opened"]:
logger.info("🔄 开始处理Gitee Issue事件")
logger.info("📝 创建Issue: #{} - {}", event_info["number"], event_info["title"])
logger.info("🔄 开始同步到GitLink和GitHub...")
# 在GitLink上创建issue
gitlink_url = f"https://www.gitlink.org.cn/api/v1/{gitlink_owner}/{gitlink_repo}/issues.json"
current_time = datetime.now()
formatted_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
gitlink_payload = {
"status_id": 1,
"priority_id": 2,
"start_date": formatted_time,
"subject": title,
"description": body
}
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
# 在GitHub上创建issue
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues"
github_payload = {
"body": body,
"labels": ["sync issue"],
"title": title
}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
logger.info("📤 正在向GitLink创建Issue...")
gitlink_response = requests.post(gitlink_url, headers=gitlink_headers, json=gitlink_payload)
gitlink_response.raise_for_status()
logger.info("✅ GitLink Issue创建成功")
logger.info("📤 正在向GitHub创建Issue...")
github_response = requests.post(github_url, headers=github_headers, json=github_payload)
github_response.raise_for_status()
logger.info("✅ GitHub Issue创建成功")
# 记录处理时间
end_time = datetime.now()
process_time = (end_time - start_time).total_seconds()
logger.info("🎉 =========================")
logger.info(f"🎉 Issue同步完成! 耗时: {process_time:.2f}")
logger.info("🎉 =========================")
return {"message": "issue创建同步成功", "processed_in": f"{process_time:.2f}s"}
except requests.RequestException as e:
logger.error(f"❌ Issue同步失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
elif data["issue"]["state"] == "rejected" and data["action"] == "state_change":
# 处理issue关闭事件
issue_title = data["issue"]["title"]
gitee_issue_index = data["issue"]["number"]
logger.info(f"🚫 关闭Issue: #{gitee_issue_index} - {issue_title}")
logger.info("🔄 开始同步关闭状态到GitLink和GitHub...")
# 同步到GitLink
if all([gitlink_token, gitlink_repo, gitlink_owner]):
try:
logger.info("📤 正在向GitLink创建Issue...")
gitlink_result = await sync_issue_to_gitlink(data, gitlink_token, gitlink_owner, gitlink_repo)
sync_results.append({"platform": "GitLink", "status": "success", "result": gitlink_result})
logger.info("✅ GitLink Issue创建成功")
except Exception as e:
logger.error(f"❌ GitLink同步失败: {str(e)}")
sync_results.append({"platform": "GitLink", "status": "error", "error": str(e)})
# 在GitLink上关闭issue
gitlink_url = f"https://www.gitlink.org.cn/api/v1/{gitlink_owner}/{gitlink_repo}/issues/{gitee_issue_index}.json"
gitlink_payload = {"status_id": 5}
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
# 在GitHub上关闭issue
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues/{gitee_issue_index}"
github_payload = {"state": "closed"}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
logger.info("📤 正在在GitLink关闭Issue...")
gitlink_response = requests.patch(gitlink_url, headers=gitlink_headers, json=gitlink_payload)
gitlink_response.raise_for_status()
logger.info("✅ GitLink Issue关闭成功")
logger.info("📤 正在在GitHub关闭Issue...")
github_response = requests.patch(github_url, headers=github_headers, json=github_payload)
github_response.raise_for_status()
logger.info("✅ GitHub Issue关闭成功")
end_time = datetime.now()
process_time = (end_time - start_time).total_seconds()
logger.info("🎉 =========================")
logger.info(f"🎉 Issue关闭同步完成! 耗时: {process_time:.2f}")
logger.info("🎉 =========================")
return {"message": "issue关闭同步成功", "processed_in": f"{process_time:.2f}s"}
except requests.RequestException as e:
logger.error(f"❌ Issue关闭同步失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
elif data["action"] == "comment" and data["issue"]:
# 处理issue评论事件
issue_title = data["issue"]["title"]
body = data["comment"]["body"]
gitee_issue_index = data["issue"]["number"]
logger.info(f"💬 新评论: Issue #{gitee_issue_index}")
logger.info(f"💬 评论内容: {body[:50]}...")
logger.info("🔄 开始同步评论到GitLink和GitHub...")
# 在GitLink上创建issue评论
gitlink_url = f"https://www.gitlink.org.cn/api/v1/{gitlink_owner}/{gitlink_repo}/issues/{gitee_issue_index}/journals.json"
gitlink_payload = {
"notes": body,
"receivers_login": ["string"]
}
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
# 在GitHub上创建issue评论
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues/{gitee_issue_index}/comments"
github_payload = {"body": body}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
logger.info("📤 正在向GitLink添加评论...")
gitlink_response = requests.post(gitlink_url, headers=gitlink_headers, json=gitlink_payload)
gitlink_response.raise_for_status()
logger.info("✅ GitLink评论添加成功")
logger.info("📤 正在向GitHub添加评论...")
github_response = requests.post(github_url, headers=github_headers, json=github_payload)
github_response.raise_for_status()
logger.info("✅ GitHub评论添加成功")
end_time = datetime.now()
process_time = (end_time - start_time).total_seconds()
logger.info("🎉 =========================")
logger.info(f"🎉 评论同步完成! 耗时: {process_time:.2f}")
logger.info("🎉 =========================")
return {"message": "issue评论同步成功", "processed_in": f"{process_time:.2f}s"}
except requests.RequestException as e:
logger.error(f"❌ 评论同步失败: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
elif flag == "gitlink":
# 处理GitLink的issue事件
if data["action"] == "opened" and data["issue"]:
# GitLink创建issue
issue_title = data["issue"]["subject"]
body = data["issue"]["description"]
gitlink_issue_index = data["number"]
# 在Gitee上创建issue
gitee_url = f'https://gitee.com/api/v5/repos/{gitee_owner}/issues'
gitee_payload = {
"access_token": gitee_token,
"repo": gitee_repo,
"title": issue_title,
"body": body
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
# 在GitHub上创建issue
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues"
github_payload = {
"body": body,
"labels": ["sync issue"],
"title": issue_title
}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
github_response = requests.post(github_url, headers=github_headers, json=github_payload)
gitee_response = requests.post(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
github_response.raise_for_status()
return {"message": "同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
elif data["action"] == "created" and data["issue"]:
# GitLink创建issue评论
issue_title = data["issue"]["subject"]
body = data["journal"]["notes"]
gitlink_issue_index = data["number"]
# 在Gitee上创建评论
gitee_url = f"https://gitee.com/api/v5/repos/{gitee_owner}/{gitee_repo}/issues/{gitlink_issue_index}/comments"
gitee_payload = {
"access_token": gitee_token,
"body": body
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
# 在GitHub上创建评论
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues/{gitlink_issue_index}/comments"
github_payload = {"body": body}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
gitee_response = requests.post(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
github_response = requests.post(github_url, headers=github_headers, json=github_payload)
github_response.raise_for_status()
return {"message": "issue评论同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
elif data["action"] == "edited" and data["issue"]["status"]["name"] == "关闭":
# GitLink关闭issue
issue_title = data["issue"]["subject"]
gitlink_issue_index = data["number"]
# 在Gitee上关闭issue
gitee_url = f"https://gitee.com/api/v5/repos/{gitee_owner}/issues/{gitlink_issue_index}"
gitee_payload = {
"access_token": gitee_token,
"repo": gitee_repo,
"state": "closed"
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
# 在GitHub上关闭issue
github_url = f"https://api.github.com/repos/{github_owner}/{github_repo}/issues/{gitlink_issue_index}"
github_payload = {"state": "closed"}
github_headers = {
'Authorization': f'Bearer {github_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
gitee_response = requests.patch(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
github_response = requests.patch(github_url, headers=github_headers, json=github_payload)
github_response.raise_for_status()
return {"message": "issue关闭同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
# 同步到GitHub只有在GitHub配置存在时才同步
if all([github_token, github_repo, github_owner]):
try:
logger.info("📤 正在向GitHub创建Issue...")
github_result = await sync_issue_to_github(data, github_token, github_owner, github_repo)
sync_results.append({"platform": "GitHub", "status": "success", "result": github_result})
logger.info("✅ GitHub Issue创建成功")
except Exception as e:
logger.error(f"❌ GitHub同步失败: {str(e)}")
sync_results.append({"platform": "GitHub", "status": "error", "error": str(e)})
elif flag == "github":
# 处理GitHub的issue事件
if data["action"] == "opened" and data["issue"]:
# GitHub创建issue
issue_title = data["issue"]["title"]
body = data["issue"]["body"]
github_issue_index = data["issue"]["number"]
# 在Gitee上创建issue
gitee_url = f'https://gitee.com/api/v5/repos/{gitee_owner}/issues'
gitee_payload = {
"access_token": gitee_token,
"repo": gitee_repo,
"title": issue_title,
"body": body
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
# 在GitLink上创建issue
gitlink_url = f"https://www.gitlink.org.cn/api/v1/{gitlink_owner}/{gitlink_repo}/issues.json"
current_time = datetime.now()
formatted_time = current_time.strftime('%Y-%m-%d %H:%M:%S')
gitlink_payload = {
"status_id": 1,
"priority_id": 2,
"start_date": formatted_time,
"subject": issue_title,
"description": body
}
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
gitlink_response = requests.post(gitlink_url, headers=gitlink_headers, json=gitlink_payload)
gitee_response = requests.post(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
gitlink_response.raise_for_status()
return {"message": "同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
elif data["action"] == "created" and data["issue"]:
# GitHub创建issue评论
issue_title = data["issue"]["title"]
body = data["comment"]["body"]
github_issue_index = data["issue"]["number"]
# 在Gitee上创建评论
gitee_url = f"https://gitee.com/api/v5/repos/{gitee_owner}/{gitee_repo}/issues/{github_issue_index}/comments"
gitee_payload = {
"access_token": gitee_token,
"body": body
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
# 在GitLink上创建issue评论
gitlink_url = f"https://www.gitlink.org.cn/api/v1/{gitlink_owner}/{gitlink_repo}/issues/{github_issue_index}/journals.json"
gitlink_payload = {
"notes": body,
"receivers_login": ["string"]
}
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'application/json'
}
try:
gitee_response = requests.post(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
gitlink_response = requests.post(gitlink_url, headers=gitlink_headers, json=gitlink_payload)
gitlink_response.raise_for_status()
return {"message": "issue评论同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
elif data["action"] == "closed" and data["issue"]:
# GitHub关闭issue
issue_title = data["issue"]["title"]
github_issue_index = data["issue"]["number"]
# 在GitLink上拒绝该PR
gitlink_url = f"https://www.gitlink.org.cn/api/{gitlink_owner}/{gitlink_repo}/pulls/{github_issue_index}/refuse_merge.json"
gitlink_headers = {
'Authorization': f'Bearer {gitlink_token}',
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)'
}
# 在Gitee上拒绝该PR
gitee_url = f"https://gitee.com/api/v5/repos/{gitee_owner}/{gitee_repo}/issues/{github_issue_index}"
gitee_payload = {
"access_token": gitee_token,
"state": "closed"
}
gitee_headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
try:
gitee_response = requests.patch(gitee_url, headers=gitee_headers, json=gitee_payload)
gitee_response.raise_for_status()
gitlink_response = requests.post(gitlink_url, headers=gitlink_headers)
gitlink_response.raise_for_status()
return {"message": "issue关闭同步成功"}
except requests.RequestException as e:
raise HTTPException(status_code=500, detail=str(e))
# 记录处理时间
end_time = datetime.now()
process_time = (end_time - start_time).total_seconds()
logger.info("🎉 =========================")
logger.info(f"🎉 处理完成! 耗时: {process_time:.2f}")
logger.info("🎉 =========================")
# 在文件的最后,"未开通的服务"返回之前添加日志
logger.warning("⚠️ =========================")
logger.warning("⚠️ 未开通的服务或未支持的事件")
logger.warning(f"⚠️ 平台: {flag}")
logger.warning(f"⚠️ 动作: {data.get('action', 'unknown')}")
logger.warning("⚠️ =========================")
return {
"status": "success",
"message": "Webhook processed successfully",
"process_time": f"{process_time:.2f}s",
"sync_results": sync_results
}
return {"message": "未开通的服务"}
except Exception as e:
logger.error(f"❌ Webhook处理失败: {str(e)}")
return {"status": "error", "message": f"Webhook processing failed: {str(e)}"}
@WEBHOOK.post("/pr")
async def pr_webhook(request: Request):
@ -717,7 +524,7 @@ async def pr_webhook(request: Request):
logger.info(f"🆔 使用项目ID: {project_id}")
# 验证签名
await verify_webhook_signature(request, body, project_id)
await verify_webhook_signature(request, body, event_info["source"], project_id)
# 获取仓库配置
row = await get_sync_repo(project_id)
@ -961,154 +768,4 @@ async def logs_viewer():
);
}
logsContainer.innerHTML = filteredLogs.map(log => `
<div class="log-entry">
<span class="log-timestamp">${log.timestamp}</span>
<span class="log-level level-${log.level}">[${log.level}]</span>
<span class="log-message">${escapeHtml(log.message)}</span>
</div>
`).join('');
// 自动滚动到底部
if (document.getElementById('autoScroll').checked) {
logsContainer.scrollTop = logsContainer.scrollHeight;
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 页面加载时自动连接
window.onload = function() {
connect();
};
// 页面关闭时断开连接
window.onbeforeunload = function() {
disconnect();
};
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@WEBHOOK.get("/health")
async def health_check():
"""健康检查接口"""
logger.info("💚 Webhook健康检查请求")
return {"status": "healthy", "message": "Webhook service is running"}
@WEBHOOK.post("/admin/logs/test")
async def test_logs():
"""测试日志功能"""
logger.info("🧪 开始测试日志功能...")
test_websocket_logging()
logger.info("✅ 测试完成")
return {"status": "success", "message": "日志测试已执行请查看WebSocket日志查看器"}
@WEBHOOK.get("/admin/logs/simple")
async def get_logs():
"""简单的日志读取接口"""
try:
with open("logs/app.log", "r", encoding="utf-8") as f:
logs = f.readlines()
# 返回最新的1000行日志
return {"logs": logs[-1000:]}
except Exception as e:
return {"error": str(e)}
@WEBHOOK.get("/admin/logs/simple/viewer", response_class=HTMLResponse)
async def simple_logs_viewer():
"""简单的日志查看页面"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>简单日志查看器</title>
<meta charset="utf-8">
<style>
body {
font-family: monospace;
margin: 20px;
background: #1e1e1e;
color: #fff;
}
#logs {
background: #000;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
font-size: 14px;
line-height: 1.5;
}
.controls {
margin-bottom: 20px;
}
button {
padding: 8px 16px;
margin-right: 10px;
cursor: pointer;
}
.info { color: #17a2b8; }
.error { color: #dc3545; }
.warning { color: #ffc107; }
</style>
</head>
<body>
<div class="controls">
<button onclick="refreshLogs()">刷新日志</button>
<button onclick="clearDisplay()">清空显示</button>
<label>
<input type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()"> 自动刷新
</label>
</div>
<pre id="logs"></pre>
<script>
let autoRefreshInterval;
function colorize(log) {
if (log.includes('ERROR')) return '<span class="error">' + log + '</span>';
if (log.includes('WARNING')) return '<span class="warning">' + log + '</span>';
if (log.includes('INFO')) return '<span class="info">' + log + '</span>';
return log;
}
async function refreshLogs() {
try {
const response = await fetch('/webhook/admin/logs/simple');
const data = await response.json();
if (data.logs) {
const logsHtml = data.logs.map(colorize).join('');
document.getElementById('logs').innerHTML = logsHtml;
// 滚动到底部
document.getElementById('logs').scrollTop = document.getElementById('logs').scrollHeight;
}
} catch (error) {
console.error('获取日志失败:', error);
}
}
function clearDisplay() {
document.getElementById('logs').innerHTML = '';
}
function toggleAutoRefresh() {
if (document.getElementById('autoRefresh').checked) {
autoRefreshInterval = setInterval(refreshLogs, 2000);
} else {
clearInterval(autoRefreshInterval);
}
}
// 页面加载时获取一次日志
refreshLogs();
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
logsContainer.innerHTML = filteredLogs.map(log => `