From 3f5d11a8ef581c5a85a2a1b2adefb2803eeceae8 Mon Sep 17 00:00:00 2001 From: duorameng <2997944583@qq.com> Date: Thu, 16 Apr 2026 21:17:09 +0800 Subject: [PATCH] feat: add baihu notify --- .gitignore | 2 +- builtin/nodejs/index.js | 57 +++++++ builtin/nodejs/package.json | 7 + builtin/python/setup.py | 9 + cmd/builtininstall/builtininstall.go | 87 ++++++++++ cmd/cmd.go | 8 +- docker/Dockerfile | 1 + docs/guide/notify.md | 79 +++++---- example/notify/test_notify.js | 26 +++ example/notify/test_notify.py | 30 ++++ internal/constant/commands.go | 4 + internal/utils/mise.go | 28 ++++ web/src/views/notify/components/ApiUsage.vue | 167 +++++++++---------- 13 files changed, 382 insertions(+), 123 deletions(-) create mode 100644 builtin/nodejs/index.js create mode 100644 builtin/nodejs/package.json create mode 100644 builtin/python/setup.py create mode 100644 cmd/builtininstall/builtininstall.go create mode 100644 example/notify/test_notify.js create mode 100644 example/notify/test_notify.py diff --git a/.gitignore b/.gitignore index a6ddef1..069d8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ agent/config.ini agent/agent.pid agent/baihu-agent web/dist/ - +web/dev-dist/ # IDE .idea/ diff --git a/builtin/nodejs/index.js b/builtin/nodejs/index.js new file mode 100644 index 0000000..35ed0b8 --- /dev/null +++ b/builtin/nodejs/index.js @@ -0,0 +1,57 @@ +const http = require('http'); +const https = require('https'); +const { URL } = require('url'); + +/** + * 环境变量强校验:导入期进行 + */ +const TOKEN = process.env.BHPKG_NOTIFY_TOKEN; +const CHANNEL = process.env.BHPKG_NOTIFY_CHANNEL; + +if (!TOKEN || !CHANNEL) { + const missing = []; + if (!TOKEN) missing.push("BHPKG_NOTIFY_TOKEN"); + if (!CHANNEL) missing.push("BHPKG_NOTIFY_CHANNEL"); + + throw new Error(`缺少必要的环境变量以使用 baihu 模块: ${missing.join(", ")}。请在白虎面板的任务设置中配置这些 Key。`); +} + +/** + * 发送通知的辅助函数 (仅使用 Node.js 标准库) + */ +function notify(title, text, channelId) { + const notifyUrl = process.env.BHPKG_NOTIFY_URL || 'http://localhost:8052/api/v1/notify/send'; + const cid = channelId || CHANNEL; + + if (!notifyUrl || !TOKEN || !cid) return; + + try { + const parsedUrl = new URL(notifyUrl); + const protocol = parsedUrl.protocol === 'https:' ? https : http; + + const data = JSON.stringify({ + channel_id: cid, + title: title || '系统通知', + text: text + }); + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.pathname + (parsedUrl.search || ''), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'notify-token': TOKEN, + 'Content-Length': Buffer.byteLength(data) + } + }; + + const req = protocol.request(options); + req.on('error', (e) => {}); + req.write(data); + req.end(); + } catch (e) {} +} + +module.exports = { notify }; diff --git a/builtin/nodejs/package.json b/builtin/nodejs/package.json new file mode 100644 index 0000000..aba49c0 --- /dev/null +++ b/builtin/nodejs/package.json @@ -0,0 +1,7 @@ +{ + "name": "baihu", + "version": "1.0.0", + "description": "Baihu Panel internal helper for Node.js", + "main": "index.js", + "license": "MIT" +} diff --git a/builtin/python/setup.py b/builtin/python/setup.py new file mode 100644 index 0000000..d3449b8 --- /dev/null +++ b/builtin/python/setup.py @@ -0,0 +1,9 @@ +from setuptools import setup, find_packages + +setup( + name='baihu', + version='1.0.0', + description='Baihu Panel internal helper for Python', + packages=find_packages(), + python_requires='>=3.6', +) diff --git a/cmd/builtininstall/builtininstall.go b/cmd/builtininstall/builtininstall.go new file mode 100644 index 0000000..9e200b8 --- /dev/null +++ b/cmd/builtininstall/builtininstall.go @@ -0,0 +1,87 @@ +package builtininstall + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + + "github.com/engigu/baihu-panel/internal/logger" + "github.com/engigu/baihu-panel/internal/utils" +) + +// Run 执行内建包安装逻辑 +func Run(args []string) { + logger.Infof("[Builtin] 开始为 mise 环境安装内建包...") + + // 1. 确定内建包路径 + // 优先使用 /www/builtin (Docker 环境),否则尝试相对于二进制文件的当前目录 + builtinPath := "/www/builtin" + if _, err := os.Stat(builtinPath); os.IsNotExist(err) { + // 回退到当前目录下的 builtin + pwd, _ := os.Getwd() + builtinPath = filepath.Join(pwd, "builtin") + } + + if _, err := os.Stat(builtinPath); os.IsNotExist(err) { + logger.Errorf("[Builtin] 找不到内建包目录: %s", builtinPath) + return + } + + // 2. 安装 Node.js 包 + installForLanguage("node", filepath.Join(builtinPath, "nodejs"), "npm install") + + // 3. 安装 Python 包 + installForLanguage("python", filepath.Join(builtinPath, "python"), "pip install -e") + + logger.Infof("[Builtin] 内建包安装流程完成") +} + +func installForLanguage(lang, pkgPath, installBaseCmd string) { + if _, err := os.Stat(pkgPath); os.IsNotExist(err) { + logger.Warnf("[Builtin] %s 的内建包目录不存在: %s", lang, pkgPath) + return + } + + versions, err := utils.ListMiseInstalledVersions(lang) + if err != nil { + logger.Errorf("[Builtin] 获取 %s 的 mise 版本列表失败: %v", lang, err) + return + } + + if len(versions) == 0 { + logger.Infof("[Builtin] 未发现已安装的 %s 版本,跳过", lang) + return + } + + for _, v := range versions { + logger.Infof("[Builtin] 正在为 %s@%s 安装内建包...", lang, v) + + var cmdStr string + if lang == "node" { + // npm install 不需要 -e + cmdStr = fmt.Sprintf("npm install %s", pkgPath) + } else { + // python 建议使用 -e (editable) 或直接安装 + cmdStr = fmt.Sprintf("pip install -e %s", pkgPath) + } + + // 使用 mise exec 跨版本执行 + fullCmd := utils.BuildMiseCommandSimple(cmdStr, lang, v) + + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/c", fullCmd) + } else { + cmd = exec.Command("sh", "-c", fullCmd) + } + + out, err := cmd.CombinedOutput() + if err != nil { + logger.Errorf("[Builtin] 为 %s@%s 安装失败: %v\n输出: %s", lang, v, err, string(out)) + } else { + logger.Infof("[Builtin] 为 %s@%s 安装成功", lang, v) + } + } +} diff --git a/cmd/cmd.go b/cmd/cmd.go index de29c36..f3e96a1 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/engigu/baihu-panel/cmd/builtininstall" "github.com/engigu/baihu-panel/cmd/reposync" "github.com/engigu/baihu-panel/cmd/resetpwd" "github.com/engigu/baihu-panel/cmd/restore" @@ -12,8 +13,9 @@ type CommandHandler func(args []string) // Handlers 维护了除了 server 之外的命令的执行入口 var Handlers = map[string]CommandHandler{ - "reposync": reposync.Run, - "resetpwd": resetpwd.Run, - "restore": restore.Run, + "reposync": reposync.Run, + "resetpwd": resetpwd.Run, + "restore": restore.Run, + "builtininstall": builtininstall.Run, // "migrate": migrate.Run, } diff --git a/docker/Dockerfile b/docker/Dockerfile index 05307d2..2575bc8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -154,6 +154,7 @@ COPY --from=frontend-builder /app/web/dist /www/baihu # Copy configs and entrypoint COPY --from=backend-builder /app/configs ./configs COPY --from=backend-builder /app/example ./example +COPY builtin/ /www/builtin COPY docker/docker-entrypoint.sh . diff --git a/docs/guide/notify.md b/docs/guide/notify.md index 5e30d85..d77b385 100644 --- a/docs/guide/notify.md +++ b/docs/guide/notify.md @@ -35,56 +35,65 @@ --- -### 路径二:脚本手动调用 (API) +### 路径二:脚本手动调用 (内置助手库 - 推荐) -如果您需要在脚本逻辑内部(例如:当抓取到特定数据时)主动触发通知,可以使用此方式。 +白虎面板提供了一套**零配置**的内建助手库(Built-in SDK),支持 Python 和 Node.js。它会自动读取系统注入的环境变量,让您在脚本中只需一行代码即可实现通知投递。 + +#### 1. 环境初始化 +在开始编写脚本前,您需要在终端执行以下命令,为面板管理的所有语言环境安装 `baihu` 包: + +```bash +baihu builtininstall +``` +*该操作会将助手库安装到 mise 管理的所有版本中,确保 import 成功。* + +#### 2. 代码示例 + +##### Python (同步调用) +```python +import baihu + +# 内部自动通过环境变量鉴权,无需填 TOKEN 和 URL +baihu.notify("任务标题", "通知正文内容") +``` + +##### Node.js (异步调用) +```javascript +const baihu = require('baihu'); + +// 极简调用,支持在 CommonJS/ESM 中使用 +baihu.notify("任务标题", "通知正文内容"); +``` + +--- + +### 路径三:其他语言/高级调用 (原始 API) + +如果您使用 Shell 或其他尚未提供助手库的语言,可以通过标准 HTTP POST 请求调用。 #### 1. 快速获取代码 -为了极大降低集成门槛,面板内置了代码生成器: -- 进入 **「消息推送」** -> **「脚本调用」** 标签。 -- 页面会根据您已配置的渠道,自动生成包含 **通知 Token** 和 **渠道 ID** 的完整代码示例。 -- 支持 **Python**、**Node.js** 和 **Shell (Curl)** 格式,直接复制即可使用。 +进入 **「消息推送」** -> **「脚本调用说明」** 标签,页面会根据您的配置自动生成包含 **通知 Token** 和 **默认渠道 ID** 的完整代码。 #### 2. 代码参考示例 -如果您需要手动编写逻辑,请参考以下实现: -##### Python 示例 +##### Shell (Curl) +```bash +curl -X POST "http://localhost:8052/api/v1/notify/send" \ + -H "notify-token: 您的_NOTIFY_TOKEN" \ + -d '{"channel_id":"渠道ID", "title":"标题", "text":"内容"}' +``` + +##### 基础 Python (requests) ```python import requests def send_notify(title, content): url = "http://localhost:8052/api/v1/notify/send" headers = { "notify-token": "您的_NOTIFY_TOKEN" } - data = { - "channel_id": "您的_渠道_ID", - "title": title, - "text": content - } + data = {"channel_id": "您的_渠道_ID", "title": title, "text": content} requests.post(url, headers=headers, json=data) ``` -##### Node.js 示例 -```javascript -const axios = require('axios'); - -async function sendNotify(title, content) { - await axios.post('http://localhost:8052/api/v1/notify/send', { - channel_id: '您的_渠道_ID', - title: title, - text: content - }, { - headers: { 'notify-token': '您的_NOTIFY_TOKEN' } - }); -} -``` - -##### Shell 示例 -```bash -curl -X POST "http://localhost:8052/api/v1/notify/send" \ - -H "notify-token: 您的_NOTIFY_TOKEN" \ - -d '{"channel_id":"渠道ID", "title":"标题", "text":"内容"}' -``` - --- ## 消息中心管理 diff --git a/example/notify/test_notify.js b/example/notify/test_notify.js new file mode 100644 index 0000000..d7feacc --- /dev/null +++ b/example/notify/test_notify.js @@ -0,0 +1,26 @@ +const baihu = require('baihu'); + +/** + * 内建通知测试示例 (Node.js) + * + * 使用说明: + * 1. 确保已在该 Node.js 环境下安装过内建包。 + * 2. 系统会自动注入 BHPKG_NOTIFY_TOKEN 和 BHPKG_NOTIFY_CHANNEL。 + */ + +console.log("正在尝试发送 Node.js 内建通知..."); + +try { + // 简单的一行代码即可完成推送 + baihu.notify( + "Node.js 任务提醒", + "这是一条来自 Node.js 示例脚本的通知消息。无需配置 API 地址或 Token。" + ); + + console.log("发送请求已提交。"); + console.log("提示:内建包采用异步非阻塞发送,不会干扰主逻辑执行。"); + +} catch (e) { + console.error(`通知失败: ${e.message}`); + console.error("请检查环境变量是否注入,或是否已运行过 'baihu builtininstall'。"); +} diff --git a/example/notify/test_notify.py b/example/notify/test_notify.py new file mode 100644 index 0000000..1717bb0 --- /dev/null +++ b/example/notify/test_notify.py @@ -0,0 +1,30 @@ +import baihu + +# 内建通知测试示例 (Python) +# +# 前提条件: +# 1. 已经在环境中安装了 baihu 包(例如通过 `baihu builtininstall`) +# 2. 环境中已注入有效环境变量: +# - BHPKG_NOTIFY_TOKEN +# - BHPKG_NOTIFY_CHANNEL + +def main(): + print("正在尝试发送 Python 内建通知...") + try: + # 调用内建 notify 函数 + # 内部会自动使用 BHPKG_NOTIFY_TOKEN & BHPKG_NOTIFY_CHANNEL 进行鉴权和投递 + response = baihu.notify( + title="Python 任务提醒", + text="这是一条来自 Python 示例脚本的通知消息。调用非常简单!" + ) + print("发送请求已处理。") + if response: + print(f"服务器响应: {response}") + + except ImportError as e: + print(f"错误: 库未正确加载。请确保已执行过环境初始化。详情: {e}") + except Exception as e: + print(f"发送过程发生异常: {e}") + +if __name__ == "__main__": + main() diff --git a/internal/constant/commands.go b/internal/constant/commands.go index 7aa9001..d1afc13 100644 --- a/internal/constant/commands.go +++ b/internal/constant/commands.go @@ -24,4 +24,8 @@ var Commands = []CommandInfo{ Name: "restore", Description: "从本地 zip 文件中全量恢复系统级备份数据", }, + { + Name: "builtininstall", + Description: "为所有 mise 管理的 Node.js 和 Python 环境安装内建助手库", + }, } diff --git a/internal/utils/mise.go b/internal/utils/mise.go index c128e93..44873ca 100644 --- a/internal/utils/mise.go +++ b/internal/utils/mise.go @@ -117,3 +117,31 @@ func BuildMiseCommandArgsSimple(cmdArgs []string, language, version string) []st } return append([]string{"mise", "exec", spec, "--"}, cmdArgs...) } + +// ListMiseInstalledVersions 获取指定语言已安装的所有版本列表 +func ListMiseInstalledVersions(language string) ([]string, error) { + // 执行 mise ls 命令 + cmd := exec.Command("mise", "ls", language) + out, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + + var versions []string + lines := strings.Split(string(out), "\n") + for _, line := range lines { + v := strings.TrimSpace(line) + if v == "" { + continue + } + // mise ls 的输出类似: + // 3.12.1 + // 3.11.5 + // 我们取第一个字段即可 + fields := strings.Fields(v) + if len(fields) > 0 { + versions = append(versions, fields[0]) + } + } + return versions, nil +} diff --git a/web/src/views/notify/components/ApiUsage.vue b/web/src/views/notify/components/ApiUsage.vue index 6c1a579..ddcf3e2 100644 --- a/web/src/views/notify/components/ApiUsage.vue +++ b/web/src/views/notify/components/ApiUsage.vue @@ -332,75 +332,106 @@ const currentExample = computed(() => {
- +
- +
- API 接口说明 + 内建助手库说明
-
-
- POST - /api/v1/notify/send + class="bg-zinc-50 dark:bg-zinc-950/50 p-4 text-xs sm:text-sm leading-relaxed text-zinc-800 dark:text-zinc-300 relative group min-h-full"> + +
+
+ RECOMMENDED + 内建助手库 (Built-in)
- +

+ 白虎面板内置了跨语言的脚本推送工具。通过环境层预装该库,您的脚本可以实现完全的“零配置”调用。 +

-
-
- Headers -
-
- Content-Type: - application/json +
+ +
+ + + 环境初始化 (一键安装) + +
+
+ $ baihu builtininstall
-
- notify-token: - <TOKEN> + +
+

+ * 该命令会为 mise 管理的所有 Python 和 Node.js 版本安装 baihu 包。 +

+
+ + +
+ + + 导入并使用 + + +
+ +
+
+ Python + BAIHU-PY +
+
+
import baihu
baihu.notify("标题", "内容")
+ +
+
+ + +
+
+ Node.js + BAIHU-JS +
+
+
const baihu = require('baihu');
baihu.notify("标题", "内容");
+ +
-
- Body - (JSON) -
-

{

-
-
- "channel_id": - "ID", - // - 渠道唯一标识 -
-
- "title": - "标题", - // 可选 -
-
- "text": - "内容" - // 必填 -
-
-

}

+ +
+
+ + 核心机制
+

+ 系统在执行脚本时会默认注入 BHPKG_NOTIFY_TOKEN & BHPKG_NOTIFY_CHANNEL。库会自动读取这些值,实现真正的免配置调用。 +

@@ -439,7 +470,7 @@ const currentExample = computed(() => { -
+
             
@@ -492,36 +523,4 @@ const currentExample = computed(() => {