@domain.js/main 0.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/.eslintrc.js +67 -0
- package/.husky/pre-commit +7 -0
- package/.test/test.ts +13 -0
- package/.test/test2.js +45 -0
- package/.travis.yml +8 -0
- package/.vscode/settings.json +3 -0
- package/README.md +6 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +221 -0
- package/dist/deps/aes/index.d.ts +4 -0
- package/dist/deps/aes/index.js +16 -0
- package/dist/deps/axios/index.d.ts +22 -0
- package/dist/deps/axios/index.js +56 -0
- package/dist/deps/cache/After.d.ts +3 -0
- package/dist/deps/cache/After.js +28 -0
- package/dist/deps/cache/Before.d.ts +4 -0
- package/dist/deps/cache/Before.js +16 -0
- package/dist/deps/cache/Define.d.ts +28 -0
- package/dist/deps/cache/Define.js +2 -0
- package/dist/deps/cache/index.d.ts +5 -0
- package/dist/deps/cache/index.js +50 -0
- package/dist/deps/checker/index.d.ts +11 -0
- package/dist/deps/checker/index.js +25 -0
- package/dist/deps/cia/errors.d.ts +6 -0
- package/dist/deps/cia/errors.js +36 -0
- package/dist/deps/cia/index.d.ts +53 -0
- package/dist/deps/cia/index.js +291 -0
- package/dist/deps/counter/index.d.ts +16 -0
- package/dist/deps/counter/index.js +16 -0
- package/dist/deps/cron/index.d.ts +33 -0
- package/dist/deps/cron/index.js +98 -0
- package/dist/deps/defines.d.ts +35 -0
- package/dist/deps/defines.js +36 -0
- package/dist/deps/graceful/index.d.ts +14 -0
- package/dist/deps/graceful/index.js +115 -0
- package/dist/deps/hash/index.d.ts +17 -0
- package/dist/deps/hash/index.js +17 -0
- package/dist/deps/logger/index.d.ts +14 -0
- package/dist/deps/logger/index.js +100 -0
- package/dist/deps/parallel/index.d.ts +34 -0
- package/dist/deps/parallel/index.js +93 -0
- package/dist/deps/redis/index.d.ts +6 -0
- package/dist/deps/redis/index.js +9 -0
- package/dist/deps/rest/Before.d.ts +5 -0
- package/dist/deps/rest/Before.js +9 -0
- package/dist/deps/rest/defines.d.ts +50 -0
- package/dist/deps/rest/defines.js +2 -0
- package/dist/deps/rest/index.d.ts +34 -0
- package/dist/deps/rest/index.js +79 -0
- package/dist/deps/rest/stats.d.ts +6 -0
- package/dist/deps/rest/stats.js +155 -0
- package/dist/deps/rest/utils.d.ts +23 -0
- package/dist/deps/rest/utils.js +419 -0
- package/dist/deps/schema/index.d.ts +11 -0
- package/dist/deps/schema/index.js +39 -0
- package/dist/deps/sequelize/index.d.ts +11 -0
- package/dist/deps/sequelize/index.js +17 -0
- package/dist/deps/signer/index.d.ts +20 -0
- package/dist/deps/signer/index.js +36 -0
- package/dist/dm/dm.d.ts +21 -0
- package/dist/dm/dm.js +57 -0
- package/dist/dm/index.d.ts +12 -0
- package/dist/dm/index.js +56 -0
- package/dist/http/index.d.ts +12 -0
- package/dist/http/index.js +34 -0
- package/dist/http/router.d.ts +23 -0
- package/dist/http/router.js +215 -0
- package/dist/http/utils.d.ts +29 -0
- package/dist/http/utils.js +185 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +18 -0
- package/dist/utils/index.d.ts +36 -0
- package/dist/utils/index.js +84 -0
- package/jest.config.js +6 -0
- package/package.json +79 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Stats = void 0;
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const Sequelize = require("sequelize");
|
|
6
|
+
function Stats(cnf, deps, utils) {
|
|
7
|
+
const defaultPagination = {
|
|
8
|
+
maxResults: 10,
|
|
9
|
+
maxStartIndex: 10000,
|
|
10
|
+
maxResultsLimit: 5000,
|
|
11
|
+
};
|
|
12
|
+
// 获取统计的条目数
|
|
13
|
+
const statsCount = async (Model, opts, dims) => {
|
|
14
|
+
if (!dims)
|
|
15
|
+
return 1;
|
|
16
|
+
if (!dims.length)
|
|
17
|
+
return 1;
|
|
18
|
+
const option = { raw: true };
|
|
19
|
+
if (opts.where)
|
|
20
|
+
option.where = opts.where;
|
|
21
|
+
if (opts.include)
|
|
22
|
+
option.include = opts.include;
|
|
23
|
+
const distincts = _.map(dims, (x) => x[0]);
|
|
24
|
+
option.attributes = [
|
|
25
|
+
Sequelize.literal(`COUNT(DISTINCT ${distincts.join(", ")}) AS count`),
|
|
26
|
+
];
|
|
27
|
+
const res = (await Model.findOne(option));
|
|
28
|
+
return (res && res.count) || 0 | 0;
|
|
29
|
+
};
|
|
30
|
+
const getDimensions = (Model, dimensions, _dims) => {
|
|
31
|
+
const dims = [];
|
|
32
|
+
if (!dimensions || !Model.stats || !Model.stats.dimensions)
|
|
33
|
+
return dims;
|
|
34
|
+
// 如果 dimensions 未定义则直接退出
|
|
35
|
+
if (!Array.isArray(dimensions))
|
|
36
|
+
throw Error("维度未定义");
|
|
37
|
+
// 循环遍历维度设置
|
|
38
|
+
_.each(dimensions, (dim) => {
|
|
39
|
+
// Model 静态的配置
|
|
40
|
+
let key;
|
|
41
|
+
if (Model.stats && Model.stats.dimensions)
|
|
42
|
+
key = Model.stats.dimensions[dim];
|
|
43
|
+
if (!key)
|
|
44
|
+
key = _dims && _dims[dim];
|
|
45
|
+
// 如果不在允许的范围内,则直接报错
|
|
46
|
+
if (!key)
|
|
47
|
+
throw Error("Dimensions dont allowed");
|
|
48
|
+
dims.push([key, dim]);
|
|
49
|
+
});
|
|
50
|
+
return dims;
|
|
51
|
+
};
|
|
52
|
+
const group = (dims) => {
|
|
53
|
+
if (!dims)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (!_.isArray(dims))
|
|
56
|
+
return undefined;
|
|
57
|
+
if (!dims.length)
|
|
58
|
+
return undefined;
|
|
59
|
+
return _.map(dims, (x) => x[1]);
|
|
60
|
+
};
|
|
61
|
+
const getMetrics = (Model, metrics, _mets) => {
|
|
62
|
+
const mets = [];
|
|
63
|
+
// 如果设置了,但是不为字符串,直接返回错误
|
|
64
|
+
if (!Array.isArray(metrics))
|
|
65
|
+
throw Error("指标未定义");
|
|
66
|
+
// 循环遍历所有的指标
|
|
67
|
+
_.each(metrics, (met) => {
|
|
68
|
+
// 处理静态的配置
|
|
69
|
+
let key;
|
|
70
|
+
if (Model.stats && Model.stats.metrics)
|
|
71
|
+
key = Model.stats.metrics[met];
|
|
72
|
+
if (!key)
|
|
73
|
+
key = _mets && _mets[met];
|
|
74
|
+
// 如果指标不在允许的范围内,则直接报错
|
|
75
|
+
if (!key)
|
|
76
|
+
throw Error("Metrics dont allowed");
|
|
77
|
+
mets.push([key, met]);
|
|
78
|
+
});
|
|
79
|
+
return mets;
|
|
80
|
+
};
|
|
81
|
+
const getSort = (Model, params) => {
|
|
82
|
+
const sort = params._sort;
|
|
83
|
+
if (!sort)
|
|
84
|
+
return undefined;
|
|
85
|
+
let allowSort = [];
|
|
86
|
+
const isDesc = sort[0] === "-";
|
|
87
|
+
const direction = isDesc ? "DESC" : "ASC";
|
|
88
|
+
const order = isDesc ? sort.substring(1) : sort;
|
|
89
|
+
_.each(["dimensions", "metrics"], (k) => {
|
|
90
|
+
if (params[k] && _.isArray(params[k])) {
|
|
91
|
+
allowSort = allowSort.concat(params[k]);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (!_.includes(allowSort, order))
|
|
95
|
+
return undefined;
|
|
96
|
+
return [[Sequelize.literal(order), direction]];
|
|
97
|
+
};
|
|
98
|
+
const pageParams = (Model, params) => {
|
|
99
|
+
var _a;
|
|
100
|
+
const pagination = ((_a = Model.stats) === null || _a === void 0 ? void 0 : _a.pagination) || defaultPagination;
|
|
101
|
+
return utils.pageParams(pagination, params);
|
|
102
|
+
};
|
|
103
|
+
const statistics = async (Model, params, where, conf) => {
|
|
104
|
+
if (!conf)
|
|
105
|
+
throw Error("Model.stats undefined");
|
|
106
|
+
const { dimensions, metrics, _ignoreTotal } = params;
|
|
107
|
+
const option = {};
|
|
108
|
+
const dims = getDimensions(Model, dimensions, conf.dimensions);
|
|
109
|
+
const mets = getMetrics(Model, metrics, conf.metrics);
|
|
110
|
+
const limit = pageParams(Model, params);
|
|
111
|
+
const listOpts = utils.findAllOpts(Model, params);
|
|
112
|
+
const ands = [];
|
|
113
|
+
if (listOpts.where)
|
|
114
|
+
ands.push(listOpts.where);
|
|
115
|
+
if (where) {
|
|
116
|
+
if (_.isString(where)) {
|
|
117
|
+
ands.push([where, [""]]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
ands.push(where);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
Object.assign(option, {
|
|
124
|
+
attributes: [].concat(dims || [], mets),
|
|
125
|
+
group: group(dims),
|
|
126
|
+
order: getSort(Model, params),
|
|
127
|
+
offset: limit.offset,
|
|
128
|
+
limit: limit.limit,
|
|
129
|
+
raw: true,
|
|
130
|
+
});
|
|
131
|
+
if (ands.length) {
|
|
132
|
+
option.where = Model.sequelize.and(...ands);
|
|
133
|
+
}
|
|
134
|
+
if (listOpts.include) {
|
|
135
|
+
option.include = _.map(listOpts.include, (x) => {
|
|
136
|
+
x.attributes = [];
|
|
137
|
+
return x;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const opt = _.omitBy(option, _.isUndefined);
|
|
141
|
+
opt.raw = true;
|
|
142
|
+
let count = 0;
|
|
143
|
+
if (_ignoreTotal !== "yes")
|
|
144
|
+
count = await statsCount(Model, opt, dims);
|
|
145
|
+
const rows = await Model.findAll(opt);
|
|
146
|
+
for (const x of rows) {
|
|
147
|
+
for (const met of metrics) {
|
|
148
|
+
x[met] = x[met] ? Number(x[met]) : 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { count, rows };
|
|
152
|
+
};
|
|
153
|
+
return statistics;
|
|
154
|
+
}
|
|
155
|
+
exports.Stats = Stats;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ModelExtraAtts, TModel, Params } from "./defines";
|
|
2
|
+
interface Cnf {
|
|
3
|
+
rest: {
|
|
4
|
+
relativeMaxRangeDays?: number;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
interface Deps {
|
|
8
|
+
errors: {
|
|
9
|
+
notAllowed: (...args: any[]) => Error;
|
|
10
|
+
resourceDuplicateAdd: (...args: any[]) => Error;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function Utils(cnf: Cnf, deps: Deps): Readonly<{
|
|
14
|
+
pickParams: (params: any, cols: string[], Model: TModel, isAdmin: boolean) => {
|
|
15
|
+
[propName: string]: any;
|
|
16
|
+
};
|
|
17
|
+
findAllOpts: (Model: TModel, params: Params) => any;
|
|
18
|
+
pageParams: (pagination: ModelExtraAtts["pagination"], params: Params) => {
|
|
19
|
+
offset: number;
|
|
20
|
+
limit: number;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Utils = void 0;
|
|
4
|
+
const _ = require("lodash");
|
|
5
|
+
const mysql = require("mysql2");
|
|
6
|
+
const Sequelize = require("sequelize");
|
|
7
|
+
const moment = require("moment");
|
|
8
|
+
function Utils(cnf, deps) {
|
|
9
|
+
const { rest: { relativeMaxRangeDays: RELATIVE_MAX_RANGE = 100 }, } = cnf;
|
|
10
|
+
const { errors } = deps;
|
|
11
|
+
/**
|
|
12
|
+
* 相对多少天的时间
|
|
13
|
+
* @param Number days 相对多少天
|
|
14
|
+
* @params Boolean [isStart] 是否是开始, 默认true, false 返回结束时间
|
|
15
|
+
*
|
|
16
|
+
* @return Date
|
|
17
|
+
*/
|
|
18
|
+
const relativeDays = (days, isStart = true) => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const ms = now + days * 86400000;
|
|
21
|
+
const offset = (isStart ? 0 : 86400000) - (now % 86400000);
|
|
22
|
+
return new Date(ms + offset);
|
|
23
|
+
};
|
|
24
|
+
const pickParams = (params, cols, Model, isAdmin) => {
|
|
25
|
+
const attr = {};
|
|
26
|
+
const { rawAttributes, onlyAdminCols = [] } = Model;
|
|
27
|
+
_.each(cols, (x) => {
|
|
28
|
+
if (!_.has(params, x))
|
|
29
|
+
return;
|
|
30
|
+
if (!_.has(rawAttributes, x))
|
|
31
|
+
return;
|
|
32
|
+
const C = rawAttributes[x];
|
|
33
|
+
// 当设置了只有管理员才可以修改的字段,并且当前用户不是管理员
|
|
34
|
+
// 则去掉那些只有管理员才能修改的字段
|
|
35
|
+
if (onlyAdminCols && !isAdmin && _.includes(onlyAdminCols, x))
|
|
36
|
+
return;
|
|
37
|
+
let value = params[x];
|
|
38
|
+
// 如果字段允许为空,且默认值为 null 则在等于空字符串的时候赋值为 null
|
|
39
|
+
if ((value === "" || value === null || value === undefined) && _.has(C, "defaultValue")) {
|
|
40
|
+
value = C.allowNull === true ? null : C.defaultValue;
|
|
41
|
+
}
|
|
42
|
+
attr[x] = value;
|
|
43
|
+
});
|
|
44
|
+
return attr;
|
|
45
|
+
};
|
|
46
|
+
// 处理排序参数
|
|
47
|
+
const sort = (params, conf, includes) => {
|
|
48
|
+
const value = params._sort;
|
|
49
|
+
if (!conf)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (!(value || conf.default))
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!value)
|
|
54
|
+
return [[conf.default, conf.defaultDirection || "ASC"]];
|
|
55
|
+
const orders = value.split(",").map((x) => {
|
|
56
|
+
const isDesc = x[0] === "-";
|
|
57
|
+
const direction = isDesc ? "DESC" : "ASC";
|
|
58
|
+
const order = isDesc ? x.slice(1) : x;
|
|
59
|
+
// 如果请求的排序方式不允许,则返回null
|
|
60
|
+
if (!conf.allow || !_.includes(conf.allow, order))
|
|
61
|
+
return undefined;
|
|
62
|
+
const theOrder = order.split(".");
|
|
63
|
+
// 处理使用模型名称作为关联名称按关联模型的 字段 排序
|
|
64
|
+
if (theOrder.length === 2) {
|
|
65
|
+
if (includes && Array.isArray(params._includes)) {
|
|
66
|
+
const ret = _.filter(params._includes, (val) => includes[val]);
|
|
67
|
+
if (!ret.includes(theOrder[0])) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
theOrder.push(direction);
|
|
76
|
+
return theOrder;
|
|
77
|
+
});
|
|
78
|
+
return _.compact(orders);
|
|
79
|
+
};
|
|
80
|
+
// searchOpt 的处理,处理参数参数里的q, 实现简易搜索功能
|
|
81
|
+
/**
|
|
82
|
+
#
|
|
83
|
+
[ # 这下面有三个子数组,代表该model有三个字段参与搜索
|
|
84
|
+
[ # 这个数组长度为2,代表此次有2个搜索关键词
|
|
85
|
+
# 这个字符串用 OR 切开有三部分,代表该字段定义的search.match 有三部分
|
|
86
|
+
'((`user`.`name` LIKE \'a\')
|
|
87
|
+
OR (`user`.`name` LIKE \'%,a\')
|
|
88
|
+
OR (`user`.`name` LIKE \'a,%\')
|
|
89
|
+
OR (`user`.`name` LIKE \'%,a,%\'))'
|
|
90
|
+
'((`user`.`name` LIKE \'b\')
|
|
91
|
+
OR (`user`.`name` LIKE \'%,b\')
|
|
92
|
+
OR (`user`.`name` LIKE \'b,%\')
|
|
93
|
+
OR (`user`.`name` LIKE \'%,b,%\'))'
|
|
94
|
+
]
|
|
95
|
+
[
|
|
96
|
+
'((`user`.`email` LIKE \'%a%\'))'
|
|
97
|
+
'((`user`.`email` LIKE \'%b%\'))'
|
|
98
|
+
]
|
|
99
|
+
[
|
|
100
|
+
'((`user`.`id` = \'a\'))'
|
|
101
|
+
'((`user`.`id` = \'b\'))'
|
|
102
|
+
]
|
|
103
|
+
]
|
|
104
|
+
*/
|
|
105
|
+
const searchOpt = (Model, searchStr, qstr, as) => {
|
|
106
|
+
if (!qstr)
|
|
107
|
+
return undefined;
|
|
108
|
+
if (!_.isString(qstr))
|
|
109
|
+
return undefined;
|
|
110
|
+
const q = qstr.trim() ? _.split(qstr.trim(), " ", 5) : null;
|
|
111
|
+
if (!q)
|
|
112
|
+
return undefined;
|
|
113
|
+
const searchs = searchStr ? _.split(searchStr, ",") : null;
|
|
114
|
+
const ors = [];
|
|
115
|
+
if (!Model.searchCols)
|
|
116
|
+
return undefined;
|
|
117
|
+
_.each(Model.searchCols, (conf, col) => {
|
|
118
|
+
// 如果设置了搜索的字段,并且当前字读不在设置的搜索字段内,则直接返回
|
|
119
|
+
// 相当于跳过这个设置
|
|
120
|
+
const _col = as ? `${as}.${col}` : col;
|
|
121
|
+
// 如果是include里的search,必须指定searchs
|
|
122
|
+
// 这么做是为了避免用户不知情的一些筛选过滤
|
|
123
|
+
if (!searchs && as)
|
|
124
|
+
return;
|
|
125
|
+
if (searchs && searchs.length && !_.includes(searchs, _col))
|
|
126
|
+
return;
|
|
127
|
+
ors.push(_.map(q, (x) => {
|
|
128
|
+
const arr = _.map(conf.match, (match) => {
|
|
129
|
+
const v = match.replace("{1}", x);
|
|
130
|
+
return [`(\`${as || Model.name}\`.\`${col}\``, conf.op, `${mysql.escape(v)})`].join(" ");
|
|
131
|
+
});
|
|
132
|
+
return `(${arr.join(" OR ")})`;
|
|
133
|
+
}));
|
|
134
|
+
});
|
|
135
|
+
return ors;
|
|
136
|
+
};
|
|
137
|
+
// 合并多个词语的搜索条件
|
|
138
|
+
// 将单个或多个 searchOpt 返回的数组正确的合并成 where 子句, 字符串类型的
|
|
139
|
+
// 这个函数的目的是为了正确的使每个关键词之间的关系是 AND 的关系
|
|
140
|
+
// 单个关键词在不同的搜索字段之间是 OR 的关系
|
|
141
|
+
const mergeSearchOrs = (orss) => {
|
|
142
|
+
const ands = [];
|
|
143
|
+
_.each(orss, (_orss) => {
|
|
144
|
+
_.each(_orss, (ors) => {
|
|
145
|
+
_.each(ors, (_or, index) => {
|
|
146
|
+
if (!ands[index])
|
|
147
|
+
ands[index] = [];
|
|
148
|
+
ands[index].push(_or);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
const andsStr = _.map(ands, (x) => `(${x.join(" OR ")})`);
|
|
153
|
+
return `(${andsStr.join(" AND ")})`;
|
|
154
|
+
};
|
|
155
|
+
// 处理关联包含
|
|
156
|
+
// 返回
|
|
157
|
+
// [Model1, Model2]
|
|
158
|
+
// 或者 undefined
|
|
159
|
+
const modelInclude = (params, includes) => {
|
|
160
|
+
if (!includes)
|
|
161
|
+
return undefined;
|
|
162
|
+
if (!Array.isArray(params._includes))
|
|
163
|
+
return undefined;
|
|
164
|
+
const ret = _.filter(params._includes, (x) => includes[x]);
|
|
165
|
+
if (ret.length === 0)
|
|
166
|
+
return undefined;
|
|
167
|
+
// 这里之所以要用 _.clone 是为了防止修改了原始了配置信息,从而导致不同请求之间的干扰
|
|
168
|
+
return _.map(ret, (x) => _.clone(includes[x]));
|
|
169
|
+
};
|
|
170
|
+
const DEFAULT_PAGE_PARAMS = Object.freeze({
|
|
171
|
+
maxResults: 10,
|
|
172
|
+
maxStartIndex: 100000,
|
|
173
|
+
maxResultsLimit: 100000,
|
|
174
|
+
});
|
|
175
|
+
const pageParams = (pagination, params) => {
|
|
176
|
+
const _pagination = { ...DEFAULT_PAGE_PARAMS, ...pagination };
|
|
177
|
+
const startIndex = Math.max(params._startIndex | 0, 0);
|
|
178
|
+
const maxResults = Math.max(params._maxResults | 0 || _pagination.maxResults, 1);
|
|
179
|
+
return {
|
|
180
|
+
offset: Math.min(startIndex, _pagination.maxStartIndex),
|
|
181
|
+
limit: Math.min(maxResults, _pagination.maxResultsLimit),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const RELATIVE_RANGE_ERROR = errors.notAllowed(`相对时间跨度最多 ${RELATIVE_MAX_RANGE} 天`);
|
|
185
|
+
// findOptFilter 的处理
|
|
186
|
+
const findOptFilter = (params, name, where, Op, col = name) => {
|
|
187
|
+
let value;
|
|
188
|
+
if (!params)
|
|
189
|
+
return;
|
|
190
|
+
if (typeof params !== "object")
|
|
191
|
+
return;
|
|
192
|
+
// 处理相对时间过滤
|
|
193
|
+
if (_.isString(params[`${name}_relative`])) {
|
|
194
|
+
let [start, end, ignoreYear] = params[`${name}_relative`].split(",");
|
|
195
|
+
start |= 0;
|
|
196
|
+
end |= 0;
|
|
197
|
+
ignoreYear = ignoreYear === "yes";
|
|
198
|
+
if (RELATIVE_MAX_RANGE < end - start)
|
|
199
|
+
throw RELATIVE_RANGE_ERROR;
|
|
200
|
+
if (!where[col])
|
|
201
|
+
where[col] = {};
|
|
202
|
+
if (!where[col][Op.and])
|
|
203
|
+
where[col][Op.and] = [];
|
|
204
|
+
if (ignoreYear) {
|
|
205
|
+
// 忽略年,这里要处理跨年的问题
|
|
206
|
+
const startDate = moment(Date.now() + start * 86400000).format("MM-DD");
|
|
207
|
+
const endDate = moment(Date.now() + end * 86400000).format("MM-DD");
|
|
208
|
+
if (endDate < startDate) {
|
|
209
|
+
where[col][Op.and].push({
|
|
210
|
+
[Op.or]: [
|
|
211
|
+
Sequelize.where(Sequelize.fn("DATE_FORMAT", Sequelize.col(name), "%m-%d"), Op.between, Sequelize.literal(`'${startDate}' AND '12-31'`)),
|
|
212
|
+
Sequelize.where(Sequelize.fn("DATE_FORMAT", Sequelize.col(name), "%m-%d"), Op.between, Sequelize.literal(`'01-01' AND '${endDate}'`)),
|
|
213
|
+
],
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
where[col][Op.and].push(Sequelize.where(Sequelize.fn("DATE_FORMAT", Sequelize.col(name), "%m-%d"), Op.between, Sequelize.literal(`'${startDate}' AND '${endDate}'`)));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
where[col][Op.and].push(Sequelize.where(Sequelize.fn("DATE", Sequelize.col(name)), {
|
|
222
|
+
[Op.between]: [relativeDays(start), relativeDays(end, false)],
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// 处理 where 的等于
|
|
227
|
+
if (_.isString(params[name])) {
|
|
228
|
+
value = params[name].trim();
|
|
229
|
+
// 特殊处理null值
|
|
230
|
+
if (value === ".null.")
|
|
231
|
+
value = null;
|
|
232
|
+
if (!where[col])
|
|
233
|
+
where[col] = {};
|
|
234
|
+
where[col][Op.eq] = value;
|
|
235
|
+
}
|
|
236
|
+
if (_.isNumber(params[name])) {
|
|
237
|
+
if (!where[col])
|
|
238
|
+
where[col] = {};
|
|
239
|
+
where[col][Op.eq] = params[name];
|
|
240
|
+
}
|
|
241
|
+
// 处理where in
|
|
242
|
+
if (_.isString(params[`${name}s`])) {
|
|
243
|
+
if (!where[col])
|
|
244
|
+
where[col] = {};
|
|
245
|
+
where[col][Op.in] = params[`${name}s`].trim().split(",");
|
|
246
|
+
}
|
|
247
|
+
// in 直接是数组的格式
|
|
248
|
+
if (_.isArray(params[`${name}s`])) {
|
|
249
|
+
if (!where[col])
|
|
250
|
+
where[col] = {};
|
|
251
|
+
where[col][Op.in] = params[`${name}s`];
|
|
252
|
+
}
|
|
253
|
+
// 处理where not in
|
|
254
|
+
if (_.isString(params[`${name}s!`])) {
|
|
255
|
+
if (!where[col])
|
|
256
|
+
where[col] = {};
|
|
257
|
+
where[col][Op.notIn] = params[`${name}s!`].trim().split(",");
|
|
258
|
+
}
|
|
259
|
+
// not in 直接是数组的格式
|
|
260
|
+
if (_.isArray(params[`${name}s!`])) {
|
|
261
|
+
if (!where[col])
|
|
262
|
+
where[col] = {};
|
|
263
|
+
where[col][Op.notIn] = params[`${name}s!`];
|
|
264
|
+
}
|
|
265
|
+
// 处理不等于的判断
|
|
266
|
+
if (_.isString(params[`${name}!`])) {
|
|
267
|
+
value = params[`${name}!`].trim();
|
|
268
|
+
if (!where[col])
|
|
269
|
+
where[col] = {};
|
|
270
|
+
// 特殊处理null值
|
|
271
|
+
if (value === ".null.") {
|
|
272
|
+
value = null;
|
|
273
|
+
where[col][Op.not] = value;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
where[col][Op.ne] = value;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// 处理like
|
|
280
|
+
if (_.isString(params[`${name}_like`])) {
|
|
281
|
+
value = params[`${name}_like`].trim().replace(/\*/g, "%");
|
|
282
|
+
if (!where[col])
|
|
283
|
+
where[col] = {};
|
|
284
|
+
where[col][Op.like] = value;
|
|
285
|
+
}
|
|
286
|
+
// 处理likes [like or]
|
|
287
|
+
if (_.isString(params[`${name}_likes`])) {
|
|
288
|
+
const likes = params[`${name}_likes`].trim().split(",");
|
|
289
|
+
if (!where[col])
|
|
290
|
+
where[col] = {};
|
|
291
|
+
where[col][Op.or] = likes.map((x) => {
|
|
292
|
+
value = x.trim().replace(/\*/g, "%");
|
|
293
|
+
return { [Op.like]: value };
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
// 处理notLike
|
|
297
|
+
if (_.isString(params[`${name}_notLike`])) {
|
|
298
|
+
value = params[`${name}_notLike`].trim().replace(/\*/g, "%");
|
|
299
|
+
if (!where[col])
|
|
300
|
+
where[col] = {};
|
|
301
|
+
where[col][Op.notLike] = value;
|
|
302
|
+
}
|
|
303
|
+
// 处理大于,小于, 大于等于,小于等于的判断
|
|
304
|
+
_.each(["gt", "gte", "lt", "lte"], (x) => {
|
|
305
|
+
const c = `${name}_${x}`;
|
|
306
|
+
if (!_.isString(params[c]) && !_.isNumber(params[c]))
|
|
307
|
+
return;
|
|
308
|
+
value = _.isString(params[c]) ? params[c].trim() : params[c];
|
|
309
|
+
if (!where[col])
|
|
310
|
+
where[col] = {};
|
|
311
|
+
where[col][Op[x]] = value;
|
|
312
|
+
});
|
|
313
|
+
// 处理 find_in_set 方式的过滤
|
|
314
|
+
if (_.isString(params[`${name}_ins`]) || _.isNumber(params[`${name}_ins`])) {
|
|
315
|
+
if (!where[Op.and])
|
|
316
|
+
where[Op.and] = [];
|
|
317
|
+
where[Op.and].push(Sequelize.where(Sequelize.fn("FIND_IN_SET", params[`${name}_ins`], Sequelize.col(name)), Op.gte, 1));
|
|
318
|
+
}
|
|
319
|
+
// 处理 find_in_set 方式的过滤 _ins_and
|
|
320
|
+
if (_.isString(params[`${name}_ins_and`])) {
|
|
321
|
+
if (!where[Op.and])
|
|
322
|
+
where[Op.and] = [];
|
|
323
|
+
for (const v of params[`${name}_ins_and`].split(",")) {
|
|
324
|
+
where[Op.and].push(Sequelize.where(Sequelize.fn("FIND_IN_SET", v.trim(), Sequelize.col(name)), Op.gte, 1));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// 处理 find_in_set 方式的过滤 _ins_or
|
|
328
|
+
if (_.isString(params[`${name}_ins_or`])) {
|
|
329
|
+
if (!where[Op.and])
|
|
330
|
+
where[Op.and] = [];
|
|
331
|
+
where[Op.and].push({
|
|
332
|
+
[Op.or]: params[`${name}_ins_or`].split(",").map((v) => Sequelize.where(Sequelize.fn("FIND_IN_SET", v.trim(), Sequelize.col(name)), Op.gte, 1)),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// 处理 find_in_set 方式的过滤 _ins_not
|
|
336
|
+
if (_.isString(params[`${name}_ins_not`])) {
|
|
337
|
+
if (!where[Op.and])
|
|
338
|
+
where[Op.and] = [];
|
|
339
|
+
for (const v of params[`${name}_ins_not`].split(",")) {
|
|
340
|
+
where[Op.and].push(Sequelize.where(Sequelize.fn("FIND_IN_SET", v.trim(), Sequelize.col(name)), Op.lt, 1));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
// 返回列表查询的条件
|
|
345
|
+
const findAllOpts = (Model, params) => {
|
|
346
|
+
const { Op } = Sequelize;
|
|
347
|
+
const where = {};
|
|
348
|
+
const searchOrs = [];
|
|
349
|
+
const includes = modelInclude(params, Model.includes);
|
|
350
|
+
_.each(Model.filterAttrs || _.keys(Model.rawAttributes), (name) => {
|
|
351
|
+
findOptFilter(params, name, where, Op);
|
|
352
|
+
});
|
|
353
|
+
if (Model.rawAttributes.isDeleted && !params._showDeleted) {
|
|
354
|
+
where.isDeleted = "no";
|
|
355
|
+
}
|
|
356
|
+
// 将搜索条件添加到主条件上
|
|
357
|
+
const searchOptRes = searchOpt(Model, params._searchs, params.q);
|
|
358
|
+
if (searchOptRes)
|
|
359
|
+
searchOrs.push(searchOptRes);
|
|
360
|
+
// 处理关联资源的过滤条件
|
|
361
|
+
// 以及关联资源允许返回的字段
|
|
362
|
+
if (includes) {
|
|
363
|
+
_.each(includes, (x) => {
|
|
364
|
+
const includeWhere = {};
|
|
365
|
+
const filterAttrs = x.model.filterAttrs || _.keys(x.model.rawAttributes);
|
|
366
|
+
_.each(filterAttrs, (name) => {
|
|
367
|
+
findOptFilter(params, `${x.as}.${name}`, includeWhere, Op, name);
|
|
368
|
+
});
|
|
369
|
+
if (x.model.rawAttributes.isDeleted && !params._showDeleted) {
|
|
370
|
+
includeWhere[Op.or] = [{ isDeleted: "no" }];
|
|
371
|
+
if (x.required === false)
|
|
372
|
+
includeWhere[Op.or].push({ id: null });
|
|
373
|
+
}
|
|
374
|
+
// 将搜索条件添加到 include 的 where 条件上
|
|
375
|
+
const searchOptResII = searchOpt(x.model, params._searchs, params.q, x.as);
|
|
376
|
+
if (searchOptResII)
|
|
377
|
+
searchOrs.push(searchOptResII);
|
|
378
|
+
x.where = includeWhere;
|
|
379
|
+
// 以及关联资源允许返回的字段
|
|
380
|
+
if (x.model.allowIncludeCols)
|
|
381
|
+
x.attributes = x.model.allowIncludeCols;
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const ret = {
|
|
385
|
+
include: includes,
|
|
386
|
+
order: sort(params, Model.sort, Model.includes),
|
|
387
|
+
};
|
|
388
|
+
// 将 searchOrs 赋到 where 上
|
|
389
|
+
const _searchOrs = _.filter(_.compact(searchOrs), (x) => x.length);
|
|
390
|
+
if (_.size(where)) {
|
|
391
|
+
if (_searchOrs.length) {
|
|
392
|
+
ret.where = Sequelize.and(where, Sequelize.literal(mergeSearchOrs(_searchOrs)));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
ret.where = where;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else if (_searchOrs.length) {
|
|
399
|
+
ret.where = Sequelize.literal(mergeSearchOrs(_searchOrs));
|
|
400
|
+
}
|
|
401
|
+
// 处理需要返回的字段
|
|
402
|
+
(() => {
|
|
403
|
+
const { _attrs } = params;
|
|
404
|
+
if (!_attrs)
|
|
405
|
+
return;
|
|
406
|
+
if (!Array.isArray(_attrs) || !_attrs.length)
|
|
407
|
+
return;
|
|
408
|
+
ret.attributes = _attrs.filter((x) => Model.rawAttributes[x]);
|
|
409
|
+
})();
|
|
410
|
+
Object.assign(ret, pageParams(Model.pagination, params));
|
|
411
|
+
return ret;
|
|
412
|
+
};
|
|
413
|
+
return Object.freeze({
|
|
414
|
+
pickParams,
|
|
415
|
+
findAllOpts,
|
|
416
|
+
pageParams,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
exports.Utils = Utils;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Ajv, { Schema } from "ajv";
|
|
2
|
+
interface Cnf {
|
|
3
|
+
schema?: ConstructorParameters<typeof Ajv>[0];
|
|
4
|
+
}
|
|
5
|
+
export declare function Main(cnf: Cnf): Readonly<{
|
|
6
|
+
auto: <F extends (...args: any[]) => any>(fn: F, schema: Schema[], errorFn: Function, extra: any) => (...args: Parameters<F>) => ReturnType<F>;
|
|
7
|
+
validate: (schema: Schema, data: any) => boolean;
|
|
8
|
+
compile: (x: Schema) => import("ajv").ValidateFunction<unknown>;
|
|
9
|
+
ajv: Ajv;
|
|
10
|
+
}>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Main = void 0;
|
|
4
|
+
const util = require("util");
|
|
5
|
+
const ajv_1 = require("ajv");
|
|
6
|
+
const ajv_formats_1 = require("ajv-formats");
|
|
7
|
+
function Main(cnf) {
|
|
8
|
+
const ajv = new ajv_1.default(cnf.schema || {});
|
|
9
|
+
(0, ajv_formats_1.default)(ajv);
|
|
10
|
+
const compile = (x) => ajv.compile(x);
|
|
11
|
+
/**
|
|
12
|
+
* 将函数处理为自动校验参数合法性
|
|
13
|
+
*/
|
|
14
|
+
function auto(fn, schema, errorFn, extra) {
|
|
15
|
+
if (!Array.isArray(schema)) {
|
|
16
|
+
throw Error(`方法参数定义必须是一个数组 ${util.format(schema)}`);
|
|
17
|
+
}
|
|
18
|
+
const validators = schema.map((x) => ajv.compile(x));
|
|
19
|
+
return (...args) => {
|
|
20
|
+
for (let i = 0; i < schema.length; i += 1) {
|
|
21
|
+
const valid = validators[i](args[i]);
|
|
22
|
+
if (!valid) {
|
|
23
|
+
throw errorFn(i + 1, validators[i].errors, args[i], extra);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return fn(...args);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 检测数据是否符合 schema 设定
|
|
31
|
+
*/
|
|
32
|
+
const validate = (schema, data) => {
|
|
33
|
+
if (ajv.validate(schema, data))
|
|
34
|
+
return true;
|
|
35
|
+
throw ajv.errors;
|
|
36
|
+
};
|
|
37
|
+
return Object.freeze({ auto, validate, compile, ajv });
|
|
38
|
+
}
|
|
39
|
+
exports.Main = Main;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Sequelize from "sequelize";
|
|
2
|
+
interface Cnf {
|
|
3
|
+
sequelize: {
|
|
4
|
+
[propName: string]: Sequelize.Options;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export declare function Main(cnf: Cnf): {
|
|
8
|
+
[propName: string]: Sequelize.Sequelize;
|
|
9
|
+
};
|
|
10
|
+
export declare const Deps: never[];
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Deps = exports.Main = void 0;
|
|
4
|
+
const Sequelize = require("sequelize");
|
|
5
|
+
function Main(cnf) {
|
|
6
|
+
// 这里之所以要注入 Sequelize 是为了保证项目自身可以灵活选择自己的 Sequelize 版本, 这样改公共模块就会更加稳定, 避免频繁升级
|
|
7
|
+
const { sequelize: dbs } = cnf;
|
|
8
|
+
const sequelizes = {};
|
|
9
|
+
for (const k of Object.keys(dbs)) {
|
|
10
|
+
const db = dbs[k];
|
|
11
|
+
// sequelizes[k] = new Sequelize(db.name, db.user, db.pass, db);
|
|
12
|
+
sequelizes[k] = new Sequelize.Sequelize(db);
|
|
13
|
+
}
|
|
14
|
+
return sequelizes;
|
|
15
|
+
}
|
|
16
|
+
exports.Main = Main;
|
|
17
|
+
exports.Deps = [];
|