@yangrunchi/a_6 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/A5_/346/270/270/346/210/217/345/256/242/346/210/267/347/253/257/346/250/241/345/235/227/345/274/200/345/217/221/350/247/204/350/214/203.md +275 -0
- package/AutoCfg/A6_generate-tablemgr.js +696 -0
- package/AutoCfg/A6_template_tablemgr.txt +37 -0
- package/AutoCfg/csvcfg.js +417 -0
- package/AutoCode/A1_996_config.json +5 -0
- package/AutoCode/A2_generate-panel.js +141 -0
- package/AutoCode/A3_watcher.js +132 -0
- package/AutoCode/A4_template_1.txt +31 -0
- package/AutoCode/A4_template_2.txt +30 -0
- package/AutoNetMsg/A4_exclude_modules.json +9 -0
- package/AutoNetMsg/A4_template_3.txt +30 -0
- package/AutoNetMsg/A4_template_4.txt +25 -0
- package/AutoNetMsg/A5_generate-funcdata.js +1021 -0
- package/AutoNetMsg/A5_sync-netmsg.js +98 -0
- package/SVNUpdate/svn_996.js +20 -0
- package/Tools/install-keys.js +117 -0
- package/Tools/project-keys.json +24 -0
- package/Tools/svn-utils.js +126 -0
- package/Tools/uninstall-keys.js +65 -0
- package/bat_manager/index.html +1248 -0
- package/bat_manager/local_scripts.json +42 -0
- package/bat_manager/run-server.bat +4 -0
- package/bat_manager/server.js +498 -0
- package/bat_manager/users.json +7 -0
- package/cli.js +29 -0
- package/index.js +9 -0
- package/package.json +47 -0
- package/run-autoCode.bat +5 -0
- package/run-client.bat +14 -0
- package/run-csvcfg.bat +4 -0
- package/run-svn.bat +4 -0
- package/run-sync.bat +4 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "1776933313049",
|
|
4
|
+
"name": "打配置",
|
|
5
|
+
"path": "H:/NiePanChuanQi/Project/996M2_Client/a_tools/a_node/run-csvcfg.bat",
|
|
6
|
+
"args": "",
|
|
7
|
+
"addedBy": "qwe1234",
|
|
8
|
+
"addedAt": "2026-04-23T08:35:13.049Z"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "1776937917012",
|
|
12
|
+
"name": "同步协议",
|
|
13
|
+
"path": "H:/NiePanChuanQi/Project/996M2_Client/a_tools/a_node/run-sync.bat",
|
|
14
|
+
"args": "",
|
|
15
|
+
"addedBy": "qqww123",
|
|
16
|
+
"addedAt": "2026-04-23T09:51:57.012Z"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "1777254823830",
|
|
20
|
+
"name": "启动客服端",
|
|
21
|
+
"path": "H:/NiePanChuanQi/Project/996M2_Client/a_tools/a_node/run-client.bat",
|
|
22
|
+
"args": "",
|
|
23
|
+
"addedBy": "yrc001",
|
|
24
|
+
"addedAt": "2026-04-27T01:53:43.830Z"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "1777254878031",
|
|
28
|
+
"name": "自动生成代码",
|
|
29
|
+
"path": "H:/NiePanChuanQi/Project/996M2_Client/a_tools/a_node/run-autoCode.bat",
|
|
30
|
+
"args": "",
|
|
31
|
+
"addedBy": "yrc001",
|
|
32
|
+
"addedAt": "2026-04-27T01:54:38.031Z"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "1781243927262",
|
|
36
|
+
"name": "9963工程(📁 G:/NiePanChuanQi/Project)SVN更新",
|
|
37
|
+
"path": "H:/NiePanChuanQi/Project/996M2_Client/a_tools/a_node/run-svn.bat",
|
|
38
|
+
"args": "",
|
|
39
|
+
"addedBy": "admin",
|
|
40
|
+
"addedAt": "2026-06-12T05:58:47.262Z"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
// server.js - 修复版
|
|
2
|
+
const WebSocket = require('ws');
|
|
3
|
+
const { exec } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const PORT = 8080;
|
|
11
|
+
const USER_FILE = path.join(__dirname, 'users.json');
|
|
12
|
+
|
|
13
|
+
const onlineUsers = new Map();
|
|
14
|
+
|
|
15
|
+
let globalTaskLock = {
|
|
16
|
+
isRunning: false,
|
|
17
|
+
currentUser: null,
|
|
18
|
+
currentTask: null,
|
|
19
|
+
startTime: null
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function getLocalIp() {
|
|
23
|
+
const interfaces = os.networkInterfaces();
|
|
24
|
+
for (let name of Object.keys(interfaces)) {
|
|
25
|
+
for (let iface of interfaces[name]) {
|
|
26
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
27
|
+
return iface.address;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return 'localhost';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadUsers() {
|
|
35
|
+
if (!fs.existsSync(USER_FILE)) return {};
|
|
36
|
+
return JSON.parse(fs.readFileSync(USER_FILE, 'utf-8'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveUsers(users) {
|
|
40
|
+
fs.writeFileSync(USER_FILE, JSON.stringify(users, null, 2));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function hashPassword(pwd) {
|
|
44
|
+
return crypto.createHash('sha256').update(pwd).digest('hex');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function verifyPassword(inputPwd, storedHash) {
|
|
48
|
+
return hashPassword(inputPwd) === storedHash;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function registerUser(username, password) {
|
|
52
|
+
const users = loadUsers();
|
|
53
|
+
if (users[username]) return { success: false, message: "用户名已存在" };
|
|
54
|
+
if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
|
|
55
|
+
return { success: false, message: "用户名须为3~20位字母数字下划线" };
|
|
56
|
+
}
|
|
57
|
+
if (password.length < 4) return { success: false, message: "密码至少4位" };
|
|
58
|
+
|
|
59
|
+
users[username] = {
|
|
60
|
+
password: hashPassword(password),
|
|
61
|
+
role: "user",
|
|
62
|
+
createdAt: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
saveUsers(users);
|
|
65
|
+
return { success: true, message: "注册成功" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function authenticate(username, password) {
|
|
69
|
+
const users = loadUsers();
|
|
70
|
+
if (users[username] && verifyPassword(password, users[username].password)) {
|
|
71
|
+
return { success: true, role: users[username].role };
|
|
72
|
+
}
|
|
73
|
+
return { success: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function broadcastTaskStatus() {
|
|
77
|
+
const statusMsg = JSON.stringify({
|
|
78
|
+
type: 'task_status',
|
|
79
|
+
isRunning: globalTaskLock.isRunning,
|
|
80
|
+
currentUser: globalTaskLock.currentUser,
|
|
81
|
+
currentTask: globalTaskLock.currentTask,
|
|
82
|
+
startTime: globalTaskLock.startTime
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
for (const [username, ws] of onlineUsers) {
|
|
86
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
87
|
+
ws.send(statusMsg);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function tryAcquireLock(username, taskDesc, ws) {
|
|
93
|
+
if (globalTaskLock.isRunning) {
|
|
94
|
+
ws.send(JSON.stringify({
|
|
95
|
+
type: 'task_busy',
|
|
96
|
+
message: `⚠️ 任务正在执行中 (${globalTaskLock.currentUser}: ${globalTaskLock.currentTask}),请稍后再试`,
|
|
97
|
+
currentTask: globalTaskLock.currentTask,
|
|
98
|
+
currentUser: globalTaskLock.currentUser,
|
|
99
|
+
startTime: globalTaskLock.startTime
|
|
100
|
+
}));
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
globalTaskLock = {
|
|
105
|
+
isRunning: true,
|
|
106
|
+
currentUser: username,
|
|
107
|
+
currentTask: taskDesc,
|
|
108
|
+
startTime: new Date().toISOString()
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
broadcastTaskStatus();
|
|
112
|
+
|
|
113
|
+
ws.send(JSON.stringify({
|
|
114
|
+
type: 'task_start',
|
|
115
|
+
message: `🔒 任务开始: ${taskDesc}`,
|
|
116
|
+
taskDesc: taskDesc
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
console.log(`🔒 [任务开始] ${username} -> ${taskDesc}`);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function releaseLock(ws, successMsg) {
|
|
124
|
+
if (!globalTaskLock.isRunning) return;
|
|
125
|
+
|
|
126
|
+
const taskInfo = {
|
|
127
|
+
user: globalTaskLock.currentUser,
|
|
128
|
+
task: globalTaskLock.currentTask,
|
|
129
|
+
duration: globalTaskLock.startTime ?
|
|
130
|
+
Math.round((Date.now() - new Date(globalTaskLock.startTime).getTime()) / 1000) : 0
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
globalTaskLock = {
|
|
134
|
+
isRunning: false,
|
|
135
|
+
currentUser: null,
|
|
136
|
+
currentTask: null,
|
|
137
|
+
startTime: null
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
broadcastTaskStatus();
|
|
141
|
+
|
|
142
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
143
|
+
ws.send(JSON.stringify({
|
|
144
|
+
type: 'task_end',
|
|
145
|
+
message: successMsg || `🔓 任务完成 (耗时 ${taskInfo.duration}秒)`,
|
|
146
|
+
taskInfo: taskInfo
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(`🔓 [任务完成] ${taskInfo.user} -> ${taskInfo.task} (${taskInfo.duration}秒)`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function forceReleaseLock() {
|
|
154
|
+
if (globalTaskLock.isRunning) {
|
|
155
|
+
console.log(`⚠️ [强制释放锁] 任务被中断: ${globalTaskLock.currentUser} -> ${globalTaskLock.currentTask}`);
|
|
156
|
+
globalTaskLock = {
|
|
157
|
+
isRunning: false,
|
|
158
|
+
currentUser: null,
|
|
159
|
+
currentTask: null,
|
|
160
|
+
startTime: null
|
|
161
|
+
};
|
|
162
|
+
broadcastTaskStatus();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 修复:正确构建 BAT 脚本执行命令
|
|
167
|
+
function executeBat(ws, scriptPath, args, username) {
|
|
168
|
+
// 将正斜杠转换为反斜杠(Windows 路径)
|
|
169
|
+
const normalizedPath = scriptPath.replace(/\//g, '\\');
|
|
170
|
+
|
|
171
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
172
|
+
ws.send(JSON.stringify({ type: 'exec_result', success: false, message: `❌ 文件不存在: ${normalizedPath}` }));
|
|
173
|
+
releaseLock(ws, null);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const scriptName = path.basename(normalizedPath);
|
|
178
|
+
const scriptDir = path.dirname(normalizedPath);
|
|
179
|
+
const taskDesc = `执行脚本: ${scriptName}`;
|
|
180
|
+
|
|
181
|
+
if (!tryAcquireLock(username, taskDesc, ws)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 正确构建命令 - 使用反斜杠路径
|
|
186
|
+
const command = `cd /d "${scriptDir}" && "${normalizedPath}" ${args}`;
|
|
187
|
+
console.log(`📝 执行命令: ${command}`);
|
|
188
|
+
|
|
189
|
+
ws.send(JSON.stringify({
|
|
190
|
+
type: 'exec_result',
|
|
191
|
+
output: `\n📌 [${username}] 执行: ${scriptName} ${args}\n📁 工作目录: ${scriptDir}\n`
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
const child = exec(command, {
|
|
195
|
+
cwd: scriptDir,
|
|
196
|
+
shell: 'cmd.exe',
|
|
197
|
+
timeout: 300000,
|
|
198
|
+
maxBuffer: 1024 * 1024 * 50 // 50MB 缓冲区
|
|
199
|
+
}, (error, stdout, stderr) => {
|
|
200
|
+
console.log(`📊 脚本执行完成: ${scriptName}, error: ${error ? error.message : '无'}`);
|
|
201
|
+
|
|
202
|
+
if (stdout) {
|
|
203
|
+
// 分块发送,避免单次消息过大
|
|
204
|
+
const chunks = stdout.match(/(.|[\r\n]){1,8000}/g) || [stdout];
|
|
205
|
+
for (const chunk of chunks) {
|
|
206
|
+
ws.send(JSON.stringify({ type: 'exec_result', output: chunk }));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (stderr) {
|
|
210
|
+
ws.send(JSON.stringify({ type: 'exec_result', output: `⚠️ ${stderr}` }));
|
|
211
|
+
}
|
|
212
|
+
if (error) {
|
|
213
|
+
ws.send(JSON.stringify({ type: 'exec_result', output: `❌ 执行错误: ${error.message}` }));
|
|
214
|
+
releaseLock(ws, `⚠️ 任务异常结束: ${error.message}`);
|
|
215
|
+
} else {
|
|
216
|
+
releaseLock(ws, `✅ 脚本执行完成: ${scriptName}`);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// 实时输出(可选,显示执行过程中的输出)
|
|
221
|
+
child.stdout.on('data', (data) => {
|
|
222
|
+
ws.send(JSON.stringify({ type: 'exec_result', output: data }));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
child.stderr.on('data', (data) => {
|
|
226
|
+
ws.send(JSON.stringify({ type: 'exec_result', output: `⚠️ ${data}` }));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function scanDirectoryForBat(dir) {
|
|
231
|
+
if (!fs.existsSync(dir)) return [];
|
|
232
|
+
const batFiles = [];
|
|
233
|
+
try {
|
|
234
|
+
const files = fs.readdirSync(dir);
|
|
235
|
+
for (const file of files) {
|
|
236
|
+
const fullPath = path.join(dir, file);
|
|
237
|
+
const stat = fs.statSync(fullPath);
|
|
238
|
+
if (!stat.isDirectory() && file.toLowerCase().endsWith('.bat')) {
|
|
239
|
+
batFiles.push({ name: file, path: fullPath });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch(e) {}
|
|
243
|
+
return batFiles;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function loadLocalScripts() {
|
|
247
|
+
const scriptFile = path.join(__dirname, 'local_scripts.json');
|
|
248
|
+
if (!fs.existsSync(scriptFile)) return [];
|
|
249
|
+
return JSON.parse(fs.readFileSync(scriptFile, 'utf-8'));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function saveLocalScripts(scripts) {
|
|
253
|
+
const scriptFile = path.join(__dirname, 'local_scripts.json');
|
|
254
|
+
fs.writeFileSync(scriptFile, JSON.stringify(scripts, null, 2));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function kickOldConnection(username, newWs) {
|
|
258
|
+
const oldWs = onlineUsers.get(username);
|
|
259
|
+
if (oldWs && oldWs !== newWs && oldWs.readyState === WebSocket.OPEN) {
|
|
260
|
+
oldWs.send(JSON.stringify({ type: 'kicked', message: '⚠️ 您的账号在其他地方登录,您已被踢下线' }));
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
if (oldWs.readyState === WebSocket.OPEN) {
|
|
263
|
+
oldWs.close(1000, '账号在其他地方登录');
|
|
264
|
+
}
|
|
265
|
+
}, 500);
|
|
266
|
+
console.log(`🔨 踢掉旧连接: ${username}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const server = http.createServer((req, res) => {
|
|
271
|
+
let filePath = req.url === '/' ? '/index.html' : req.url;
|
|
272
|
+
filePath = path.join(__dirname, filePath);
|
|
273
|
+
|
|
274
|
+
if (!filePath.startsWith(__dirname)) {
|
|
275
|
+
res.writeHead(403);
|
|
276
|
+
res.end('Forbidden');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const ext = path.extname(filePath);
|
|
281
|
+
const contentType = {
|
|
282
|
+
'.html': 'text/html; charset=utf-8',
|
|
283
|
+
'.css': 'text/css',
|
|
284
|
+
'.js': 'application/javascript',
|
|
285
|
+
'.json': 'application/json',
|
|
286
|
+
'.png': 'image/png',
|
|
287
|
+
'.jpg': 'image/jpeg'
|
|
288
|
+
}[ext] || 'text/plain';
|
|
289
|
+
|
|
290
|
+
fs.readFile(filePath, (err, content) => {
|
|
291
|
+
if (err) {
|
|
292
|
+
if (err.code === 'ENOENT') {
|
|
293
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
294
|
+
res.end(`<!DOCTYPE html>
|
|
295
|
+
<html>
|
|
296
|
+
<head><meta charset="UTF-8"><title>BAT 脚本管理器</title></head>
|
|
297
|
+
<body style="background:#0a0f1e;color:#cfddee;font-family:monospace;padding:20px;">
|
|
298
|
+
<h2>🚀 BAT 脚本管理器</h2>
|
|
299
|
+
<p>✅ 服务已启动</p>
|
|
300
|
+
<p>📡 WebSocket: ws://${getLocalIp()}:${PORT}</p>
|
|
301
|
+
<p>🔑 请先注册账号</p>
|
|
302
|
+
</body>
|
|
303
|
+
</html>`);
|
|
304
|
+
} else {
|
|
305
|
+
res.writeHead(500);
|
|
306
|
+
res.end('Server Error');
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
310
|
+
res.end(content);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const wss = new WebSocket.Server({ server });
|
|
316
|
+
|
|
317
|
+
console.log(`
|
|
318
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
319
|
+
║ 🚀 BAT 脚本管理器 - 已启动 (单点登录 + 任务互斥锁) ║
|
|
320
|
+
║ 🌐 本机访问: http://localhost:${PORT} ║
|
|
321
|
+
║ 🌐 局域网: http://${getLocalIp()}:${PORT} ║
|
|
322
|
+
║ 📡 WebSocket: ws://${getLocalIp()}:${PORT} ║
|
|
323
|
+
║ 🔒 同一账号同时只能一个在线 ║
|
|
324
|
+
║ 🔐 全局任务锁:同一时间只能执行一个任务 ║
|
|
325
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
326
|
+
`);
|
|
327
|
+
|
|
328
|
+
wss.on('connection', (ws, req) => {
|
|
329
|
+
let authenticated = false;
|
|
330
|
+
let currentUser = null;
|
|
331
|
+
const clientIp = req.socket.remoteAddress;
|
|
332
|
+
console.log(`✅ 新连接: ${clientIp}`);
|
|
333
|
+
|
|
334
|
+
ws.send(JSON.stringify({
|
|
335
|
+
type: 'task_status',
|
|
336
|
+
isRunning: globalTaskLock.isRunning,
|
|
337
|
+
currentUser: globalTaskLock.currentUser,
|
|
338
|
+
currentTask: globalTaskLock.currentTask,
|
|
339
|
+
startTime: globalTaskLock.startTime
|
|
340
|
+
}));
|
|
341
|
+
|
|
342
|
+
ws.on('message', async (message) => {
|
|
343
|
+
const msgStr = message.toString();
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const data = JSON.parse(msgStr);
|
|
347
|
+
|
|
348
|
+
if (data.type === 'login') {
|
|
349
|
+
const auth = authenticate(data.username, data.password);
|
|
350
|
+
if (auth.success) {
|
|
351
|
+
kickOldConnection(data.username, ws);
|
|
352
|
+
authenticated = true;
|
|
353
|
+
currentUser = data.username;
|
|
354
|
+
onlineUsers.set(data.username, ws);
|
|
355
|
+
|
|
356
|
+
ws.send(JSON.stringify({
|
|
357
|
+
type: 'login_result',
|
|
358
|
+
success: true,
|
|
359
|
+
username: data.username,
|
|
360
|
+
role: auth.role,
|
|
361
|
+
scripts: loadLocalScripts()
|
|
362
|
+
}));
|
|
363
|
+
console.log(`✅ 登录: ${data.username} (当前在线: ${onlineUsers.size})`);
|
|
364
|
+
} else {
|
|
365
|
+
ws.send(JSON.stringify({ type: 'login_result', success: false, message: '用户名或密码错误' }));
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (data.type === 'register') {
|
|
371
|
+
const result = registerUser(data.username, data.password);
|
|
372
|
+
ws.send(JSON.stringify({ type: 'register_result', success: result.success, message: result.message }));
|
|
373
|
+
if (result.success) console.log(`📝 注册: ${data.username}`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!authenticated) {
|
|
378
|
+
ws.send(JSON.stringify({ type: 'error', message: '未登录' }));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (data.type === 'exec_bat') {
|
|
383
|
+
executeBat(ws, data.path, data.args || '', currentUser);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (data.type === 'add_script') {
|
|
388
|
+
if (globalTaskLock.isRunning) {
|
|
389
|
+
ws.send(JSON.stringify({
|
|
390
|
+
type: 'task_busy',
|
|
391
|
+
message: `⚠️ 当前有任务正在执行 (${globalTaskLock.currentUser}: ${globalTaskLock.currentTask}),请稍后再添加脚本`
|
|
392
|
+
}));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const scripts = loadLocalScripts();
|
|
396
|
+
const newId = Date.now().toString();
|
|
397
|
+
scripts.push({
|
|
398
|
+
id: newId,
|
|
399
|
+
name: data.name,
|
|
400
|
+
path: data.path,
|
|
401
|
+
args: data.args || '',
|
|
402
|
+
addedBy: currentUser,
|
|
403
|
+
addedAt: new Date().toISOString()
|
|
404
|
+
});
|
|
405
|
+
saveLocalScripts(scripts);
|
|
406
|
+
ws.send(JSON.stringify({ type: 'script_added', success: true, scripts: scripts }));
|
|
407
|
+
console.log(`📌 添加: ${data.name} (by ${currentUser})`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (data.type === 'delete_script') {
|
|
412
|
+
if (globalTaskLock.isRunning) {
|
|
413
|
+
ws.send(JSON.stringify({
|
|
414
|
+
type: 'task_busy',
|
|
415
|
+
message: `⚠️ 当前有任务正在执行,请稍后再删除脚本`
|
|
416
|
+
}));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
let scripts = loadLocalScripts();
|
|
420
|
+
scripts = scripts.filter(s => s.id !== data.id);
|
|
421
|
+
saveLocalScripts(scripts);
|
|
422
|
+
ws.send(JSON.stringify({ type: 'script_deleted', success: true, scripts: scripts }));
|
|
423
|
+
console.log(`🗑 删除: ${data.id} (by ${currentUser})`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (data.type === 'get_scripts') {
|
|
428
|
+
ws.send(JSON.stringify({ type: 'script_list', scripts: loadLocalScripts() }));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (data.type === 'scan_dir') {
|
|
433
|
+
const bats = scanDirectoryForBat(data.directory);
|
|
434
|
+
ws.send(JSON.stringify({ type: 'scan_result', files: bats, count: bats.length }));
|
|
435
|
+
console.log(`🔍 扫描: ${data.directory} -> ${bats.length}个`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (data.type === 'free_cmd') {
|
|
440
|
+
const taskDesc = `自由命令: ${data.command.substring(0, 50)}`;
|
|
441
|
+
|
|
442
|
+
if (!tryAcquireLock(currentUser, taskDesc, ws)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
exec(data.command, {
|
|
447
|
+
shell: 'cmd.exe',
|
|
448
|
+
timeout: 120000,
|
|
449
|
+
maxBuffer: 1024 * 1024 * 50
|
|
450
|
+
}, (error, stdout, stderr) => {
|
|
451
|
+
if (stdout) ws.send(JSON.stringify({ type: 'cmd_result', output: stdout }));
|
|
452
|
+
if (stderr) ws.send(JSON.stringify({ type: 'cmd_result', output: `⚠️ ${stderr}` }));
|
|
453
|
+
if (error) {
|
|
454
|
+
ws.send(JSON.stringify({ type: 'cmd_result', output: `❌ ${error.message}` }));
|
|
455
|
+
releaseLock(ws, `⚠️ 命令执行异常`);
|
|
456
|
+
} else {
|
|
457
|
+
releaseLock(ws, `✅ 命令执行完成`);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (data.type === 'check_task_status') {
|
|
464
|
+
ws.send(JSON.stringify({
|
|
465
|
+
type: 'task_status',
|
|
466
|
+
isRunning: globalTaskLock.isRunning,
|
|
467
|
+
currentUser: globalTaskLock.currentUser,
|
|
468
|
+
currentTask: globalTaskLock.currentTask,
|
|
469
|
+
startTime: globalTaskLock.startTime
|
|
470
|
+
}));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
} catch (e) {
|
|
475
|
+
console.log(`⚠️ 解析错误: ${e.message}`);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
ws.on('close', () => {
|
|
480
|
+
if (currentUser && onlineUsers.get(currentUser) === ws) {
|
|
481
|
+
onlineUsers.delete(currentUser);
|
|
482
|
+
console.log(`🔌 断开: ${currentUser} (剩余在线: ${onlineUsers.size})`);
|
|
483
|
+
} else if (currentUser) {
|
|
484
|
+
console.log(`🔌 断开: ${currentUser} (已被新连接替代)`);
|
|
485
|
+
} else {
|
|
486
|
+
console.log(`🔌 断开: 未认证`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (globalTaskLock.isRunning && globalTaskLock.currentUser === currentUser) {
|
|
490
|
+
console.log(`⚠️ 任务执行者 ${currentUser} 断开连接,强制释放锁`);
|
|
491
|
+
forceReleaseLock();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
497
|
+
console.log(`🌐 监听所有网络接口: 0.0.0.0:${PORT}`);
|
|
498
|
+
});
|
package/cli.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// a_6 命令行入口
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const action = process.argv[2];
|
|
7
|
+
|
|
8
|
+
const commands = {
|
|
9
|
+
'1': { file: 'run-client.bat', desc: '启动客户端' },
|
|
10
|
+
'2': { file: 'run-csvcfg.bat', desc: '打配置' },
|
|
11
|
+
'3': { file: 'run-sync.bat', desc: '同步协议' },
|
|
12
|
+
'4': { file: 'run-svn.bat', desc: '工程更新' },
|
|
13
|
+
'5': { file: 'run-autoCode.bat',desc: '自动代码' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (!commands[action]) {
|
|
17
|
+
console.log('\n📦 a_6 - 996M2 客户端工具集\n');
|
|
18
|
+
console.log('用法: a_6 <编号>\n');
|
|
19
|
+
for (const [key, cmd] of Object.entries(commands)) {
|
|
20
|
+
console.log(` ${key} - ${cmd.desc}`);
|
|
21
|
+
}
|
|
22
|
+
console.log('');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cmd = commands[action];
|
|
27
|
+
const batFile = path.join(__dirname, cmd.file);
|
|
28
|
+
console.log(`🚀 执行: ${cmd.desc}`);
|
|
29
|
+
execSync(`"${batFile}"`, { stdio: 'inherit', shell: true });
|
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// a_6 - 996M2 客户端 Node.js 工具集
|
|
2
|
+
// CLI: a_6 <编号>
|
|
3
|
+
// 编程调用: require('a_6').syncNetMsg()
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
syncNetMsg: require('./AutoNetMsg/A5_sync-netmsg.js'),
|
|
7
|
+
generatePanel: require('./AutoCode/A2_generate-panel.js'),
|
|
8
|
+
csvcfg: require('./AutoCfg/csvcfg.js'),
|
|
9
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yangrunchi/a_6",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "996M2 客户端 Node.js 工具集 — 协议同步、自动生成代码、CSV配置、SVN管理、BAT脚本管理器",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"a_6": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"1": "node run-client.bat",
|
|
11
|
+
"2": "node run-csvcfg.bat",
|
|
12
|
+
"3": "node run-sync.bat",
|
|
13
|
+
"4": "node run-svn.bat",
|
|
14
|
+
"5": "node run-autoCode.bat",
|
|
15
|
+
"start": "node bat_manager/server.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"AutoCfg/",
|
|
19
|
+
"AutoCode/",
|
|
20
|
+
"AutoNetMsg/",
|
|
21
|
+
"bat_manager/",
|
|
22
|
+
"SVNUpdate/",
|
|
23
|
+
"Tools/",
|
|
24
|
+
"cli.js",
|
|
25
|
+
"index.js",
|
|
26
|
+
"run-autoCode.bat",
|
|
27
|
+
"run-client.bat",
|
|
28
|
+
"run-csvcfg.bat",
|
|
29
|
+
"run-svn.bat",
|
|
30
|
+
"run-sync.bat",
|
|
31
|
+
"A5_游戏客户端模块开发规范.md"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [
|
|
34
|
+
"996m2",
|
|
35
|
+
"mir2",
|
|
36
|
+
"game-tools",
|
|
37
|
+
"code-generator",
|
|
38
|
+
"传奇"
|
|
39
|
+
],
|
|
40
|
+
"author": "996M2",
|
|
41
|
+
"license": "ISC",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"iconv-lite": "^0.7.0",
|
|
44
|
+
"safer-buffer": "^2.1.2",
|
|
45
|
+
"ws": "^8.20.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/run-autoCode.bat
ADDED
package/run-client.bat
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
set "client_path=%~dp0..\..\996M2_Client.exe"
|
|
4
|
+
|
|
5
|
+
if not exist "%client_path%" (
|
|
6
|
+
echo 错误: 找不到 996M2_Client.exe
|
|
7
|
+
echo 期望路径: %client_path%
|
|
8
|
+
pause
|
|
9
|
+
exit /b 1
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
echo 启动 996M2 客户端...
|
|
13
|
+
start "" "%client_path%"
|
|
14
|
+
echo 客户端已启动
|
package/run-csvcfg.bat
ADDED
package/run-svn.bat
ADDED
package/run-sync.bat
ADDED