Skip to content

Commit d6ce2eb

Browse files
author
Taois
committed
feat: 支持完善的插件机制
1 parent c8ff181 commit d6ce2eb

File tree

9 files changed

+166
-52
lines changed

9 files changed

+166
-52
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,5 @@ dist
149149
*.pyc
150150
/scripts/python/appMap.txt
151151
/binary/
152+
/plugins/
153+
/plugin.js

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# drpyS(drpy-node)
22

33
nodejs作为服务端的drpy实现。全面升级异步写法
4-
~~积极开发中,每日一更~~,当前进度 `86%`
4+
~~积极开发中,每日一更~~,当前进度 `87%`
55
~~找工作中,随缘更新~~
66
上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新
77

index.js

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import path from 'path';
33
import os from 'os';
44
import qs from 'qs';
55
import {fileURLToPath} from 'url';
6-
import {spawn} from "child_process";
7-
import {existsSync} from 'fs';
86
import formBody from '@fastify/formbody';
97
import {validateBasicAuth, validateJs, validatePwd} from "./utils/api_validate.js";
10-
import {goProxy, getGoBinary} from "./utils/binaryManager.js"
8+
import {startAllPlugins} from "./utils/pluginManager.js";
119
// 注册自定义import钩子
1210
import './utils/esm-register.mjs';
1311
// 引入python守护进程
@@ -20,7 +18,6 @@ const {fastify} = fastlogger;
2018
// 获取当前路径
2119
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2220
const PORT = 5757;
23-
const GOPORT = 57571;
2421
const MAX_TEXT_SIZE = 0.1 * 1024 * 1024; // 设置最大文本大小为 0.1 MB
2522
// 定义options的目录
2623
const docsDir = path.join(__dirname, 'docs');
@@ -36,8 +33,10 @@ const catLibDir = path.join(__dirname, 'spider/catLib');
3633
const xbpqDir = path.join(__dirname, 'spider/xbpq');
3734
const viewsDir = path.join(__dirname, 'views');
3835
const configDir = path.join(__dirname, 'config');
39-
const goBinary = getGoBinary(__dirname);
40-
let goProc = null;
36+
37+
const pluginProcs = startAllPlugins(__dirname);
38+
// console.log('pluginProcs:', pluginProcs);
39+
4140
// 注册插件以支持 application/x-www-form-urlencoded
4241
fastify.register(formBody);
4342

@@ -140,8 +139,9 @@ process.on('exit', async (code) => {
140139
console.log(`Process exiting with code: ${code}`);
141140
// 这里不能直接用 await fastify.close()(Node 在 exit 里不等异步)
142141
// 但 Fastify 的 SIGINT/SIGTERM 会提前触发,所以这里只记录日志
143-
if (goProc) {
144-
goProc.kill();
142+
for (const [name, proc] of Object.entries(pluginProcs)) {
143+
console.log(`[pluginManager] 结束插件 ${name} ${proc.pid}`);
144+
proc.kill();
145145
}
146146
});
147147

@@ -171,23 +171,6 @@ registerRoutes(fastify, {
171171
// 启动服务
172172
const start = async () => {
173173
try {
174-
175-
if (existsSync(goBinary)) { // 启动golang二进制
176-
console.log('[goBinary]准备启动go二进制文件:', goBinary);
177-
goProc = spawn(goBinary, ["-p", GOPORT], {
178-
stdio: ["ignore", "pipe", "pipe"],
179-
});
180-
181-
// 打印 Go 服务输出
182-
goProc.stdout.on("data", (data) => {
183-
console.log("[Go-Server]", data.toString().trim());
184-
});
185-
goProc.stderr.on("data", (data) => {
186-
console.log("[Go-Server-STD]", data.toString().trim());
187-
});
188-
} else {
189-
console.log(`[goBinary]找不到go二进制文件:${goBinary},跳过启动`);
190-
}
191174
// 启动 Fastify 服务
192175
// await fastify.listen({port: PORT, host: '0.0.0.0'});
193176
await fastify.listen({port: PORT, host: '::'});

package.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {join, basename, dirname, resolve, relative} from 'path';
44
import url from 'url';
55

66
// 要排除的目录列表
7-
const EXCLUDE_DIRS = ['.git', '.idea', 'soft', 'binary', 'pyTools', 'drop_code', 'jstest', 'local', 'logs', '对话1.txt', 'vod_cache', 'data/mv'];
7+
const EXCLUDE_DIRS = ['.git', '.idea', 'soft', 'binary', 'plugins', 'pyTools', 'drop_code', 'jstest', 'local', 'logs', '对话1.txt', 'vod_cache', 'data/mv'];
88

99
// 要排除的文件列表
10-
const EXCLUDE_FILES = ['config/env.json', '.env', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', 'json/UC分享.json', 'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json'];
10+
const EXCLUDE_FILES = ['config/env.json', '.env', 'plugin.js', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', 'json/UC分享.json', 'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json'];
1111

1212
// 获取脚本所在目录
1313
const getScriptDir = () => dirname(resolve(url.fileURLToPath(import.meta.url)));

package.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import re
55

66
# 要排除的目录列表
7-
EXCLUDE_DIRS = ['.git', '.idea', 'soft', 'binary', 'pyTools', 'drop_code', 'jstest', 'local', 'logs', '对话1.txt',
7+
EXCLUDE_DIRS = ['.git', '.idea', 'soft', 'binary', 'plugins', 'pyTools', 'drop_code', 'jstest', 'local', 'logs',
8+
'对话1.txt',
89
'vod_cache', 'data/mv']
910

1011
# 要排除的文件列表
11-
EXCLUDE_FILES = ['config/env.json', '.env', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', 'json/UC分享.json',
12+
EXCLUDE_FILES = ['config/env.json', '.env', 'plugin.js', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js',
13+
'json/UC分享.json',
1214
'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json']
1315

1416

plugin.example.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const plugins = [
2+
{
3+
name: 'req-proxy',
4+
path: 'plugins/req-proxy',
5+
params: '-p 57571',
6+
desc: 'req代理服务',
7+
active: true
8+
},
9+
]
10+
11+
export default plugins;

public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</head>
99
<body>
1010
<h1 id="drpysdrpy-node">drpyS(drpy-node)</h1>
11-
<p>nodejs作为服务端的drpy实现。全面升级异步写法<br><del>积极开发中,每日一更</del>,当前进度 <code>86%</code><br><del>找工作中,随缘更新</del><br>上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新</p>
11+
<p>nodejs作为服务端的drpy实现。全面升级异步写法<br><del>积极开发中,每日一更</del>,当前进度 <code>87%</code><br><del>找工作中,随缘更新</del><br>上班当牛马,下班要带娃,阶段性佛系趁娃睡觉熬夜更新</p>
1212
<ul>
1313
<li><a href="docs/apidoc.md">接口文档</a> | <a href="docs/apiList.md">接口列表如定时任务</a> | <a href="https://github.com/waifu-project/movie/pull/135">小猫影视-待对接T4</a></li>
1414
<li><a href="/config?pwd=dzyyds">本地配置接口-动态本地</a></li>

utils/pluginManager.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import {spawn} from "child_process";
4+
import {fileURLToPath, pathToFileURL} from "url";
5+
6+
// 获取 pluginManager.js 的目录
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
10+
// plugin.js 和 plugin.example.js 在上级目录
11+
const userConfigPath = path.join(__dirname, "../plugin.js");
12+
const exampleConfigPath = path.join(__dirname, "../plugin.example.js");
13+
14+
// 尝试加载用户配置,如果没有就用 example
15+
let plugins = [];
16+
try {
17+
console.log(`检查插件配置文件: ${userConfigPath} 是否存在`);
18+
if (fs.existsSync(userConfigPath)) {
19+
plugins = (await import(pathToFileURL(userConfigPath).href)).default;
20+
console.log("[pluginManager] 使用用户 plugin.js 配置");
21+
} else if (fs.existsSync(exampleConfigPath)) {
22+
plugins = (await import(pathToFileURL(exampleConfigPath).href)).default;
23+
console.log("[pluginManager] 使用默认 plugin.example.js 配置");
24+
}
25+
} catch (err) {
26+
console.error("[pluginManager] 加载插件配置失败:", err);
27+
plugins = [];
28+
}
29+
30+
/**
31+
* 获取插件对应的二进制文件路径
32+
* @param {string} rootDir 项目根目录
33+
* @param {string} pluginPath 插件目录路径 (例: plugins/req-proxy)
34+
* @param {string} pluginName 插件名 (例: req-proxy)
35+
*/
36+
function getPluginBinary(rootDir, pluginPath, pluginName) {
37+
const platform = process.platform;
38+
const binDir = path.join(rootDir, pluginPath);
39+
40+
let binaryName = null;
41+
if (platform === "win32") {
42+
binaryName = `${pluginName}-windows.exe`;
43+
} else if (platform === "linux") {
44+
binaryName = `${pluginName}-linux`;
45+
} else if (platform === "darwin") {
46+
binaryName = `${pluginName}-darwin`;
47+
} else if (platform === "android") {
48+
binaryName = `${pluginName}-android`;
49+
} else {
50+
console.log("[getPluginBinary] Unsupported platform: " + platform);
51+
return null;
52+
}
53+
54+
return path.join(binDir, binaryName);
55+
}
56+
57+
/**
58+
* 启动插件
59+
* @param {Object} plugin 插件配置
60+
* @param {string} rootDir 项目根目录
61+
*/
62+
function startPlugin(plugin, rootDir) {
63+
if (!plugin.active) {
64+
console.log(`[pluginManager] 插件 ${plugin.name} 未激活,跳过`);
65+
return null;
66+
}
67+
68+
const binary = getPluginBinary(rootDir, plugin.path, plugin.name);
69+
if (!binary || !fs.existsSync(binary)) {
70+
console.error(`[pluginManager] 插件 ${plugin.name} 的二进制文件不存在: ${binary}`);
71+
return null;
72+
}
73+
74+
console.log(`[pluginManager] 启动插件 ${plugin.name}: ${binary} ${plugin.params || ""}`);
75+
76+
const args = plugin.params ? plugin.params.split(" ") : [];
77+
let proc;
78+
79+
try {
80+
proc = spawn(binary, args, {
81+
stdio: ["ignore", "pipe", "pipe"],
82+
});
83+
} catch (err) {
84+
console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (spawn 出错):`, err.message);
85+
return null;
86+
}
87+
88+
// 检查是否真的启动了
89+
if (!proc || !proc.pid) {
90+
console.error(`[pluginManager] 插件 ${plugin.name} 启动失败 (无效的进程 PID)`);
91+
return null;
92+
}
93+
94+
proc.stdout.on("data", (data) => {
95+
console.log(`[${plugin.name}]`, data.toString().trim());
96+
});
97+
98+
proc.stderr.on("data", (data) => {
99+
console.error(`[${plugin.name}-STD]`, data.toString().trim());
100+
});
101+
102+
proc.on("exit", (code, signal) => {
103+
console.log(`[pluginManager] 插件 ${plugin.name} 退出 (code=${code}, signal=${signal})`);
104+
});
105+
106+
proc.on("error", (err) => {
107+
console.error(`[pluginManager] 插件 ${plugin.name} 运行中出错:`, err.message);
108+
});
109+
110+
return proc;
111+
}
112+
113+
/**
114+
* 启动所有插件
115+
* @param {string} rootDir 项目根目录
116+
*/
117+
export function startAllPlugins(rootDir = process.cwd()) {
118+
console.log("[pluginManager] 准备启动所有插件...");
119+
const processes = {};
120+
for (const plugin of plugins) {
121+
const proc = startPlugin(plugin, rootDir);
122+
if (proc) {
123+
processes[plugin.name] = proc;
124+
} else {
125+
console.error(`[pluginManager] 插件 ${plugin.name} 启动失败,未加入到 processes`);
126+
}
127+
}
128+
return processes;
129+
}
Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import path from "path";
2-
31
/**
42
* 调用本地 Go /proxy 接口
53
* @param {Object} options
@@ -11,14 +9,14 @@ import path from "path";
119
* @param {string} [options.goHost] - Go 服务 host 默认 http://127.0.0.1:57571
1210
* @returns {Promise<Object>} { status, headers, body }
1311
*/
14-
export async function goProxy({
15-
method = "GET",
16-
url,
17-
headers = {},
18-
body,
19-
timeout,
20-
goHost = "http://127.0.0.1:57571",
21-
}) {
12+
export async function reqProxy({
13+
method = "GET",
14+
url,
15+
headers = {},
16+
body,
17+
timeout,
18+
goHost = "http://127.0.0.1:57571",
19+
}) {
2220
if (!url) throw new Error("url is required");
2321

2422
const proxyUrl = new URL("/proxy", goHost);
@@ -42,15 +40,4 @@ export async function goProxy({
4240

4341
const data = await resp.json();
4442
return data;
45-
}
46-
47-
export function getGoBinary(rootDir) {
48-
const platform = process.platform;
49-
const go_bin = './binary/go_proxy/'
50-
if (platform === "win32") return path.join(rootDir, go_bin, './server-windows.exe');
51-
if (platform === "linux") return path.join(rootDir, go_bin, './server-linux');
52-
if (platform === "android") return path.join(rootDir, go_bin, './server-android'); // 安卓设备
53-
console.log("[getGoBinary] Unsupported platform: " + platform);
54-
return null;
55-
// throw new Error("Unsupported platform: " + platform);
5643
}

0 commit comments

Comments
 (0)