Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cfdcb88d3 | ||
|
|
00f0898d98 | ||
|
|
1de9b5e958 | ||
|
|
c8e422a94b | ||
|
|
60e9f9235d | ||
|
|
46baa416f4 | ||
|
|
ecdb8af14a | ||
|
|
d6027e48eb | ||
|
|
f97bd5b9d9 |
@@ -36,6 +36,49 @@ jobs:
|
|||||||
echo "Extracted version: $VERSION"
|
echo "Extracted version: $VERSION"
|
||||||
echo "Full tag: $TAG_NAME"
|
echo "Full tag: $TAG_NAME"
|
||||||
|
|
||||||
|
- name: Send Telegram notification (build started)
|
||||||
|
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_URL="${{ github.event.release.html_url }}"
|
||||||
|
|
||||||
|
# 构建消息内容(仅包含关键信息)
|
||||||
|
MESSAGE="🔨 <b>Docker 镜像构建中</b>"$'\n'$'\n'"📦 <b>版本:</b> ${VERSION}"$'\n'"🏷️ <b>Tag:</b> <code>${TAG}</code>"$'\n'"🔗 <a href=\"${RELEASE_URL}\">查看 Release</a>"
|
||||||
|
|
||||||
|
# 发送 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
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -65,3 +108,49 @@ jobs:
|
|||||||
GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
|
GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes
|
||||||
cache-from: type=registry,ref=wrbug/polyhermes:latest
|
cache-from: type=registry,ref=wrbug/polyhermes:latest
|
||||||
cache-to: type=inline
|
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 }}"
|
||||||
|
|
||||||
|
# 构建消息内容(仅包含关键信息)
|
||||||
|
DEPLOY_DOC_URL="https://github.com/WrBug/PolyHermes/blob/main/docs/zh/DEPLOYMENT.md"
|
||||||
|
MESSAGE="✅ <b>Docker 镜像构建成功</b>"$'\n'$'\n'"📦 <b>版本:</b> ${VERSION}"$'\n'"🏷️ <b>Tag:</b> <code>${TAG}</code>"$'\n'"🔗 <a href=\"${RELEASE_URL}\">查看 Release</a>"$'\n'"📚 <a href=\"${DEPLOY_DOC_URL}\">Docker 部署文档</a>"
|
||||||
|
|
||||||
|
# 发送 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,103 @@
|
|||||||
|
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 特殊字符
|
||||||
|
PR_TITLE_ESCAPED=$(echo "$PR_TITLE" | sed 's/&/\&/g' | sed 's/</\</g' | sed 's/>/\>/g')
|
||||||
|
|
||||||
|
# 构建消息内容(仅包含关键信息)
|
||||||
|
MESSAGE="🚀 <b>main 分支代码更新</b>"$'\n'$'\n'"📝 <b>PR #${PR_NUMBER}:</b> ${PR_TITLE_ESCAPED}"$'\n'"🔗 <a href=\"${PR_URL}\">查看 PR</a>"
|
||||||
|
|
||||||
|
# 发送 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
|
||||||
|
|
||||||
+7
-3
@@ -46,6 +46,10 @@
|
|||||||
- `failed_trade` 表的删除通过 V16 migration 处理
|
- `failed_trade` 表的删除通过 V16 migration 处理
|
||||||
- 确保已有数据库的 migration checksum 保持一致
|
- 确保已有数据库的 migration checksum 保持一致
|
||||||
|
|
||||||
|
### 修复前端编译错误
|
||||||
|
- 修复 `PositionList.tsx` 中引用不存在的 `bestBid` 属性导致的编译错误
|
||||||
|
- 使用 `currentPrice` 替代 `bestBid`,确保前端代码可以正常编译
|
||||||
|
|
||||||
## 📚 文档更新
|
## 📚 文档更新
|
||||||
|
|
||||||
- 新增智能资金分析文档(`docs/zh/smart-money-analysis.md`)
|
- 新增智能资金分析文档(`docs/zh/smart-money-analysis.md`)
|
||||||
@@ -72,9 +76,9 @@
|
|||||||
|
|
||||||
## 📊 统计信息
|
## 📊 统计信息
|
||||||
|
|
||||||
- **文件变更**: 28 个文件
|
- **文件变更**: 29 个文件
|
||||||
- **代码变更**: +1595 行 / -678 行
|
- **代码变更**: +1597 行 / -678 行
|
||||||
- **主要提交**: 7 个提交
|
- **主要提交**: 8 个提交
|
||||||
|
|
||||||
## ⚠️ 重要提醒
|
## ⚠️ 重要提醒
|
||||||
|
|
||||||
|
|||||||
+34
-15
@@ -199,22 +199,29 @@ class OrderStatusUpdateService(
|
|||||||
// 查询订单详情
|
// 查询订单详情
|
||||||
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
||||||
|
|
||||||
if (!orderResponse.isSuccessful) {
|
// 先检查 HTTP 状态码,非 200 的都跳过
|
||||||
// HTTP 错误,可能是订单不存在,删除
|
if (orderResponse.code() != 200) {
|
||||||
logger.info("订单查询失败(HTTP错误),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
// HTTP 非 200,记录日志并跳过,等待下次轮询
|
||||||
try {
|
// 不删除订单,因为可能是临时网络问题或 API 错误
|
||||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情"
|
||||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
logger.debug("订单查询失败(HTTP非200),等待下次轮询: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}, errorBody=$errorBody")
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTP 200,检查响应体
|
||||||
|
// 响应体也可能返回字符串 "null",Gson 解析时会返回 null
|
||||||
val orderDetail = orderResponse.body()
|
val orderDetail = orderResponse.body()
|
||||||
if (orderDetail == null) {
|
if (orderDetail == null) {
|
||||||
// HTTP 200 但响应体为空,表示订单不存在,删除
|
// HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在
|
||||||
logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
|
||||||
|
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 {
|
try {
|
||||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
copyOrderTrackingRepository.deleteById(order.id!!)
|
||||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||||
@@ -577,16 +584,28 @@ class OrderStatusUpdateService(
|
|||||||
|
|
||||||
// 查询订单详情
|
// 查询订单详情
|
||||||
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
val orderResponse = clobApi.getOrder(order.buyOrderId)
|
||||||
if (!orderResponse.isSuccessful) {
|
|
||||||
|
// 先检查 HTTP 状态码,非 200 的都跳过
|
||||||
|
if (orderResponse.code() != 200) {
|
||||||
val errorBody = orderResponse.errorBody()?.string()?.take(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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTP 200,检查响应体
|
||||||
|
// 响应体也可能返回字符串 "null",Gson 解析时会返回 null
|
||||||
val orderDetail = orderResponse.body()
|
val orderDetail = orderResponse.body()
|
||||||
if (orderDetail == null) {
|
if (orderDetail == null) {
|
||||||
// HTTP 200 但响应体为空,表示订单不存在(没有交易成功),删除本地订单
|
// HTTP 200 且响应体为 null(或字符串 "null"),表示订单不存在
|
||||||
logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}")
|
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
|
||||||
|
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 {
|
try {
|
||||||
copyOrderTrackingRepository.deleteById(order.id!!)
|
copyOrderTrackingRepository.deleteById(order.id!!)
|
||||||
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
|
||||||
|
|||||||
Reference in New Issue
Block a user