diff --git a/.gitea/scripts/notify.sh b/.gitea/scripts/notify.sh index 5065224..4f74909 100644 --- a/.gitea/scripts/notify.sh +++ b/.gitea/scripts/notify.sh @@ -30,6 +30,12 @@ fi COLOR="${COLOR:-$DEFAULT_COLOR}" +# 校验 COLOR 必须是数字 +if ! [[ "$COLOR" =~ ^[0-9]+$ ]]; then + echo "⚠️ 非法 COLOR 值: $COLOR,使用默认颜色 $DEFAULT_COLOR" + COLOR="$DEFAULT_COLOR" +fi + # 获取 Git 信息 REPO="${GITEA_REPO:-unknown}" BRANCH="${GITEA_REF_NAME:-unknown}" @@ -40,6 +46,16 @@ WORKFLOW="${GITEA_WORKFLOW:-unknown}" RUN_NUMBER="${GITEA_RUN_NUMBER:-unknown}" RUN_URL="${GITEA_SERVER_URL:-}/${GITEA_REPO}/actions/runs/${GITEA_RUN_ID:-}" +# HTML 转义函数(用于 Telegram) +html_escape() { + local s="$1" + s="${s//&/&}" + s="${s///>}" + s="${s//\"/"}" + printf '%s' "$s" +} + # ==================== Telegram 通知 ==================== send_telegram() { if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then @@ -47,28 +63,41 @@ send_telegram() { return 0 fi - local tg_message="${EMOJI} ${TITLE} + # 转义所有用户可控的变量 + local escaped_title escaped_message escaped_repo escaped_branch escaped_author escaped_workflow escaped_run_url + escaped_title=$(html_escape "$TITLE") + escaped_message=$(html_escape "$MESSAGE") + escaped_repo=$(html_escape "$REPO") + escaped_branch=$(html_escape "$BRANCH") + escaped_author=$(html_escape "$AUTHOR") + escaped_workflow=$(html_escape "$WORKFLOW") + escaped_run_url=$(html_escape "$RUN_URL") + + local tg_message="${EMOJI} ${escaped_title} 状态: ${STATUS_TEXT} -仓库: ${REPO} -分支: ${BRANCH} +仓库: ${escaped_repo} +分支: ${escaped_branch} 提交: ${COMMIT_SHORT} -作者: ${AUTHOR} -工作流: ${WORKFLOW} #${RUN_NUMBER} +作者: ${escaped_author} +工作流: ${escaped_workflow} #${RUN_NUMBER} -${MESSAGE} +${escaped_message} -查看详情" +查看详情" echo "📤 发送 Telegram 通知..." local response - response=$(curl -s -X POST \ + response=$(curl -sS --connect-timeout 5 --max-time 15 -X POST \ "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d "chat_id=${TELEGRAM_CHAT_ID}" \ - -d "text=${tg_message}" \ + --data-urlencode "text=${tg_message}" \ -d "parse_mode=HTML" \ - -d "disable_web_page_preview=true") + -d "disable_web_page_preview=true" 2>&1) || { + echo "❌ Telegram 请求失败: $response" + return 1 + } if echo "$response" | grep -q '"ok":true'; then echo "✅ Telegram 通知发送成功" @@ -87,66 +116,63 @@ send_discord() { echo "📤 发送 Discord 通知..." - # 构建 Discord Embed JSON + # 使用 python3 构建 JSON,防止注入 local discord_payload - discord_payload=$(cat <&1) || { + echo "❌ Discord 请求失败: $http_code" + rm -f "$response_body" + return 1 + } - if [ -z "$response" ] || echo "$response" | grep -q '"id"'; then - echo "✅ Discord 通知发送成功" + if [ "$http_code" = "204" ] || [ "$http_code" = "200" ]; then + echo "✅ Discord 通知发送成功 (HTTP $http_code)" + rm -f "$response_body" else - echo "❌ Discord 通知发送失败: $response" + echo "❌ Discord 通知发送失败 (HTTP $http_code): $(cat "$response_body")" + rm -f "$response_body" return 1 fi } @@ -176,9 +202,12 @@ main() { echo "" if [ $failed -eq 0 ]; then echo "✅ 所有通知发送完成" + elif [ $failed -ge 2 ]; then + echo "❌ 所有通知渠道均发送失败" + exit 1 # 全部失败时退出非 0,让 CI 可见 else echo "⚠️ 部分通知发送失败 ($failed 个)" - # 不退出脚本,避免影响主流程 + # 部分失败不退出,避免影响主流程 fi } diff --git a/.gitea/workflows/miniapp-preview.yml b/.gitea/workflows/miniapp-preview.yml index 5e0feac..79283d4 100644 --- a/.gitea/workflows/miniapp-preview.yml +++ b/.gitea/workflows/miniapp-preview.yml @@ -102,10 +102,10 @@ jobs: 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 \ + bash .gitea/scripts/notify.sh success \ "小程序开发版上传成功" \ - "版本: ${VERSION}\n上传结果已保存到: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" + "版本: ${VERSION} +上传结果已保存到: /opt/lunar/ci-artifacts/miniapp-upload-latest.json" - name: Send failure notification if: failure() @@ -114,8 +114,7 @@ jobs: 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 \ + bash .gitea/scripts/notify.sh failure \ "小程序开发版上传失败" \ "请检查 Actions 日志了解失败原因" diff --git a/.gitea/workflows/server-deploy.yml b/.gitea/workflows/server-deploy.yml index 67b9021..e9e3ee7 100644 --- a/.gitea/workflows/server-deploy.yml +++ b/.gitea/workflows/server-deploy.yml @@ -76,8 +76,7 @@ jobs: 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 \ + bash .gitea/scripts/notify.sh success \ "后端服务部署成功" \ "部署包已保存到: /opt/lunar/ci-artifacts/server-deploy-latest.tar.gz" @@ -88,8 +87,7 @@ jobs: 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 \ + bash .gitea/scripts/notify.sh failure \ "后端服务部署失败" \ "请检查 Actions 日志了解失败原因" diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js index 3ff3b78..62c5bd2 100644 --- a/mini/pages/wish-tree/wish-tree.js +++ b/mini/pages/wish-tree/wish-tree.js @@ -172,8 +172,8 @@ Page({ if (res.code === 0) { this.setData({ wishes: res.data.list || [] }); - this.initRibbons(); - this.initDanmaku(); + this.safeInitRibbons(); + this.safeInitDanmaku(); } } catch (err) { console.error('加载许愿失败:', err); @@ -181,8 +181,28 @@ Page({ this.setData({ wishes: this.getMockWishes(), }); + this.safeInitRibbons(); + this.safeInitDanmaku(); + } + }, + + // 安全初始化丝带 + safeInitRibbons() { + try { this.initRibbons(); + } catch (err) { + console.error('初始化丝带失败:', err); + this.ribbons = []; + } + }, + + // 安全初始化弹幕 + safeInitDanmaku() { + try { this.initDanmaku(); + } catch (err) { + console.error('初始化弹幕失败:', err); + this.danmaku = []; } }, @@ -931,7 +951,16 @@ Page({ // 获取当前树 getCurrentTree() { const { trees, currentTreeIndex } = this.data; - return trees[currentTreeIndex] || { type: 'pine' }; + const tree = trees[currentTreeIndex]; + // 确保返回有效的树对象,带默认类型 + if (!tree) { + return { type: 'pine', name: '许愿树' }; + } + // 确保有type字段 + if (!tree.type) { + tree.type = 'pine'; + } + return tree; }, // 切换树 diff --git a/mini/utils/core/index.js b/mini/utils/core/index.js index d97d460..c607dd7 100644 --- a/mini/utils/core/index.js +++ b/mini/utils/core/index.js @@ -269,14 +269,16 @@ function getConstellation(month, day) { } } -/** 计算某年某节气的公历日期(21世纪 C 值法,误差±1天) */ +/** 计算某年某节气的公历日期(21世纪 C 值法,误差±1天) + * termIndex 按天文顺序:小寒=0, 大寒=1, 立春=2, 雨水=3, ..., 冬至=23 + */ function getSolarTermDate(year, termIndex) { - // 21世纪 C 值表(小寒=0, 大寒=1, 立春=2 ... 冬至=23) + // 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 // 寒露 霜降 立冬 小雪 大雪 冬至 + 5.11, 19.84, 3.6295, 18.4599, 5.3826, 20.4155, // 小寒 大寒 立春 雨水 惊蛰 春分 + 4.59, 19.888, 5.318, 20.86, 5.5, 21.20, // 清明 谷雨 立夏 小满 芒种 夏至 + 6.928, 22.65, 7.35, 22.95, 7.44, 22.822, // 小暑 大暑 立秋 处暑 白露 秋分 + 8.098, 23.218, 7.218, 22.08, 6.9, 21.60 // 寒露 霜降 立冬 小雪 大雪 冬至 ]; // 20世纪 C 值表 const C20 = [ @@ -311,16 +313,22 @@ function getSolarTermDate(year, termIndex) { 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; + return Math.floor(Y * 0.2422 + C) - Math.floor(Y / 4) + leapAdjust; } -/** 获取节气(准确算法) */ +/** 获取节气(准确算法) + * SOLAR_TERMS 数组从冬至开始:冬至=0, 小寒=1, 大寒=2, 立春=3, ..., 大雪=23 + */ function getSolarTerm(year, month, day) { - // 每月两个节气:第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); + // month 月的两个节气在 SOLAR_TERMS 中的索引: + // 1月→小寒(1)、大寒(2);2月→立春(3)、雨水(4);...;12月→大雪(23)、冬至(0) + const idx1 = month === 12 ? 23 : (month * 2 - 1); // 每月第一个节气 + const idx2 = month === 12 ? 0 : (month * 2); // 每月第二个节气 + // 转为天文顺序(小寒=0):SOLAR_TERMS 索引 - 1(冬至特殊为23) + const astroIdx1 = idx1 === 0 ? 23 : idx1 - 1; + const astroIdx2 = idx2 === 0 ? 23 : idx2 - 1; + const d1 = getSolarTermDate(year, astroIdx1); + const d2 = getSolarTermDate(year, astroIdx2); if (day === d1) return SOLAR_TERMS[idx1]; if (day === d2) return SOLAR_TERMS[idx2]; return null;