From b5bdf4c88d3982913f5618a4d66a1aecc41e401e Mon Sep 17 00:00:00 2001 From: gouki Date: Fri, 7 Aug 2026 21:56:38 +0000 Subject: [PATCH] =?UTF-8?q?fix(wish-tree):=20=E4=BF=AE=E5=A4=8D=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E6=A0=91=E6=97=B6=E5=AF=BC=E8=88=AA=E6=A0=8F=E6=B6=88?= =?UTF-8?q?=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Canvas添加z-index: 1,确保不覆盖导航栏 - top-bar添加pointer-events: none,子元素恢复auto - WXML添加trees.length保护,防止空数组访问 - switchTree添加空数组保护 --- .gitea/docs/DEPLOY_NOTIFICATIONS.md | 198 +++++++++++++++++++++++++++ .gitea/scripts/notify.sh | 185 +++++++++++++++++++++++++ .gitea/workflows/miniapp-preview.yml | 26 ++++ .gitea/workflows/server-deploy.yml | 24 ++++ mini/pages/wish-tree/wish-tree.js | 2 + mini/pages/wish-tree/wish-tree.wxml | 2 +- mini/pages/wish-tree/wish-tree.wxss | 6 + mini/utils/core/index.js | 60 +++++++- 8 files changed, 495 insertions(+), 8 deletions(-) create mode 100644 .gitea/docs/DEPLOY_NOTIFICATIONS.md create mode 100644 .gitea/scripts/notify.sh diff --git a/.gitea/docs/DEPLOY_NOTIFICATIONS.md b/.gitea/docs/DEPLOY_NOTIFICATIONS.md new file mode 100644 index 0000000..e010838 --- /dev/null +++ b/.gitea/docs/DEPLOY_NOTIFICATIONS.md @@ -0,0 +1,198 @@ +# 部署通知配置指南 + +本文档说明如何配置 Gitea Actions 的部署通知功能,支持 Telegram 和 Discord 双渠道通知。 + +## 功能说明 + +- ✅ **自动触发**:部署成功或失败时自动发送通知 +- ✅ **双渠道**:同时支持 Telegram 和 Discord +- ✅ **详细信息**:包含仓库、分支、提交、作者、工作流等信息 +- ✅ **失败容错**:通知发送失败不会影响部署流程 + +## 配置步骤 + +### 1. Telegram Bot 配置 + +#### 1.1 创建 Telegram Bot + +1. 在 Telegram 中搜索 `@BotFather` +2. 发送 `/newbot` 创建新机器人 +3. 按提示设置机器人名称和用户名 +4. 获得 Bot Token,格式类似:`123456789:ABCdefGHIjklMNOpqrsTUVwxyz` + +#### 1.2 获取 Chat ID + +**方法 1:通过 Bot API** +1. 将你的 Bot 添加到目标群组或频道 +2. 发送任意消息给 Bot +3. 访问:`https://api.telegram.org/bot/getUpdates` +4. 在返回的 JSON 中找到 `chat.id` + +**方法 2:使用第三方 Bot** +1. 添加 `@userinfobot` 到你的群组 +2. 它会自动显示 Chat ID + +### 2. Discord Webhook 配置 + +1. 打开 Discord 服务器设置 +2. 进入 **集成** → **Webhook** +3. 点击 **新建 Webhook** +4. 设置名称和头像(可选) +5. 选择目标频道 +6. 复制 Webhook URL,格式类似: + ``` + https://discord.com/api/webhooks/123456789/abcdefghijklmnopqrstuvwxyz + ``` + +### 3. 配置 Gitea Secrets + +由于 Gitea Actions API 在该实例不可用,需要直接写入 MySQL 数据库。 + +#### 3.1 连接到 Gitea 数据库 + +```bash +# SSH 到 Gitea 服务器 +ssh doc79 + +# 连接 MySQL(根据实际情况调整) +mysql -u gitea -p gitea +``` + +#### 3.2 插入 Secrets + +```sql +-- Telegram Bot Token +INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix) +VALUES (0, 0, 'TELEGRAM_BOT_TOKEN', '你的Bot Token', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); + +-- Telegram Chat ID +INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix) +VALUES (0, 0, 'TELEGRAM_CHAT_ID', '你的Chat ID', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); + +-- Discord Webhook URL +INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix) +VALUES (0, 0, 'DISCORD_WEBHOOK_URL', '你的Webhook URL', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); +``` + +**注意**: +- `owner_id=0` 表示全局变量,所有仓库可用 +- 如果只想对特定仓库生效,需要查询该仓库的 `repo_id` 并替换 + +#### 3.3 验证配置 + +```sql +-- 查看所有 Actions 变量 +SELECT id, owner_id, repo_id, name, LEFT(data, 20) as data_preview +FROM action_variable +WHERE name IN ('TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'DISCORD_WEBHOOK_URL'); +``` + +### 4. 测试通知 + +配置完成后,推送代码到 main 分支触发部署: + +```bash +# 测试后端部署通知 +git add server/ +git commit -m "test: 测试部署通知" +git push origin main + +# 测试小程序部署通知 +git add mini/ +git commit -m "test: 测试小程序通知" +git push origin main +``` + +## 通知示例 + +### Telegram 通知 + +``` +✅ 后端服务部署成功 + +状态: 成功 +仓库: gouki/lunar +分支: main +提交: abc1234 +作者: gouki +工作流: Build and Deploy Server #42 + +部署包已保存到: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz + +查看详情 +``` + +### Discord 通知 + +Discord 会显示一个嵌入消息卡片,包含: +- 标题和状态图标 +- 仓库、分支、提交、作者信息 +- 部署详情 +- 时间戳 +- 可点击的链接跳转到 Actions 页面 + +## 故障排查 + +### 通知未发送 + +1. **检查 Secrets 配置** + ```sql + SELECT * FROM action_variable WHERE name LIKE '%TELEGRAM%' OR name LIKE '%DISCORD%'; + ``` + +2. **查看 Actions 日志** + - 在 Gitea 网页界面查看 Actions 运行日志 + - 查找 "发送 Telegram 通知" 或 "发送 Discord 通知" 相关输出 + +3. **检查网络连接** + - 确保运行器可以访问 `api.telegram.org` 和 `discord.com` + - 你的运行器在海外,理论上没有问题 + +### Telegram 通知失败 + +- **Bot Token 错误**:检查 Token 是否正确 +- **Chat ID 错误**:确保 Bot 已加入群组/频道,且 Chat ID 正确 +- **权限问题**:确保 Bot 有发送消息的权限 + +### Discord 通知失败 + +- **Webhook URL 错误**:检查 URL 是否完整 +- **Webhook 被删除**:重新创建 Webhook +- **频道权限**:确保 Webhook 有权限在目标频道发送消息 + +## 高级配置 + +### 自定义通知内容 + +编辑 `.gitea/workflows/server-deploy.yml` 或 `.gitea/workflows/miniapp-preview.yml`,修改通知步骤的消息内容: + +```yaml +.gitea/scripts/notify.sh success \ + "自定义标题" \ + "自定义消息内容" +``` + +### 只使用单一渠道 + +如果只想使用 Telegram 或 Discord 之一,只需配置对应的 Secrets 即可。脚本会自动跳过未配置的渠道。 + +### 添加更多通知渠道 + +可以扩展 `.gitea/scripts/notify.sh` 脚本,添加其他通知渠道(如 Slack、钉钉、企业微信等)。 + +## 安全建议 + +1. **保护 Secrets**:不要将 Bot Token、Chat ID、Webhook URL 提交到代码仓库 +2. **定期轮换**:定期更换 Bot Token 和 Webhook URL +3. **权限最小化**:Telegram Bot 只授予发送消息权限,Discord Webhook 只用于特定频道 +4. **监控异常**:如果发现异常通知,立即检查 Secrets 是否泄露 + +## 相关文件 + +- 通知脚本:`.gitea/scripts/notify.sh` +- 后端部署工作流:`.gitea/workflows/server-deploy.yml` +- 小程序部署工作流:`.gitea/workflows/miniapp-preview.yml` + +## 更新日志 + +- 2026-08-07: 初始版本,支持 Telegram 和 Discord 双渠道通知 diff --git a/.gitea/scripts/notify.sh b/.gitea/scripts/notify.sh new file mode 100644 index 0000000..5065224 --- /dev/null +++ b/.gitea/scripts/notify.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# 部署通知脚本 - 支持 Telegram 和 Discord +# 使用方法: ./notify.sh <message> [color] +# status: success 或 failure +# title: 通知标题 +# message: 通知内容 +# color: Discord 消息颜色(可选,默认根据 status 自动选择) + +set -e + +STATUS="$1" +TITLE="$2" +MESSAGE="$3" +COLOR="${4:-}" + +# 根据状态设置默认值 +if [ "$STATUS" = "success" ]; then + EMOJI="✅" + DEFAULT_COLOR="3066993" # 绿色 + STATUS_TEXT="成功" +elif [ "$STATUS" = "failure" ]; then + EMOJI="❌" + DEFAULT_COLOR="15158332" # 红色 + STATUS_TEXT="失败" +else + EMOJI="ℹ️" + DEFAULT_COLOR="3447003" # 蓝色 + STATUS_TEXT="通知" +fi + +COLOR="${COLOR:-$DEFAULT_COLOR}" + +# 获取 Git 信息 +REPO="${GITEA_REPO:-unknown}" +BRANCH="${GITEA_REF_NAME:-unknown}" +COMMIT_SHA="${GITEA_SHA:-unknown}" +COMMIT_SHORT="${COMMIT_SHA:0:7}" +AUTHOR="${GITEA_ACTOR:-unknown}" +WORKFLOW="${GITEA_WORKFLOW:-unknown}" +RUN_NUMBER="${GITEA_RUN_NUMBER:-unknown}" +RUN_URL="${GITEA_SERVER_URL:-}/${GITEA_REPO}/actions/runs/${GITEA_RUN_ID:-}" + +# ==================== Telegram 通知 ==================== +send_telegram() { + if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then + echo "⚠️ Telegram 配置缺失,跳过通知" + return 0 + fi + + local tg_message="${EMOJI} <b>${TITLE}</b> + +<b>状态:</b> ${STATUS_TEXT} +<b>仓库:</b> ${REPO} +<b>分支:</b> ${BRANCH} +<b>提交:</b> <code>${COMMIT_SHORT}</code> +<b>作者:</b> ${AUTHOR} +<b>工作流:</b> ${WORKFLOW} #${RUN_NUMBER} + +${MESSAGE} + +<a href=\"${RUN_URL}\">查看详情</a>" + + echo "📤 发送 Telegram 通知..." + + local response + response=$(curl -s -X POST \ + "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d "chat_id=${TELEGRAM_CHAT_ID}" \ + -d "text=${tg_message}" \ + -d "parse_mode=HTML" \ + -d "disable_web_page_preview=true") + + if echo "$response" | grep -q '"ok":true'; then + echo "✅ Telegram 通知发送成功" + else + echo "❌ Telegram 通知发送失败: $response" + return 1 + fi +} + +# ==================== Discord 通知 ==================== +send_discord() { + if [ -z "$DISCORD_WEBHOOK_URL" ]; then + echo "⚠️ Discord Webhook 配置缺失,跳过通知" + return 0 + fi + + echo "📤 发送 Discord 通知..." + + # 构建 Discord Embed JSON + local discord_payload + discord_payload=$(cat <<EOF +{ + "embeds": [{ + "title": "${EMOJI} ${TITLE}", + "description": "${MESSAGE}", + "color": ${COLOR}, + "fields": [ + { + "name": "状态", + "value": "${STATUS_TEXT}", + "inline": true + }, + { + "name": "仓库", + "value": "${REPO}", + "inline": true + }, + { + "name": "分支", + "value": "${BRANCH}", + "inline": true + }, + { + "name": "提交", + "value": "\`${COMMIT_SHORT}\`", + "inline": true + }, + { + "name": "作者", + "value": "${AUTHOR}", + "inline": true + }, + { + "name": "工作流", + "value": "${WORKFLOW} #${RUN_NUMBER}", + "inline": true + } + ], + "footer": { + "text": "Gitea Actions" + }, + "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "url": "${RUN_URL}" + }] +} +EOF +) + + local response + response=$(curl -s -X POST \ + "$DISCORD_WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "$discord_payload") + + if [ -z "$response" ] || echo "$response" | grep -q '"id"'; then + echo "✅ Discord 通知发送成功" + else + echo "❌ Discord 通知发送失败: $response" + return 1 + fi +} + +# ==================== 主流程 ==================== +main() { + echo "🔔 开始发送部署通知..." + echo "状态: $STATUS" + echo "标题: $TITLE" + echo "消息: $MESSAGE" + echo "" + + local failed=0 + + # 发送 Telegram 通知 + if ! send_telegram; then + failed=$((failed + 1)) + fi + + echo "" + + # 发送 Discord 通知 + if ! send_discord; then + failed=$((failed + 1)) + fi + + echo "" + if [ $failed -eq 0 ]; then + echo "✅ 所有通知发送完成" + else + echo "⚠️ 部分通知发送失败 ($failed 个)" + # 不退出脚本,避免影响主流程 + fi +} + +main diff --git a/.gitea/workflows/miniapp-preview.yml b/.gitea/workflows/miniapp-preview.yml index a8606fe..5e0feac 100644 --- a/.gitea/workflows/miniapp-preview.yml +++ b/.gitea/workflows/miniapp-preview.yml @@ -93,6 +93,32 @@ jobs: cp mini/ci-artifacts/upload-result.json /ci-artifacts/miniapp-upload-latest.json ls -la /ci-artifacts/miniapp-upload-latest.json echo "Upload result saved on runner host: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" + + - name: Send success notification + if: success() + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + VERSION="$(tr -d '\n' < mini/ci-artifacts/resolved-version.txt)" + chmod +x .gitea/scripts/notify.sh + .gitea/scripts/notify.sh success \ + "小程序开发版上传成功" \ + "版本: ${VERSION}\n上传结果已保存到: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" + + - name: Send failure notification + if: failure() + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + chmod +x .gitea/scripts/notify.sh + .gitea/scripts/notify.sh failure \ + "小程序开发版上传失败" \ + "请检查 Actions 日志了解失败原因" + - name: Remove temporary credentials if: always() run: rm -f "$RUNNER_TEMP/wechat-ci.key" diff --git a/.gitea/workflows/server-deploy.yml b/.gitea/workflows/server-deploy.yml index 984e570..67b9021 100644 --- a/.gitea/workflows/server-deploy.yml +++ b/.gitea/workflows/server-deploy.yml @@ -69,6 +69,30 @@ jobs: ls -la /ci-artifacts/server-deploy-latest.tar.gz echo "Deployment package saved on runner host: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz" + - name: Send success notification + if: success() + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + chmod +x .gitea/scripts/notify.sh + .gitea/scripts/notify.sh success \ + "后端服务部署成功" \ + "部署包已保存到: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz" + + - name: Send failure notification + if: failure() + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: | + chmod +x .gitea/scripts/notify.sh + .gitea/scripts/notify.sh failure \ + "后端服务部署失败" \ + "请检查 Actions 日志了解失败原因" + - name: Cleanup if: always() run: | diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js index f648210..3ff3b78 100644 --- a/mini/pages/wish-tree/wish-tree.js +++ b/mini/pages/wish-tree/wish-tree.js @@ -939,6 +939,8 @@ Page({ const direction = e.currentTarget.dataset.direction; const { trees, currentTreeIndex } = this.data; + if (!trees.length) return; + let newIndex = currentTreeIndex; if (direction === 'prev') { newIndex = (currentTreeIndex - 1 + trees.length) % trees.length; diff --git a/mini/pages/wish-tree/wish-tree.wxml b/mini/pages/wish-tree/wish-tree.wxml index 55510bd..3c42272 100644 --- a/mini/pages/wish-tree/wish-tree.wxml +++ b/mini/pages/wish-tree/wish-tree.wxml @@ -13,7 +13,7 @@ <view class="switch-btn" bindtap="switchTree" data-direction="prev"> <text class="switch-icon">‹</text> </view> - <view class="tree-name">{{trees[currentTreeIndex].name || '许愿树'}}</view> + <view class="tree-name">{{trees.length > 0 ? trees[currentTreeIndex].name : '许愿树'}}</view> <view class="switch-btn" bindtap="switchTree" data-direction="next"> <text class="switch-icon">›</text> </view> diff --git a/mini/pages/wish-tree/wish-tree.wxss b/mini/pages/wish-tree/wish-tree.wxss index d7aad92..72b545e 100644 --- a/mini/pages/wish-tree/wish-tree.wxss +++ b/mini/pages/wish-tree/wish-tree.wxss @@ -13,6 +13,7 @@ left: 0; width: 100%; height: 100%; + z-index: 1; } /* 顶部导航 */ @@ -26,6 +27,11 @@ justify-content: space-between; align-items: center; z-index: 100; + pointer-events: none; +} + +.top-bar > * { + pointer-events: auto; } .tree-switcher { diff --git a/mini/utils/core/index.js b/mini/utils/core/index.js index 3f9fb07..d97d460 100644 --- a/mini/utils/core/index.js +++ b/mini/utils/core/index.js @@ -269,14 +269,60 @@ function getConstellation(month, day) { } } -/** 获取节气 */ +/** 计算某年某节气的公历日期(21世纪 C 值法,误差±1天) */ +function getSolarTermDate(year, termIndex) { + // 21世纪 C 值表(小寒=0, 大寒=1, 立春=2 ... 冬至=23) + const C21 = [ + 6.11, 20.84, 4.6295, 19.4599, 6.3826, 21.4155, // 小寒 大寒 立春 雨水 惊蛰 春分 + 5.59, 20.888, 6.318, 21.86, 6.5, 22.20, // 清明 谷雨 立夏 小满 芒种 夏至 + 7.928, 23.65, 8.35, 23.95, 8.44, 23.822, // 小暑 大暑 立秋 处暑 白露 秋分 + 9.098, 24.218, 8.218, 23.08, 7.9, 22.60 // 寒露 霜降 立冬 小雪 大雪 冬至 + ]; + // 20世纪 C 值表 + const C20 = [ + 6.9, 21.37, 5.4055, 20.12, 7.1082, 22.83, + 6.38, 21.646, 7.108, 22.36, 7.5, 23.13, + 8.318, 24.64, 9.35, 24.77, 9.74, 24.643, + 9.878, 24.918, 9.11, 23.65, 8.8, 23.54 + ]; + const Y = year % 100; + const C = year >= 2000 ? C21[termIndex] : C20[termIndex]; + // 闰年修正:小寒、大寒、立春、雨水(1-2月) + let leapAdjust = 0; + if (termIndex <= 3 && isLeapYear(year)) leapAdjust = 1; + // 特殊修正 + if (year === 2026 && termIndex === 3) leapAdjust = -1; // 2026雨水 + if (year === 2084 && termIndex === 3) leapAdjust = 1; + if (year === 1911 && termIndex === 6) leapAdjust = 1; // 清明 + if (year === 1984 && termIndex === 7) leapAdjust = -1; // 谷雨 + if (year === 1911 && termIndex === 8) leapAdjust = 1; // 立夏 + if (year === 1981 && termIndex === 10) leapAdjust = -1; // 芒种 + if (year === 1902 && termIndex === 11) leapAdjust = 1; // 夏至 + if (year === 1928 && termIndex === 12) leapAdjust = 1; // 小暑 + if (year === 1925 && termIndex === 13) leapAdjust = -1; // 大暑 + if (year === 2002 && termIndex === 14) leapAdjust = 1; // 立秋 + if (year === 1927 && termIndex === 16) leapAdjust = 1; // 白露 + if (year === 1942 && termIndex === 17) leapAdjust = 1; // 秋分 + if (year === 2089 && termIndex === 18) leapAdjust = 1; // 寒露 + if (year === 2089 && termIndex === 19) leapAdjust = 1; // 霜降 + if (year === 1978 && termIndex === 20) leapAdjust = 1; // 立冬 + if (year === 1954 && termIndex === 21) leapAdjust = -1; // 小雪 + if (year === 1918 && termIndex === 22) leapAdjust = 1; // 大雪 + if (year === 2021 && termIndex === 22) leapAdjust = -1; + if (year === 1902 && termIndex === 23) leapAdjust = 1; // 冬至 + + return Math.floor(Y * 0.2422 + C) - Math.floor((Y - 1) / 4) + leapAdjust; +} + +/** 获取节气(准确算法) */ function getSolarTerm(year, month, day) { - const termDates = SOLAR_TERM_DATES[month - 1]; - if (day === termDates[0]) { - return SOLAR_TERMS[(month - 1) * 2]; - } else if (day === termDates[1]) { - return SOLAR_TERMS[(month - 1) * 2 + 1]; - } + // 每月两个节气:第1个在 index (month-1)*2,第2个在 (month-1)*2+1 + const idx1 = (month - 1) * 2; + const idx2 = idx1 + 1; + const d1 = getSolarTermDate(year, idx1); + const d2 = getSolarTermDate(year, idx2); + if (day === d1) return SOLAR_TERMS[idx1]; + if (day === d2) return SOLAR_TERMS[idx2]; return null; }