@x-9lab/xlab 1.6.0 → 2.0.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.
Files changed (72) hide show
  1. package/@types/cluster.d.ts +15 -3
  2. package/@types/components/common/index.d.ts +1 -2
  3. package/@types/components/cron/index.d.ts +4 -4
  4. package/@types/components/log/index.d.ts +37 -2
  5. package/@types/config/index.d.ts +9 -5
  6. package/@types/custom.d.ts +25 -3
  7. package/@types/dot-file.d.ts +6 -0
  8. package/@types/global.d.ts +44 -54
  9. package/@types/index.d.ts +18 -0
  10. package/@types/init-env.d.ts +3 -1
  11. package/@types/middleware/handle-pre-dir.d.ts +1 -5
  12. package/@types/middleware/request-filter.d.ts +0 -1
  13. package/@types/middleware/service-mark.d.ts +1 -6
  14. package/@types/router.d.ts +25 -0
  15. package/@types/server.d.ts +28 -6
  16. package/MIGRATION.md +170 -0
  17. package/README.md +37 -17
  18. package/dist/@config/config.dev.js +7 -3
  19. package/dist/@config/config.js +7 -3
  20. package/dist/@config/config.sand.js +7 -3
  21. package/dist/bin/watch.js +13 -9
  22. package/dist/cluster.js +121 -44
  23. package/dist/components/assets/index.js +9 -5
  24. package/dist/components/cluster/index.js +17 -4
  25. package/dist/components/common/index.js +65 -45
  26. package/dist/components/cron/index.js +50 -27
  27. package/dist/components/header/index.js +32 -17
  28. package/dist/components/header/time.js +6 -2
  29. package/dist/components/index.js +1 -1
  30. package/dist/components/log/index.js +91 -87
  31. package/dist/components/mime/index.js +6 -2
  32. package/dist/components/return-code/index.js +33 -18
  33. package/dist/components/uuid/index.js +22 -13
  34. package/dist/config/getEnv.js +9 -5
  35. package/dist/config/index.js +98 -66
  36. package/dist/config/process-custom-config-files.js +26 -22
  37. package/dist/config/process-def-config-file.js +15 -11
  38. package/dist/custom.js +77 -28
  39. package/dist/default-x-config.js +9 -5
  40. package/dist/dot-file.js +42 -17
  41. package/dist/global.js +37 -31
  42. package/dist/index.js +100 -0
  43. package/dist/init-env.js +17 -10
  44. package/dist/middleware/@bin/html-filter.js +19 -7
  45. package/dist/middleware/bad-request.js +12 -8
  46. package/dist/middleware/compress.js +11 -7
  47. package/dist/middleware/cors.js +20 -7
  48. package/dist/middleware/fresh-filter.js +13 -7
  49. package/dist/middleware/handle-pre-dir.js +22 -17
  50. package/dist/middleware/request-filter.js +39 -25
  51. package/dist/middleware/service-mark.js +22 -25
  52. package/dist/middlewares.js +22 -19
  53. package/dist/router.js +174 -141
  54. package/dist/server.js +195 -93
  55. package/package.json +25 -29
  56. package/@types/business/utils/cookie/1.1.0/clean/index.d.ts +0 -3
  57. package/@types/business/utils/cookie/1.1.0/clean/js/index.d.ts +0 -2
  58. package/@types/components/cache/index.d.ts +0 -2
  59. package/@types/components/cache/lru.d.ts +0 -21
  60. package/@types/components/html-processor/index.d.ts +0 -9
  61. package/@types/components/injection/index.d.ts +0 -25
  62. package/@types/components/js-processor/index.d.ts +0 -5
  63. package/@types/components/md5/index.d.ts +0 -2
  64. package/@types/components/platform/index.d.ts +0 -21
  65. package/@types/components/proxy/index.d.ts +0 -26
  66. package/@types/components/querystring/index.d.ts +0 -25
  67. package/@types/components/redirect/index.d.ts +0 -11
  68. package/@types/components/request/helper.d.ts +0 -3
  69. package/@types/components/request/index.d.ts +0 -51
  70. package/@types/components/request/resolve-uri.d.ts +0 -6
  71. package/@types/components/version/index.d.ts +0 -26
  72. package/@types/env.d.ts +0 -1
package/dist/router.js CHANGED
@@ -2,170 +2,203 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- exports.default = void 0;
6
- var _utils = require("@x-drive/utils");
7
- var _common = require("./components/common");
8
- var _defaultXConfig = _interopRequireDefault(require("./default-x-config"));
9
- var _path = _interopRequireDefault(require("path"));
10
- var _fs = _interopRequireDefault(require("fs"));
11
- function _interopRequireDefault(obj) {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get default () {
13
+ return _default;
14
+ },
15
+ get defineApi () {
16
+ return defineApi;
17
+ }
18
+ });
19
+ const _utils = require("@x-drive/utils");
20
+ const _common = require("./components/common");
21
+ const _config = require("./config");
22
+ const _defaultxconfig = /*#__PURE__*/ _interop_require_default(require("./default-x-config"));
23
+ const _path = /*#__PURE__*/ _interop_require_default(require("path"));
24
+ const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
25
+ function _interop_require_default(obj) {
12
26
  return obj && obj.__esModule ? obj : {
13
27
  default: obj
14
28
  };
15
29
  }
16
30
  /**API 服务目录 */ const API_BUSINESS_DIR = "business";
31
+ /**API 地址前缀检测 */ const IS_API_REGEXP = /^\/api\//;
32
+ /**支持的请求方法, 同时也是「文件名即方法」约定支持的文件名 */ const HTTP_METHODS = [
33
+ "get",
34
+ "post",
35
+ "put",
36
+ "delete",
37
+ "patch",
38
+ "head",
39
+ "options",
40
+ "all"
41
+ ];
42
+ /**
43
+ * 定义一个业务接口
44
+
45
+ * 运行时恒等返回, 仅为路由模块提供类型推导
46
+ */ function defineApi(def) {
47
+ return def;
48
+ }
17
49
  /**生成 api */ function buildApi(api) {
18
50
  if (!api.startsWith("/")) {
19
51
  api = `/${api}`;
20
52
  }
21
53
  return `/api${api}`;
22
54
  }
23
- /**
24
- * 递归处理文件夹
25
- * @param path 文件目录
26
- * @param middlewares 接口中间件
27
- * @param callback 查找到文件时的处理函数
28
- */ function walk(dir, middlewares, callback) {
29
- var files = _fs.default.readdirSync(dir);
30
- files.forEach(function(item) {
31
- if (!item.startsWith(".") && !item.endsWith(".d.ts")) {
32
- var tmpPath = _path.default.join(dir, item);
33
- var stats = _fs.default.statSync(tmpPath);
34
- if (stats.isDirectory() && item.startsWith("$")) {
35
- // 接口级别中间件
36
- let middlewaresFiles = _fs.default.readdirSync(tmpPath);
37
- let mws = [];
38
- middlewaresFiles.forEach(function(name) {
39
- let tmpMwPath = _path.default.join(tmpPath, name);
40
- let fileStat = _fs.default.statSync(tmpMwPath);
41
- if (!fileStat.isDirectory() && !item.startsWith(".") && !item.endsWith(".d.ts")) {
42
- mws.push(tmpMwPath);
43
- }
44
- });
45
- // 简单做下排序
46
- mws.sort();
47
- const partPath = dir.split(API_BUSINESS_DIR)[1] || _path.default.posix.sep;
48
- middlewares[partPath] = mws;
49
- }
50
- if (stats.isDirectory() && !item.startsWith("@") && !item.startsWith("$")) {
51
- walk(tmpPath, middlewares, callback);
52
- } else if (!stats.isDirectory()) {
53
- callback(tmpPath, item, middlewares);
55
+ /**把相对路径统一为以 / 开头的 url 风格路径 */ function toUrlPath(rel) {
56
+ return "/" + rel.split(_path.default.sep).filter(Boolean).join("/");
57
+ }
58
+ /**
59
+ * 递归收集业务目录下的路由文件与目录级中间件
60
+ * @param dir 当前目录
61
+ * @param root business 根目录
62
+ * @param middlewares 目录级中间件收集对象
63
+ * @param files 路由文件收集数组
64
+ */ function collect(dir, root, middlewares, files) {
65
+ _fs.default.readdirSync(dir).forEach(function(item) {
66
+ if (item.startsWith(".") || item.endsWith(".d.ts")) {
67
+ return;
68
+ }
69
+ const tmpPath = _path.default.join(dir, item);
70
+ const stats = _fs.default.statSync(tmpPath);
71
+ if (stats.isDirectory()) {
72
+ if (item.startsWith("$")) {
73
+ // 目录级中间件, 生效于所在层级及以下; 不递归, 按文件名排序执行
74
+ const mws = _fs.default.readdirSync(tmpPath).filter(function(name) {
75
+ return !name.startsWith(".") && !name.endsWith(".d.ts") && _fs.default.statSync(_path.default.join(tmpPath, name)).isFile();
76
+ }).sort().map((name)=>_path.default.join(tmpPath, name));
77
+ const key = toUrlPath(_path.default.relative(root, dir));
78
+ middlewares[key] = (middlewares[key] || []).concat(mws);
79
+ } else if (!item.startsWith("@")) {
80
+ collect(tmpPath, root, middlewares, files);
54
81
  }
82
+ } else {
83
+ files.push(tmpPath);
55
84
  }
56
85
  });
57
86
  }
58
- /**加载业务服务接口 */ function appRouter(router) {
59
- const IS_API_REGEXP = /^\/api\//;
60
- var processBusiness = function(modPath, item, middlewares) {
61
- var mod = require(_path.default.resolve(modPath));
62
- if (mod) {
63
- mod = (0, _common).getRealDefaultMod(mod);
64
- let api_path = modPath.split(API_BUSINESS_DIR);
65
- let api;
66
- let apiPart;
67
- let modName;
68
- // 根据路径生成 url 规则
69
- api = api_path[1].replace(".js", "").split(_path.default.posix.sep);
70
- // 文件名是 index
71
- const lastIsIndex = api[api.length - 1] === "index";
72
- // 文件名是 index 的则会被从 api 上先强行去掉
73
- // 如果需要命名成 index 的需要自己用 object 形式,用 api 字段定义
74
- if (lastIsIndex) {
75
- api.pop();
76
- }
77
- // 如果模块返回的是数组,则使用文件名做为方法名,并在 api 上去掉文件名
78
- if ((0, _utils).isArray(mod)) {
79
- if (!lastIsIndex) {
80
- api.pop();
81
- }
82
- apiPart = api;
83
- api = buildApi(api.join("/"));
84
- mod.unshift(api);
85
- } else {
86
- apiPart = api;
87
- // 不是数组则把文件当作正常的 api 地址
88
- api = buildApi(api.join("/"));
89
- }
90
- // 是对象则取对应的字段
91
- if ((0, _utils).isObject(mod)) {
92
- const subjectMod = mod;
93
- if ((0, _utils).isFunction(subjectMod.handler) || (0, _utils).isAsyncFunction(subjectMod.handler)) {
94
- modName = subjectMod.method || "get";
95
- api = subjectMod.api || api;
96
- apiPart = api.split("/");
97
- if (apiPart[0] !== "") {
98
- apiPart.unshift("");
99
- }
100
- // 如果没有声明忽略检测则保证接口一定是以 api 开头的
101
- if (!subjectMod.ignoreApiNameCheck && !IS_API_REGEXP.test(api)) {
102
- api = buildApi(api);
103
- }
104
- if ((0, _utils).isArray(subjectMod.middleware)) {
105
- mod = [
106
- api,
107
- ...subjectMod.middleware,
108
- subjectMod.handler
109
- ];
110
- } else {
111
- mod = [
112
- api,
113
- subjectMod.handler
114
- ];
115
- }
116
- }
117
- }
118
- var dirMws = [];
119
- var prevPart = "";
120
- for(let i = 0; i < apiPart.length; i++){
121
- if (!apiPart[i] || apiPart[i] === "api") {
122
- continue;
123
- }
124
- const part = `${prevPart}${_path.default.posix.sep}${apiPart[i]}`;
125
- if (middlewares[part]) {
126
- dirMws = dirMws.concat(middlewares[part]);
87
+ /**
88
+ * 把一个路由模块归一化为注册单元
89
+
90
+ * 支持的导出形态:
91
+ * 1. 函数: 绑定为处理函数, 方法由文件名约定决定
92
+ * 2. 对象(推荐配合 defineApi): { method?, api?, middleware?, handler, ignoreApiNameCheck? }
93
+ *
94
+ * 文件名约定: `index` 从 api 上去掉; 文件名是请求方法名(get/post/...)时,
95
+ * 从 api 上去掉并作为该接口的请求方法
96
+ * @param modPath 模块文件地址
97
+ * @param root business 根目录
98
+ */ function normalize(modPath, root) {
99
+ var mod = (0, _common.getRealDefaultMod)(require(_path.default.resolve(modPath)));
100
+ if (!mod) {
101
+ return null;
102
+ }
103
+ if ((0, _utils.isArray)(mod)) {
104
+ throw new Error(`[xlab] 数组形式的路由定义已在 v2 移除: ${modPath}\n` + "请改用 defineApi 对象形式或「文件名即方法」约定, 参见 MIGRATION.md");
105
+ }
106
+ const rel = _path.default.relative(root, modPath);
107
+ const ext = _path.default.extname(rel);
108
+ const parts = rel.slice(0, rel.length - ext.length).split(_path.default.sep).filter(Boolean);
109
+ const fileName = parts[parts.length - 1];
110
+ var method = "get";
111
+ if (fileName === "index") {
112
+ parts.pop();
113
+ } else if (HTTP_METHODS.indexOf(fileName) !== -1) {
114
+ method = fileName;
115
+ parts.pop();
116
+ }
117
+ var api = buildApi(parts.join("/"));
118
+ var middlewares = [];
119
+ var handler = null;
120
+ if ((0, _utils.isFunction)(mod) || (0, _utils.isAsyncFunction)(mod)) {
121
+ handler = mod;
122
+ } else if ((0, _utils.isObject)(mod)) {
123
+ const def = mod;
124
+ if ((0, _utils.isFunction)(def.handler) || (0, _utils.isAsyncFunction)(def.handler)) {
125
+ handler = def.handler;
126
+ method = def.method || method;
127
+ if ((0, _utils.isString)(def.api)) {
128
+ api = def.api;
129
+ if (!api.startsWith("/")) {
130
+ api = `/${api}`;
127
131
  }
128
- prevPart = part;
129
- }
130
- // 直接返回函数则直接绑定
131
- if ((0, _utils).isFunction(mod) || (0, _utils).isAsyncFunction(mod)) {
132
- mod = [
133
- api,
134
- mod
135
- ];
136
- }
137
- if (!modName) {
138
- modName = item.replace(".js", "");
139
- if (modName === "index") {
140
- modName = "get";
132
+ // 如果没有声明忽略检测则保证接口一定是以 api 开头的
133
+ if (!def.ignoreApiNameCheck && !IS_API_REGEXP.test(api)) {
134
+ api = buildApi(api);
141
135
  }
142
136
  }
143
- if (dirMws && dirMws.length) {
144
- let mws = dirMws.map((name)=>(0, _common).getRealDefaultMod(require(name))
145
- );
146
- mod.splice.apply(mod, [
147
- 1,
148
- 0,
149
- ...mws
150
- ]);
151
- mws = null;
137
+ if ((0, _utils.isArray)(def.middleware)) {
138
+ middlewares = def.middleware;
152
139
  }
153
- router[modName].apply(router, mod);
154
- masterLog("router", `Handling API >>> [${modName}] ${api}`);
155
140
  }
141
+ }
142
+ if (!handler) {
143
+ return null;
144
+ }
145
+ return {
146
+ method,
147
+ api,
148
+ middlewares,
149
+ handler
156
150
  };
157
- // 业务基本目录
151
+ }
152
+ /**
153
+ * 解析一个 api 命中的目录级中间件链
154
+ * @param api api 地址
155
+ * @param middlewares 目录级中间件映射
156
+ */ function resolveDirMiddlewares(api, middlewares) {
157
+ var chain = [];
158
+ if (middlewares["/"]) {
159
+ chain = chain.concat(middlewares["/"]);
160
+ }
161
+ const parts = api.split("/").filter((part)=>part && part !== "api");
162
+ var prev = "";
163
+ for (const part of parts){
164
+ prev = `${prev}/${part}`;
165
+ if (middlewares[prev]) {
166
+ chain = chain.concat(middlewares[prev]);
167
+ }
168
+ }
169
+ return chain.map((file)=>(0, _common.getRealDefaultMod)(require(file)));
170
+ }
171
+ /**加载业务服务接口 */ function appRouter(router) {
172
+ function register(root) {
173
+ const middlewares = {};
174
+ const files = [];
175
+ collect(root, root, middlewares, files);
176
+ files.forEach(function(modPath) {
177
+ const route = normalize(modPath, root);
178
+ if (!route) {
179
+ (0, _config.log)("router", "warn", `Ignore invalid api module: ${modPath}`);
180
+ return;
181
+ }
182
+ const dirMws = resolveDirMiddlewares(route.api, middlewares);
183
+ router[route.method === "delete" ? "del" : route.method].apply(router, [
184
+ route.api,
185
+ ...dirMws,
186
+ ...route.middlewares,
187
+ route.handler
188
+ ]);
189
+ (0, _config.log)("router", `Handling API >>> [${route.method}] ${route.api}`);
190
+ });
191
+ }
192
+ // 框架内置业务
158
193
  var _p = _path.default.resolve(__dirname, API_BUSINESS_DIR);
159
- if ((0, _common).checkFileStat(_p)) {
160
- walk(_p, {}, processBusiness);
194
+ if ((0, _common.checkFileStat)(_p)) {
195
+ register(_p);
161
196
  }
162
- // 支持自定义 api
163
- _p = _path.default.resolve(process.cwd(), _defaultXConfig.default.businessDir, API_BUSINESS_DIR);
164
- if (process.cwd() !== __dirname && (0, _common).checkFileStat(_p)) {
165
- walk(_p, {}, processBusiness);
197
+ // 业务自定义业务
198
+ _p = _path.default.resolve(process.cwd(), _defaultxconfig.default.businessDir, API_BUSINESS_DIR);
199
+ if (process.cwd() !== __dirname && (0, _common.checkFileStat)(_p)) {
200
+ register(_p);
166
201
  }
167
- processBusiness = null;
168
202
  console.log("");
169
203
  }
170
- var _default = appRouter;
171
- exports.default = _default;
204
+ const _default = appRouter;
package/dist/server.js CHANGED
@@ -2,120 +2,222 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- exports.default = exports.exit = exports.boot = exports.app = void 0;
6
- require("./global");
7
- require("./init-env");
8
- var _log = require("./components/log");
9
- var _cron = require("./components/cron");
10
- var _defaultXConfig = _interopRequireDefault(require("./default-x-config"));
11
- var _dotFile = require("./dot-file");
12
- var _koaStatic = _interopRequireDefault(require("koa-static"));
13
- var _koaRouter = _interopRequireDefault(require("koa-router"));
14
- var _path = _interopRequireDefault(require("path"));
15
- var _koa = _interopRequireDefault(require("koa"));
16
- var _config = _interopRequireWildcard(require("./config"));
17
- var _custom = _interopRequireDefault(require("./custom"));
18
- var _router = _interopRequireDefault(require("./router"));
19
- var _cluster = _interopRequireDefault(require("./cluster"));
20
- var _middlewares = _interopRequireDefault(require("./middlewares"));
21
- var _badRequest = _interopRequireDefault(require("./middleware/bad-request"));
22
- function _interopRequireDefault(obj) {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get app () {
13
+ return app;
14
+ },
15
+ get boot () {
16
+ return boot;
17
+ },
18
+ get default () {
19
+ return _default;
20
+ },
21
+ get exit () {
22
+ return exit;
23
+ },
24
+ get getApp () {
25
+ return getApp;
26
+ },
27
+ get shutdown () {
28
+ return shutdown;
29
+ }
30
+ });
31
+ const _log = require("./components/log");
32
+ const _cron = require("./components/cron");
33
+ const _defaultxconfig = /*#__PURE__*/ _interop_require_default(require("./default-x-config"));
34
+ const _dotfile = require("./dot-file");
35
+ const _config = /*#__PURE__*/ _interop_require_wildcard(require("./config"));
36
+ const _custom = require("./custom");
37
+ const _initenv = /*#__PURE__*/ _interop_require_default(require("./init-env"));
38
+ const _cluster = /*#__PURE__*/ _interop_require_wildcard(require("./cluster"));
39
+ const _router = /*#__PURE__*/ _interop_require_default(require("./router"));
40
+ const _middlewares = /*#__PURE__*/ _interop_require_default(require("./middlewares"));
41
+ const _badrequest = /*#__PURE__*/ _interop_require_default(require("./middleware/bad-request"));
42
+ const _utils = require("@x-drive/utils");
43
+ const _koastatic = /*#__PURE__*/ _interop_require_default(require("koa-static"));
44
+ const _koarouter = /*#__PURE__*/ _interop_require_default(require("koa-router"));
45
+ const _path = /*#__PURE__*/ _interop_require_default(require("path"));
46
+ const _koa = /*#__PURE__*/ _interop_require_default(require("koa"));
47
+ function _interop_require_default(obj) {
23
48
  return obj && obj.__esModule ? obj : {
24
49
  default: obj
25
50
  };
26
51
  }
27
- function _interopRequireWildcard(obj) {
28
- if (obj && obj.__esModule) {
29
- return obj;
30
- } else {
31
- var newObj = {};
32
- if (obj != null) {
33
- for(var key in obj){
34
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
35
- var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};
36
- if (desc.get || desc.set) {
37
- Object.defineProperty(newObj, key, desc);
38
- } else {
39
- newObj[key] = obj[key];
40
- }
41
- }
42
- }
52
+ function _getRequireWildcardCache(nodeInterop) {
53
+ if (typeof WeakMap !== "function") return null;
54
+ var cacheBabelInterop = new WeakMap();
55
+ var cacheNodeInterop = new WeakMap();
56
+ return (_getRequireWildcardCache = function(nodeInterop) {
57
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
58
+ })(nodeInterop);
59
+ }
60
+ function _interop_require_wildcard(obj, nodeInterop) {
61
+ if (!nodeInterop && obj && obj.__esModule) return obj;
62
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") return {
63
+ default: obj
64
+ };
65
+ var cache = _getRequireWildcardCache(nodeInterop);
66
+ if (cache && cache.has(obj)) return cache.get(obj);
67
+ var newObj = {
68
+ __proto__: null
69
+ };
70
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
71
+ for(var key in obj){
72
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
73
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
74
+ if (desc && (desc.get || desc.set)) Object.defineProperty(newObj, key, desc);
75
+ else newObj[key] = obj[key];
43
76
  }
44
- newObj.default = obj;
45
- return newObj;
46
77
  }
78
+ newObj.default = obj;
79
+ if (cache) cache.set(obj, newObj);
80
+ return newObj;
47
81
  }
48
- process.env.TZ = _config.default.timezone;
49
82
  const logger = _log.globalLog.getLogger("system");
50
- (0, _dotFile).processDotFile(_config.default.env, logger);
51
- // 只在 master 上输出的日志
52
- global.masterLog = _config.log;
53
- // 服务实例
54
- const app = new _koa.default();
55
- /**
56
- * 获取应用实例对象
57
- * @deprecated v1.7.0 后将弃用,请直接从模块中引用
58
- * @return 实例对象
59
- */ global.getApp = function() {
60
- return app;
61
- };
83
+ /**服务实例 */ const app = new _koa.default();
62
84
  // 不输出X-Powered-By
63
85
  // @ts-ignore
64
86
  app.poweredBy = false;
65
- if (_config.default.custom) {
66
- // 自定义初始化业务
67
- (0, _custom).default();
87
+ /**
88
+ * 获取应用实例对象
89
+ * @return 实例对象
90
+ */ function getApp() {
91
+ return app;
68
92
  }
69
- // 加载中间件
70
- (0, _middlewares).default(app);
71
- const koaRouter = new _koaRouter.default();
72
- // 路由中间件
73
- app.use(koaRouter.routes());
74
- // 具体路由
75
- (0, _router).default(koaRouter);
76
- // 静态文件服务
77
- var staticServeOpts = {};
78
- if (_config.default.isProd) {
79
- staticServeOpts.maxage = _config.default.staticMaxage;
93
+ /**http 服务实例, master fork 模式下为空 */ var server = null;
94
+ /**是否已启动 */ var booted = false;
95
+ /**是否正在退出 */ var shuttingDown = false;
96
+ /**
97
+ * 关闭 http 服务, 等待在途请求结束, 超时强制返回
98
+ * @param timeout 超时时间(毫秒)
99
+ */ function closeServer(timeout) {
100
+ return new Promise(function(resolve) {
101
+ const timer = setTimeout(function() {
102
+ logger.warn(`等待在途请求结束超时(${timeout}ms), 强制关闭`);
103
+ resolve();
104
+ }, timeout);
105
+ server.close(function() {
106
+ clearTimeout(timer);
107
+ resolve();
108
+ });
109
+ // 主动断开空闲的 keep-alive 连接, 避免拖住 close 回调
110
+ if ((0, _utils.isExecutable)(server.closeIdleConnections)) {
111
+ server.closeIdleConnections();
112
+ }
113
+ });
80
114
  }
81
- staticServeOpts.setHeaders = function(res, path) {
82
- if (_config.default.isProd && _config.default.staticHtmlFileMaxage && path.endsWith(".html")) {
83
- res.setHeader("Cache-Control", `max-age=${_config.default.staticHtmlFileMaxage}`);
115
+ /**
116
+ * 退出服务
117
+
118
+ * 停止定时任务 -> 通知 worker 退出(master) -> 等待在途请求 -> 执行 shutdown 钩子 -> 退出进程
119
+ * @param code 进程退出码
120
+ */ async function shutdown(code = 0) {
121
+ if (shuttingDown) {
122
+ return;
84
123
  }
85
- if (_config.default.staticCros) {
86
- const { origin } = res.req.headers;
87
- if (origin) {
88
- res.setHeader("X-Cros-Static", origin);
89
- res.setHeader("Access-Control-Allow-Origin", origin);
90
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
124
+ shuttingDown = true;
125
+ const config = (0, _config.get)();
126
+ (0, _config.log)("system", `Shutting down (pid ${process.pid}) ...`);
127
+ try {
128
+ (0, _cron.kill)();
129
+ if (config.isMaster && config.workers) {
130
+ await (0, _cluster.shutdownWorkers)(config.shutdownTimeout);
91
131
  }
132
+ if (server) {
133
+ await closeServer(config.shutdownTimeout || 10 * 1000);
134
+ }
135
+ await (0, _custom.runShutdown)();
136
+ } catch (e) {
137
+ logger.error("Shutdown error:", e);
92
138
  }
93
- };
94
- app.use((0, _koaStatic).default(_path.default.resolve(process.cwd(), _config.default.root), staticServeOpts));
95
- // 异常状态处理中间件
96
- app.use(_badRequest.default);
97
- if (_config.default.enableCron) {
98
- if (_config.default.isMaster || _config.default.enableWorkerCron) {
99
- // 定时任务
100
- (0, _cron).on(_path.default.resolve(process.cwd(), _defaultXConfig.default.businessDir, "cron"));
101
- }
139
+ process.exit(code);
140
+ }
141
+ /**
142
+ * 退出服务
143
+ * @deprecated 请使用 shutdown
144
+ */ function exit() {
145
+ shutdown();
102
146
  }
103
- /**启动服务 */ function boot() {
104
- (0, _cluster).default();
105
- process.on("uncaughtException", (err)=>{
147
+ /**
148
+ * 启动服务
149
+
150
+ * 按固定顺序执行启动阶段:
151
+ * initEnv -> loadConfig -> dotFile -> custom(setup) -> middleware
152
+ * -> router -> static/badRequest -> cron -> listen -> custom(ready)
153
+ */ async function boot() {
154
+ if (booted) {
155
+ return;
156
+ }
157
+ booted = true;
158
+ // 1. 业务环境预处理(@server/@env)
159
+ (0, _initenv.default)();
160
+ // 2. 配置装载
161
+ const config = (0, _config.default)();
162
+ process.env.TZ = config.timezone;
163
+ // 3. .xlab 环境数据
164
+ (0, _dotfile.processDotFile)(config.env, logger);
165
+ // 4. 自定义初始化(setup 钩子)
166
+ if (config.custom) {
167
+ await (0, _custom.loadCustom)();
168
+ }
169
+ // 5. 中间件
170
+ (0, _middlewares.default)(app);
171
+ // 6. 业务路由
172
+ const koaRouter = new _koarouter.default();
173
+ app.use(koaRouter.routes());
174
+ (0, _router.default)(koaRouter);
175
+ // 7. 静态文件服务
176
+ var staticServeOpts = {};
177
+ if (config.isProd) {
178
+ staticServeOpts.maxage = config.staticMaxage;
179
+ }
180
+ staticServeOpts.setHeaders = function(res, filePath) {
181
+ if (config.isProd && config.staticHtmlFileMaxage && filePath.endsWith(".html")) {
182
+ res.setHeader("Cache-Control", `max-age=${config.staticHtmlFileMaxage}`);
183
+ }
184
+ if (config.staticCros) {
185
+ const { origin } = res.req.headers;
186
+ if (origin) {
187
+ res.setHeader("X-Cros-Static", origin);
188
+ res.setHeader("Access-Control-Allow-Origin", origin);
189
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
190
+ }
191
+ }
192
+ };
193
+ app.use((0, _koastatic.default)(_path.default.resolve(process.cwd(), config.root), staticServeOpts));
194
+ // 8. 异常状态处理中间件
195
+ app.use(_badrequest.default);
196
+ // 9. 定时任务
197
+ if (config.enableCron) {
198
+ if (config.isMaster || config.enableWorkerCron) {
199
+ (0, _cron.on)(_path.default.resolve(process.cwd(), _defaultxconfig.default.businessDir, "cron"));
200
+ }
201
+ }
202
+ // 10. 监听
203
+ server = (0, _cluster.default)(app);
204
+ // 11. 进程信号与异常处理
205
+ process.on("SIGTERM", function() {
206
+ shutdown();
207
+ }).on("SIGINT", function() {
208
+ shutdown();
209
+ }).on("uncaughtException", (err)=>{
106
210
  logger.error(`uncaughtException:${err.message}\n`, err.stack);
107
211
  }).on("unhandledRejection", (err)=>{
108
212
  logger.error(`unhandledRejection:${err.message}\n`, err.stack);
109
213
  });
214
+ // 12. ready 钩子
215
+ await (0, _custom.runReady)();
110
216
  }
111
- /**退出服务 */ function exit() {
112
- process.exit();
113
- }
114
- var _default = app;
115
- exports.default = _default;
217
+ const _default = app;
116
218
  if (require.main === module) {
117
- boot();
219
+ boot().catch(function(err) {
220
+ logger.error("Boot failed:", err);
221
+ process.exit(1);
222
+ });
118
223
  }
119
- exports.app = app;
120
- exports.boot = boot;
121
- exports.exit = exit;