forked from Lesin/reposync
This commit is contained in:
parent
aef79cb5fa
commit
e39f32dbf4
|
|
@ -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 => `
|
||||
Loading…
Reference in New Issue