mm_cmd 0.0.0-stage → 1.1.1
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/README.md +11 -3
- package/com.json +5 -0
- package/config.tpl.json +48 -0
- package/example/index.js +76 -0
- package/index.js +17 -0
- package/job/config.json +66 -0
- package/job/index.js +241 -0
- package/job/test.js +188 -0
- package/lib/cmd.js +330 -0
- package/lib/local_cache.js +61 -0
- package/lib/manager.js +347 -0
- package/msg.json +48 -0
- package/package.json +29 -4
- package/script.tpl.js +12 -0
- package/test.js +18 -0
package/lib/cmd.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const Item = require('mm_mod').Base;
|
|
3
|
+
// 指令匹配依赖 mm_matchs 注入的 String.prototype.matchs(需 mm_matchs 环境)
|
|
4
|
+
require('mm_matchs');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 指令驱动类(迁移自 mm_os com/cmd,基于 mm_corejs.Base)
|
|
8
|
+
* @augments {Item}
|
|
9
|
+
* @class
|
|
10
|
+
*/
|
|
11
|
+
class Cmd extends Item {
|
|
12
|
+
/**
|
|
13
|
+
* 配置参数
|
|
14
|
+
* @type {object}
|
|
15
|
+
*/
|
|
16
|
+
static config = {
|
|
17
|
+
/**
|
|
18
|
+
* 名称
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
name: '',
|
|
22
|
+
/**
|
|
23
|
+
* 标题
|
|
24
|
+
* @type {string}
|
|
25
|
+
*/
|
|
26
|
+
title: '',
|
|
27
|
+
/**
|
|
28
|
+
* 描述
|
|
29
|
+
* @type {string}
|
|
30
|
+
*/
|
|
31
|
+
description: '',
|
|
32
|
+
/**
|
|
33
|
+
* 状态 0未启用,1启用
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
36
|
+
state: 1,
|
|
37
|
+
app: 'server',
|
|
38
|
+
plugin: 'sys',
|
|
39
|
+
match: [],
|
|
40
|
+
not: [],
|
|
41
|
+
end: 1,
|
|
42
|
+
sort: 100,
|
|
43
|
+
tense: 'main',
|
|
44
|
+
params: null,
|
|
45
|
+
// 主脚本文件路径(相对 dir 解析)
|
|
46
|
+
main: './index.js',
|
|
47
|
+
// 回调函数名,决定调用脚本的哪个函数
|
|
48
|
+
func_name: 'main',
|
|
49
|
+
// 驱动目录(方案B:手动配置,替代目录扫描)
|
|
50
|
+
dir: '',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 构造函数
|
|
55
|
+
* @param {object} config 配置参数
|
|
56
|
+
* @param {object} parent 父对象(管理器实例)
|
|
57
|
+
*/
|
|
58
|
+
constructor(config, parent) {
|
|
59
|
+
super({ ...Cmd.config, ...config });
|
|
60
|
+
|
|
61
|
+
// 父级管理器引用(用于配置回退与插件查找)
|
|
62
|
+
this.parent = parent || null;
|
|
63
|
+
// 脚本方法集合(loadScript 后注册)
|
|
64
|
+
this.methods = {};
|
|
65
|
+
// 脚本加载标记
|
|
66
|
+
this.loaded = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 解析主脚本文件绝对路径(config.main 相对 config.dir 解析,dir 为空时相对本模块目录)
|
|
72
|
+
* @returns {string} 脚本绝对路径,未配置 main 返回空字符串
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
Cmd.prototype._getScriptFile = function () {
|
|
76
|
+
var main = this.config.main || '';
|
|
77
|
+
if (!main) {
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
if (path.isAbsolute(main)) {
|
|
81
|
+
return main;
|
|
82
|
+
}
|
|
83
|
+
return path.resolve(this.config.dir || __dirname, main);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 加载主脚本文件并注册导出方法(替代旧基类 loadScript + _setMainMethod)
|
|
88
|
+
* @returns {object|null} 加载的模块对象
|
|
89
|
+
*/
|
|
90
|
+
Cmd.prototype.loadScript = function () {
|
|
91
|
+
var file = this._getScriptFile();
|
|
92
|
+
if (!file) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
var mod = require(file);
|
|
97
|
+
this._registerMethods(mod);
|
|
98
|
+
this.loaded = true;
|
|
99
|
+
return mod;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
this.log('error', '加载脚本文件失败', file, err);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 注册脚本导出的函数方法
|
|
108
|
+
* @param {object} mod 脚本模块对象
|
|
109
|
+
* @returns {void}
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
Cmd.prototype._registerMethods = function (mod) {
|
|
113
|
+
if (!mod) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
for (var key in mod) {
|
|
117
|
+
if (typeof mod[key] === 'function') {
|
|
118
|
+
this.methods[key] = mod[key];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 卸载脚本(清除模块缓存与方法集合)
|
|
125
|
+
* @returns {void}
|
|
126
|
+
*/
|
|
127
|
+
Cmd.prototype.unloadScript = function () {
|
|
128
|
+
var file = this._getScriptFile();
|
|
129
|
+
if (file) {
|
|
130
|
+
try {
|
|
131
|
+
delete require.cache[require.resolve(file)];
|
|
132
|
+
} catch {
|
|
133
|
+
// 文件未加载或不存在时忽略
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.methods = {};
|
|
137
|
+
this.loaded = false;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 重新加载脚本
|
|
142
|
+
* @returns {object|null} 加载的模块对象
|
|
143
|
+
*/
|
|
144
|
+
Cmd.prototype.reloadScript = function () {
|
|
145
|
+
this.unloadScript();
|
|
146
|
+
return this.loadScript();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 调用脚本方法(支持 xxxBefore / xxxAfter 钩子,保留原基类 call 调用链语义)
|
|
151
|
+
* @param {string} method 方法名
|
|
152
|
+
* @param {...*} params 参数列表
|
|
153
|
+
* @returns {Promise<*>} 执行结果
|
|
154
|
+
*/
|
|
155
|
+
Cmd.prototype.call = async function (method, ...params) {
|
|
156
|
+
if (!this.loaded) {
|
|
157
|
+
this.loadScript();
|
|
158
|
+
}
|
|
159
|
+
var func = this.methods[method];
|
|
160
|
+
if (typeof func !== 'function') {
|
|
161
|
+
throw new Error(`方法 ${method} 未注册,请确保脚本已加载`);
|
|
162
|
+
}
|
|
163
|
+
var before_func = this.methods[method + 'Before'];
|
|
164
|
+
if (typeof before_func === 'function') {
|
|
165
|
+
var before_result = await before_func.call(this, ...params);
|
|
166
|
+
if (before_result) {
|
|
167
|
+
return before_result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var result = await func.call(this, ...params);
|
|
171
|
+
var after_func = this.methods[method + 'After'];
|
|
172
|
+
if (typeof after_func === 'function') {
|
|
173
|
+
var after_result = await after_func.call(this, result, ...params);
|
|
174
|
+
if (after_result !== undefined) {
|
|
175
|
+
result = after_result;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 匹配指令
|
|
183
|
+
*
|
|
184
|
+
* 支持三种匹配方式:
|
|
185
|
+
* 1. 正则表达式 - 以 "/" 开头的字符串, 如 "/[0-9]+/"
|
|
186
|
+
* 2. 语义占位符 - 含 {xxx} 的字符串, 如 "{数字}"、"{日期}"、"{城市}"、"{手机号码}" 等
|
|
187
|
+
* 3. 纯字符串 - 默认匹配
|
|
188
|
+
*
|
|
189
|
+
* 注意: 匹配调用 String.prototype.matchs,由 mm_matchs 提供(需 mm_matchs 环境)
|
|
190
|
+
*
|
|
191
|
+
* @param {string} content 消息内容
|
|
192
|
+
* @returns {string} 匹配返回关键词字符串, 否则返回空字符串
|
|
193
|
+
*/
|
|
194
|
+
Cmd.prototype.match = function (content) {
|
|
195
|
+
var match_arr = this.config.match;
|
|
196
|
+
if (!match_arr || match_arr.length === 0) {
|
|
197
|
+
return '';
|
|
198
|
+
}
|
|
199
|
+
var not_arr = this.config.not;
|
|
200
|
+
if (not_arr && not_arr.length > 0) {
|
|
201
|
+
for (var i = 0; i < not_arr.length; i++) {
|
|
202
|
+
if (content.matchs(not_arr[i])) {
|
|
203
|
+
return '';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (var i = 0; i < match_arr.length; i++) {
|
|
208
|
+
var ret = content.matchs(match_arr[i]);
|
|
209
|
+
if (ret) {
|
|
210
|
+
return ret;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return '';
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 执行指令
|
|
218
|
+
* @param {object} msg 消息对象
|
|
219
|
+
* @param {object} db 数据管理器
|
|
220
|
+
* @returns {object|string} 返回执行结果, 不匹配返回null
|
|
221
|
+
*/
|
|
222
|
+
Cmd.prototype.run = async function (msg, db) {
|
|
223
|
+
if (!msg || !msg.content) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
// msg.cmd已设置表示会话恢复, 跳过关键字匹配
|
|
227
|
+
if (!msg.cmd) {
|
|
228
|
+
if (!this.match(msg.content)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
msg.cmd = this.config.name;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
var ret;
|
|
235
|
+
if (this.config.params) {
|
|
236
|
+
ret = this._fillParams(msg);
|
|
237
|
+
if (ret !== null) {
|
|
238
|
+
return typeof ret === 'string' ? { content: ret } : ret;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
ret = await this.call('main', msg, db);
|
|
242
|
+
return typeof ret === 'string' ? { content: ret } : ret;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.log('error', '指令执行失败', error.message);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 槽位填充
|
|
251
|
+
*
|
|
252
|
+
* 遍历 params.properties, 从 msg.content 中提取参数值。
|
|
253
|
+
* 已填充的参数跳过, 缺失的必填参数返回 question 提示语。
|
|
254
|
+
* 全部必填项满足时返回 null, 交由 main() 执行。
|
|
255
|
+
*
|
|
256
|
+
* @param {object} msg 消息对象
|
|
257
|
+
* @returns {string|null} 返回追问内容, 全部满足返回null
|
|
258
|
+
* @private
|
|
259
|
+
*/
|
|
260
|
+
Cmd.prototype._fillParams = function (msg) {
|
|
261
|
+
var params = this.config.params;
|
|
262
|
+
if (!params || !params.properties) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (!msg.form) {
|
|
266
|
+
msg.form = {};
|
|
267
|
+
}
|
|
268
|
+
var props = params.properties;
|
|
269
|
+
for (var k in props) {
|
|
270
|
+
this._tryExtractParam(msg, k, props[k]);
|
|
271
|
+
}
|
|
272
|
+
var required = params.required || [];
|
|
273
|
+
for (var i = 0; i < required.length; i++) {
|
|
274
|
+
var name = required[i];
|
|
275
|
+
if (!msg.form[name]) {
|
|
276
|
+
msg.end = 0;
|
|
277
|
+
return props[name].tip || props[name].question || '请填写' + name;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 尝试从消息中提取单个参数
|
|
285
|
+
* @param {object} msg 消息对象
|
|
286
|
+
* @param {string} name 参数名
|
|
287
|
+
* @param {object} param 参数配置
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
Cmd.prototype._tryExtractParam = function (msg, name, param) {
|
|
291
|
+
if (msg.form[name]) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (param.match) {
|
|
295
|
+
var val = this._cleanVal(msg.content.matchs(param.match), param);
|
|
296
|
+
if (val) {
|
|
297
|
+
var match_arr = this.config.match;
|
|
298
|
+
if (match_arr && match_arr.indexOf(val) !== -1) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
msg.form[name] = val;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!msg.form[name] && param.default != null) {
|
|
305
|
+
msg.form[name] = param.default;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 清理提取值: 移除多余部分, 枚举验证
|
|
311
|
+
* @param {string} val 原始提取值
|
|
312
|
+
* @param {object} param 参数配置
|
|
313
|
+
* @returns {string} 清理后的值
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
Cmd.prototype._cleanVal = function (val, param) {
|
|
317
|
+
if (!val) return '';
|
|
318
|
+
var result = val;
|
|
319
|
+
if (param.remove && param.remove.length) {
|
|
320
|
+
for (var i = 0; i < param.remove.length; i++) {
|
|
321
|
+
result = result.replace(param.remove[i], '');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (param.enum && param.enum.length && param.enum.indexOf(result) === -1) {
|
|
325
|
+
return '';
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
module.exports = Cmd;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 内置会话缓存类(替代原框架注入的全局缓存服务,进程内 Map + 过期时间)
|
|
5
|
+
* @class
|
|
6
|
+
*/
|
|
7
|
+
class LocalCache {
|
|
8
|
+
/**
|
|
9
|
+
* 构造函数
|
|
10
|
+
* @class
|
|
11
|
+
*/
|
|
12
|
+
constructor() {
|
|
13
|
+
/**
|
|
14
|
+
* 缓存存储(key => { value, expire })
|
|
15
|
+
* @type {Map}
|
|
16
|
+
*/
|
|
17
|
+
this.store = new Map();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 读取缓存(过期自动删除)
|
|
22
|
+
* @param {string} key 缓存键
|
|
23
|
+
* @returns {*} 缓存值,不存在返回 undefined
|
|
24
|
+
*/
|
|
25
|
+
get(key) {
|
|
26
|
+
var item = this.store.get(key);
|
|
27
|
+
if (!item) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (item.expire && item.expire < Date.now()) {
|
|
31
|
+
this.store.delete(key);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return item.value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 写入缓存
|
|
39
|
+
* @param {string} key 缓存键
|
|
40
|
+
* @param {*} value 缓存值
|
|
41
|
+
* @param {number} timeout 有效期秒数,0 或不传为不过期
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
set(key, value, timeout = 0) {
|
|
45
|
+
this.store.set(key, {
|
|
46
|
+
value,
|
|
47
|
+
expire: timeout > 0 ? Date.now() + timeout * 1000 : 0,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 删除缓存
|
|
53
|
+
* @param {string} key 缓存键
|
|
54
|
+
* @returns {boolean} 是否删除成功
|
|
55
|
+
*/
|
|
56
|
+
del(key) {
|
|
57
|
+
return this.store.delete(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = LocalCache;
|