mm_api 0.0.0-stage → 1.1.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/README.md +3 -3
- package/com.json +5 -0
- package/config.tpl.json +41 -0
- package/index.js +21 -0
- package/lib/drive.js +985 -0
- package/lib/manager.js +224 -0
- package/lib/oauth.js +277 -0
- package/package.json +27 -4
- package/script.tpl.js +32 -0
- package/test.js +18 -0
package/lib/drive.js
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const Item = require("mm_corejs").Base;
|
|
4
|
+
const { string, date } = require("mm_helper");
|
|
5
|
+
const Oauth = require("./oauth.js"); // 是MM自带的身份验证机制,基于Oauth2.0,可以不使用
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 加载可选依赖模块
|
|
9
|
+
* @param {string} id 模块标识
|
|
10
|
+
* @returns {object|null} 模块对象,缺失时返回null
|
|
11
|
+
*/
|
|
12
|
+
function loadOptional(id) {
|
|
13
|
+
try {
|
|
14
|
+
return require(id);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Param = loadOptional("mm_param/lib/drive.js"); // 是MM自带的参数机制,可以不使用
|
|
21
|
+
const Sql = loadOptional("mm_sql/lib/drive.js"); // 是MM自带的sql机制,可以不使用
|
|
22
|
+
|
|
23
|
+
/** param 实例缓存(模块级,替代原全局 param 模块列表) */
|
|
24
|
+
const PARAM_CACHE = new Map();
|
|
25
|
+
/** sql 实例缓存(模块级,替代原全局 sql 模块列表) */
|
|
26
|
+
const SQL_CACHE = new Map();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 补全文件路径(替代原全局路径补全能力,支持相对/绝对路径)
|
|
30
|
+
* @param {string} file 文件路径
|
|
31
|
+
* @param {string} dir 基准目录
|
|
32
|
+
* @returns {string} 完整路径
|
|
33
|
+
*/
|
|
34
|
+
function _resolvePath(file, dir) {
|
|
35
|
+
if (!file) {
|
|
36
|
+
return process.cwd();
|
|
37
|
+
}
|
|
38
|
+
if (path.isAbsolute(file)) {
|
|
39
|
+
return file;
|
|
40
|
+
}
|
|
41
|
+
return path.resolve(dir || process.cwd(), file);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 生成XML节点(_toXml 的内部递归辅助)
|
|
46
|
+
* @param {string} name 节点名
|
|
47
|
+
* @param {*} value 节点值
|
|
48
|
+
* @returns {string} XML片段字符串
|
|
49
|
+
*/
|
|
50
|
+
function _xmlNode(name, value) {
|
|
51
|
+
if (value && typeof value === "object") {
|
|
52
|
+
return "<" + name + ">" + _toXml(value) + "</" + name + ">";
|
|
53
|
+
}
|
|
54
|
+
var text = value === undefined || value === null ? "" : String(value);
|
|
55
|
+
return "<" + name + ">" + text + "</" + name + ">";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 对象转XML字符串(替代原全局 toXml 的精简等价实现)
|
|
60
|
+
* @param {object} source 被转换的对象
|
|
61
|
+
* @returns {string} xml格式字符串
|
|
62
|
+
*/
|
|
63
|
+
function _toXml(source) {
|
|
64
|
+
var parts = [];
|
|
65
|
+
for (var key in source) {
|
|
66
|
+
var val = source[key];
|
|
67
|
+
if (Array.isArray(val)) {
|
|
68
|
+
for (var i = 0; i < val.length; i++) {
|
|
69
|
+
parts.push(_xmlNode(key, val[i]));
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
parts.push(_xmlNode(key, val));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return parts.join("");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 构建错误响应体(替代原全局 ret.error,默认 json-rpc 形态)
|
|
80
|
+
* @param {number} code 错误码
|
|
81
|
+
* @param {string} message 错误信息
|
|
82
|
+
* @returns {object} 形如 { error: { code, message } } 的响应体
|
|
83
|
+
*/
|
|
84
|
+
function _retError(code, message) {
|
|
85
|
+
return {
|
|
86
|
+
error: {
|
|
87
|
+
code,
|
|
88
|
+
message,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 去除 JSON 文本中的注释(支持 // 与整块注释,不处理字符串内部)
|
|
95
|
+
* @param {string} text 原始文本
|
|
96
|
+
* @returns {string} 去除注释后的文本
|
|
97
|
+
*/
|
|
98
|
+
function _stripComments(text) {
|
|
99
|
+
var out = "";
|
|
100
|
+
var in_string = false;
|
|
101
|
+
var in_line = false;
|
|
102
|
+
var in_block = false;
|
|
103
|
+
var quote = "";
|
|
104
|
+
|
|
105
|
+
for (var i = 0; i < text.length; i++) {
|
|
106
|
+
var char = text[i];
|
|
107
|
+
var next = text[i + 1];
|
|
108
|
+
|
|
109
|
+
if (in_line) {
|
|
110
|
+
if (char === "\n") {
|
|
111
|
+
in_line = false;
|
|
112
|
+
out += char;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (in_block) {
|
|
117
|
+
if (char === "*" && next === "/") {
|
|
118
|
+
in_block = false;
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (in_string) {
|
|
124
|
+
out += char;
|
|
125
|
+
if (char === "\\") {
|
|
126
|
+
out += next === undefined ? "" : next;
|
|
127
|
+
i++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (char === quote) {
|
|
131
|
+
in_string = false;
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (char === '"' || char === "'") {
|
|
136
|
+
in_string = true;
|
|
137
|
+
quote = char;
|
|
138
|
+
out += char;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (char === "/" && next === "/") {
|
|
142
|
+
in_line = true;
|
|
143
|
+
i++;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (char === "/" && next === "*") {
|
|
147
|
+
in_block = true;
|
|
148
|
+
i++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
out += char;
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 读取并解析 JSON 配置文件(自动去除注释)
|
|
158
|
+
* @param {string} file 配置文件绝对路径
|
|
159
|
+
* @returns {object} 解析后的配置对象
|
|
160
|
+
* @throws {Error} 文件读取或解析失败时抛出
|
|
161
|
+
*/
|
|
162
|
+
function _readJsonConfig(file) {
|
|
163
|
+
var text = fs.readFileSync(file, "utf-8");
|
|
164
|
+
return JSON.parse(_stripComments(text));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Api接口驱动类
|
|
169
|
+
* @augments {Item}
|
|
170
|
+
* @class
|
|
171
|
+
*/
|
|
172
|
+
class Drive extends Item {
|
|
173
|
+
static config = {
|
|
174
|
+
// 应用名称 例如:demo_app
|
|
175
|
+
app: "server",
|
|
176
|
+
// 插件名称 例如:demo_plugin
|
|
177
|
+
plugin: "sys",
|
|
178
|
+
// 名称
|
|
179
|
+
name: "",
|
|
180
|
+
// 状态 0未启用,1启用
|
|
181
|
+
state: 1,
|
|
182
|
+
// 主脚本文件(方案B:由 add() 手动配置,替代目录扫描)
|
|
183
|
+
main: "./index.js",
|
|
184
|
+
// 驱动目录(方案B:手动配置,替代目录扫描)
|
|
185
|
+
dir: "",
|
|
186
|
+
// 路径 例如:/demo
|
|
187
|
+
path: "",
|
|
188
|
+
// 接口类型,api表示服务端数据类型,web表示网页类型,app表示第三方应用类型
|
|
189
|
+
type: "api",
|
|
190
|
+
// 请求方式, POST或GET, 为空或ALL表示都可以
|
|
191
|
+
method: "ALL",
|
|
192
|
+
// 缓存时长 (单位:秒) 默认:60秒,建议600秒
|
|
193
|
+
cache: 0,
|
|
194
|
+
// 是否用客户端缓存,即 http 304 状态
|
|
195
|
+
client_cache: false,
|
|
196
|
+
// param参数验证路径 例如: ./param.json
|
|
197
|
+
param_path: "",
|
|
198
|
+
// sql模板路径 例如: ./sql.json
|
|
199
|
+
sql_path: "",
|
|
200
|
+
// 是否验证参数
|
|
201
|
+
check_param: true,
|
|
202
|
+
// 登录token请求头键名(替代原全局 dict 中的 token 键)
|
|
203
|
+
token_key: "token",
|
|
204
|
+
// 会话缓存键前缀(替代原全局 dict 中的 session_id 键)
|
|
205
|
+
session_key: "mm:uuid",
|
|
206
|
+
// 缓存实例获取函数(可选注入,未配置则跳过缓存读写)
|
|
207
|
+
get_cache: null,
|
|
208
|
+
// oauth身份验证模型, 参考oauth文件
|
|
209
|
+
oauth: {
|
|
210
|
+
scope: true,
|
|
211
|
+
sign_in: false,
|
|
212
|
+
vip: 0,
|
|
213
|
+
gm: 0,
|
|
214
|
+
mc: 0,
|
|
215
|
+
user_admin: [],
|
|
216
|
+
user_group: [],
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* 构造函数
|
|
221
|
+
* @param {object} config 配置项
|
|
222
|
+
* @param {object} parent 父对象(管理器实例)
|
|
223
|
+
* @class
|
|
224
|
+
*/
|
|
225
|
+
constructor(config, parent) {
|
|
226
|
+
super({ ...Drive.config, ...config });
|
|
227
|
+
|
|
228
|
+
// 父级管理器引用(用于缓存、插件与默认配置回退)
|
|
229
|
+
this.parent = parent || null;
|
|
230
|
+
|
|
231
|
+
// 脚本加载标记(方案B:替代旧基类 isLoaded 机制)
|
|
232
|
+
this._loaded = false;
|
|
233
|
+
|
|
234
|
+
// 初始化运行时数据结构(原由旧基类加载流程触发)
|
|
235
|
+
this._preset();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 预设配置
|
|
241
|
+
*/
|
|
242
|
+
Drive.prototype._preset = function () {
|
|
243
|
+
this.save_dir = "./static/file/";
|
|
244
|
+
this.url_path = "/file/";
|
|
245
|
+
|
|
246
|
+
// oauth身份验证配置 + 函数
|
|
247
|
+
this.oauth = null;
|
|
248
|
+
// param参数配置 + 函数
|
|
249
|
+
this.param = null;
|
|
250
|
+
// sql模板配置 + 函数
|
|
251
|
+
this.sql = null;
|
|
252
|
+
|
|
253
|
+
var cg = this.config;
|
|
254
|
+
this._loadParam(cg.param_path);
|
|
255
|
+
this._loadSql(cg.sql_path);
|
|
256
|
+
this._loadOauth();
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 获取驱动目录(方案B:来自手动配置的 dir,替代旧基类 getDir)
|
|
261
|
+
* @returns {string} 驱动目录
|
|
262
|
+
*/
|
|
263
|
+
Drive.prototype.getDir = function () {
|
|
264
|
+
return this.config.dir || __dirname;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 读取 JSON 配置驱动文件(自动去除注释,失败仅记录日志不抛错)
|
|
269
|
+
* @param {string} file 配置文件绝对路径
|
|
270
|
+
* @returns {object|null} 配置对象,文件不存在或解析失败返回 null
|
|
271
|
+
* @private
|
|
272
|
+
*/
|
|
273
|
+
Drive.prototype._loadJsonConfig = function (file) {
|
|
274
|
+
if (!fs.existsSync(file)) {
|
|
275
|
+
this.log("warn", "配置文件不存在", file);
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
return _readJsonConfig(file);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
this.log("warn", "配置解析失败", file, err.message);
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 获取缓存实例(方案B:可选注入,驱动配置优先,回退父级管理器配置)
|
|
288
|
+
* @returns {object|null} 缓存实例;未配置返回 null
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
Drive.prototype._getCache = function () {
|
|
292
|
+
var fn = this.config.get_cache;
|
|
293
|
+
if (typeof fn !== "function" && this.parent) {
|
|
294
|
+
fn = this.parent.config.get_cache;
|
|
295
|
+
}
|
|
296
|
+
return typeof fn === "function" ? fn() : null;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 设置参数对象(写入模块级缓存,替代原全局 param 模块列表)
|
|
301
|
+
* @param {object} param 参数对象
|
|
302
|
+
*/
|
|
303
|
+
Drive.prototype.updateParam = function (param) {
|
|
304
|
+
PARAM_CACHE.set(param.config_file, param);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 获取现有参数
|
|
309
|
+
* @param {string} file 文件名
|
|
310
|
+
* @returns {object} 获取的对象
|
|
311
|
+
*/
|
|
312
|
+
Drive.prototype.getParam = function (file) {
|
|
313
|
+
return PARAM_CACHE.get(file) || null;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 加载参数配置(方案B:读取 param.json 作为驱动配置,替代旧脚本动态加载)
|
|
318
|
+
* @param {string} file_path 参数文件路径
|
|
319
|
+
* @returns {object|null} 参数驱动实例,未配置或加载失败返回 null
|
|
320
|
+
*/
|
|
321
|
+
Drive.prototype._loadParam = function (file_path) {
|
|
322
|
+
if (!file_path || !Param) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
var p = _resolvePath(file_path, this.getDir());
|
|
326
|
+
var param = this.getParam(p);
|
|
327
|
+
if (param) {
|
|
328
|
+
this.param = param;
|
|
329
|
+
return param;
|
|
330
|
+
}
|
|
331
|
+
var cfg = this._loadJsonConfig(p);
|
|
332
|
+
if (!cfg) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
cfg.config_file = p;
|
|
336
|
+
cfg.dir = path.dirname(p);
|
|
337
|
+
this.param = new Param(cfg, this);
|
|
338
|
+
this.updateParam(this.param);
|
|
339
|
+
return this.param;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 设置Sql(写入模块级缓存,替代原全局 sql 模块列表)
|
|
344
|
+
* @param {object} sql sql对象
|
|
345
|
+
* @param {string} sql.config_file 文件名
|
|
346
|
+
* @param {object} sql.sql sql语句
|
|
347
|
+
*/
|
|
348
|
+
Drive.prototype.updateSql = function (sql) {
|
|
349
|
+
SQL_CACHE.set(sql.config_file, sql);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 获取现有sql模板
|
|
354
|
+
* @param {string} file 文件名
|
|
355
|
+
* @returns {object} 获取的对象
|
|
356
|
+
*/
|
|
357
|
+
Drive.prototype.getSql = function (file) {
|
|
358
|
+
return SQL_CACHE.get(file) || null;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 加载sql模板配置(方案B:读取 sql.json 作为驱动配置,替代旧脚本动态加载)
|
|
363
|
+
* @param {string} file_path sql模板文件路径
|
|
364
|
+
* @returns {object|null} sql驱动实例,未配置或加载失败返回 null
|
|
365
|
+
*/
|
|
366
|
+
Drive.prototype._loadSql = function (file_path) {
|
|
367
|
+
if (!file_path || !Sql) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
var p = _resolvePath(file_path, this.getDir());
|
|
371
|
+
var sql = this.getSql(p);
|
|
372
|
+
if (sql) {
|
|
373
|
+
this.sql = sql;
|
|
374
|
+
return sql;
|
|
375
|
+
}
|
|
376
|
+
var cfg = this._loadJsonConfig(p);
|
|
377
|
+
if (!cfg) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
cfg.name = cfg.name || this.config.name;
|
|
381
|
+
cfg.config_file = p;
|
|
382
|
+
cfg.dir = path.dirname(p);
|
|
383
|
+
this.sql = new Sql(cfg, this);
|
|
384
|
+
this.updateSql(this.sql);
|
|
385
|
+
return this.sql;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 加载身份验证配置
|
|
390
|
+
*/
|
|
391
|
+
Drive.prototype._loadOauth = function () {
|
|
392
|
+
var cg = this.config.oauth;
|
|
393
|
+
if (cg) {
|
|
394
|
+
this.oauth = new Oauth({
|
|
395
|
+
dir: this.getDir(),
|
|
396
|
+
token_key: this.config.token_key,
|
|
397
|
+
session_key: this.config.session_key,
|
|
398
|
+
get_cache: () => this._getCache(),
|
|
399
|
+
});
|
|
400
|
+
this.oauth.loadObj(cg);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 获取脚本文件完整路径
|
|
406
|
+
* @returns {string|null} 脚本文件路径,未配置返回null
|
|
407
|
+
* @private
|
|
408
|
+
*/
|
|
409
|
+
Drive.prototype._getScriptFile = function () {
|
|
410
|
+
var main = this.config.main || "";
|
|
411
|
+
if (!main) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
return _resolvePath(main, this.getDir());
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* 脚本是否已加载(方案B:替代旧基类 isLoaded 机制)
|
|
419
|
+
* @returns {boolean} 是否已加载
|
|
420
|
+
*/
|
|
421
|
+
Drive.prototype.isLoaded = function () {
|
|
422
|
+
return this._loaded;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 加载主脚本文件(方案B:标准 require,替代旧基类 do("load") 与热重载模式)
|
|
427
|
+
* @returns {object|null} 加载的模块对象
|
|
428
|
+
*/
|
|
429
|
+
Drive.prototype.load = function () {
|
|
430
|
+
var file = this._getScriptFile();
|
|
431
|
+
if (!file || !fs.existsSync(file)) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
var cs = require(file);
|
|
436
|
+
this._setMainMethod(cs);
|
|
437
|
+
return cs;
|
|
438
|
+
} catch (err) {
|
|
439
|
+
this.log("error", "加载脚本文件失败", err);
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 将脚本模块成员挂到驱动实例
|
|
446
|
+
* @param {object} mod 模块对象
|
|
447
|
+
* @private
|
|
448
|
+
*/
|
|
449
|
+
Drive.prototype._setMainMethod = function (mod) {
|
|
450
|
+
if (!mod) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
var name = this.config.func_name || "main";
|
|
454
|
+
if (typeof mod[name] === "function") {
|
|
455
|
+
this.main = mod[name];
|
|
456
|
+
}
|
|
457
|
+
for (var key in mod) {
|
|
458
|
+
var obj = mod[key];
|
|
459
|
+
if (obj !== undefined && obj !== null) {
|
|
460
|
+
this[key] = obj;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
this._loaded = true;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 保存文件
|
|
468
|
+
* @param {object} files 上传的文件
|
|
469
|
+
* @param {string} files.file 上传的文件对象
|
|
470
|
+
* @returns {object} 返回文件路径和URL
|
|
471
|
+
*/
|
|
472
|
+
Drive.prototype.saveFile = function (files) {
|
|
473
|
+
var file;
|
|
474
|
+
var url;
|
|
475
|
+
if (files.file) {
|
|
476
|
+
var f = files.file;
|
|
477
|
+
// 安全处理文件名: 移除路径穿越字符和特殊字符
|
|
478
|
+
var name = f.name || f.originalFilename;
|
|
479
|
+
name = name.replace(/[\\/]|\.\./g, "_");
|
|
480
|
+
file = _resolvePath("./" + name, this.save_dir);
|
|
481
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
482
|
+
// 创建写入流
|
|
483
|
+
var read_stream = fs.createReadStream(f.filepath);
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
let write_stream = fs.createWriteStream(file);
|
|
487
|
+
read_stream.pipe(write_stream);
|
|
488
|
+
|
|
489
|
+
read_stream.on("error", (err) => {
|
|
490
|
+
this.log("error", "读取文件流失败", err);
|
|
491
|
+
write_stream.destroy();
|
|
492
|
+
});
|
|
493
|
+
write_stream.on("error", (err) => {
|
|
494
|
+
this.log("error", "写入文件流失败", err);
|
|
495
|
+
read_stream.destroy();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
url = this.url_path + name;
|
|
499
|
+
} catch (err) {
|
|
500
|
+
this.log("error", "保存文件失败", err);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
file,
|
|
505
|
+
url,
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* 从数据库获取登录态(方案B:实例私有方法,替代原全局 getState 回调)
|
|
511
|
+
* @param {object} db 数据管理器
|
|
512
|
+
* @param {string} token token
|
|
513
|
+
* @returns {object|null} 登录态与用户信息
|
|
514
|
+
* @private
|
|
515
|
+
*/
|
|
516
|
+
Drive.prototype._getState = async function (db, token) {
|
|
517
|
+
var db1 = db.new("user_state", "user_id");
|
|
518
|
+
var state = await db1.getObj(
|
|
519
|
+
{
|
|
520
|
+
token,
|
|
521
|
+
},
|
|
522
|
+
null,
|
|
523
|
+
null,
|
|
524
|
+
false,
|
|
525
|
+
);
|
|
526
|
+
if (!state) {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
var now = new Date();
|
|
530
|
+
if (date.interval(state.time_sign, now, "day") <= 30) {
|
|
531
|
+
db.table = "user_account";
|
|
532
|
+
var user = await db.getObj({
|
|
533
|
+
user_id: state.user_id,
|
|
534
|
+
});
|
|
535
|
+
var o = { ...user };
|
|
536
|
+
o.password_pay = o.password_pay ? "******" : "";
|
|
537
|
+
o.password = o.password ? "******" : "";
|
|
538
|
+
delete o.salt;
|
|
539
|
+
delete o.time_create;
|
|
540
|
+
return {
|
|
541
|
+
state,
|
|
542
|
+
user: o,
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* 获取登录态(原名 getState,因与 mm_corejs 生命周期 getState 冲突而更名)
|
|
551
|
+
* @param {object} ctx 请求上下文
|
|
552
|
+
* @param {object} db 数据管理器
|
|
553
|
+
* @returns {object} 执行结果
|
|
554
|
+
*/
|
|
555
|
+
Drive.prototype.getLoginState = async function (ctx, options) {
|
|
556
|
+
var o = await this._getUserFromSession(ctx);
|
|
557
|
+
if (!o) {
|
|
558
|
+
o = await this._getUserFromToken(ctx, options);
|
|
559
|
+
}
|
|
560
|
+
return this._processUserInfo(o);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 从 session 中获取用户信息
|
|
565
|
+
* @param {object} ctx 请求上下文
|
|
566
|
+
* @returns {object|null} 用户信息
|
|
567
|
+
* @private
|
|
568
|
+
*/
|
|
569
|
+
Drive.prototype._getUserFromSession = function (ctx) {
|
|
570
|
+
var u = ctx.session?.user;
|
|
571
|
+
if (u) {
|
|
572
|
+
var o = { ...u };
|
|
573
|
+
o.token = ctx.session.uuid;
|
|
574
|
+
return o;
|
|
575
|
+
}
|
|
576
|
+
return null;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* 从 token 中获取用户信息
|
|
581
|
+
* @param {object} ctx 请求上下文
|
|
582
|
+
* @param {object} db 数据管理器
|
|
583
|
+
* @returns {object|null} 用户信息
|
|
584
|
+
* @private
|
|
585
|
+
*/
|
|
586
|
+
Drive.prototype._getUserFromToken = async function (ctx, options) {
|
|
587
|
+
var token = ctx.headers[this.config.token_key];
|
|
588
|
+
if (!token) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
var cache = this._getCache();
|
|
592
|
+
if (cache) {
|
|
593
|
+
var u = await cache.get(this.config.session_key + "_" + token);
|
|
594
|
+
if (u) {
|
|
595
|
+
return await this._processUserFromCache(u, ctx, options, token);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return await this._getUserFromDb(token, ctx, options);
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* 处理缓存中的用户信息
|
|
603
|
+
* @param {object|string} u 缓存中的用户信息
|
|
604
|
+
* @param {object} ctx 请求上下文
|
|
605
|
+
* @param {object} db 数据管理器
|
|
606
|
+
* @param {string} token token
|
|
607
|
+
* @returns {object|null} 用户信息
|
|
608
|
+
* @private
|
|
609
|
+
*/
|
|
610
|
+
Drive.prototype._processUserFromCache = async function (u, ctx, options, token) {
|
|
611
|
+
var o;
|
|
612
|
+
if (typeof u === "string") {
|
|
613
|
+
o = { ...string.toJson(u) };
|
|
614
|
+
} else if (Object.keys(u).length > 0) {
|
|
615
|
+
o = { ...u };
|
|
616
|
+
}
|
|
617
|
+
if (!o) {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
if (!o.user_id) {
|
|
621
|
+
return await this._getUserFromDb(token, ctx, options);
|
|
622
|
+
}
|
|
623
|
+
o.token = token;
|
|
624
|
+
return o;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* 从数据库中获取用户信息
|
|
629
|
+
* @param {string} token token
|
|
630
|
+
* @param {object} ctx 请求上下文
|
|
631
|
+
* @param {object} db 数据管理器
|
|
632
|
+
* @returns {object|null} 用户信息
|
|
633
|
+
* @private
|
|
634
|
+
*/
|
|
635
|
+
Drive.prototype._getUserFromDb = async function (token, ctx, options) {
|
|
636
|
+
var obj = await this._getState(db, token);
|
|
637
|
+
if (obj) {
|
|
638
|
+
var o = { ...obj.user };
|
|
639
|
+
ctx.session.user = obj.user;
|
|
640
|
+
o.token = ctx.session.uuid;
|
|
641
|
+
obj.state.token = o.token;
|
|
642
|
+
return o;
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* 处理用户信息
|
|
649
|
+
* @param {object|null} o 用户信息
|
|
650
|
+
* @returns {object|null} 处理后的用户信息
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
653
|
+
Drive.prototype._processUserInfo = function (o) {
|
|
654
|
+
if (o) {
|
|
655
|
+
o.password_pay = o.password_pay ? "******" : "";
|
|
656
|
+
o.password = o.password ? "******" : "";
|
|
657
|
+
delete o.salt;
|
|
658
|
+
delete o.time_create;
|
|
659
|
+
}
|
|
660
|
+
return o;
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 获取登录态
|
|
665
|
+
* @param {object} ctx 请求上下文
|
|
666
|
+
* @param {object} db 数据管理器
|
|
667
|
+
* @returns {object} 执行结果
|
|
668
|
+
*/
|
|
669
|
+
Drive.prototype.before = async function (ctx, options) {
|
|
670
|
+
if (this.config.oauth && this.config.oauth.sign_in) {
|
|
671
|
+
db.user = await this.getLoginState(ctx, options);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* 主要函数
|
|
677
|
+
* @param {object} ctx 请求上下文
|
|
678
|
+
* @param {object} db 数据管理器
|
|
679
|
+
* @returns {object} 执行结果
|
|
680
|
+
*/
|
|
681
|
+
Drive.prototype.main = async function (ctx, options) {
|
|
682
|
+
if (this.sql) {
|
|
683
|
+
var req = ctx.request;
|
|
684
|
+
// 获取文件
|
|
685
|
+
if (req.files) {
|
|
686
|
+
var fobj = this.saveFile(req.files);
|
|
687
|
+
req.body.file = fobj.file;
|
|
688
|
+
req.body.url = fobj.url;
|
|
689
|
+
}
|
|
690
|
+
var ret = await this.sql.run(req.query, req.body, db);
|
|
691
|
+
return ret;
|
|
692
|
+
} else {
|
|
693
|
+
return _retError(10000, "接口函数未定义");
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* 调用函数
|
|
699
|
+
* @param {object} ctx 请求上下文
|
|
700
|
+
* @param {object} db 数据管理器
|
|
701
|
+
* @returns {object} 执行结果
|
|
702
|
+
*/
|
|
703
|
+
Drive.prototype.run = async function (ctx, options) {
|
|
704
|
+
try {
|
|
705
|
+
var body = await this.getCache(ctx);
|
|
706
|
+
if (!body) {
|
|
707
|
+
var req = ctx.request;
|
|
708
|
+
var md = this.config["method"].toLocaleUpperCase();
|
|
709
|
+
if (md !== req.method && md !== "ALL") {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
await this.before(ctx, options);
|
|
713
|
+
var ret = await this.check(ctx, options);
|
|
714
|
+
if (!ret) {
|
|
715
|
+
try {
|
|
716
|
+
ret = await this.main(ctx, options);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
this.log("error", "脚本文件错误", req.path, err);
|
|
719
|
+
ret = _retError(10000, "脚本文件错误:" + err.toString());
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
var res = ctx.response;
|
|
723
|
+
body = this.body(ret, res, req.type);
|
|
724
|
+
this.setCache(ctx, body, res.type);
|
|
725
|
+
}
|
|
726
|
+
} catch (err) {
|
|
727
|
+
this.log("error", "接口错误", ctx.path, err);
|
|
728
|
+
}
|
|
729
|
+
return body;
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* 获取缓存
|
|
734
|
+
* @param {object} ctx 请求上下文
|
|
735
|
+
* @returns {string} 响应内容
|
|
736
|
+
*/
|
|
737
|
+
Drive.prototype.getCache = async function (ctx) {
|
|
738
|
+
var cg = this.config;
|
|
739
|
+
if (cg.cache) {
|
|
740
|
+
var req = ctx.request;
|
|
741
|
+
if (cg.client_cache) {
|
|
742
|
+
var etag = req.headers["if-none-match"];
|
|
743
|
+
if (etag) {
|
|
744
|
+
var stamp = Number(etag.replace('"', "").replace('"', ""));
|
|
745
|
+
var cha = stamp * 1000 - Date.parse(new Date());
|
|
746
|
+
if (cha > 0) {
|
|
747
|
+
ctx.response.status = 304;
|
|
748
|
+
return " ";
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
var cache = this._getCache();
|
|
753
|
+
if (cache) {
|
|
754
|
+
var data = await cache.get("api_" + req.url);
|
|
755
|
+
if (data) {
|
|
756
|
+
var obj = JSON.parse(data);
|
|
757
|
+
ctx.response.type = obj.type;
|
|
758
|
+
return obj.body;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return null;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* 设置缓存
|
|
768
|
+
* @param {object} ctx 请求上下文
|
|
769
|
+
* @param {object} body 正文参数
|
|
770
|
+
* @param {string} type 响应类型
|
|
771
|
+
*/
|
|
772
|
+
Drive.prototype.setCache = async function (ctx, body, type = null) {
|
|
773
|
+
var cg = this.config;
|
|
774
|
+
if (cg.cache && ctx.method === "GET") {
|
|
775
|
+
if (cg.client_cache) {
|
|
776
|
+
var age = cg.cache * 60;
|
|
777
|
+
ctx.set("Cache-Control", "max-age=" + age);
|
|
778
|
+
ctx.etag = Date.parse(new Date()) / 1000 + age;
|
|
779
|
+
} else {
|
|
780
|
+
var cache = this._getCache();
|
|
781
|
+
if (cache) {
|
|
782
|
+
var req = ctx.request;
|
|
783
|
+
var o = {};
|
|
784
|
+
o.body = body;
|
|
785
|
+
o.type = ctx.response.type;
|
|
786
|
+
cache.set("api_" + req.url, JSON.stringify(o), cg.cache * 60);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* 调用函数
|
|
794
|
+
* @param {object} result 设置响应结果
|
|
795
|
+
* @param {object} res 响应器
|
|
796
|
+
* @param {string} t 请求类型
|
|
797
|
+
* @returns {string} 处理后的响应结果
|
|
798
|
+
*/
|
|
799
|
+
Drive.prototype.body = function (result, res, t) {
|
|
800
|
+
var ret = result;
|
|
801
|
+
var type = res.type;
|
|
802
|
+
if (!ret) {
|
|
803
|
+
return ret;
|
|
804
|
+
}
|
|
805
|
+
var tp = typeof ret;
|
|
806
|
+
if (tp === "object") {
|
|
807
|
+
type = this._handleObjectType(res, type, t);
|
|
808
|
+
return this._convertObject(ret, type);
|
|
809
|
+
} else if (tp === "string") {
|
|
810
|
+
ret = ret.trim();
|
|
811
|
+
this._handleStringType(res, type, ret);
|
|
812
|
+
}
|
|
813
|
+
return ret;
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* 处理对象类型
|
|
818
|
+
* @param {object} res 响应器
|
|
819
|
+
* @param {string} type 类型
|
|
820
|
+
* @param {string} t 请求类型
|
|
821
|
+
* @returns {string} 处理后的类型
|
|
822
|
+
* @private
|
|
823
|
+
*/
|
|
824
|
+
Drive.prototype._handleObjectType = function (res, type, t) {
|
|
825
|
+
var new_type = type;
|
|
826
|
+
if (!new_type) {
|
|
827
|
+
new_type = t.indexOf("xml") !== -1 ? t : "application/json; charset=utf-8";
|
|
828
|
+
res.type = new_type;
|
|
829
|
+
} else {
|
|
830
|
+
new_type = this._normType(new_type);
|
|
831
|
+
res.type = new_type;
|
|
832
|
+
}
|
|
833
|
+
return new_type;
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* 标准化类型
|
|
838
|
+
* @param {string} type 类型
|
|
839
|
+
* @returns {string} 标准化后的类型
|
|
840
|
+
* @private
|
|
841
|
+
*/
|
|
842
|
+
Drive.prototype._normType = function (type) {
|
|
843
|
+
if (type.indexOf("json") !== -1) {
|
|
844
|
+
return "application/json; charset=utf-8";
|
|
845
|
+
} else if (type.indexOf("html") !== -1) {
|
|
846
|
+
return "text/html";
|
|
847
|
+
} else if (type.indexOf("xml") !== -1) {
|
|
848
|
+
return "text/xml; charset=utf-8";
|
|
849
|
+
}
|
|
850
|
+
return "text/plain; charset=utf-8";
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* 转换对象
|
|
855
|
+
* @param {object} obj 对象
|
|
856
|
+
* @param {string} type 类型
|
|
857
|
+
* @returns {string} 转换后的字符串
|
|
858
|
+
* @private
|
|
859
|
+
*/
|
|
860
|
+
Drive.prototype._convertObject = function (obj, type) {
|
|
861
|
+
return type.indexOf("/xml") !== -1 ? _toXml(obj) : JSON.stringify(obj);
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* 处理字符串类型
|
|
866
|
+
* @param {object} res 响应器
|
|
867
|
+
* @param {string} type 类型
|
|
868
|
+
* @param {string} str 字符串
|
|
869
|
+
* @private
|
|
870
|
+
*/
|
|
871
|
+
Drive.prototype._handleStringType = function (res, type, str) {
|
|
872
|
+
if (!type) {
|
|
873
|
+
if (
|
|
874
|
+
(str.startsWith("{") && str.endsWith("}")) ||
|
|
875
|
+
(str.startsWith("[") && str.endsWith("]"))
|
|
876
|
+
) {
|
|
877
|
+
res.type = "application/json; charset=utf-8";
|
|
878
|
+
} else if (str.endsWith("</html>")) {
|
|
879
|
+
res.type = "text/html";
|
|
880
|
+
} else if (str.startsWith("<?xml")) {
|
|
881
|
+
res.type = "text/xml; charset=utf-8";
|
|
882
|
+
} else {
|
|
883
|
+
res.type = "text/plain; charset=utf-8";
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* 验证参数
|
|
890
|
+
* @param {object} query url参数
|
|
891
|
+
* @param {object} body 内容参数
|
|
892
|
+
* @param {string} method 方法
|
|
893
|
+
* @returns {object} 验证结果
|
|
894
|
+
*/
|
|
895
|
+
Drive.prototype.checkParam = function (query, body, method) {
|
|
896
|
+
if (this.param) {
|
|
897
|
+
var msg = this.param.check(query, body, method);
|
|
898
|
+
if (msg) {
|
|
899
|
+
var code;
|
|
900
|
+
if (msg.indexOf("必须") !== -1 || msg.indexOf("为空") !== -1) {
|
|
901
|
+
code = 30001;
|
|
902
|
+
} else {
|
|
903
|
+
code = 30002;
|
|
904
|
+
}
|
|
905
|
+
return _retError(code, msg);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* 验证参数
|
|
913
|
+
* @param {object} ctx 内容上下文
|
|
914
|
+
* @returns {object} 验证结果
|
|
915
|
+
*/
|
|
916
|
+
Drive.prototype.check = async function (ctx) {
|
|
917
|
+
var error;
|
|
918
|
+
if (this.config.check_param) {
|
|
919
|
+
var req = ctx.request;
|
|
920
|
+
error = this.checkParam(req.query, req.body, req.method);
|
|
921
|
+
}
|
|
922
|
+
if (!error) {
|
|
923
|
+
error = await this.checkOauth(ctx);
|
|
924
|
+
if (error) {
|
|
925
|
+
error = {
|
|
926
|
+
error,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return error;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* 验证身份
|
|
935
|
+
* @param {object} ctx 内容上下文
|
|
936
|
+
* @returns {object} 验证结果
|
|
937
|
+
*/
|
|
938
|
+
Drive.prototype.checkOauth = async function (ctx) {
|
|
939
|
+
var aouth = this.oauth;
|
|
940
|
+
if (aouth) {
|
|
941
|
+
return await aouth.check(ctx);
|
|
942
|
+
} else {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* 获取插件(方案B:从父级管理器插件注册表查找,替代原全局插件池)
|
|
949
|
+
* @param {string} app 应用名称
|
|
950
|
+
* @param {string} name 插件名称
|
|
951
|
+
* @returns {object} 返回获取到的插件
|
|
952
|
+
*/
|
|
953
|
+
Drive.prototype.plugin = function (app, name) {
|
|
954
|
+
var l = path.sep;
|
|
955
|
+
var dir = this.config.dir || "";
|
|
956
|
+
var app_name = app || string.between(dir, "app" + l, l);
|
|
957
|
+
var plugin_name = name || string.between(dir, "plugin" + l, l);
|
|
958
|
+
var plus;
|
|
959
|
+
var plugins = this.parent?.plugins?.[app_name];
|
|
960
|
+
if (plugins) {
|
|
961
|
+
plus = plugins.get(plugin_name);
|
|
962
|
+
}
|
|
963
|
+
return plus;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* 获取模型
|
|
968
|
+
* @param {string} type 模型类型
|
|
969
|
+
* @returns {object} 返回获取到的模型
|
|
970
|
+
*/
|
|
971
|
+
Drive.prototype.getModel = function (type) {
|
|
972
|
+
let model = { ...this.config };
|
|
973
|
+
let dir = this.getDir();
|
|
974
|
+
let l = path.sep;
|
|
975
|
+
let app_name = string.between(dir, "app" + l, l);
|
|
976
|
+
let plugin_name = string.between(dir, "plugin" + l, l);
|
|
977
|
+
let name = path.basename(dir);
|
|
978
|
+
model.app = app_name;
|
|
979
|
+
model.plugin = plugin_name;
|
|
980
|
+
model.name = model.name || name;
|
|
981
|
+
model.path = "/" + app_name + "/" + name;
|
|
982
|
+
return model;
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
module.exports = Drive;
|