From cd0ce9a9c0224f23b4d87642085f0aa0af660238 Mon Sep 17 00:00:00 2001 From: zhiyu1998 <542716863@qq.com> Date: Mon, 25 Nov 2024 17:06:52 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat:=20=E6=94=AF=E6=8C=81ipv6=5Fpatch?= =?UTF-8?q?-3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/webUI.js | 8 +--- index.js | 4 +- package.json | 1 + utils/network.js | 105 ++++++++++++++++++++++++++++++++++++++++++ utils/start-nextjs.js | 7 ++- 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 utils/network.js diff --git a/apps/webUI.js b/apps/webUI.js index a193c0e..01e7822 100644 --- a/apps/webUI.js +++ b/apps/webUI.js @@ -1,6 +1,6 @@ -import os from "os"; import { REDIS_YUNZAI_WEBUI } from "../constants/constant.js"; import config from "../model/config.js"; +import { constructPublicIPsMsg } from "../utils/network.js"; import { redisSetKey } from "../utils/redis-util.js"; import { getBotLoginInfo, getBotStatus, getBotVersionInfo, sendPrivateMsg } from "../utils/yunzai-util.js"; @@ -53,11 +53,7 @@ export class WebUI extends plugin { // 这里有点延迟,需要写反 e.reply(`R插件可视化面板:${ realIsOpenWebUI ? "✅已开启" : "❌已关闭" },重启后生效`); if (realIsOpenWebUI) { - const networkInterfaces = os.networkInterfaces(); - const ipAddress = Object.values(networkInterfaces) - .flat() - .filter(detail => detail.family === 'IPv4' && !detail.internal)[0].address; - await sendPrivateMsg(e, `R插件可视化面板地址:${ ipAddress }:4016`); + await sendPrivateMsg(e, constructPublicIPsMsg()); } return true; } diff --git a/index.js b/index.js index 8fadf29..51d3779 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import fs from "node:fs"; import path from "path"; import config from "./model/config.js"; +import { constructPublicIPsMsg } from "./utils/network.js"; import { startNextJs } from "./utils/start-nextjs.js"; if (!global.segment) { global.segment = (await import("oicq")).segment @@ -41,7 +42,8 @@ for (let i in files) { // 检查是否启动 webui if (isOpenWebUI) { - startNextJs('dev') + startNextJs('dev'); + logger.info(constructPublicIPsMsg()); } export { apps }; diff --git a/package.json b/package.json index 221716a..ed6ff48 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "scripts": { "dev": "cd server && next dev -p 4016", + "dev6": "cd server && HOST=:: next dev -p 4016", "start": "cd server && next start -p 4016", "build": "cd server && next build" }, diff --git a/utils/network.js b/utils/network.js new file mode 100644 index 0000000..1ba8375 --- /dev/null +++ b/utils/network.js @@ -0,0 +1,105 @@ +import os from 'os'; + +/** + * 判断是否是公网地址 + * @param ip + * @returns {boolean} + */ +function isPublicIP(ip) { + if (ip.includes(':')) { + // IPv6 检测 + if (ip.startsWith('fe80') || ip.startsWith('fc00')) { + return false; // 本地链路或私有 IPv6 + } + return true; // 其他 IPv6 认为是公网 + } else { + // IPv4 检测 + const parts = ip.split('.').map(Number); + if ( + (parts[0] === 10) || // 10.0.0.0/8 + (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || // 172.16.0.0/12 + (parts[0] === 192 && parts[1] === 168) // 192.168.0.0/16 + ) { + return false; // 私有 IPv4 + } + return true; // 其他 IPv4 认为是公网 + } +} + +/** + * 判断是否有公网 IPv6 + * @returns {boolean} + */ +export function hasIPv6Only() { + const interfaces = os.networkInterfaces(); + let hasPublicIPv4 = false; + let hasPublicIPv6 = false; + + for (const iface of Object.values(interfaces)) { + for (const config of iface) { + if (!config.internal && isPublicIP(config.address)) { + if (config.family === 'IPv4') { + hasPublicIPv4 = true; + } + if (config.family === 'IPv6') { + hasPublicIPv6 = true; + } + } + } + } + + if (hasPublicIPv6 && !hasPublicIPv4) { + logger.info('[R插件][公网检测]服务器仅拥有一个公网IPv6地址'); + return true; + } else if (hasPublicIPv4 && hasPublicIPv6) { + logger.info('[R插件][公网检测]服务器同时拥有公共IPv4和IPv6地址'); + return false; + } else if (hasPublicIPv4) { + logger.info('[R插件][公网检测]服务器仅拥有一个公网IPv4地址'); + return false; + } else { + logger.info('[R插件][公网检测]服务器未配置公网IP地址'); + return false; + } +} + +/** + * 获取所有公网IP地址 + * @returns {*[]} + */ +export function getPublicIPs() { + const interfaces = os.networkInterfaces(); + const publicIPs = []; + + for (const [name, iface] of Object.entries(interfaces)) { + for (const config of iface) { + if (!config.internal && isPublicIP(config.address)) { + publicIPs.push({ + interface: name, + address: config.address, + family: config.family, + }); + } + } + } + + return publicIPs; +} + +export function constructPublicIPsMsg() { + const networkInterfaces = os.networkInterfaces(); + const ipAddress = Object.values(networkInterfaces) + .flat() + .filter(detail => detail.family === 'IPv4' && !detail.internal)[0].address; + const publicIPs = getPublicIPs(); + let publicIPsStr = ''; + // 如果有公网地址 + if (publicIPs.length > 0) { + publicIPsStr = `\n公网地址:${ getPublicIPs().map(item => { + logger.info('[R插件][公网检测]公网IP地址', item.address); + return `#${ item.address }:4016#\n`; + }) }`; + } + publicIPsStr = `R插件可视化面板内网地址:${ ipAddress }:4016${ publicIPsStr }`; + return publicIPsStr; +} diff --git a/utils/start-nextjs.js b/utils/start-nextjs.js index 2f59e9b..2f231e6 100644 --- a/utils/start-nextjs.js +++ b/utils/start-nextjs.js @@ -1,4 +1,5 @@ import { spawn } from 'child_process'; +import { hasIPv6Only } from "./network.js"; logger.mark(`[R插件][WebUI], 父进程 PID: ${process.pid}`); @@ -6,10 +7,14 @@ let nextjsProcess = null; // 启动子进程运行 Next.js export const startNextJs = (mode = 'start') => { - const script = mode === 'start' ? 'start' : 'dev'; + let script = mode === 'start' ? 'start' : 'dev'; logger.info(logger.yellow(`[R插件][WebUI监测],启动 WebUI ${mode} 进程...`)); + if (hasIPv6Only()) { + script = 'dev6'; + } + nextjsProcess = spawn('pnpm', ['run', script], { cwd: './plugins/rconsole-plugin', // 指定工作目录 stdio: 'ignore',