Merge pull request #10 from WrBug/pre_release
Release v1.1.3: 添加 Telegram 通知功能
This commit is contained in:
@@ -65,3 +65,54 @@ jobs:
|
||||
GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
|
||||
cache-from: type=registry,ref=wrbug/polyhermes:latest
|
||||
cache-to: type=inline
|
||||
|
||||
- name: Send Telegram notification
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
run: |
|
||||
# 检查必要的环境变量
|
||||
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
|
||||
echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 获取构建信息
|
||||
VERSION="${{ steps.extract_version.outputs.VERSION }}"
|
||||
TAG="${{ steps.extract_version.outputs.TAG }}"
|
||||
RELEASE_NAME="${{ github.event.release.name }}"
|
||||
RELEASE_URL="${{ github.event.release.html_url }}"
|
||||
REPO_NAME="${{ github.repository }}"
|
||||
|
||||
# 构建消息内容(使用 HTML 格式)
|
||||
MESSAGE="✅ <b>Docker 镜像构建成功</b>"$'\n'$'\n'"📦 <b>版本:</b> ${VERSION}"$'\n'"🏷️ <b>Tag:</b> <code>${TAG}</code>"$'\n'"🔗 <b>Release:</b> <a href=\"${RELEASE_URL}\">查看 Release</a>"$'\n'"🐳 <b>Docker 镜像:</b> <code>wrbug/polyhermes:${TAG}</code>"
|
||||
|
||||
# 添加 Release 名称(如果有)
|
||||
if [ -n "$RELEASE_NAME" ] && [ "$RELEASE_NAME" != "null" ] && [ "$RELEASE_NAME" != "" ]; then
|
||||
RELEASE_NAME_ESCAPED=$(echo "$RELEASE_NAME" | sed 's/&/\&/g' | sed 's/</\</g' | sed 's/>/\>/g')
|
||||
MESSAGE="${MESSAGE}"$'\n'$'\n'"📝 <b>Release 名称:</b> ${RELEASE_NAME_ESCAPED}"
|
||||
fi
|
||||
|
||||
# 发送 Telegram 消息(使用 jq 转义 JSON)
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
||||
--arg text "$MESSAGE" \
|
||||
'{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json
|
||||
|
||||
# 检查发送结果
|
||||
if [ $? -eq 0 ]; then
|
||||
RESPONSE=$(cat /tmp/telegram_response.json)
|
||||
if echo "$RESPONSE" | grep -q '"ok":true'; then
|
||||
echo "✅ Telegram 通知发送成功"
|
||||
else
|
||||
echo "❌ Telegram 通知发送失败: $RESPONSE"
|
||||
# 构建成功,通知失败不应该导致整个 job 失败
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "❌ 发送 Telegram 消息时发生错误"
|
||||
# 构建成功,通知失败不应该导致整个 job 失败
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,134 @@
|
||||
name: Telegram Notification on PR Merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed # 当 PR 被关闭(合并或关闭)时触发
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# 只在 PR 被合并到 main 分支时执行
|
||||
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get PR details
|
||||
id: pr_details
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# 获取 PR 详细信息
|
||||
PR_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}")
|
||||
|
||||
# 获取 PR 变更的文件列表
|
||||
FILES_RESPONSE=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/files")
|
||||
|
||||
# 提取 PR 描述(body),保留换行,限制长度
|
||||
PR_BODY=$(echo "$PR_RESPONSE" | jq -r '.body // ""')
|
||||
if [ ${#PR_BODY} -gt 500 ]; then
|
||||
PR_BODY="${PR_BODY:0:500}..."
|
||||
fi
|
||||
|
||||
# 保存到输出变量(使用 base64 编码避免特殊字符问题)
|
||||
echo "pr_body<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$PR_BODY" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Send Telegram notification
|
||||
env:
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
run: |
|
||||
# 检查 PR 是否被合并(而不是仅关闭)
|
||||
PR_MERGED="${{ github.event.pull_request.merged }}"
|
||||
if [ "$PR_MERGED" != "true" ]; then
|
||||
echo "ℹ️ PR 仅关闭,未合并,跳过通知"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查必要的环境变量
|
||||
# 注意:TELEGRAM_CHAT_ID 可以是个人聊天 ID(正数)或群组 ID(负数,如 -1001234567890)
|
||||
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
|
||||
echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 获取 PR 基本信息
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
PR_URL="${{ github.event.pull_request.html_url }}"
|
||||
PR_MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
|
||||
# 获取 PR 详细信息
|
||||
PR_BODY="${{ steps.pr_details.outputs.pr_body }}"
|
||||
|
||||
# 转义 PR 标题和描述中的 HTML 特殊字符(但保留已有的 HTML 标签)
|
||||
PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&/g' | sed 's/</\</g' | sed 's/>/\>/g')
|
||||
|
||||
# 构建消息内容(使用 HTML 格式)
|
||||
MESSAGE="🚀 <b>PR 已合并到 main 分支</b>"$'\n'$'\n'"📝 <b>PR #${PR_NUMBER}:</b> ${PR_TITLE_ESCAPED}"$'\n'"🔗 <b>链接:</b> <a href=\"${PR_URL}\">查看 PR</a>"$'\n'"🔀 <b>合并提交:</b> <code>${PR_MERGE_COMMIT:0:7}</code>"
|
||||
|
||||
# 添加 PR 描述(如果有)
|
||||
if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ] && [ "$PR_BODY" != "" ]; then
|
||||
# 使用 Python 将 Markdown 转换为 HTML(更可靠)
|
||||
# 使用 echo 创建临时 Python 脚本,避免 heredoc 导致的 YAML 解析问题
|
||||
echo 'import sys' > /tmp/markdown_to_html.py
|
||||
echo 'import re' >> /tmp/markdown_to_html.py
|
||||
echo '' >> /tmp/markdown_to_html.py
|
||||
echo 'text = sys.stdin.read()' >> /tmp/markdown_to_html.py
|
||||
echo '' >> /tmp/markdown_to_html.py
|
||||
echo 'text = text.replace("&", "&")' >> /tmp/markdown_to_html.py
|
||||
echo 'text = text.replace("<", "<")' >> /tmp/markdown_to_html.py
|
||||
echo 'text = text.replace(">", ">")' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"```([^`]+)```", r"<pre><code>\\1</code></pre>", text, flags=re.DOTALL)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"`([^`]+)`", r"<code>\\1</code>", text)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"\\*\\*([^*]+)\\*\\*", r"<b>\\1</b>", text)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"(?<!<[^>]*)\\*([^*<>]+)\\*(?![^<]*>)", r"<i>\\1</i>", text)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"\\[([^\\]]+)\\]\\(([^)]+)\\)", r"<a href=\\"\\2\\">\\1</a>", text)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^### (.+)$", r"<b>\\1</b>", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^## (.+)$", r"<b>\\1</b>", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^# (.+)$", r"<b>\\1</b>", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^- (.+)$", r"• \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^ - (.+)$", r" • \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'text = re.sub(r"^ - (.+)$", r" • \\1", text, flags=re.MULTILINE)' >> /tmp/markdown_to_html.py
|
||||
echo 'if len(text) > 1000:' >> /tmp/markdown_to_html.py
|
||||
echo ' text = text[:1000] + "...\\n<i>(内容已截断)</i>"' >> /tmp/markdown_to_html.py
|
||||
echo 'print(text, end="")' >> /tmp/markdown_to_html.py
|
||||
PR_BODY_HTML=$(echo "$PR_BODY" | python3 /tmp/markdown_to_html.py)
|
||||
|
||||
MESSAGE="${MESSAGE}"$'\n'$'\n'"📄 <b>PR 描述:</b>"$'\n'"${PR_BODY_HTML}"
|
||||
fi
|
||||
|
||||
# 发送 Telegram 消息(使用 jq 转义 JSON)
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
||||
--arg text "$MESSAGE" \
|
||||
'{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json
|
||||
|
||||
# 检查发送结果
|
||||
if [ $? -eq 0 ]; then
|
||||
RESPONSE=$(cat /tmp/telegram_response.json)
|
||||
if echo "$RESPONSE" | grep -q '"ok":true'; then
|
||||
echo "✅ Telegram 通知发送成功"
|
||||
else
|
||||
echo "❌ Telegram 通知发送失败: $RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ 发送 Telegram 消息时发生错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+34
-15
@@ -199,22 +199,29 @@ class OrderStatusUpdateService(
|
||||
// 查询订单详情
|
||||
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
||||
|
||||
if (!orderResponse.isSuccessful) {
|
||||
// HTTP 错误,可能是订单不存在,删除
|
||||
logger.info("订单查询失败(HTTP错误),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
||||
try {
|
||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||
} catch (e: Exception) {
|
||||
logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e)
|
||||
}
|
||||
// 先检查 HTTP 状态码,非 200 的都跳过
|
||||
if (orderResponse.code() != 200) {
|
||||
// HTTP 非 200,记录日志并跳过,等待下次轮询
|
||||
// 不删除订单,因为可能是临时网络问题或 API 错误
|
||||
val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情"
|
||||
logger.debug("订单查询失败(HTTP非200),等待下次轮询: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}, errorBody=$errorBody")
|
||||
continue
|
||||
}
|
||||
|
||||
// HTTP 200,检查响应体
|
||||
// 响应体也可能返回字符串 "null",Gson 解析时会返回 null
|
||||
val orderDetail = orderResponse.body()
|
||||
if (orderDetail == null) {
|
||||
// HTTP 200 但响应体为空,表示订单不存在,删除
|
||||
logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
||||
// HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在
|
||||
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
|
||||
val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty()
|
||||
if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) {
|
||||
logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}")
|
||||
continue
|
||||
}
|
||||
|
||||
// 订单不存在且未部分卖出,删除本地订单
|
||||
logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||
try {
|
||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||
@@ -577,16 +584,28 @@ class OrderStatusUpdateService(
|
||||
|
||||
// 查询订单详情
|
||||
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
||||
if (!orderResponse.isSuccessful) {
|
||||
|
||||
// 先检查 HTTP 状态码,非 200 的都跳过
|
||||
if (orderResponse.code() != 200) {
|
||||
val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情"
|
||||
logger.debug("查询订单详情失败,等待下次轮询: orderId=${order.buyOrderId}, code=${orderResponse.code()}, errorBody=$errorBody")
|
||||
logger.debug("查询订单详情失败(HTTP非200),等待下次轮询: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}, errorBody=$errorBody")
|
||||
continue
|
||||
}
|
||||
|
||||
// HTTP 200,检查响应体
|
||||
// 响应体也可能返回字符串 "null",Gson 解析时会返回 null
|
||||
val orderDetail = orderResponse.body()
|
||||
if (orderDetail == null) {
|
||||
// HTTP 200 但响应体为空,表示订单不存在(没有交易成功),删除本地订单
|
||||
logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
||||
// HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在
|
||||
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
|
||||
val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty()
|
||||
if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) {
|
||||
logger.debug("订单不存在但已部分卖出,保留订单用于统计: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, matchedQuantity=${order.matchedQuantity}")
|
||||
continue
|
||||
}
|
||||
|
||||
// 订单不存在且未部分卖出,删除本地订单
|
||||
logger.info("订单不存在(HTTP 200 但响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||
try {
|
||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||
|
||||
Reference in New Issue
Block a user