@domain.js/main 0.1.14 → 0.2.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/.eslintrc.js +1 -0
- package/.husky/pre-commit +0 -1
- package/dist/deps/rest/index.d.ts +7 -7
- package/dist/deps/rest/index.js +1 -1
- package/dist/deps/rest/stats.d.ts +11 -3
- package/dist/deps/rest/stats.js +1 -1
- package/dist/deps/rest/utils.d.ts +9 -5
- package/dist/deps/rest/utils.js +6 -3
- package/dist/deps/sequelize/index.d.ts +73 -4
- package/dist/deps/sequelize/index.js +48 -1
- package/dist/http/defines.d.ts +9 -14
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +7 -0
- package/dist/http/router.d.ts +2 -2
- package/dist/http/router.js +2 -1
- package/dist/http/socket.d.ts +13 -0
- package/dist/http/socket.js +153 -0
- package/dist/http/utils.d.ts +1 -1
- package/dist/http/utils.js +13 -10
- package/package.json +5 -4
- package/dist/cli/schema2ts.d.ts +0 -1
- package/dist/cli/schema2ts.js +0 -37
- package/dist/deps/cia/errors.d.ts +0 -6
- package/dist/deps/cia/errors.js +0 -36
- package/dist/deps/cia/index.d.ts +0 -63
- package/dist/deps/cia/index.js +0 -308
- package/dist/deps/rest/defines.d.ts +0 -50
- package/dist/deps/rest/defines.js +0 -2
- package/dist/deps/typeorm/index.d.ts +0 -14
- package/dist/deps/typeorm/index.js +0 -16
package/.eslintrc.js
CHANGED
package/.husky/pre-commit
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
import * as Sequelize from "sequelize";
|
|
3
|
-
import {
|
|
3
|
+
import { ModelBase, ModelStatic } from "../sequelize";
|
|
4
4
|
import { Stats } from "./stats";
|
|
5
5
|
import { Utils } from "./utils";
|
|
6
6
|
export { Before } from "./Before";
|
|
@@ -24,14 +24,14 @@ export interface CreatorAndClientIp {
|
|
|
24
24
|
* @returns modify, add, remove, list, stats five methods
|
|
25
25
|
*/
|
|
26
26
|
export declare function Main(cnf: Cnf, deps: Deps, utils: ReturnType<typeof Utils>): {
|
|
27
|
-
modify: (Model:
|
|
28
|
-
add: (Model:
|
|
27
|
+
modify: <T extends ModelBase<any, any>>(Model: ModelStatic<T>, model: T, params: Record<string, any>, isAdmin?: boolean, _cols?: string[] | undefined) => Promise<T>;
|
|
28
|
+
add: <T_1 extends ModelBase<any, any>>(Model: ModelStatic<T_1>, params: Record<string, any>, isAdmin: boolean | undefined, _cols: string[] | undefined, { creatorId, clientIp }: CreatorAndClientIp) => Promise<T_1>;
|
|
29
29
|
remove: (model: Sequelize.Model, deletorId: UserId) => Promise<void | Sequelize.Model<any, any>>;
|
|
30
|
-
list: (Model:
|
|
30
|
+
list: <T_2 extends ModelBase<any, any>>(Model: ModelStatic<T_2>, params: Record<string, any>, allowAttrs?: string[] | undefined, toJSON?: boolean | undefined) => Promise<{
|
|
31
31
|
count: number;
|
|
32
|
-
rows:
|
|
32
|
+
rows: T_2[];
|
|
33
33
|
}>;
|
|
34
|
-
stats: (Model:
|
|
34
|
+
stats: <T_3 extends ModelBase<any, any>>(Model: ModelStatic<T_3>, params: Record<string, any>, where?: any, conf?: {
|
|
35
35
|
dimensions?: Record<string, string> | undefined;
|
|
36
36
|
metrics: Record<string, string>;
|
|
37
37
|
pagination?: {
|
|
@@ -41,7 +41,7 @@ export declare function Main(cnf: Cnf, deps: Deps, utils: ReturnType<typeof Util
|
|
|
41
41
|
} | undefined;
|
|
42
42
|
} | undefined) => Promise<{
|
|
43
43
|
count: number;
|
|
44
|
-
rows:
|
|
44
|
+
rows: Record<string, string | number>[];
|
|
45
45
|
}>;
|
|
46
46
|
};
|
|
47
47
|
export declare const Deps: string[];
|
package/dist/deps/rest/index.js
CHANGED
|
@@ -116,7 +116,7 @@ function Main(cnf, deps, utils) {
|
|
|
116
116
|
count = await Model.count(lodash_1.default.pick(opt, COUNT_OPT));
|
|
117
117
|
if (Array.isArray(allowAttrs) && allowAttrs.length)
|
|
118
118
|
opt.attributes = allowAttrs;
|
|
119
|
-
const rows =
|
|
119
|
+
const rows = await Model.findAll(opt);
|
|
120
120
|
if (toJSON) {
|
|
121
121
|
for (let i = 0; i < rows.length; i += 1) {
|
|
122
122
|
rows[i] = rows[i].toJSON();
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
2
|
import * as Sequelize from "sequelize";
|
|
3
|
-
import {
|
|
3
|
+
import { ModelBase, ModelStatic } from "../sequelize";
|
|
4
4
|
import { Utils } from "./utils";
|
|
5
5
|
interface Deps {
|
|
6
6
|
_: typeof _;
|
|
7
7
|
Sequelize: Pick<typeof Sequelize, "literal" | "and" | "fn">;
|
|
8
8
|
}
|
|
9
|
-
export declare function Stats(cnf: {}, deps: Deps, utils: ReturnType<typeof Utils>): (Model:
|
|
9
|
+
export declare function Stats(cnf: {}, deps: Deps, utils: ReturnType<typeof Utils>): <T extends ModelBase<any, any>>(Model: ModelStatic<T>, params: Record<string, any>, where?: any, conf?: {
|
|
10
|
+
dimensions?: Record<string, string> | undefined;
|
|
11
|
+
metrics: Record<string, string>;
|
|
12
|
+
pagination?: {
|
|
13
|
+
maxResults: number;
|
|
14
|
+
maxStartIndex: number;
|
|
15
|
+
maxResultsLimit: number;
|
|
16
|
+
} | undefined;
|
|
17
|
+
} | undefined) => Promise<{
|
|
10
18
|
count: number;
|
|
11
|
-
rows:
|
|
19
|
+
rows: Record<string, string | number>[];
|
|
12
20
|
}>;
|
|
13
21
|
export {};
|
package/dist/deps/rest/stats.js
CHANGED
|
@@ -149,7 +149,7 @@ function Stats(cnf, deps, utils) {
|
|
|
149
149
|
let count = 0;
|
|
150
150
|
if (_ignoreTotal !== "yes")
|
|
151
151
|
count = await statsCount(Model, opt, dims);
|
|
152
|
-
const rows = await Model.findAll(opt);
|
|
152
|
+
const rows = (await Model.findAll(opt));
|
|
153
153
|
for (const x of rows) {
|
|
154
154
|
for (const met of metrics) {
|
|
155
155
|
x[met] = x[met] ? Number(x[met]) : 0;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { LoDashStatic } from "lodash";
|
|
2
|
+
import moment from "moment";
|
|
2
3
|
import * as mysql from "mysql2";
|
|
3
4
|
import * as Sequelize from "sequelize";
|
|
4
|
-
import
|
|
5
|
-
import { ModelExtraAtts, TModel, Params } from "./defines";
|
|
5
|
+
import { ModelBase, ModelStatic } from "../sequelize";
|
|
6
6
|
interface Cnf {
|
|
7
7
|
rest: {
|
|
8
8
|
relativeMaxRangeDays?: number;
|
|
@@ -19,11 +19,15 @@ interface Deps {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
export declare function Utils(cnf: Cnf, deps: Deps): Readonly<{
|
|
22
|
-
pickParams: (params: any, cols: string[], Model:
|
|
22
|
+
pickParams: <T extends ModelBase<any, any>>(params: any, cols: string[], Model: ModelStatic<T>, isAdmin: boolean) => {
|
|
23
23
|
[propName: string]: any;
|
|
24
24
|
};
|
|
25
|
-
findAllOpts: (Model:
|
|
26
|
-
pageParams: (pagination:
|
|
25
|
+
findAllOpts: <T_1 extends ModelBase<any, any>>(Model: ModelStatic<T_1>, params: Record<string, any>) => any;
|
|
26
|
+
pageParams: (pagination: {
|
|
27
|
+
maxResults: number;
|
|
28
|
+
maxStartIndex: number;
|
|
29
|
+
maxResultsLimit: number;
|
|
30
|
+
} | undefined, params: Record<string, any>) => {
|
|
27
31
|
offset: number;
|
|
28
32
|
limit: number;
|
|
29
33
|
};
|
package/dist/deps/rest/utils.js
CHANGED
|
@@ -199,6 +199,7 @@ function Utils(cnf, deps) {
|
|
|
199
199
|
};
|
|
200
200
|
const RELATIVE_RANGE_ERROR = errors.notAllowed(`相对时间跨度最多 ${RELATIVE_MAX_RANGE} 天`);
|
|
201
201
|
// findOptFilter 的处理
|
|
202
|
+
// eslint-disable-next-line complexity
|
|
202
203
|
const findOptFilter = (params, name, where, Op, col = name) => {
|
|
203
204
|
let value;
|
|
204
205
|
if (!params)
|
|
@@ -391,10 +392,12 @@ function Utils(cnf, deps) {
|
|
|
391
392
|
const searchOptResII = searchOpt(x.model, params._searchs, params.q, x.as);
|
|
392
393
|
if (searchOptResII)
|
|
393
394
|
searchOrs.push(searchOptResII);
|
|
394
|
-
x.where = includeWhere;
|
|
395
395
|
// 以及关联资源允许返回的字段
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
let attributes;
|
|
397
|
+
if (x.model.allowIncludeCols && x.model.allowIncludeCols.length)
|
|
398
|
+
attributes = x.model.allowIncludeCols;
|
|
399
|
+
// 将过滤条件和查询的字段附加上去
|
|
400
|
+
Object.assign(x, { where: includeWhere, attributes });
|
|
398
401
|
});
|
|
399
402
|
}
|
|
400
403
|
const ret = {
|
|
@@ -1,16 +1,85 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Model, Options, Sequelize } from "sequelize";
|
|
2
2
|
interface Cnf {
|
|
3
3
|
sequelize: {
|
|
4
|
-
[propName: string]:
|
|
4
|
+
[propName: string]: Options;
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
7
|
interface Deps {
|
|
8
8
|
Sequelize: {
|
|
9
|
-
Sequelize: typeof Sequelize
|
|
9
|
+
Sequelize: typeof Sequelize;
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
+
declare type NonConstructorKeys<T> = {
|
|
13
|
+
[P in keyof T]: T[P] extends new () => any ? never : P;
|
|
14
|
+
}[keyof T];
|
|
15
|
+
declare type NonConstructor<T> = Pick<T, NonConstructorKeys<T>>;
|
|
16
|
+
export declare type ModelStatic<M extends ModelBase> = NonConstructor<typeof ModelBase> & (new () => M);
|
|
12
17
|
export declare function Main(cnf: Cnf, deps: Deps): {
|
|
13
|
-
[propName: string]: Sequelize
|
|
18
|
+
[propName: string]: Sequelize;
|
|
14
19
|
};
|
|
15
20
|
export declare const Deps: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Model 基类
|
|
23
|
+
*/
|
|
24
|
+
export declare class ModelBase<Attrs extends {} = any, Attrs4Create extends {} = Attrs> extends Model<Attrs, Attrs4Create> {
|
|
25
|
+
/**
|
|
26
|
+
* 基于主键获取某条数据的Mode实例,自动维护内存级 cache
|
|
27
|
+
* @param pk 主键
|
|
28
|
+
*/
|
|
29
|
+
static getByPk<M extends ModelBase>(this: ModelStatic<M>, pk: string | number): Promise<M | null>;
|
|
30
|
+
/**
|
|
31
|
+
* 基于主键获取某些数据的Mode实例列表,维持参数的顺序,自动维护内存级 cache
|
|
32
|
+
* @param pks 主键数组
|
|
33
|
+
*/
|
|
34
|
+
static getByPks<M extends ModelBase>(this: ModelStatic<M>, pks: string[] | number[]): Promise<M[]>;
|
|
35
|
+
/** 允许过滤的字段, 对于某些隐私、敏感信息,应该禁止基于其过滤, 使用者反复尝试可以暴力破解敏感信息 */
|
|
36
|
+
static filterAttrs?: string[];
|
|
37
|
+
/** 新增资源的时候可以写入的列名称集合 */
|
|
38
|
+
static writableCols?: string[];
|
|
39
|
+
/** 编辑资源的时候可以写入的列名称集合 */
|
|
40
|
+
static editableCols?: string[];
|
|
41
|
+
/** 关联资源的时候允许被关联展示的列名称集合 */
|
|
42
|
+
static allowIncludeCols?: string[];
|
|
43
|
+
/** 编辑过程中,仅管理可以更改的列名称集合 */
|
|
44
|
+
static onlyAdminCols?: string[];
|
|
45
|
+
/** 列表查询时候分页控制参数 */
|
|
46
|
+
static pagination?: {
|
|
47
|
+
maxResults: number;
|
|
48
|
+
maxStartIndex: number;
|
|
49
|
+
maxResultsLimit: number;
|
|
50
|
+
};
|
|
51
|
+
/** 列表查询时候排序控制参数 */
|
|
52
|
+
static sort?: {
|
|
53
|
+
default: string;
|
|
54
|
+
allow: string[];
|
|
55
|
+
defaultDirection?: "DESC" | "ASC";
|
|
56
|
+
};
|
|
57
|
+
/** 关联资源设定, 除非要关联过滤,否则不要设置资源之间的关联关系 */
|
|
58
|
+
static includes?: {
|
|
59
|
+
[k: string]: {
|
|
60
|
+
as: string;
|
|
61
|
+
required: boolean;
|
|
62
|
+
model: typeof ModelBase;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/** 模糊搜索相关设定 */
|
|
66
|
+
static searchCols?: {
|
|
67
|
+
[k: string]: {
|
|
68
|
+
op: "=" | "LIKE";
|
|
69
|
+
match: string[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/** 统计相关设定 */
|
|
73
|
+
static stats?: {
|
|
74
|
+
dimensions?: Record<string, string>;
|
|
75
|
+
metrics: Record<string, string>;
|
|
76
|
+
pagination?: {
|
|
77
|
+
maxResults: number;
|
|
78
|
+
maxStartIndex: number;
|
|
79
|
+
maxResultsLimit: number;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/** 联合唯一列名称集合,用来自动恢复软删除的资源 */
|
|
83
|
+
static unique?: string[];
|
|
84
|
+
}
|
|
16
85
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Deps = exports.Main = void 0;
|
|
3
|
+
exports.ModelBase = exports.Deps = exports.Main = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
4
5
|
function Main(cnf, deps) {
|
|
5
6
|
// 这里之所以要注入 Sequelize 是为了保证项目自身可以灵活选择自己的 Sequelize 版本, 这样改公共模块就会更加稳定, 避免频繁升级
|
|
6
7
|
const { sequelize: dbs } = cnf;
|
|
@@ -14,3 +15,49 @@ function Main(cnf, deps) {
|
|
|
14
15
|
}
|
|
15
16
|
exports.Main = Main;
|
|
16
17
|
exports.Deps = ["Sequelize"];
|
|
18
|
+
/**
|
|
19
|
+
* Model 基类
|
|
20
|
+
*/
|
|
21
|
+
class ModelBase extends sequelize_1.Model {
|
|
22
|
+
/**
|
|
23
|
+
* 基于主键获取某条数据的Mode实例,自动维护内存级 cache
|
|
24
|
+
* @param pk 主键
|
|
25
|
+
*/
|
|
26
|
+
static getByPk(pk) {
|
|
27
|
+
return this.findByPk(pk);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 基于主键获取某些数据的Mode实例列表,维持参数的顺序,自动维护内存级 cache
|
|
31
|
+
* @param pks 主键数组
|
|
32
|
+
*/
|
|
33
|
+
static async getByPks(pks) {
|
|
34
|
+
if (!Array.isArray(pks) || !pks.length)
|
|
35
|
+
return [];
|
|
36
|
+
//静态方法调用同一个类中的其他静态方法,可使用 this 关键字
|
|
37
|
+
// eslint-disable-next-line no-undef
|
|
38
|
+
const list = [];
|
|
39
|
+
for await (const x of pks) {
|
|
40
|
+
const item = await this.getByPk(x);
|
|
41
|
+
if (item)
|
|
42
|
+
list.push(item);
|
|
43
|
+
}
|
|
44
|
+
return list;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ModelBase = ModelBase;
|
|
48
|
+
/** 新增资源的时候可以写入的列名称集合 */
|
|
49
|
+
ModelBase.writableCols = [];
|
|
50
|
+
/** 编辑资源的时候可以写入的列名称集合 */
|
|
51
|
+
ModelBase.editableCols = [];
|
|
52
|
+
/** 列表查询时候分页控制参数 */
|
|
53
|
+
ModelBase.pagination = {
|
|
54
|
+
maxResults: 10,
|
|
55
|
+
maxStartIndex: 50000,
|
|
56
|
+
maxResultsLimit: 5000,
|
|
57
|
+
};
|
|
58
|
+
/** 列表查询时候排序控制参数 */
|
|
59
|
+
ModelBase.sort = {
|
|
60
|
+
default: "id",
|
|
61
|
+
defaultDirection: "DESC",
|
|
62
|
+
allow: ["id"],
|
|
63
|
+
};
|
package/dist/http/defines.d.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
+
import { Opt as Sign } from "../deps/signer";
|
|
1
2
|
export interface Cnf {
|
|
2
3
|
proxyIps?: string;
|
|
3
4
|
port?: number;
|
|
4
5
|
host?: string;
|
|
5
6
|
bodyMaxBytes?: number;
|
|
6
7
|
apisRoute?: string;
|
|
8
|
+
socket?: boolean;
|
|
7
9
|
[propName: string]: any;
|
|
8
10
|
}
|
|
9
|
-
interface Sign {
|
|
10
|
-
signature: string;
|
|
11
|
-
uri: string;
|
|
12
|
-
key: string;
|
|
13
|
-
timestamp: number;
|
|
14
|
-
signMethod: string;
|
|
15
|
-
signVersion: string;
|
|
16
|
-
method: string;
|
|
17
|
-
}
|
|
18
11
|
export interface Profile {
|
|
19
12
|
clientIp: string;
|
|
20
13
|
remoteIp: string;
|
|
@@ -27,7 +20,12 @@ export interface Profile {
|
|
|
27
20
|
revision?: string;
|
|
28
21
|
uuid?: string;
|
|
29
22
|
token?: string;
|
|
30
|
-
sign?: Sign
|
|
23
|
+
sign?: Sign & {
|
|
24
|
+
signature: string;
|
|
25
|
+
};
|
|
26
|
+
isSocket?: boolean;
|
|
27
|
+
/** socket 的时候加入的房间 */
|
|
28
|
+
roomId?: string;
|
|
31
29
|
}
|
|
32
30
|
export interface HttpCodes {
|
|
33
31
|
[propName: string]: number;
|
|
@@ -35,12 +33,9 @@ export interface HttpCodes {
|
|
|
35
33
|
export interface Domain {
|
|
36
34
|
[propName: string]: (profile: Profile, params: any) => any | Domain;
|
|
37
35
|
}
|
|
38
|
-
export
|
|
39
|
-
(methodPath: string): [any, any];
|
|
40
|
-
}
|
|
36
|
+
export declare type GetSchemaByPath = (methodPath: string) => [any, any];
|
|
41
37
|
export interface Err {
|
|
42
38
|
message: string;
|
|
43
39
|
code?: number | string;
|
|
44
40
|
data?: any;
|
|
45
41
|
}
|
|
46
|
-
export {};
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as restify from "restify";
|
|
2
|
+
import { Cnf, Domain, GetSchemaByPath, HttpCodes, Profile } from "./defines";
|
|
2
3
|
import { Router } from "./router";
|
|
3
|
-
import { Cnf, Domain, Profile, HttpCodes, GetSchemaByPath } from "./defines";
|
|
4
4
|
interface Deps {
|
|
5
|
-
routers(r: ReturnType<typeof Router>)
|
|
5
|
+
routers: (r: ReturnType<typeof Router>) => void;
|
|
6
6
|
domain: Domain;
|
|
7
7
|
httpCodes: HttpCodes;
|
|
8
8
|
getSchemaByPath: GetSchemaByPath;
|
package/dist/http/index.js
CHANGED
|
@@ -21,7 +21,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.Main = void 0;
|
|
23
23
|
const restify = __importStar(require("restify"));
|
|
24
|
+
const socket_io_1 = require("socket.io");
|
|
24
25
|
const router_1 = require("./router");
|
|
26
|
+
const socket_1 = require("./socket");
|
|
25
27
|
const utils_1 = require("./utils");
|
|
26
28
|
function Main(cnf, deps) {
|
|
27
29
|
const utils = (0, utils_1.Utils)(cnf);
|
|
@@ -42,6 +44,11 @@ function Main(cnf, deps) {
|
|
|
42
44
|
apisRoute: cnf.apisRoute,
|
|
43
45
|
});
|
|
44
46
|
routers(router);
|
|
47
|
+
// 根据需求起送socket服务
|
|
48
|
+
if (cnf.socket) {
|
|
49
|
+
const io = new socket_io_1.Server(server);
|
|
50
|
+
(0, socket_1.BridgeSocket)(io, domain);
|
|
51
|
+
}
|
|
45
52
|
// Http server start
|
|
46
53
|
return () => {
|
|
47
54
|
server.listen(cnf.port || 8088, cnf.host || "127.0.0.1", () => {
|
package/dist/http/router.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as restify from "restify";
|
|
2
|
+
import { Domain, GetSchemaByPath, HttpCodes, Profile } from "./defines";
|
|
2
3
|
import { Utils } from "./utils";
|
|
3
|
-
import { HttpCodes, Domain, Profile, GetSchemaByPath } from "./defines";
|
|
4
4
|
interface Deps {
|
|
5
5
|
domain: Domain;
|
|
6
6
|
getSchemaByPath: GetSchemaByPath;
|
|
7
7
|
utils: ReturnType<typeof Utils>;
|
|
8
8
|
server: restify.Server;
|
|
9
9
|
httpCodes: HttpCodes;
|
|
10
|
-
makeProfileHook
|
|
10
|
+
makeProfileHook?: (obj: Profile, req: restify.Request) => any;
|
|
11
11
|
apisRoute?: string;
|
|
12
12
|
swagger?: [any, any];
|
|
13
13
|
}
|
package/dist/http/router.js
CHANGED
|
@@ -71,6 +71,7 @@ function Router(deps) {
|
|
|
71
71
|
next();
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
// eslint-disable-next-line max-params
|
|
74
75
|
function register(verb, route, methodPath, code = 200, isList = false, handler, resHandler) {
|
|
75
76
|
/**
|
|
76
77
|
* 暂存起来,提供给apis接口来用
|
|
@@ -93,7 +94,7 @@ function Router(deps) {
|
|
|
93
94
|
try {
|
|
94
95
|
let results = await method(profile, params);
|
|
95
96
|
res.header("X-ConsumedTime", Date.now() - profile.startedAt.valueOf());
|
|
96
|
-
if (results
|
|
97
|
+
if (results === null || results === undefined)
|
|
97
98
|
results = "Ok";
|
|
98
99
|
if (resHandler) {
|
|
99
100
|
resHandler(results, res);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Server, Socket } from "socket.io";
|
|
2
|
+
import { DefaultEventsMap } from "socket.io/dist/typed-events";
|
|
3
|
+
import { Domain, Profile } from "./defines";
|
|
4
|
+
export declare type Listener = Client["emit"] & {
|
|
5
|
+
roomId?: string;
|
|
6
|
+
};
|
|
7
|
+
declare type Client = Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any> & {
|
|
8
|
+
profile?: ReturnType<typeof makeProfile>;
|
|
9
|
+
listener?: Listener;
|
|
10
|
+
};
|
|
11
|
+
declare const makeProfile: (client: Client, token: string, params: any, extra: Profile["extra"]) => Profile;
|
|
12
|
+
export declare function BridgeSocket(io: Server, domain: Domain): void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BridgeSocket = void 0;
|
|
4
|
+
const proxyIps = new Set(["127.0.0.1"]);
|
|
5
|
+
class MyError extends Error {
|
|
6
|
+
constructor(code, message, data) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.data = data;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const utils = {
|
|
13
|
+
/** 真实的连接请求端ip */
|
|
14
|
+
remoteIp(client) {
|
|
15
|
+
return client.handshake.address;
|
|
16
|
+
},
|
|
17
|
+
/**
|
|
18
|
+
* 获取客户端真实ip地址
|
|
19
|
+
*/
|
|
20
|
+
clientIp(client) {
|
|
21
|
+
const { headers } = client.handshake;
|
|
22
|
+
const clientIp = headers["x-forwarded-for"] || headers["x-real-ip"] || utils.remoteIp(client);
|
|
23
|
+
if (Array.isArray(clientIp))
|
|
24
|
+
return clientIp[0];
|
|
25
|
+
return clientIp.split(",")[0];
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* 获取可信任的真实ip
|
|
29
|
+
*/
|
|
30
|
+
realIp(client) {
|
|
31
|
+
const remoteIp = utils.remoteIp(client);
|
|
32
|
+
if (!proxyIps.has(remoteIp))
|
|
33
|
+
return remoteIp;
|
|
34
|
+
const realIp = client.handshake.headers["x-real-ip"] || remoteIp;
|
|
35
|
+
if (Array.isArray(realIp))
|
|
36
|
+
return realIp[0];
|
|
37
|
+
return realIp.split(",")[0];
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const makeProfile = (client, token, params, extra) => {
|
|
41
|
+
const obj = {
|
|
42
|
+
token,
|
|
43
|
+
clientIp: utils.clientIp(client),
|
|
44
|
+
remoteIp: utils.remoteIp(client),
|
|
45
|
+
realIp: utils.realIp(client),
|
|
46
|
+
isSocket: true,
|
|
47
|
+
startedAt: new Date(),
|
|
48
|
+
userAgent: client.handshake.headers["user-agent"] || "Not captured",
|
|
49
|
+
requestId: client.id,
|
|
50
|
+
/** 客户端发布号 */
|
|
51
|
+
revision: params.revision,
|
|
52
|
+
/** 用户uuid 可以长期跨app */
|
|
53
|
+
uuid: params.uuid,
|
|
54
|
+
/** 额外信息,自由扩展 */
|
|
55
|
+
extra,
|
|
56
|
+
};
|
|
57
|
+
return obj;
|
|
58
|
+
};
|
|
59
|
+
function BridgeSocket(io, domain) {
|
|
60
|
+
const subscribe = domain["message.subscribe"];
|
|
61
|
+
const unsubscribe = domain["message.unsubscribe"];
|
|
62
|
+
const entrance = domain["message.entrance"];
|
|
63
|
+
if (!subscribe)
|
|
64
|
+
throw Error("要启用 socket 服务,必须要要有 message.subscribe 方法,用来处理 socket 订阅");
|
|
65
|
+
if (!unsubscribe)
|
|
66
|
+
throw Error("要启用 socket 服务,必须要要有 message.unsubscribe 方法,用来处理 socket 退订");
|
|
67
|
+
if (!entrance)
|
|
68
|
+
throw Error("要启用 socket 服务,必须要要有 message.entrance 方法,用来处理 加入某个房间");
|
|
69
|
+
io.on("connection", (client) => {
|
|
70
|
+
console.log("[%s] connection: client.id: %s", new Date(), client.id);
|
|
71
|
+
client.on("init", async (token, params, extra) => {
|
|
72
|
+
console.log("[%s] socket.init: client.id: %s", new Date(), client.id);
|
|
73
|
+
if (!token) {
|
|
74
|
+
client.emit("initError", "Token lost");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
Object.assign(client, { profile: makeProfile(client, token, params, extra) });
|
|
79
|
+
if (!client.profile)
|
|
80
|
+
throw new MyError("noAuth", "请先登录");
|
|
81
|
+
// 创建消息监听函数
|
|
82
|
+
if (!client.listener)
|
|
83
|
+
client.listener = client.emit.bind(client);
|
|
84
|
+
// 向领域注册改用户的监听函数
|
|
85
|
+
const session = subscribe(client.profile, client.listener);
|
|
86
|
+
client.emit("inited", session);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
if (e instanceof MyError) {
|
|
90
|
+
client.emit("internalError", e.message, e.code || "unknown");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.error(e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
client.on("entrance", async (roomId) => {
|
|
97
|
+
try {
|
|
98
|
+
if (!client.profile || !client.listener)
|
|
99
|
+
return;
|
|
100
|
+
const res = await entrance({ roomId, ...client.profile }, client.listener);
|
|
101
|
+
client.profile.roomId = roomId;
|
|
102
|
+
client.listener.roomId = roomId;
|
|
103
|
+
client.emit("entranced", res);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
if (e instanceof MyError) {
|
|
107
|
+
client.emit("internalError", e.message, e.code || "unknown");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.error(e);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
client.use(async ([name, params, responseId], next) => {
|
|
114
|
+
if (name === "init" || name === "entrance")
|
|
115
|
+
return next();
|
|
116
|
+
const method = domain[name];
|
|
117
|
+
try {
|
|
118
|
+
if (!method)
|
|
119
|
+
throw new MyError("notFound", "不存在该领域方法");
|
|
120
|
+
if (!client.profile)
|
|
121
|
+
throw new MyError("noAuth", "请先执行 init");
|
|
122
|
+
const res = await method(client.profile, params);
|
|
123
|
+
if (responseId) {
|
|
124
|
+
client.emit("response", responseId, res);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
if (e instanceof Error) {
|
|
129
|
+
if (responseId) {
|
|
130
|
+
client.emit("responseError", responseId, e.code, e.message, e.data);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
client.emit(`${name}Error`, e.code, e.message, e.data);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error(e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return next();
|
|
141
|
+
});
|
|
142
|
+
// 掉线
|
|
143
|
+
client.on("disconnect", () => {
|
|
144
|
+
if (!client.profile)
|
|
145
|
+
return;
|
|
146
|
+
if (!client.listener)
|
|
147
|
+
return;
|
|
148
|
+
// 这里要取消对领域消息的监听
|
|
149
|
+
unsubscribe(client.profile, client.listener);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
exports.BridgeSocket = BridgeSocket;
|
package/dist/http/utils.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function Utils(cnf: Cnf): {
|
|
|
15
15
|
/**
|
|
16
16
|
* 构造profile参数
|
|
17
17
|
*/
|
|
18
|
-
makeProfile(req: restify.Request, method: string, customFn?:
|
|
18
|
+
makeProfile<T extends {} = {}>(req: restify.Request, method: string, customFn?: ((obj: Profile, req: restify.Request) => T) | undefined): Profile & T;
|
|
19
19
|
/**
|
|
20
20
|
* 构造领域方法所需的 params 参数
|
|
21
21
|
*/
|
package/dist/http/utils.js
CHANGED
|
@@ -18,14 +18,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
18
18
|
__setModuleDefault(result, mod);
|
|
19
19
|
return result;
|
|
20
20
|
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
21
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
25
|
exports.Utils = void 0;
|
|
23
|
-
const os = __importStar(require("os"));
|
|
24
|
-
const fs = __importStar(require("fs"));
|
|
25
26
|
const crypto = __importStar(require("crypto"));
|
|
26
|
-
const _ = __importStar(require("lodash"));
|
|
27
|
-
const xlsx = __importStar(require("xlsx"));
|
|
28
27
|
const csv_stringify_1 = require("csv-stringify");
|
|
28
|
+
const fs = __importStar(require("fs"));
|
|
29
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
30
|
+
const os = __importStar(require("os"));
|
|
31
|
+
const xlsx = __importStar(require("xlsx"));
|
|
29
32
|
const str2arr = ["_includes", "dimensions", "metrics", "_attrs"];
|
|
30
33
|
const enc = encodeURI;
|
|
31
34
|
const TMPDIR = os.tmpdir();
|
|
@@ -103,7 +106,7 @@ function Utils(cnf) {
|
|
|
103
106
|
*/
|
|
104
107
|
makeParams(req) {
|
|
105
108
|
let params = { ...req.params, ...req.query };
|
|
106
|
-
if (
|
|
109
|
+
if (lodash_1.default.isObject(req.body) && !Array.isArray(req.body)) {
|
|
107
110
|
params = { ...req.body, ...params };
|
|
108
111
|
}
|
|
109
112
|
else if (req.body) {
|
|
@@ -111,10 +114,10 @@ function Utils(cnf) {
|
|
|
111
114
|
}
|
|
112
115
|
// 逗号分隔的属性,自动转换为 array
|
|
113
116
|
for (const k of str2arr) {
|
|
114
|
-
if (params[k] &&
|
|
117
|
+
if (params[k] && lodash_1.default.isString(params[k]))
|
|
115
118
|
params[k] = params[k].split(",");
|
|
116
119
|
}
|
|
117
|
-
if (
|
|
120
|
+
if (lodash_1.default.size(req.files))
|
|
118
121
|
params.__files = req.files;
|
|
119
122
|
return params;
|
|
120
123
|
},
|
|
@@ -124,9 +127,9 @@ function Utils(cnf) {
|
|
|
124
127
|
*/
|
|
125
128
|
async outputCSV(rows, params, res, isXLSX = false) {
|
|
126
129
|
const { _names, _cols, _filename } = params;
|
|
127
|
-
if (!
|
|
130
|
+
if (!lodash_1.default.isString(_cols))
|
|
128
131
|
return false;
|
|
129
|
-
if (_names && !
|
|
132
|
+
if (_names && !lodash_1.default.isString(_names))
|
|
130
133
|
return false;
|
|
131
134
|
const keys = _cols.split(",");
|
|
132
135
|
const titles = (_names || _cols).split(",");
|
|
@@ -156,7 +159,7 @@ function Utils(cnf) {
|
|
|
156
159
|
await new Promise((resolve) => {
|
|
157
160
|
const stream = (0, csv_stringify_1.stringify)(rows, {
|
|
158
161
|
header: true,
|
|
159
|
-
columns:
|
|
162
|
+
columns: lodash_1.default.zipObject(keys, titles),
|
|
160
163
|
});
|
|
161
164
|
stream.pipe(res);
|
|
162
165
|
stream.on("end", resolve);
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@domain.js/main",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "DDD framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"domain-cli": "dist/cli/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "tsc",
|
|
10
|
+
"build": "rm -rf ./dist/* && tsc",
|
|
11
11
|
"test": "export NODE_ENV=test && jest ./src --coverage",
|
|
12
12
|
"test:watch": "export NODE_ENV=test && jest ./src --watch",
|
|
13
13
|
"prepare": "husky install",
|
|
14
14
|
"lint-staged": "lint-staged",
|
|
15
15
|
"lint-staged:js": "eslint --ext .js,.ts",
|
|
16
|
-
"loadDeps": "ts-node src/cli/index.ts loadDeps ./ ts"
|
|
16
|
+
"loadDeps": "ts-node src/cli/index.ts loadDeps ./src/deps ts"
|
|
17
17
|
},
|
|
18
18
|
"author": "Redstone Zhao",
|
|
19
19
|
"license": "MIT",
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
"mysql2": "^2.3.3",
|
|
78
78
|
"restify": "^8.6.0",
|
|
79
79
|
"restify-errors": "^8.0.2",
|
|
80
|
-
"sequelize": "6.
|
|
80
|
+
"sequelize": "6.16.1",
|
|
81
|
+
"socket.io": "^4.4.1",
|
|
81
82
|
"type-fest": "^2.8.0",
|
|
82
83
|
"uuid": "^8.3.2",
|
|
83
84
|
"xlsx": "^0.17.4"
|
package/dist/cli/schema2ts.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/cli/schema2ts.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const json_schema_to_typescript_1 = require("json-schema-to-typescript");
|
|
6
|
-
const _require = require;
|
|
7
|
-
async function main(file, single = false, name = "Params") {
|
|
8
|
-
const stats = fs.statSync(file);
|
|
9
|
-
if (stats.isFile()) {
|
|
10
|
-
const arr = file.split(".");
|
|
11
|
-
if (arr.pop() !== "js")
|
|
12
|
-
return;
|
|
13
|
-
const obj = _require(file);
|
|
14
|
-
if (!single) {
|
|
15
|
-
if (!Array.isArray(obj))
|
|
16
|
-
return;
|
|
17
|
-
if (typeof obj[1] !== "object")
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
const ts = await (0, json_schema_to_typescript_1.compile)(single ? obj : obj[1], name);
|
|
22
|
-
arr.push("d.ts");
|
|
23
|
-
fs.writeFileSync(arr.join("."), ts);
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
console.error(file, e);
|
|
27
|
-
}
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const files = fs.readdirSync(file);
|
|
31
|
-
for await (const x of files) {
|
|
32
|
-
if (x === "." || x === "..")
|
|
33
|
-
continue;
|
|
34
|
-
await main(path.resolve(file, x), single, name);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
main(process.argv[2], process.argv[3] === "single", process.argv[4]);
|
package/dist/deps/cia/errors.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const util = require("util");
|
|
4
|
-
exports.default = () => {
|
|
5
|
-
const defines = [
|
|
6
|
-
["duplicatRegistMessage", "The message has been registed: %s"],
|
|
7
|
-
["registWhenReadyAfter", "The message dont registed when mcenter be ready after: %s"],
|
|
8
|
-
[
|
|
9
|
-
"submitUnregistedMessage",
|
|
10
|
-
"The message has not been registed: %s, data: %o, when will submit",
|
|
11
|
-
],
|
|
12
|
-
["linkUnregistedMessage", "The message has not been registed: %s, when will link"],
|
|
13
|
-
[
|
|
14
|
-
"linkUnknowTypes",
|
|
15
|
-
"The message link type unknown, message name is: %s, type is: %s, when will link",
|
|
16
|
-
],
|
|
17
|
-
[
|
|
18
|
-
"linkDuplicateType",
|
|
19
|
-
"The message link type duplicate, message name is: %s, type is: %s, when will link",
|
|
20
|
-
],
|
|
21
|
-
[
|
|
22
|
-
"linkListernerMustBeFunctionType",
|
|
23
|
-
"The message link waiter must be a function, message name is: %s, type is: %s, when will link",
|
|
24
|
-
],
|
|
25
|
-
["setFnNotAllowed", "Set function but unknown type: %s"],
|
|
26
|
-
];
|
|
27
|
-
const fns = {};
|
|
28
|
-
for (const [code, message] of defines) {
|
|
29
|
-
fns[code] = (...args) => {
|
|
30
|
-
const error = Error(util.format(message, ...args));
|
|
31
|
-
error.code = code;
|
|
32
|
-
return error;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
return fns;
|
|
36
|
-
};
|
package/dist/deps/cia/index.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import * as _ from "lodash";
|
|
2
|
-
import * as async from "async";
|
|
3
|
-
import { v4 } from "uuid";
|
|
4
|
-
interface Cnf {
|
|
5
|
-
cia?: {
|
|
6
|
-
concurrency?: number;
|
|
7
|
-
storeKey?: string;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
interface Deps {
|
|
11
|
-
_: Pick<typeof _, "pick" | "map" | "isFunction">;
|
|
12
|
-
async: Pick<typeof async, "eachSeries" | "queue">;
|
|
13
|
-
uuid: {
|
|
14
|
-
v4: typeof v4;
|
|
15
|
-
};
|
|
16
|
-
logger: {
|
|
17
|
-
info: (...args: any[]) => void;
|
|
18
|
-
error: (...args: any[]) => void;
|
|
19
|
-
};
|
|
20
|
-
redis: {
|
|
21
|
-
hset: Function;
|
|
22
|
-
hdel: Function;
|
|
23
|
-
hgetall: Function;
|
|
24
|
-
};
|
|
25
|
-
graceful: {
|
|
26
|
-
exit: (fn: () => Promise<void>) => void;
|
|
27
|
-
};
|
|
28
|
-
U: {
|
|
29
|
-
tryCatchLog<Fn extends (...args: any[]) => any>(fn: Fn, errorFn: (...args: any[]) => any): Fn;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
export interface Stats {
|
|
33
|
-
pendings: number;
|
|
34
|
-
doings: number;
|
|
35
|
-
errors: number;
|
|
36
|
-
dones: number;
|
|
37
|
-
}
|
|
38
|
-
declare type Type = {
|
|
39
|
-
type: string;
|
|
40
|
-
timeout?: number;
|
|
41
|
-
validator?: Function;
|
|
42
|
-
};
|
|
43
|
-
export declare function Main(cnf: Cnf, deps: Deps): {
|
|
44
|
-
isExiting: () => boolean;
|
|
45
|
-
isExited: () => boolean;
|
|
46
|
-
checkReady: () => boolean;
|
|
47
|
-
getStats: () => {
|
|
48
|
-
[name: string]: Stats & {
|
|
49
|
-
_types: ({
|
|
50
|
-
type: string;
|
|
51
|
-
} & Stats)[];
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
getUnlinks: () => string[];
|
|
55
|
-
regist: (name: string, validator: Function | undefined, types: Type[]) => number;
|
|
56
|
-
link: (name: string, type: string, waiter: Function) => void;
|
|
57
|
-
submit: (name: string, data: any, callback?: Function | undefined) => void;
|
|
58
|
-
setFn: (type: "error" | "timeout", fn: (...args: any[]) => any) => void;
|
|
59
|
-
domainPaths: Set<string>;
|
|
60
|
-
modelHooks: Set<string>;
|
|
61
|
-
};
|
|
62
|
-
export declare const Deps: string[];
|
|
63
|
-
export {};
|
package/dist/deps/cia/index.js
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Deps = exports.Main = void 0;
|
|
4
|
-
const errors_1 = require("./errors");
|
|
5
|
-
function Main(cnf, deps) {
|
|
6
|
-
const { _, async, uuid: { v4: uuid }, logger, redis, graceful, U: { tryCatchLog }, } = deps;
|
|
7
|
-
const { cia } = cnf;
|
|
8
|
-
const concurrency = Math.max(1, ((cia && cia.concurrency) || 10) | 0);
|
|
9
|
-
const storeKey = (cia && cia.storeKey) || "cia-store";
|
|
10
|
-
const errors = (0, errors_1.default)();
|
|
11
|
-
let doingCount = 0; // 正在执行的消息数量
|
|
12
|
-
let exited = false; // 是否已经完成退出
|
|
13
|
-
let exiting = false; // 是否正在退出
|
|
14
|
-
let readyToExitFn; // 完成退出前准备后执行函数
|
|
15
|
-
let unlinkdCount = 0; // 未被订阅的数量, 基于 {name}::{type} 判断
|
|
16
|
-
let isReady = false; // 系统是否已准备妥当
|
|
17
|
-
const registeds = {};
|
|
18
|
-
// 默认通知函数
|
|
19
|
-
const fns = {
|
|
20
|
-
error: logger.error,
|
|
21
|
-
timeout: logger.info,
|
|
22
|
-
};
|
|
23
|
-
// 记录监听回调函数
|
|
24
|
-
// { [${name}::${type}]: { [type]: fn } }
|
|
25
|
-
const waiters = new Map();
|
|
26
|
-
// 更新等待数量
|
|
27
|
-
const updatePendings = (registed) => {
|
|
28
|
-
const { result = {}, types } = registed;
|
|
29
|
-
const withouts = new Set(Object.keys(result));
|
|
30
|
-
registed.pendings += 1;
|
|
31
|
-
types.forEach((x) => {
|
|
32
|
-
if (!withouts.has(x.type))
|
|
33
|
-
x.pendings += 1;
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
// 更新 doings 统计信息
|
|
37
|
-
const updateDoings = (item) => {
|
|
38
|
-
item.pendings -= 1;
|
|
39
|
-
item.doings += 1;
|
|
40
|
-
};
|
|
41
|
-
// 更新 errors 统计信息
|
|
42
|
-
const updateErrors = (item) => {
|
|
43
|
-
item.doings -= 1;
|
|
44
|
-
item.errors += 1;
|
|
45
|
-
};
|
|
46
|
-
// 更新 dones 统计信息
|
|
47
|
-
const updateDones = (item) => {
|
|
48
|
-
item.doings -= 1;
|
|
49
|
-
item.dones += 1;
|
|
50
|
-
};
|
|
51
|
-
// 消息分发函数,分发到对应的订阅函数上
|
|
52
|
-
const dispatch = async (item) => {
|
|
53
|
-
const { id, name, data, result = {}, callback } = item;
|
|
54
|
-
const registed = registeds[name];
|
|
55
|
-
const { types } = registed;
|
|
56
|
-
const withouts = new Set(Object.keys(result));
|
|
57
|
-
updateDoings(registed);
|
|
58
|
-
doingCount += 1;
|
|
59
|
-
let errorCount = 0;
|
|
60
|
-
await async.eachSeries(types, async (_type) => {
|
|
61
|
-
const { type, timeout, validator } = _type;
|
|
62
|
-
// 看看是否有设置要忽略掉某些订阅者
|
|
63
|
-
// 这个功能主要是留给应用无故中断后系统自动恢复的任务执行
|
|
64
|
-
if (withouts && withouts.has(type))
|
|
65
|
-
return;
|
|
66
|
-
if (exiting)
|
|
67
|
-
return;
|
|
68
|
-
const fn = waiters.get(`${name}::${type}`);
|
|
69
|
-
const startAt = Date.now();
|
|
70
|
-
let err = null;
|
|
71
|
-
let ret = null;
|
|
72
|
-
try {
|
|
73
|
-
updateDoings(_type);
|
|
74
|
-
ret = await fn(data);
|
|
75
|
-
if (validator)
|
|
76
|
-
validator(ret);
|
|
77
|
-
updateDones(_type);
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
updateErrors(_type);
|
|
81
|
-
fns.error(e, id, name, type, data);
|
|
82
|
-
errorCount += 1;
|
|
83
|
-
err = e;
|
|
84
|
-
}
|
|
85
|
-
const consumedMS = Date.now() - startAt;
|
|
86
|
-
if (timeout && timeout < consumedMS)
|
|
87
|
-
fns.timeout(consumedMS, id, name, type);
|
|
88
|
-
result[type] = [err, ret, consumedMS];
|
|
89
|
-
// 记录执行结果
|
|
90
|
-
logger.info(`cia.dispatch\t${id}\t${type}`, result[type]);
|
|
91
|
-
});
|
|
92
|
-
doingCount -= 1;
|
|
93
|
-
if (errorCount) {
|
|
94
|
-
updateErrors(registed);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
updateDones(registed);
|
|
98
|
-
}
|
|
99
|
-
// submit 设置了callback 要记得执行回调函数
|
|
100
|
-
if (callback)
|
|
101
|
-
callback(result);
|
|
102
|
-
// 正在退出,且完成的不等于总共的,则需要储存, 以备下次启动后执行
|
|
103
|
-
if (exiting) {
|
|
104
|
-
if (Object.keys(result).length !== types.length) {
|
|
105
|
-
item.result = result;
|
|
106
|
-
// 存储以备下次启动恢复执行
|
|
107
|
-
await redis.hset(storeKey, item.id, JSON.stringify(item));
|
|
108
|
-
}
|
|
109
|
-
// 全部处理完毕后,执行退出
|
|
110
|
-
if (!doingCount) {
|
|
111
|
-
exited = true;
|
|
112
|
-
exiting = false;
|
|
113
|
-
readyToExitFn();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
const statsFields = Object.freeze(["pendings", "doings", "dones", "errors"]);
|
|
118
|
-
// 获取统计信息
|
|
119
|
-
const getStats = () => {
|
|
120
|
-
const stats = {};
|
|
121
|
-
for (const name of Object.keys(registeds)) {
|
|
122
|
-
const { types } = registeds[name];
|
|
123
|
-
stats[name] = {
|
|
124
|
-
..._.pick(registeds[name], statsFields),
|
|
125
|
-
_types: types.map((x) => _.pick(x, "type", ...statsFields)),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
return stats;
|
|
129
|
-
};
|
|
130
|
-
// 内部消息队列, 初始化后立即暂定,等待 regist, link 都准备好了在开始执行
|
|
131
|
-
// 这样就不会有未成功订阅函数执行遗漏的问题了
|
|
132
|
-
// 例如: A 函数要监听 1 好消息的 save 类型,结果在完成订阅前,已经有某个区域 submit 了 1 号事件
|
|
133
|
-
// 如果队列一开始不暂停就会出现A函数遗漏执行
|
|
134
|
-
const queue = async.queue(dispatch, concurrency);
|
|
135
|
-
queue.pause();
|
|
136
|
-
graceful.exit(async () => {
|
|
137
|
-
exiting = true;
|
|
138
|
-
await new Promise((resolve) => {
|
|
139
|
-
// 如果队列已经清空,且没有正在执行的消息,则直接退出
|
|
140
|
-
if (!queue.length() && !doingCount) {
|
|
141
|
-
exited = true;
|
|
142
|
-
exiting = false;
|
|
143
|
-
resolve();
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
readyToExitFn = resolve;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
// 恢复上次残留的消息订阅执行
|
|
151
|
-
const recover = async () => {
|
|
152
|
-
const items = await redis.hgetall(storeKey);
|
|
153
|
-
if (!items)
|
|
154
|
-
return;
|
|
155
|
-
for await (const id of Object.keys(items)) {
|
|
156
|
-
const item = items[id];
|
|
157
|
-
const ok = await redis.hdel(storeKey, id);
|
|
158
|
-
if (ok !== 1)
|
|
159
|
-
continue;
|
|
160
|
-
try {
|
|
161
|
-
const data = JSON.parse(item);
|
|
162
|
-
const { name } = data;
|
|
163
|
-
queue.push(data);
|
|
164
|
-
updatePendings(registeds[name]);
|
|
165
|
-
logger.info("cia-recover: %s", item);
|
|
166
|
-
}
|
|
167
|
-
catch (e) {
|
|
168
|
-
logger.error(e);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
// regist 消息注册,提前注册好需要submit和link的消息
|
|
173
|
-
// 这么做的目的是可以随时检测是否所有的消息都消费者,消费者类型是否正确
|
|
174
|
-
// 同时在submit的时候也可以检测发送的数据是否符合规定的格式
|
|
175
|
-
// name: String 消息名称
|
|
176
|
-
// validator?: Function 消息体数据格式验证函数
|
|
177
|
-
// types: [{
|
|
178
|
-
// type: 'updateUser', // 类型名称
|
|
179
|
-
// timeout?: 100, // 执行超时限定, 单位毫秒,可选 默认为 0, 不限制
|
|
180
|
-
// validator?: fn, // 返回值格式验证函数, 可选
|
|
181
|
-
// }]
|
|
182
|
-
const regist = (name, validator, types) => {
|
|
183
|
-
if (isReady)
|
|
184
|
-
throw errors.registWhenReadyAfter(name);
|
|
185
|
-
if (registeds[name])
|
|
186
|
-
throw errors.duplicatRegistMessage(name);
|
|
187
|
-
const typeNames = new Set(_.map(types, "type"));
|
|
188
|
-
types.forEach((x) => {
|
|
189
|
-
Object.assign(x, { pendings: 0, dones: 0, doings: 0, errors: 0 });
|
|
190
|
-
});
|
|
191
|
-
const item = {
|
|
192
|
-
validator,
|
|
193
|
-
types: types,
|
|
194
|
-
typeNames,
|
|
195
|
-
pendings: 0,
|
|
196
|
-
dones: 0,
|
|
197
|
-
doings: 0,
|
|
198
|
-
errors: 0,
|
|
199
|
-
};
|
|
200
|
-
unlinkdCount += typeNames.size;
|
|
201
|
-
registeds[name] = item;
|
|
202
|
-
return Object.keys(registeds).length;
|
|
203
|
-
};
|
|
204
|
-
// start 启动系统执行, 这之前一定要regist 和 link 都准备好
|
|
205
|
-
const start = async () => {
|
|
206
|
-
queue.resume();
|
|
207
|
-
await recover();
|
|
208
|
-
};
|
|
209
|
-
// check 消息注册、监听检测
|
|
210
|
-
// 检查是否存在注册了的消息,但没有人监听消费
|
|
211
|
-
const checkReady = () => {
|
|
212
|
-
if (unlinkdCount !== 0)
|
|
213
|
-
return false;
|
|
214
|
-
if (!isReady) {
|
|
215
|
-
isReady = true;
|
|
216
|
-
start();
|
|
217
|
-
}
|
|
218
|
-
return true;
|
|
219
|
-
};
|
|
220
|
-
// link 消息订阅
|
|
221
|
-
const link = (name, type, waiter) => {
|
|
222
|
-
if (!registeds[name])
|
|
223
|
-
throw errors.linkUnregistedMessage(name);
|
|
224
|
-
const { typeNames } = registeds[name];
|
|
225
|
-
if (!typeNames.has(type))
|
|
226
|
-
throw errors.linkUnknowTypes(name, type);
|
|
227
|
-
if (!_.isFunction(waiter))
|
|
228
|
-
throw errors.linkListernerMustBeFunctionType(name, type);
|
|
229
|
-
const key = `${name}::${type}`;
|
|
230
|
-
if (waiters.get(key))
|
|
231
|
-
throw errors.linkDuplicateType(name, type);
|
|
232
|
-
waiters.set(key, waiter);
|
|
233
|
-
unlinkdCount -= 1;
|
|
234
|
-
checkReady();
|
|
235
|
-
};
|
|
236
|
-
// submit 消息发布
|
|
237
|
-
// name string 消息名称
|
|
238
|
-
// data any 消息数据
|
|
239
|
-
// callback function 消息执行完毕回调
|
|
240
|
-
const submit = (name, data, callback) => {
|
|
241
|
-
if (!registeds[name]) {
|
|
242
|
-
// 这里记录error就可以了。throw没有意义,因为submit是异步的
|
|
243
|
-
// throw error并没有被捕获,还会导致调用方的后续代码不执行
|
|
244
|
-
logger.error(errors.submitUnregistedMessage(name, data));
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (callback && !_.isFunction(callback))
|
|
248
|
-
callback = undefined;
|
|
249
|
-
const { validator } = registeds[name];
|
|
250
|
-
if (validator)
|
|
251
|
-
validator(data);
|
|
252
|
-
const id = uuid();
|
|
253
|
-
queue.push({ id, name, data, callback });
|
|
254
|
-
updatePendings(registeds[name]);
|
|
255
|
-
logger.info(`cia.submit\t${id}`, { name, data });
|
|
256
|
-
};
|
|
257
|
-
// 设置通知函数,错误通知,超时通知
|
|
258
|
-
// 在消息分发执行的时候遇到错误会调用错误通知函数
|
|
259
|
-
// 在消息分发执行的时候遇到超时会调用超时通知函数
|
|
260
|
-
// type string 类型,error or timeout
|
|
261
|
-
// fn function 通知函数
|
|
262
|
-
const setFn = (type, fn) => {
|
|
263
|
-
if (!fns[type])
|
|
264
|
-
throw errors.setFnNotAllowed(type);
|
|
265
|
-
// 这里之所以会用 tryCatchLog 封装函数,是不想让这些函数的执行影响主流程
|
|
266
|
-
// 这些函数内部抛出的异常不会导致主流程执行中断
|
|
267
|
-
fns[type] = tryCatchLog(fn, logger.error);
|
|
268
|
-
};
|
|
269
|
-
// 获取未被连接的任务消息
|
|
270
|
-
const getUnlinks = () => {
|
|
271
|
-
const losts = [];
|
|
272
|
-
for (const name of Object.keys(registeds)) {
|
|
273
|
-
for (const { type } of registeds[name].types) {
|
|
274
|
-
const key = `${name}::${type}`;
|
|
275
|
-
if (!waiters.has(key))
|
|
276
|
-
losts.push(key);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return losts;
|
|
280
|
-
};
|
|
281
|
-
// 进程是否正在退出
|
|
282
|
-
const isExiting = () => Boolean(exiting);
|
|
283
|
-
// 进程是否已经退出
|
|
284
|
-
const isExited = () => Boolean(exited);
|
|
285
|
-
/**
|
|
286
|
-
* 领域方法注册到 cia 上的路径集合
|
|
287
|
-
*/
|
|
288
|
-
const domainPaths = new Set();
|
|
289
|
-
/**
|
|
290
|
-
* Model hook 注册到 cia 上的路径集合
|
|
291
|
-
*/
|
|
292
|
-
const modelHooks = new Set();
|
|
293
|
-
return {
|
|
294
|
-
isExiting,
|
|
295
|
-
isExited,
|
|
296
|
-
checkReady,
|
|
297
|
-
getStats,
|
|
298
|
-
getUnlinks,
|
|
299
|
-
regist,
|
|
300
|
-
link,
|
|
301
|
-
submit,
|
|
302
|
-
setFn,
|
|
303
|
-
domainPaths,
|
|
304
|
-
modelHooks,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
exports.Main = Main;
|
|
308
|
-
exports.Deps = ["_", "async", "logger", "utils", "redis", "graceful", "uuid"];
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import * as Sequelize from "sequelize";
|
|
2
|
-
export interface Params {
|
|
3
|
-
[propName: string]: any;
|
|
4
|
-
}
|
|
5
|
-
export declare type TModel = typeof Sequelize.Model & {
|
|
6
|
-
filterAttrs?: string[];
|
|
7
|
-
writableCols?: string[];
|
|
8
|
-
editableCols?: string[];
|
|
9
|
-
allowIncludeCols?: string[];
|
|
10
|
-
onlyAdminCols?: string[];
|
|
11
|
-
pagination?: {
|
|
12
|
-
maxResults: number;
|
|
13
|
-
maxStartIndex: number;
|
|
14
|
-
maxResultsLimit: number;
|
|
15
|
-
};
|
|
16
|
-
sort?: {
|
|
17
|
-
default: string;
|
|
18
|
-
allow: string[];
|
|
19
|
-
defaultDirection?: "DESC" | "ASC";
|
|
20
|
-
};
|
|
21
|
-
includes?: {
|
|
22
|
-
[k: string]: {
|
|
23
|
-
as: string;
|
|
24
|
-
required: boolean;
|
|
25
|
-
model: TModel;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
searchCols?: {
|
|
29
|
-
[k: string]: {
|
|
30
|
-
op: "=" | "LIKE";
|
|
31
|
-
match: string[];
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
stats?: {
|
|
35
|
-
dimensions?: Record<string, string>;
|
|
36
|
-
metrics: Record<string, string>;
|
|
37
|
-
pagination?: {
|
|
38
|
-
maxResults: number;
|
|
39
|
-
maxStartIndex: number;
|
|
40
|
-
maxResultsLimit: number;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
unique?: string[];
|
|
44
|
-
};
|
|
45
|
-
export declare type ModelExtraAtts = Omit<TModel, keyof typeof Sequelize.Model>;
|
|
46
|
-
export interface Include {
|
|
47
|
-
as: string;
|
|
48
|
-
required: boolean;
|
|
49
|
-
model: TModel;
|
|
50
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as TypeORM from "typeorm";
|
|
2
|
-
interface Cnf {
|
|
3
|
-
typeorm: {
|
|
4
|
-
[propName: string]: Parameters<typeof TypeORM.createConnection>[0];
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
interface Deps {
|
|
8
|
-
TypeORM: typeof TypeORM;
|
|
9
|
-
}
|
|
10
|
-
export declare function Main(cnf: Cnf, deps: Deps): Promise<{
|
|
11
|
-
[propName: string]: TypeORM.Connection;
|
|
12
|
-
}>;
|
|
13
|
-
export declare const Deps: string[];
|
|
14
|
-
export {};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Deps = exports.Main = void 0;
|
|
4
|
-
async function Main(cnf, deps) {
|
|
5
|
-
// 这里之所以要注入 Sequelize 是为了保证项目自身可以灵活选择自己的 Sequelize 版本, 这样改公共模块就会更加稳定, 避免频繁升级
|
|
6
|
-
const { typeorm: dbs } = cnf;
|
|
7
|
-
const { TypeORM } = deps;
|
|
8
|
-
const links = {};
|
|
9
|
-
for await (const k of Object.keys(dbs)) {
|
|
10
|
-
const db = dbs[k];
|
|
11
|
-
links[k] = await TypeORM.createConnection(db);
|
|
12
|
-
}
|
|
13
|
-
return links;
|
|
14
|
-
}
|
|
15
|
-
exports.Main = Main;
|
|
16
|
-
exports.Deps = ["TypeORM"];
|