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/manager.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const Manager = require("mm_corejs").Manager;
|
|
2
|
+
const Drive = require("./drive");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Api接口类(Web API 网关管理器,基于 mm_corejs,无全局/原型扩展依赖)
|
|
6
|
+
* @class
|
|
7
|
+
* @augments {Manager}
|
|
8
|
+
*/
|
|
9
|
+
class Api extends Manager {
|
|
10
|
+
/**
|
|
11
|
+
* 配置参数
|
|
12
|
+
* @type {object}
|
|
13
|
+
*/
|
|
14
|
+
static config = {
|
|
15
|
+
/**
|
|
16
|
+
* 名称
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
name: "",
|
|
20
|
+
/**
|
|
21
|
+
* 标题
|
|
22
|
+
* @type {string}
|
|
23
|
+
*/
|
|
24
|
+
title: "API接口",
|
|
25
|
+
/**
|
|
26
|
+
* 描述
|
|
27
|
+
* @type {string}
|
|
28
|
+
*/
|
|
29
|
+
description: "这是API接口管理器",
|
|
30
|
+
/**
|
|
31
|
+
* 最大深度
|
|
32
|
+
* @type {number}
|
|
33
|
+
*/
|
|
34
|
+
max_depth: 6,
|
|
35
|
+
/**
|
|
36
|
+
* 排序键(接口匹配排序用的配置字段名)
|
|
37
|
+
* @type {string}
|
|
38
|
+
*/
|
|
39
|
+
sort_key: "sort",
|
|
40
|
+
/**
|
|
41
|
+
* 缓存实例获取函数(可选注入,返回 cache 实例;未配置则跳过接口缓存与缓存登录态)
|
|
42
|
+
* @type {Function|null}
|
|
43
|
+
*/
|
|
44
|
+
get_cache: null,
|
|
45
|
+
/**
|
|
46
|
+
* 资源清单文件名(update 扫描目录时使用)
|
|
47
|
+
* @type {string}
|
|
48
|
+
*/
|
|
49
|
+
filename: "api.json",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 构造函数
|
|
54
|
+
* @param {object} config 配置参数
|
|
55
|
+
* @param {object} parent 父级模块(仅作兼容传入,不使用)
|
|
56
|
+
*/
|
|
57
|
+
constructor(config, parent) {
|
|
58
|
+
super({ ...Api.config, ...config }, parent);
|
|
59
|
+
// 插件注册表(方案B:手动注册,供驱动 plugin() 查找)
|
|
60
|
+
this.plugins = {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Api驱动类
|
|
66
|
+
* @type {Drive}
|
|
67
|
+
*/
|
|
68
|
+
Api.prototype.Drive = Drive;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 注册一个 api 驱动(方案B:手动注册,不扫描目录)
|
|
72
|
+
* @param {object} config 驱动配置(必须包含 name)
|
|
73
|
+
* @returns {object} 驱动实例
|
|
74
|
+
*/
|
|
75
|
+
Api.prototype.add = function (config) {
|
|
76
|
+
var drive = new Drive({ ...config }, this);
|
|
77
|
+
this.set(drive.config.name, drive);
|
|
78
|
+
return drive;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 注销一个 api 驱动(先停止再删除注册表项)
|
|
83
|
+
* @param {string} name 驱动名称
|
|
84
|
+
* @returns {Promise<boolean>} 是否删除成功
|
|
85
|
+
*/
|
|
86
|
+
Api.prototype.del = async function (name) {
|
|
87
|
+
var drive = this.get(name);
|
|
88
|
+
if (drive) {
|
|
89
|
+
await drive.stop();
|
|
90
|
+
}
|
|
91
|
+
return await Manager.prototype.del.call(this, name);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 重启一个 api 驱动(stop 后重新 start)
|
|
96
|
+
* 注意:生命周期状态机不支持 stopped 后再次 start,失败时需 del 后重新 add 实现手动重启
|
|
97
|
+
* @param {string} name 驱动名称
|
|
98
|
+
* @returns {Promise<boolean>} 是否执行成功
|
|
99
|
+
*/
|
|
100
|
+
Api.prototype.reload = async function (name) {
|
|
101
|
+
var drive = this.get(name);
|
|
102
|
+
if (!drive) {
|
|
103
|
+
this.log("warn", "驱动不存在,无法重启", name);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await drive.stop();
|
|
108
|
+
await this._initDrive(drive);
|
|
109
|
+
await drive.start();
|
|
110
|
+
return true;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
this.log("warn", "驱动重启失败(生命周期不支持停止后重启,请 del 后重新 add)", name, err);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 注册插件集合(供驱动 plugin() 查找,替代原全局插件池)
|
|
119
|
+
* @param {string} name 插件作用域名称(app 名)
|
|
120
|
+
* @param {object} obj 插件集合对象(需含 get 方法)
|
|
121
|
+
* @returns {object} 当前管理器实例
|
|
122
|
+
*/
|
|
123
|
+
Api.prototype.usePlugin = function (name, obj) {
|
|
124
|
+
this.plugins[name] = obj;
|
|
125
|
+
return this;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 初始化驱动实例(仅未初始化时执行生命周期 init,重复调用安全)
|
|
130
|
+
* @param {object} drive 驱动实例
|
|
131
|
+
* @returns {Promise<void>} 初始化完成
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
Api.prototype._initDrive = async function (drive) {
|
|
135
|
+
if (drive.isState("created")) {
|
|
136
|
+
await drive.init();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 获取所有驱动配置列表(方案B:来自注册表,替代旧基类 getInfos)
|
|
142
|
+
* @returns {Array} 驱动配置数组
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
Api.prototype._getInfos = function () {
|
|
146
|
+
return this.values().map((d) => d.config);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取按匹配优先级排序后的启用驱动列表
|
|
151
|
+
* 排序规则:路径长的优先,其次按排序键升序(保留原 _sort 的比较语义)
|
|
152
|
+
* @returns {Array} 驱动实例数组
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
Api.prototype._sortedDrives = function () {
|
|
156
|
+
var sort_key = this.config.sort_key || "sort";
|
|
157
|
+
var drives = this.values().filter((d) => d.config.state === 1);
|
|
158
|
+
drives.sort((m1, m2) => {
|
|
159
|
+
var p1 = m1.config.path || "";
|
|
160
|
+
var p2 = m2.config.path || "";
|
|
161
|
+
var n = p2.length - p1.length;
|
|
162
|
+
if (n) {
|
|
163
|
+
return n;
|
|
164
|
+
} else {
|
|
165
|
+
return m1.config[sort_key] - m2.config[sort_key];
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return drives;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 匹配目标
|
|
173
|
+
* @param {string} str 目标
|
|
174
|
+
* @param {string} pattern 模式
|
|
175
|
+
* @returns {boolean} 是否匹配
|
|
176
|
+
*/
|
|
177
|
+
Api.prototype.isMatch = function (str, pattern) {
|
|
178
|
+
if (str === pattern) {
|
|
179
|
+
return true;
|
|
180
|
+
} else if (pattern.startsWith("*")) {
|
|
181
|
+
let clean = pattern.replaceAll("*", "");
|
|
182
|
+
if (pattern.endsWith("*")) {
|
|
183
|
+
return str.indexOf(clean) !== -1;
|
|
184
|
+
} else {
|
|
185
|
+
return str.endsWith(clean);
|
|
186
|
+
}
|
|
187
|
+
} else if (pattern.endsWith("*")) {
|
|
188
|
+
let clean = pattern.replaceAll("*", "");
|
|
189
|
+
return str.startsWith(clean);
|
|
190
|
+
} else {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 执行Api
|
|
197
|
+
* @param {object} ctx 请求上下文
|
|
198
|
+
* @param {object} db 数据管理器
|
|
199
|
+
* @returns {object | string} 返回一个对象或字符串
|
|
200
|
+
*/
|
|
201
|
+
Api.prototype.run = async function (ctx, options = {}) {
|
|
202
|
+
let path = ctx.request.path;
|
|
203
|
+
let drives = this._sortedDrives();
|
|
204
|
+
|
|
205
|
+
for (var i = 0; i < drives.length; i++) {
|
|
206
|
+
var mod = drives[i];
|
|
207
|
+
if (this.isMatch(path, mod.config.path)) {
|
|
208
|
+
if (!mod.isLoaded()) {
|
|
209
|
+
await mod.load();
|
|
210
|
+
}
|
|
211
|
+
var ret = await mod.run(ctx, options);
|
|
212
|
+
if (ret) {
|
|
213
|
+
db.ret = ret;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return db.ret;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @module 导出API管理器类
|
|
223
|
+
*/
|
|
224
|
+
module.exports = Api;
|
package/lib/oauth.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { string } = require("mm_helper");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 转换值类型(等价复刻 mm_expand 的 convertValue)
|
|
7
|
+
* @param {any} target_val 目标值
|
|
8
|
+
* @param {any} source_val 源值
|
|
9
|
+
* @returns {any} 转换后的值
|
|
10
|
+
*/
|
|
11
|
+
function _convertValue(target_val, source_val) {
|
|
12
|
+
var target_type = typeof target_val;
|
|
13
|
+
if (target_type === "number") {
|
|
14
|
+
var num = Number(source_val);
|
|
15
|
+
return isNaN(num) ? 0 : num;
|
|
16
|
+
} else if (target_type === "boolean") {
|
|
17
|
+
if (source_val === "0" || source_val === "false" || source_val === "False") {
|
|
18
|
+
return false;
|
|
19
|
+
} else if (source_val === "1" || source_val === "true" || source_val === "True") {
|
|
20
|
+
return true;
|
|
21
|
+
} else {
|
|
22
|
+
return Boolean(source_val);
|
|
23
|
+
}
|
|
24
|
+
} else if (target_type === "string") {
|
|
25
|
+
return String(source_val);
|
|
26
|
+
} else {
|
|
27
|
+
return source_val;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 添加对象属性(替代原全局 push,等价复刻 mm_expand 的 push)
|
|
33
|
+
* @param {object} target 被添加的对象
|
|
34
|
+
* @param {object} source 用作添加的对象
|
|
35
|
+
* @param {boolean} supplement 是否补充没有的对象
|
|
36
|
+
* @returns {object} 修改后的目标对象
|
|
37
|
+
*/
|
|
38
|
+
function _push(target, source, supplement = false) {
|
|
39
|
+
if (!target || !source || typeof target !== "object" || typeof source !== "object") {
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
42
|
+
for (var key in source) {
|
|
43
|
+
if (target.hasOwnProperty(key)) {
|
|
44
|
+
var target_val = target[key];
|
|
45
|
+
var source_val = source[key];
|
|
46
|
+
if (typeof target_val === "object" && typeof source_val === "object") {
|
|
47
|
+
_push(target_val, source_val, supplement);
|
|
48
|
+
} else if (typeof target_val === typeof source_val) {
|
|
49
|
+
target[key] = _convertValue(target_val, source_val);
|
|
50
|
+
}
|
|
51
|
+
} else if (supplement) {
|
|
52
|
+
target[key] = source[key];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return target;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Oauth身份验证驱动类
|
|
60
|
+
* @class
|
|
61
|
+
*/
|
|
62
|
+
class Oauth {
|
|
63
|
+
static config = {
|
|
64
|
+
// 是否登录
|
|
65
|
+
'sign_in': false,
|
|
66
|
+
// 要求会员级别
|
|
67
|
+
'vip': 0,
|
|
68
|
+
// 要求管理级别
|
|
69
|
+
'gm': 0,
|
|
70
|
+
// 要求商户级别
|
|
71
|
+
'mc': 0,
|
|
72
|
+
// 要求用户组
|
|
73
|
+
'user_group': [],
|
|
74
|
+
// 要求管理组
|
|
75
|
+
'user_admin': [],
|
|
76
|
+
// 文件路径, 当调用函数不存在时,会先从文件中加载
|
|
77
|
+
'main': '',
|
|
78
|
+
// 当前目录(方案B:由驱动传入,替代旧基类 getDir)
|
|
79
|
+
'dir': '',
|
|
80
|
+
// 登录token请求头键名(替代原全局 dict 中的 token 键)
|
|
81
|
+
'token_key': 'token',
|
|
82
|
+
// 会话缓存键前缀(替代原全局 dict 中的 session_id 键)
|
|
83
|
+
'session_key': 'mm:uuid',
|
|
84
|
+
// 缓存实例获取函数(可选注入,未配置则跳过缓存登录态)
|
|
85
|
+
'get_cache': null
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* 构造函数
|
|
89
|
+
* @param {object} config 配置参数(含 dir/token_key/session_key/get_cache)
|
|
90
|
+
* @class
|
|
91
|
+
*/
|
|
92
|
+
constructor(config) {
|
|
93
|
+
// 配置参数
|
|
94
|
+
this.config = { ...Oauth.config, ...config };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取当前目录(方案B:来自构造注入的 dir)
|
|
100
|
+
* @returns {string} 当前目录
|
|
101
|
+
*/
|
|
102
|
+
Oauth.prototype.getDir = function () {
|
|
103
|
+
return this.config.dir || __dirname;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 加载配置对象
|
|
108
|
+
* @param {object} obj 配置对象
|
|
109
|
+
*/
|
|
110
|
+
Oauth.prototype.loadObj = function (obj) {
|
|
111
|
+
_push(this.config, obj);
|
|
112
|
+
var f = this.config.main;
|
|
113
|
+
if (f) {
|
|
114
|
+
var file = path.isAbsolute(f) ? f : path.resolve(this.getDir(), f);
|
|
115
|
+
if (fs.existsSync(file)) {
|
|
116
|
+
var cs = require(file);
|
|
117
|
+
if (cs) {
|
|
118
|
+
_push(this, cs, true);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 身份验证主程序
|
|
126
|
+
* @param {object} ctx HTTP请求上下文
|
|
127
|
+
* @returns {object} 验证失败返回错误提示,验证通过返回null
|
|
128
|
+
*/
|
|
129
|
+
Oauth.prototype.main = async function (ctx) {
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 小校验
|
|
135
|
+
* @param {object} ctx HTTP请求上下文
|
|
136
|
+
*/
|
|
137
|
+
Oauth.prototype.checkSub = async function (ctx) {
|
|
138
|
+
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 验证身份
|
|
143
|
+
* @param {object} ctx HTTP请求上下文
|
|
144
|
+
* @returns {object} 验证失败返回错误提示,验证通过返回null
|
|
145
|
+
*/
|
|
146
|
+
Oauth.prototype.check = async function (ctx) {
|
|
147
|
+
// 无需登录则不验证身份和权限
|
|
148
|
+
var cg = this.config;
|
|
149
|
+
var user = await this._getUser(ctx);
|
|
150
|
+
if (!cg.sign_in) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
var error = await this.main(ctx);
|
|
154
|
+
if (error) {
|
|
155
|
+
return error;
|
|
156
|
+
}
|
|
157
|
+
if (!user) {
|
|
158
|
+
return {
|
|
159
|
+
code: 60000,
|
|
160
|
+
message: '账户未登录!'
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (await this._checkAccess(user, cg)) {
|
|
164
|
+
return {
|
|
165
|
+
code: 70000,
|
|
166
|
+
message: '没有访问权限'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取用户信息
|
|
174
|
+
* @param {object} ctx HTTP请求上下文
|
|
175
|
+
* @returns {object|null} 用户信息
|
|
176
|
+
* @private
|
|
177
|
+
*/
|
|
178
|
+
Oauth.prototype._getUser = async function (ctx) {
|
|
179
|
+
var user = ctx.session?.user;
|
|
180
|
+
if (user) {
|
|
181
|
+
return user;
|
|
182
|
+
}
|
|
183
|
+
var token = ctx.headers[this.config.token_key];
|
|
184
|
+
if (!token) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
var cache = typeof this.config.get_cache === 'function' ? this.config.get_cache() : null;
|
|
188
|
+
if (!cache) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
var u = await cache.get(this.config.session_key + '_' + token);
|
|
192
|
+
if (!u) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
var user_info = this._extractUserFromCache(u);
|
|
196
|
+
if (user_info) {
|
|
197
|
+
ctx.session.user = user_info;
|
|
198
|
+
}
|
|
199
|
+
return user_info;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 从缓存中提取用户信息
|
|
204
|
+
* @param {object|string} u 缓存数据
|
|
205
|
+
* @returns {object|null} 用户信息
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
Oauth.prototype._extractUserFromCache = function (u) {
|
|
209
|
+
var type = typeof (u);
|
|
210
|
+
if (type === 'string') {
|
|
211
|
+
var o = string.toJson(u);
|
|
212
|
+
return o.user;
|
|
213
|
+
} else if (type === 'object' && Object.keys(u).length > 0) {
|
|
214
|
+
return u.user;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 检查用户访问权限
|
|
221
|
+
* @param {object} user 用户信息
|
|
222
|
+
* @param {object} config 配置信息
|
|
223
|
+
* @returns {boolean} 是否无权限
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
Oauth.prototype._checkAccess = function (user, config) {
|
|
227
|
+
return this._checkAccessLevels(user, config) ||
|
|
228
|
+
this._checkUserGroups(user, config) ||
|
|
229
|
+
this._checkAdminGroups(user, config);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 检查用户级别权限
|
|
234
|
+
* @param {object} user 用户信息
|
|
235
|
+
* @param {object} config 配置信息
|
|
236
|
+
* @returns {boolean} 是否无权限
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
Oauth.prototype._checkAccessLevels = function (user, config) {
|
|
240
|
+
return (config.gm !== 0 && user.gm < config.gm) ||
|
|
241
|
+
(config.vip !== 0 && user.vip < config.vip) ||
|
|
242
|
+
(config.mc !== 0 && user.mc < config.mc);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 检查用户组权限
|
|
247
|
+
* @param {object} user 用户信息
|
|
248
|
+
* @param {object} config 配置信息
|
|
249
|
+
* @returns {boolean} 是否无权限
|
|
250
|
+
* @private
|
|
251
|
+
*/
|
|
252
|
+
Oauth.prototype._checkUserGroups = function (user, config) {
|
|
253
|
+
if (config.user_group.length === 0) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
var user_groups = user.user_group.split(',');
|
|
257
|
+
var has_group = config.user_group.some(group => user_groups.includes(group));
|
|
258
|
+
return !has_group;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 检查管理组权限
|
|
263
|
+
* @param {object} user 用户信息
|
|
264
|
+
* @param {object} config 配置信息
|
|
265
|
+
* @returns {boolean} 是否无权限
|
|
266
|
+
* @private
|
|
267
|
+
*/
|
|
268
|
+
Oauth.prototype._checkAdminGroups = function (user, config) {
|
|
269
|
+
if (config.user_admin.length === 0) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
var admin_groups = user.user_admin.split(',');
|
|
273
|
+
var has_admin = config.user_admin.some(group => admin_groups.includes(group));
|
|
274
|
+
return !has_admin;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
module.exports = Oauth;
|
package/package.json
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_api",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "API框架,用于实现web api和mvc模式路由请求,负责配置路由路径、访问权限、校验机制;基于 mm_corejs,内置管理器池,无全局/原型扩展依赖",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mm_mod",
|
|
8
|
+
"mm_port",
|
|
9
|
+
"mm_in_port",
|
|
10
|
+
"api",
|
|
11
|
+
"mvc",
|
|
12
|
+
"router",
|
|
13
|
+
"oauth",
|
|
14
|
+
"API管理器"
|
|
15
|
+
],
|
|
16
|
+
"layer": "port",
|
|
17
|
+
"group": "web",
|
|
18
|
+
"author": "邱文武",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"mm_corejs": "^1.1.4",
|
|
22
|
+
"mm_param": "^1.1.0",
|
|
23
|
+
"mm_helper": "^1.0.0",
|
|
24
|
+
"mm_sql": "^1.8.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node test.js"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/script.tpl.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = {
|
|
3
|
+
/**
|
|
4
|
+
* 接口主函数
|
|
5
|
+
* @param {object} ctx HTTP上下文
|
|
6
|
+
* @param {object} db 数据管理器,如: { next: async function{}, ret: {} }
|
|
7
|
+
* @returns {object} 执行结果
|
|
8
|
+
*/
|
|
9
|
+
async main(ctx, options) {
|
|
10
|
+
// 获取请求参数
|
|
11
|
+
var req = ctx.request;
|
|
12
|
+
var {
|
|
13
|
+
query,
|
|
14
|
+
body
|
|
15
|
+
} = req;
|
|
16
|
+
// return "hello world" + JSON.stringify(body);
|
|
17
|
+
|
|
18
|
+
// 操作sql模板
|
|
19
|
+
var m = query.method;
|
|
20
|
+
if (m === 'add') {
|
|
21
|
+
|
|
22
|
+
} else if (m === 'del') {
|
|
23
|
+
|
|
24
|
+
} else if (m === 'set') {
|
|
25
|
+
|
|
26
|
+
} else {
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
var ret = await this.sql.run(query, body, db);
|
|
30
|
+
return ret;
|
|
31
|
+
}
|
|
32
|
+
};
|
package/test.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const mod = require('./index.js');
|
|
4
|
+
|
|
5
|
+
// 验证导出结构
|
|
6
|
+
assert.ok(mod.Manager, '应导出 Manager');
|
|
7
|
+
assert.ok(mod.admin, '应导出 admin');
|
|
8
|
+
assert.strictEqual(typeof mod.apiAdmin, 'function', '应导出 apiAdmin 函数');
|
|
9
|
+
|
|
10
|
+
// 验证通过工厂函数获取实例
|
|
11
|
+
const inst = mod.apiAdmin('test');
|
|
12
|
+
assert.ok(inst instanceof mod.Manager, 'apiAdmin 返回的实例应为 Manager 类型');
|
|
13
|
+
|
|
14
|
+
// 验证通过 admin 获取实例
|
|
15
|
+
const inst2 = mod.admin.get('test2');
|
|
16
|
+
assert.ok(inst2 instanceof mod.Manager, 'admin.get 返回的实例应为 Manager 类型');
|
|
17
|
+
|
|
18
|
+
console.log('mm_api 模块测试通过');
|