@codertqy/elpis 1.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 (83) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +55 -0
  3. package/README.md +218 -0
  4. package/app/controller/base.js +40 -0
  5. package/app/controller/project.js +81 -0
  6. package/app/controller/view.js +22 -0
  7. package/app/extend/logger.js +43 -0
  8. package/app/middleware/api-params-verify.js +73 -0
  9. package/app/middleware/api-sign-verify.js +49 -0
  10. package/app/middleware/error-handler.js +31 -0
  11. package/app/middleware/project-handler.js +26 -0
  12. package/app/middleware.js +44 -0
  13. package/app/pages/assets/custom.css +14 -0
  14. package/app/pages/boot.js +56 -0
  15. package/app/pages/common/curl.js +84 -0
  16. package/app/pages/common/index.css +3 -0
  17. package/app/pages/common/utils.js +1 -0
  18. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +19 -0
  19. package/app/pages/dashboard/complex-view/header-view/header-view.vue +126 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +35 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +120 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +87 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +100 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +122 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +161 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +95 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +19 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +135 -0
  31. package/app/pages/dashboard/dashboard.vue +86 -0
  32. package/app/pages/dashboard/entry.dashboard.js +48 -0
  33. package/app/pages/store/index.js +3 -0
  34. package/app/pages/store/menu.js +68 -0
  35. package/app/pages/store/project.js +12 -0
  36. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  37. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  38. package/app/pages/widgets/header-container/header-container.vue +107 -0
  39. package/app/pages/widgets/schema-form/complex-view/input/input.vue +138 -0
  40. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +140 -0
  41. package/app/pages/widgets/schema-form/complex-view/select/select.vue +122 -0
  42. package/app/pages/widgets/schema-form/form-item-config.js +20 -0
  43. package/app/pages/widgets/schema-form/schema-form.vue +135 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +51 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +63 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +41 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +49 -0
  48. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +126 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +22 -0
  50. package/app/pages/widgets/schema-table/schema-table.vue +259 -0
  51. package/app/pages/widgets/sider-container/sider-container.vue +27 -0
  52. package/app/public/output/entry.page1.tpl +40 -0
  53. package/app/public/output/entry.page2.tpl +11 -0
  54. package/app/public/static/logo.png +0 -0
  55. package/app/public/static/normalize.css +239 -0
  56. package/app/router/project.js +22 -0
  57. package/app/router/view.js +17 -0
  58. package/app/router-schema/project.js +34 -0
  59. package/app/service/base.js +15 -0
  60. package/app/service/project.js +59 -0
  61. package/app/view/entry.tpl +25 -0
  62. package/app/webpack/config/webpack.base.js +280 -0
  63. package/app/webpack/config/webpack.dev.js +57 -0
  64. package/app/webpack/config/webpack.prod.js +127 -0
  65. package/app/webpack/dev.js +63 -0
  66. package/app/webpack/libs/blank.js +1 -0
  67. package/app/webpack/prod.js +22 -0
  68. package/config/config.beta.js +1 -0
  69. package/config/config.default.js +3 -0
  70. package/config/config.prod.js +1 -0
  71. package/elpis-core/env.js +27 -0
  72. package/elpis-core/index.js +98 -0
  73. package/elpis-core/loader/config.js +58 -0
  74. package/elpis-core/loader/controller.js +93 -0
  75. package/elpis-core/loader/extend.js +63 -0
  76. package/elpis-core/loader/middleware.js +84 -0
  77. package/elpis-core/loader/router-schema.js +56 -0
  78. package/elpis-core/loader/router.js +50 -0
  79. package/elpis-core/loader/service.js +85 -0
  80. package/index.js +38 -0
  81. package/model/index.js +129 -0
  82. package/package.json +92 -0
  83. package/test/controller/project.test.js +214 -0
package/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ public/
package/.eslintrc ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "extends": [
3
+ "plugin:vue/base",
4
+ "plugin:vue/recommended"
5
+ ],
6
+ "plugins": ["vue"],
7
+ "env": {
8
+ "browser": true,
9
+ "node": true
10
+ },
11
+ "parser": "vue-eslint-parser",
12
+ "parserOptions": {
13
+ "parser": "babel-eslint",
14
+ "ecmaVersion": 2017,
15
+ "sourceType": "module"
16
+ },
17
+ "rules": {
18
+ "no-unused-vars": [2, {"args": "none"}],
19
+ "strict": "off",
20
+ "valid-jsdoc": "off",
21
+ "jsdoc/require-param-description": "off",
22
+ "jsdoc/require-param-type": "off",
23
+ "jsdoc/check-param-names": "off",
24
+ "jsdoc/require-param": "off",
25
+ "jsdoc/check-tag-names": "off",
26
+ "linebreak-style": "off",
27
+ "array-bracket-spacing": "off",
28
+ "prefer-promise-reject-errors": "off",
29
+ "comma-dangle": "off",
30
+ "newline-per-chained-call": "off",
31
+ "no-loop-func": "off",
32
+ "no-empty": "off",
33
+ "no-else-return": "off",
34
+ "no-unneeded-ternary": "off",
35
+ "no-eval": "off",
36
+ "prefer-destructuring": "off",
37
+ "no-param-reassign": "off",
38
+ "max-len": "off",
39
+ "no-restricted-syntax": "off",
40
+ "no-plusplus": "off",
41
+ "no-useless-escape": "off",
42
+ "no-nested-ternary": "off",
43
+ "radix": "off",
44
+ "arrow-body-style": "off",
45
+ "arrow-parens": "off",
46
+ "vue/multi-word-component-names": "off",
47
+ "vue/valid-v-for": "off",
48
+ "vue/no-multiple-template-root": "off"
49
+ },
50
+ "globals": {
51
+ "$": true,
52
+ "axios": true,
53
+ "Vue": true
54
+ }
55
+ }
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # elpis
2
+
3
+ ## 这是一个企业级应用框架,通过全栈实现
4
+
5
+ ### model 配置
6
+
7
+ ```javascript
8
+ {
9
+ mode: "dashboard"; // 模版类型,不同模版类型对应不一样的模版数据结构
10
+ name: ""; // 名称
11
+ desc: ""; // 描述
12
+ icon: ""; // icon
13
+ homePgae: ""; // 首页(项目配置)
14
+ // 头部菜单 多个
15
+ menu: [
16
+ {
17
+ key: "", //菜单唯一描述
18
+ name: "", //菜单名称
19
+ menuType: "", // 枚举值:group / module
20
+
21
+ // 当 menuType === group 时,可填
22
+ subMenu: [
23
+ {
24
+ // 可递归 menuItem
25
+ },
26
+ // ... 表示多个
27
+ ],
28
+ // 当 menuType === module 时,可填
29
+ moduleType: "", // 枚举值:sider / iframe / custom / schema
30
+
31
+ // 当 moduleType === sider 时 左侧菜单
32
+ siderConfig: {
33
+ menu: [
34
+ {
35
+ // 可递归 menuItem ( 除 moduleType === sider)
36
+ },
37
+ ],
38
+ },
39
+ // 当 moduleType === iframe 时
40
+ iframeConfig: {
41
+ path: "", // iframe 路径
42
+ },
43
+ // 当 moduleType === custom 时
44
+ customConfig: {
45
+ path: "", // custom 路径
46
+ },
47
+ // 当 moduleType === schema 时
48
+ schemaConfig: {
49
+ api: "", // 数据源 API (遵循 RESTFUL 规范)
50
+ shcema: {
51
+ // 板块数据结构
52
+ type: "object",
53
+ properties: {
54
+ key: {
55
+ ...schema, // 标准 schema 配置
56
+ type: "", // 字段类型
57
+ tabel: "", // 字段的中文名
58
+ // 字段在 table 中的相关配置
59
+ tableOption: {
60
+ ...elTableColumnConfig, // 标准 el-table-column 配置
61
+ visiable: true, // 默认为 true (false ,表示不在表单中显示)
62
+ toFixed: 2,
63
+ },
64
+ // 字段在 search-abr 中的相关配置
65
+ searchOption: {
66
+ ...elComponentConfig, // 标准 el-component-column 配置 比如组件库中的input、form等
67
+ comType: "", // 配置组件类型 input/select/....
68
+ default: "", // 默认值
69
+
70
+ // comType ===='select'
71
+ enumList: [], // 下拉框可选项
72
+ // comType ===='dynamicSelect'
73
+ api: "", // 请求api获取
74
+ },
75
+ // 字段在不同状态 component 中的相关配置,前缀对应 componentConfig 中的键值
76
+ // 如:componentConfig.createForm 这里对应 createFormOption
77
+ // 字段在 createForm 中相关配置
78
+ createFormOption: {
79
+ ...elTableColumnConfig, // 标准 el-component-column 配置
80
+ comType: "", // 控件类型 input/select/....
81
+ visible: true, // 是否展示(true/false) 默认为 true
82
+ disabled: false, //是否禁用(true/false) 默认为 false
83
+ default: "", //默认值
84
+
85
+ // comType ===='select'时生效
86
+ enumList: [], //枚举列表
87
+ },
88
+
89
+ // 字段在 editForm 表单中的相关配置
90
+ editFormOption: {
91
+ ...elTableColumnConfig, // 标准 el-component-column 配置
92
+ comType: "", // 控件类型 input/select/....
93
+ visible: true, // 是否展示(true/false) 默认为 true
94
+ disabled: false, //是否禁用(true/false) 默认为 false
95
+ default: "", //默认值
96
+
97
+ // comType ===='select'时生效
98
+ enumList: [], //枚举列表
99
+ },
100
+ detailPanelOption: {
101
+ ...elTableColumnConfig, // 标准 el-component-column 配置
102
+ },
103
+ },
104
+ },
105
+ required: [], // 标记哪些字段是必填
106
+ },
107
+ // table 相关配置
108
+ tableConfig: {
109
+ // 多个
110
+ headerButtons: [
111
+ {
112
+ label: "", //按钮中文名
113
+ eventKey: "", // 按钮事件名
114
+ // 按钮事件具体操作配置
115
+ eventOption: {
116
+ // 当 eventKey === 'showComponent'
117
+ comName: "", //组件名称
118
+ },
119
+ ...elButtonConfig, // 标准 el-button 配置 样式外观
120
+ },
121
+ ],
122
+ // 多个
123
+ rowButtons: [
124
+ {
125
+ label: "", //按钮中文名
126
+ eventKey: "", // 按钮事件名
127
+ eventOption: {}, // 按钮事件具体操作配置
128
+
129
+ // 当 eventKey === 'showComponent'
130
+ comName: "", //组件名称
131
+
132
+ // 当eventKey === 'remove'
133
+ params: {
134
+ // paramKey 为参数的键
135
+ // rowValueKey 为参数值(当格式为 schema::xxx 的时候,到 table中找响应字段)
136
+ // user_id: schema::1
137
+ paramKey: rowValueKey,
138
+ },
139
+ ...elButtonConfig, // 标准 el-button 配置 样式外观
140
+ },
141
+ ],
142
+ },
143
+ // search-bar 相关配置
144
+ searchConfig: {},
145
+ // 动态组件 相关配置 模块组件
146
+ componentConfig: {
147
+ // create-form 表单相关配置
148
+ createForm: {
149
+ title: "", // 表单标题
150
+ saveBtnText: "", //保存按钮文案
151
+ },
152
+ editForm: {
153
+ mainKey: "", // 表单主键,用于唯一标识要修改的数据对象
154
+ title: "", // 表单标题
155
+ saveBtnText: "", // 保存按钮文案
156
+ },
157
+ // detail-panel 相关配置
158
+ detailPanel: {
159
+ mainKey: "", // 表单主键,用于唯一标识要修改的数据对象
160
+ title: "", // 表单标题
161
+ },
162
+ // ...支持用户动态扩展
163
+ },
164
+ },
165
+ },
166
+ ];
167
+ }
168
+ ```
169
+
170
+ ### 服务端启动
171
+
172
+ ```javascript
173
+ const { serverStart } = require("@codertqy/elpis");
174
+
175
+ // 启动 elpis 服务
176
+ const app = serverStart({});
177
+ ```
178
+
179
+ ### 自定义服务端
180
+
181
+ - router-schema
182
+ - router
183
+ - controller
184
+ - service
185
+ - extend
186
+ - config
187
+
188
+ ### 前端构建
189
+
190
+ ```javascript
191
+ const { frontendBuild } = require("@codertqy/elpis");
192
+
193
+ // 编译构建前端工程
194
+ frontendBuild(process.env._ENV);
195
+ ```
196
+
197
+ ### 自定义页面扩展
198
+
199
+ - 在 `app/pages/` 目录下写入口 entry.xxx.js
200
+
201
+ ### dashboard/custom-view 自定义页面扩展
202
+
203
+ - 在 `app/pages/dashboard/xxx` 下写页面
204
+
205
+ ### dashboard/schema-view/components 动态组件扩展
206
+
207
+ 1. 在 `app/pages/dashboard/complex-view/schema-view/components` 下写组件
208
+ 2. 配置到 `app/pages/dashboard/complex-view/schema-view/components/component-config.js`
209
+
210
+ ### dashboard/schema-view/components 动态组件扩展
211
+
212
+ 1. 在 `app/widgets/schema-form/complex-view` 下写控件
213
+ 2. 配置到 `app/widgets/schema-form/form-item-config.js`
214
+
215
+ ### dashboard/schema-view/components 动态组件扩展
216
+
217
+ 1. 在 `app/widgets/schema-search-bar/complex-view` 下写控件
218
+ 2. 配置到 `app/widgets/schema-search-bar/search-item-config.js`
@@ -0,0 +1,40 @@
1
+ module.exports = (app) => {
2
+ return class BaseController {
3
+ /**
4
+ * 基类
5
+ * 统一收拢 controller 相关的公共方法
6
+ */
7
+ constructor() {
8
+ this.app = app;
9
+ this.config = app.config;
10
+ }
11
+
12
+ /**
13
+ * API处理成功时统一返回结构
14
+ * @param {Object} ctx 上下文
15
+ * @param {Object} data 核心数据
16
+ * @param {Object} metadata 附加数据
17
+ */
18
+ success(ctx, data = {}, metadata = {}) {
19
+ ctx.status = 200;
20
+ ctx.body = {
21
+ success: true,
22
+ data,
23
+ metadata,
24
+ };
25
+ }
26
+ /**
27
+ * API处理失败时统一返回结构
28
+ * @param {Object} ctx 上下文
29
+ * @param {Object} message 错误信息
30
+ * @param {Object} code 错误码
31
+ */
32
+ error(ctx, message, code) {
33
+ ctx.body = {
34
+ success: false,
35
+ message,
36
+ code,
37
+ };
38
+ }
39
+ };
40
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 第二步:编写controller层 对数据进行处理
3
+ * @param {Object} app
4
+ * @returns
5
+ */
6
+
7
+ module.exports = (app) => {
8
+ const BaseController = require("./base")(app);
9
+ return class ProjectController extends BaseController {
10
+ /**
11
+ * 根据 proj_key 获取项目配置
12
+ * @param {Object} ctx
13
+ */
14
+ async get(ctx) {
15
+ const { proj_key: projKey } = ctx.request.query;
16
+ const { project: ProjectService } = app.service;
17
+ const projConfig = await ProjectService.get(projKey);
18
+ if (!projConfig) {
19
+ return this.error(ctx, "获取项目异常", 50000);
20
+ }
21
+ this.success(ctx, projConfig);
22
+ }
23
+ /**
24
+ * 获取当前 projKey 对应模型下的项目列表(如果无 projKey ,全量获取)
25
+ */
26
+ async getList(ctx) {
27
+ const { proj_key: projKey } = ctx.request.query;
28
+ const { project: ProjectService } = app.service;
29
+ const projectList = await ProjectService.getList({ projKey });
30
+ // 构造关键数据 list
31
+ const dtoProjectList = projectList.map((item) => {
32
+ return {
33
+ modelKey: item.modelKey,
34
+ key: item.key,
35
+ name: item.name,
36
+ desc: item.desc,
37
+ homePage: item.homePage,
38
+ };
39
+ });
40
+
41
+ this.success(ctx, dtoProjectList);
42
+ }
43
+ /**
44
+ * 获取所有模型与项目的结构化数据
45
+ *
46
+ */
47
+ async getModelList(ctx) {
48
+ const { project: ProjectService } = app.service;
49
+ const modelList = await ProjectService.getModelList();
50
+
51
+ // 构造返回结构,只返回关键数据
52
+ const dtoModelList = modelList.reduce((preList, item) => {
53
+ const { model, project } = item;
54
+
55
+ // 构造 model 关键数据
56
+ const { key, name, desc } = model;
57
+ const dtoModel = { key, name, desc };
58
+
59
+ // 构造 project 关键数据
60
+ const dtoProject = Object.keys(project).reduce((preObj, projKey) => {
61
+ const { key, name, desc, homePage } = project[projKey];
62
+ preObj[projKey] = { key, name, desc, homePage };
63
+ return preObj;
64
+ }, {});
65
+ // for (const projKey in project) {
66
+ // const { key, name, desc, homePage } = project[projKey];
67
+ // dtoProject[projKey] = { key, name, desc, homePage };
68
+ // }
69
+
70
+ preList.push({
71
+ model: dtoModel,
72
+ project: dtoProject,
73
+ });
74
+
75
+ return preList;
76
+ }, []);
77
+
78
+ this.success(ctx, dtoModelList);
79
+ }
80
+ };
81
+ };
@@ -0,0 +1,22 @@
1
+ module.exports = (app) => {
2
+ return class ViewController {
3
+ /**
4
+ * 渲染页面
5
+ * @param {*} ctx 上下文
6
+ */
7
+ async renderPage(ctx) {
8
+ const { query, params } = ctx.request;
9
+ app.logger.info(`[ViewController] query: ${JSON.stringify(query)}`);
10
+ app.logger.info(`[ViewController] params: ${JSON.stringify(params)}`);
11
+ // 拿到用户传递的page1,page2 ,第二个参数可以是给页面传递的一些值
12
+ await ctx.render(`dist/entry.${ctx.params.page}`, {
13
+ // 通过 SSR 的形式注入到页面中
14
+ projKey: ctx.query?.proj_key,
15
+ // 因为options的一些配置已经挂载到app上
16
+ name: app.options?.name,
17
+ env: app.env.get(),
18
+ options: JSON.stringify(app.options),
19
+ });
20
+ }
21
+ };
22
+ };
@@ -0,0 +1,43 @@
1
+ const log4js = require("log4js");
2
+
3
+ /**
4
+ * 日志工具
5
+ * 外部调用 因为是通过app[name]的方式挂载的,所以可以直接.调用
6
+ * 在之后使用console打印,都使用app.logger.对应的信息
7
+ * 如果是本地,经过转换就是console.log
8
+ * 如果不是本地,则直接调用log4js上的方法,带有时间
9
+ * @param {Object} app
10
+ * @returns
11
+ */
12
+ module.exports = (app) => {
13
+ let logger;
14
+ if (app.env.isLocal()) {
15
+ // 打印在控制台
16
+ logger = console;
17
+ } else {
18
+ // 把日志输出并落地到磁盘(日志落盘)
19
+ log4js.configure({
20
+ appenders: {
21
+ console: {
22
+ type: "console",
23
+ },
24
+ // 日志文件切分
25
+ dateFile: {
26
+ type: "dateFile",
27
+ filename: "./logs/application.log",
28
+ pattern: ".yyyy-MM-dd",
29
+ },
30
+ },
31
+ categories: {
32
+ default: {
33
+ appenders: ["console", "dateFile"],
34
+ level: "trace",
35
+ },
36
+ },
37
+ });
38
+
39
+ logger = log4js.getLogger();
40
+ }
41
+
42
+ return logger;
43
+ };
@@ -0,0 +1,73 @@
1
+ const Ajv = require("ajv");
2
+ const ajv = new Ajv();
3
+
4
+ /**
5
+ * API 参数校验
6
+ * @param {Object} app
7
+ */
8
+ module.exports = (app) => {
9
+ const $schema = "http://json-schema.org/draft-07/schema#";
10
+
11
+ return async (ctx, next) => {
12
+ // 只对 API 做参数校验
13
+ if (ctx.path.indexOf("/api") < 0) {
14
+ return await next();
15
+ }
16
+ // 获取请求参数
17
+ const { body, query, headers } = ctx.request;
18
+ const { path, params, method } = ctx;
19
+ app.logger.info(`[${method} ${path}] body: ${JSON.stringify(body)}`);
20
+ app.logger.info(`[${method} ${path}] query: ${JSON.stringify(query)}`);
21
+ app.logger.info(`[${method} ${path}] params: ${JSON.stringify(params)}`);
22
+ app.logger.info(`[${method} ${path}] headers: ${JSON.stringify(headers)}`);
23
+
24
+ const schema = app.routerSchema[path]?.[method.toLowerCase()];
25
+ if (!schema) {
26
+ return await next();
27
+ }
28
+
29
+ let valid = true;
30
+
31
+ // ajv校验器
32
+ let validate;
33
+
34
+ // 校验headers
35
+ if (valid && headers && schema.headers) {
36
+ schema.headers.$schema = $schema;
37
+ // 解析
38
+ validate = ajv.compile(schema.headers);
39
+ // 校验用户传递的值校验
40
+ valid = validate(headers);
41
+ }
42
+ // 校验body
43
+ if (valid && body && schema.body) {
44
+ schema.body.$schema = $schema;
45
+
46
+ validate = ajv.compile(schema.body);
47
+ valid = validate(body);
48
+ }
49
+ // 校验query
50
+ if (valid && query && schema.query) {
51
+ schema.query.$schema = $schema;
52
+ validate = ajv.compile(schema.query);
53
+ valid = validate(query);
54
+ }
55
+ // 校验params
56
+ if (valid && params && schema.params) {
57
+ schema.params.$schema = $schema;
58
+ validate = ajv.compile(schema.params);
59
+ valid = validate(params);
60
+ }
61
+
62
+ if (!valid) {
63
+ ctx.status = 200;
64
+ ctx.body = {
65
+ success: false,
66
+ code: 442,
67
+ message: `request validate fail: ${ajv.errorsText(validate.errors)}`,
68
+ };
69
+ return;
70
+ }
71
+ await next();
72
+ };
73
+ };
@@ -0,0 +1,49 @@
1
+ const md5 = require("md5");
2
+
3
+ /**
4
+ * API 签名合法性校验中间件
5
+ * @param {Object} app
6
+ */
7
+ module.exports = (app) => {
8
+ return async (ctx, next) => {
9
+ // 只对 API 请求做签名校验,针对页面的请求路径不会拦截处理
10
+ if (ctx.path.indexOf("/api") < 0) {
11
+ return await next();
12
+ }
13
+ // 解析请求的路径和方式
14
+ const { path, method } = ctx;
15
+ // 拿到请求头的信息
16
+ const { headers } = ctx.request;
17
+ // 解析请求的签名和时间戳
18
+ const { s_sign: sSign, s_t: st } = headers;
19
+
20
+ // 后端根据秘钥与前端请求的时间戳生成签名
21
+ const signKey = "bgkjibal2kbhkc4vbakjl3sdvhja";
22
+ const signature = md5(`${signKey}_${st}`);
23
+
24
+ app.logger.info(`[${method} ${path}] signature: ${signature}}`);
25
+
26
+ /**
27
+ * 校验:有一个为真的都会校验失败,返回报错信息
28
+ * 1. 签名是否为空
29
+ * 2. 时间戳是否为空
30
+ * 3. 后端生成的签名与前端生成的签名是否一致
31
+ * 4. 时间戳是否超时
32
+ */
33
+ if (
34
+ !sSign ||
35
+ !st ||
36
+ (signature !== sSign.toLowerCase()) | (Date.now() - st > 600000)
37
+ ) {
38
+
39
+ ctx.status = 200;
40
+ ctx.body = {
41
+ success: false,
42
+ code: 445,
43
+ message: "signature no correct or timeout !!!!",
44
+ };
45
+ return;
46
+ }
47
+ await next();
48
+ };
49
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 运行时异常错误处理,兜底所有异常
3
+ * @param {Object} app koa实例 ctx是Koa的上下文对象,是Koa框架融合了req与res以及封装便捷属性和方法
4
+ */
5
+ module.exports = (app) => {
6
+ return async (ctx, next) => {
7
+ try {
8
+ await next();
9
+ } catch (error) {
10
+ // 异常处理
11
+ const { status, message, detail } = error;
12
+ app.logger.info(JSON.stringify(error));
13
+ app.logger.error("[-- expection --]", error);
14
+ app.logger.error("[-- expection --]", status, message, detail);
15
+
16
+ if (message && message.indexOf("template not found") > -1) {
17
+ // 进行页面临时重定向 不是301永久的,301除非清缓存
18
+ ctx.status = 302;
19
+ ctx.redirect(`${app.options?.homePage}`);
20
+ return;
21
+ }
22
+ const resBody = {
23
+ success: false,
24
+ code: 500000,
25
+ message: "网络异常,请稍后重试",
26
+ };
27
+ ctx.status = 200;
28
+ ctx.body = resBody;
29
+ }
30
+ };
31
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * projectHandler 项目相关处理内容
3
+ * @param {Object} app
4
+ */
5
+ module.exports = (app) => {
6
+ return async (ctx, next) => {
7
+ // 只针对 业务API 进行 proj_key 处理
8
+ if (ctx.path.indexOf("/api/projk") < 0) {
9
+ return await next();
10
+ }
11
+ // 获取 projKey
12
+ const { proj_key: projKey } = ctx.request.headers;
13
+
14
+ if (!projKey) {
15
+ ctx.status = 200;
16
+ ctx.body = {
17
+ success: false,
18
+ code: 446,
19
+ message: "proj_key not found",
20
+ };
21
+ return;
22
+ }
23
+ ctx.projKey = projKey;
24
+ await next();
25
+ };
26
+ };
@@ -0,0 +1,44 @@
1
+ const path = require("path");
2
+ /**
3
+ * 注册全局中间件,利用Koa实例,进行注册
4
+ * @param {Object} app
5
+ */
6
+ module.exports = (app) => {
7
+ // 配置静态根目录
8
+ const koaStatic = require("koa-static");
9
+ app.use(koaStatic(path.resolve(process.cwd(), "./app/public")));
10
+ // 模版渲染引擎
11
+ const koaNunjucks = require("koa-nunjucks-2");
12
+ app.use(
13
+ koaNunjucks({
14
+ ext: "tpl", // 有动态数据,并且有服务端注入的。方便后续tsx方式编写
15
+ path: path.resolve(process.cwd(), "./app/public"),
16
+ nunjucksConfig: {
17
+ noCache: true,
18
+ trimBlocks: true, //去掉多余的行
19
+ },
20
+ }),
21
+ );
22
+
23
+ // 引入 ctx.body 解析中间件
24
+ const bodyParser = require("koa-bodyparser");
25
+ app.use(
26
+ bodyParser({
27
+ formLimit: "1000mb",
28
+ enableTypes: ["form", "json", "text"],
29
+ }),
30
+ );
31
+
32
+ // 异常捕获引入
33
+ app.use(app.middlewares.errorHandler);
34
+
35
+ // 请求签名合法性校验,限制请求频率
36
+ app.use(app.middlewares.apiSignVerify);
37
+
38
+ // API请求参数校验 params在 handler 链中临时存在,一旦路由执行完毕就清空
39
+ // 所以下面这段代码注册在router.routes()之后,不在handler链中,拿不到params
40
+ app.use(app.middlewares.apiParamsVerify);
41
+
42
+ // 引入 项目处理中间价
43
+ app.use(app.middlewares.projectHandler);
44
+ };