@james-zhang/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 (86) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +55 -0
  3. package/.tmp-check.js +47 -0
  4. package/README.md +287 -0
  5. package/app/controller/base.js +37 -0
  6. package/app/controller/project.js +76 -0
  7. package/app/controller/view.js +17 -0
  8. package/app/extend/logger.js +36 -0
  9. package/app/middleware/api-params-verify.js +78 -0
  10. package/app/middleware/api-sign-verify.js +31 -0
  11. package/app/middleware/error-handler.js +36 -0
  12. package/app/middleware/project-handler.js +26 -0
  13. package/app/middleware.js +69 -0
  14. package/app/pages/asserts/custom.css +10 -0
  15. package/app/pages/boot.js +44 -0
  16. package/app/pages/common/curl.js +76 -0
  17. package/app/pages/common/utils.js +2 -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 +131 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +29 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-pannel/search-pannel.vue +36 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-pannel/table-pannel.vue +125 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +19 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +119 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +131 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +149 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +126 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +111 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +28 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +138 -0
  31. package/app/pages/dashboard/dashboard.vue +121 -0
  32. package/app/pages/dashboard/entry.dashboard.js +52 -0
  33. package/app/pages/dashboard/router.js +3 -0
  34. package/app/pages/dashboard/todo/todo.vue +10 -0
  35. package/app/pages/store/index.js +3 -0
  36. package/app/pages/store/menu.js +71 -0
  37. package/app/pages/store/project.js +15 -0
  38. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  39. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  40. package/app/pages/widgets/header-container/header-container.vue +118 -0
  41. package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
  42. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +164 -0
  43. package/app/pages/widgets/schema-form/complex-view/select/select.vue +138 -0
  44. package/app/pages/widgets/schema-form/form-item-config.js +20 -0
  45. package/app/pages/widgets/schema-form/schema-form.vue +154 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
  48. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +44 -0
  49. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
  50. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +134 -0
  51. package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
  52. package/app/pages/widgets/schema-table/schema-table.vue +277 -0
  53. package/app/pages/widgets/sider-container/sider-container.vue +42 -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 +7 -0
  57. package/app/router/view.js +7 -0
  58. package/app/router-schema/project.js +34 -0
  59. package/app/service/base.js +10 -0
  60. package/app/service/project.js +43 -0
  61. package/app/view/entry.tpl +31 -0
  62. package/app/webpack/config/webpack.base.js +224 -0
  63. package/app/webpack/config/webpack.dev.js +53 -0
  64. package/app/webpack/config/webpack.prod.js +104 -0
  65. package/app/webpack/dev.js +54 -0
  66. package/app/webpack/libs/blank.js +1 -0
  67. package/app/webpack/prod.js +17 -0
  68. package/config/config.default.js +3 -0
  69. package/docs/dashboard-model.js +150 -0
  70. package/elpis-core/env.js +21 -0
  71. package/elpis-core/index.js +82 -0
  72. package/elpis-core/loader/config.js +58 -0
  73. package/elpis-core/loader/controller.js +55 -0
  74. package/elpis-core/loader/extend.js +42 -0
  75. package/elpis-core/loader/middleware.js +50 -0
  76. package/elpis-core/loader/router-schema.js +28 -0
  77. package/elpis-core/loader/router.js +42 -0
  78. package/elpis-core/loader/service.js +51 -0
  79. package/index.js +37 -0
  80. package/logs/application.log +50 -0
  81. package/logs/application.log.2026-06-13 +159 -0
  82. package/model/index.js +119 -0
  83. package/package.json +88 -0
  84. package/scripts/generate-cover.py +93 -0
  85. package/scripts/generate-diagrams.js +80 -0
  86. package/test/controller/project.test.js +218 -0
package/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ public/
3
+ docs/
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/.tmp-check.js ADDED
@@ -0,0 +1,47 @@
1
+ const { JSDOM } = require('C:/Users/孤夜~/AppData/Local/Temp/jsdom-test/node_modules/jsdom');
2
+
3
+ (async () => {
4
+ const url = 'http://127.0.0.1:8080/view/project';
5
+ const dom = await JSDOM.fromURL(url, {
6
+ runScripts: 'dangerously',
7
+ resources: 'usable',
8
+ pretendToBeVisual: true,
9
+ beforeParse(window) {
10
+ window.EventSource = class FakeEventSource {
11
+ constructor() { console.log('EventSource stub created'); }
12
+ set onopen(fn) {}
13
+ set onmessage(fn) {}
14
+ set onerror(fn) {}
15
+ close() {}
16
+ };
17
+ const orig = window.document.createElement;
18
+ window.document.createElement = function(tag) {
19
+ const el = orig.call(this, tag);
20
+ if (tag.toLowerCase() === 'style') {
21
+ console.log('STYLE element created');
22
+ const append = el.appendChild.bind(el);
23
+ el.appendChild = function(child) {
24
+ if (child && child.data && child.data.length > 0) {
25
+ console.log('STYLE content length', child.data.length, 'starts:', child.data.slice(0, 80));
26
+ }
27
+ return append(child);
28
+ };
29
+ }
30
+ return el;
31
+ };
32
+ },
33
+ });
34
+
35
+ await new Promise(r => setTimeout(r, 5000));
36
+ const doc = dom.window.document;
37
+ console.log('--- head style tags ---');
38
+ const styles = doc.querySelectorAll('style');
39
+ console.log('style count:', styles.length);
40
+ styles.forEach((s, i) => {
41
+ console.log(`style #${i} length:`, s.textContent.length, 'starts:', s.textContent.slice(0, 60));
42
+ });
43
+ console.log('--- root content ---');
44
+ const root = doc.getElementById('root');
45
+ console.log('root innerHTML:', root.innerHTML);
46
+ dom.window.close();
47
+ })().catch(e => { console.error(e); process.exit(1); });
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # elpis
2
+
3
+ ## 一个企业级全栈应用框架。
4
+
5
+ ### model配置
6
+
7
+ javascript
8
+
9
+ mode: 'dashboard', // 模版类型,不同模版类型对应不一样的模版数据结构
10
+ name: '// 名称
11
+ desc: '// 描述
12
+ icon: '// icon
13
+ homePage: '// 首页(项目配置)
14
+ // 头部菜单
15
+ menu: [
16
+ key: '// 菜单唯一描述,
17
+ name: '// 菜单名称
18
+ menuType: '// 枚举值: group / module
19
+ // 当 menuType == group 时, 可填
20
+ subMenu: [
21
+ // 可递归 menuItem
22
+ ],
23
+ // 当 menuType == module 时, 可填
24
+ moduleType: '// 枚举值: sider/iframe/custom/schema
25
+ // 当 moduleType == sider 时
26
+ siderConfig: {
27
+ menu: [
28
+ // 可递归 menuItem(除 moduleType == sider)
29
+ ],
30
+ // 当 moduleType == iframe 时
31
+ iframeConfig: {
32
+ path: '// iframe 路径
33
+ }
34
+ }
35
+ ]
36
+
37
+ // 当 menuType == module 时,可
38
+ moduleType: '', // 枚举值: sider/frame/custom/schema
39
+
40
+ // 当 moduleType == sider 时
41
+ siderConfig: {
42
+ menu: {
43
+ // 可递归 menuItem(除 moduleType == sider)
44
+ },
45
+ },
46
+
47
+ // 当 moduleType == iframe 时
48
+ iframeConfig: {
49
+ path: '', // iframe 路径
50
+ },
51
+
52
+ // 当 moduleType == custom 时
53
+ customConfig: {
54
+ path: '', // 自定义路由路径
55
+ },
56
+
57
+ // 当 moduleType == schema 时
58
+ schemaConfig: {
59
+ api: '', // 数据源API(遵循 RESTFUL 规范)
60
+ schema: { // 模块数据结构
61
+ type: 'object',
62
+ properties: {
63
+ key: {
64
+ schema, // 标准 schema 配置
65
+ type: '', // 字段类型
66
+ label: '', // 字段的中文名
67
+ // 字段在 table 中的相关配置
68
+ tableOption: {
69
+ elTableColumnConfig, // 标准 el-table-column 配置
70
+ toFixed: 0, // 保留小数点后几位
71
+ visible: true, // 默认为 true (false 时,表示不在表单中显示)
72
+ },
73
+ },
74
+ },
75
+ },
76
+ },
77
+
78
+ schema, // 标准 schema 配置
79
+ type: '', // 字段类型
80
+ label: '', // 字段的中文名
81
+ // 字段在 table 中的相关配置
82
+ tableOption: {
83
+ elTableColumnConfig, // 标准 el-table-column 配置
84
+ toFixed: 0, // 保留小数点后几位
85
+ visible: true, // 默认为 true (false 时,表示不在表单中显示)
86
+ },
87
+ // 字段在 search-bar 中的相关配置
88
+ searchOption: {
89
+ eleComponentConfig, // 标准 el-component-column 配置
90
+ comType: '', // 配置组件类型 input/select/......
91
+ default: '', // 默认值
92
+ },
93
+ // comType === 'select'
94
+ enumList: [], // 下拉框可选项
95
+
96
+ // comType === 'dynamicSelect'
97
+ api: ''
98
+ },
99
+ // 字段在不同动态 component 中的相关配置,前缀对应 componentConfig 中的键值
100
+ // 如:componentConfig.createForm, 这里对应 createFormOption
101
+ // 字段在 createForm 中相关配置
102
+ createFormOption: {
103
+ eleComponentConfig, // 标准 el-component 配置
104
+ comType: '', // 组件类型 input/select/input-number
105
+ visible: true, // 是否展示 (true/false), 默认为 true
106
+ disabled: false, // 是否禁用 (true/false), 默认为 false
107
+ default: '', // 默认值
108
+
109
+ // comType === 'select' 时生效
110
+ enumList: [] // 枚举列表
111
+ },
112
+ // 字段在 editForm 表单中的相关配置
113
+ editFormOption: {
114
+
115
+ visible: true, // 是否展示 (true/false),默认为 true
116
+ disabled: false, // 是否禁用 (true/false),默认为 false
117
+ default: '', // 默认值
118
+
119
+ // comType === 'select' 时生效
120
+ enumList: [] // 枚举列表
121
+ detailPanelOption: {
122
+ eleComponentConfig // 标准 el-component 配置
123
+ }
124
+
125
+ required: [], // 标记哪些字段是必填项
126
+
127
+ // table 相关配置
128
+ tableConfig: {
129
+ headerButtons: {
130
+ label: '', // 按钮中文名
131
+ eventKey: '', // 按钮事件名
132
+ // 按钮事件具体配置
133
+ eventOption: {
134
+ // 当 eventKey === 'showComponent'
135
+ comName: '' // 组件名称
136
+ },
137
+ elButtonConfig // 标准 el-button 配置
138
+ },
139
+ rowButtons: {
140
+ label: '', // 按钮中文名
141
+ eventKey: '', // 按钮事件名
142
+ eventOption: {
143
+ // 当 eventKey === 'showComponent'
144
+ comName: '' // 组件名称
145
+ },
146
+ // 当 eventKey === 'remove'
147
+ params: {
148
+ }
149
+ }
150
+ }
151
+
152
+ paramKey: rowValueKey
153
+ }, // 按钮事件具体配置
154
+ elButtonConfig // 标准 el-button 配置
155
+ }, ...]
156
+
157
+ // search-bar 相关配置
158
+ searchConfig: {},
159
+ // 动态组件 相关配置
160
+ componentConfig: {
161
+ // create-form 表单相关配置
162
+ createForm: {
163
+ title: '', // 表单标题
164
+ saveBtnText: '', // 保存按钮文案
165
+ },
166
+ // edit-form 表单相关配置
167
+ editForm: {
168
+ mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
169
+ title: '', // 表单标题
170
+ saveBtnText: '', // 保存按钮文案
171
+ },
172
+ // detail-page 相关配置
173
+ detailPanel: {
174
+ mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
175
+ title: '', // 表单标题
176
+ },
177
+ // ...支持用户动态扩展
178
+ },
179
+ }, ...]
180
+
181
+ ### 服务端启动
182
+
183
+ // javascript
184
+ const {
185
+ serverStart
186
+ }
187
+
188
+ # 服务端启动
189
+
190
+ const {
191
+ serverStart
192
+ } = require('@fsiaonma/elpis');
193
+
194
+ // 启动 elpis 服务
195
+ const app = serverStart({});
196
+
197
+ # 自定义服务端
198
+
199
+ - router-schema
200
+ - router
201
+ - controller
202
+ - service
203
+ - extend
204
+ - config
205
+
206
+ # 前端构建
207
+
208
+ const { frontendBuild } = require('@fsiaonma/elpis');
209
+
210
+ // 编译构建前端工程
211
+ frontendBuild(process.env.\_ENV);
212
+
213
+ # 自定义页面扩展
214
+
215
+ 服务端启动
216
+ javascript
217
+ const {
218
+ serverStart
219
+ } = require('@fsiaonna/elpis');
220
+
221
+ // 启动 elpis 服务
222
+ const app = serverStart({});
223
+
224
+ 自定义服务端
225
+
226
+ - router-schema
227
+ - router
228
+ - controller
229
+ - service
230
+ - extend
231
+ - config
232
+
233
+ 前端构建
234
+ javascript
235
+ const { frontendBuild } = require('@fsiaonna/elpis');
236
+
237
+ // 编译构建前端工程
238
+ frontendBuild(process.env.\_ENV);
239
+
240
+ 自定义页面扩展
241
+
242
+ - 在 `app/pages/` 目录下写入 `entry.xxx.js`
243
+
244
+ dashboard / custom-view 自定义页面扩展
245
+
246
+ - 在 `app/pages/dashboard/xxxx` 下写页面
247
+
248
+ dashboard / schema-ui / commonites 动态组件扩展
249
+
250
+ ### 自定义服务端
251
+
252
+ - router-schema
253
+ - router
254
+ - controller
255
+ - service
256
+ - extend
257
+ - config
258
+
259
+ ### 前端构建
260
+
261
+ const { frontendBuild } = require('@fsiaonna/elpis');
262
+
263
+ // 编译构建前端工程
264
+ frontendBuild(process.env.\_ENV);
265
+
266
+ ### 自定义页面扩展
267
+
268
+ - 在 `app/pages/` 目录下写入 `entry.xxx.js`
269
+
270
+ ### dashboard / custom-view 自定义页面扩展
271
+
272
+ - 在 `app/pages/dashboard/xxxx` 下写页面
273
+
274
+ ### dashboard / schema-view / components 动态组件扩展
275
+
276
+ 1. 在 `app/pages/dashboard/complex-view/schema-view/components` 下写组件
277
+ 2. 配置 `app/pages/dashboard/complex-view/schema-view/components/component-config.js`
278
+
279
+ ### schema-form 控件扩展
280
+
281
+ 1. 在 `app/widgets/schema-form/complex-view` 下写控件
282
+ 2. 配置 `app/widgets/schema-form/form-item-config.js`
283
+
284
+ ### schema-search-bar 控件扩展
285
+
286
+ 1. 在 `app/widgets/schema-search/bar/complex-view` 下写控件
287
+ 2. 配置 `app/widgets/schema-search/bar/search-item-config.js`
@@ -0,0 +1,37 @@
1
+ module.exports = (app) => class BaseController {
2
+ //controller基类,统一收拢controller的公共方法
3
+ constructor() {
4
+ this.app = app
5
+ this.config = app.config
6
+ }
7
+ /**
8
+ * 成功响应
9
+ * @param {object} ctx上下文
10
+ * @param {object} data 核心数据
11
+ * @param {object} metadata 附加 数据
12
+ */
13
+
14
+ success(ctx,data={},metadata={}){
15
+ ctx.status=200
16
+ ctx.body={
17
+ success:true,
18
+ data,
19
+ metadata
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 失败响应
25
+ * @param {object} ctx上下文
26
+ * @param {object} message 错误信息
27
+ * @param {object} code 错误码
28
+ */
29
+
30
+ fail(ctx,message,code){
31
+ ctx.body={
32
+ success:false,
33
+ message,
34
+ code
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,76 @@
1
+ module.exports=(app)=>{
2
+ const BaseController=require('./base')(app)
3
+ const modelList=require('../../model/index')(app)
4
+ // console.log(JSON.stringify(modelList))
5
+ //项目控制器
6
+ return class ProjectController extends BaseController{
7
+ /**
8
+ * 根据proj_key获取项目配置
9
+ */
10
+ async get(ctx){
11
+ const {proj_key}=ctx.request.query
12
+
13
+ const {project:projectService}=app.service
14
+ const projectConfig=await projectService.get({proj_key})
15
+
16
+ if(!projectConfig){
17
+ this.fail(ctx,'网络异常 请稍后重试',50000)
18
+ return
19
+ }
20
+ this.success(ctx,projectConfig)
21
+ app.logger.info('项目配置请求成功')
22
+ }
23
+
24
+ /**
25
+ * 获取当前projectKey 对应模型下的项目列表(如果无 projectKey 则返回所有项目)
26
+ */
27
+ async getList(ctx){
28
+ const {
29
+ proj_key:projKey,
30
+ }=ctx.request.query
31
+ const { project:projectService}=app.service
32
+ const projectList=await projectService.getList({projKey})
33
+ const dtoProjectList=projectList.map((item)=>{
34
+ const {modelKey,key,name,desc,homePage}=item
35
+ return {modelKey,key,name,desc,homePage}
36
+ })
37
+
38
+
39
+ this.success(ctx,dtoProjectList)
40
+ app.logger.info('项目列表请求成功')
41
+ }
42
+
43
+ /**
44
+ * 获取所有模型项目的结构化数据
45
+ */
46
+ async getModelList(ctx){
47
+ const { project:projectService}=app.service
48
+ const modelList=await projectService.getModelList()
49
+
50
+ //构造返回结果,只返回关键数据
51
+ const dtoModelList=modelList.reduce((preList,item)=>{
52
+ const {model,project}=item
53
+
54
+ //构造model关键数据
55
+ const {key,name,desc}=model
56
+ const dtoModel={key,name,desc}
57
+
58
+ //构造project关键数据
59
+ const dtoProject=Object.keys(project).reduce((preObj,projKey)=>{
60
+ const {key,name,desc,homePage}=project[projKey]
61
+ preObj[projKey]={key,name,desc,homePage}
62
+ return preObj
63
+ },{})
64
+
65
+ //整合返回结构
66
+ preList.push({model:dtoModel,project:dtoProject})
67
+ return preList
68
+ },[])
69
+
70
+ this.success(ctx,dtoModelList)
71
+ app.logger.info('项目模型列表请求成功')
72
+ }
73
+
74
+ }
75
+
76
+ }
@@ -0,0 +1,17 @@
1
+ module.exports=(app)=>{
2
+ return class ViewController{
3
+ //渲染页面
4
+ async renderPage(ctx){
5
+ const {params,query}=ctx;
6
+ app.logger.info(`[ViewController] params:${JSON.stringify(params)}`)
7
+ app.logger.info(`[ViewController] query:${JSON.stringify(query)}`)
8
+ await ctx.render(`dist/entry.${ctx.params.page}`,{
9
+ projKey:ctx.query?.proj_key,
10
+ name:app.options?.name,
11
+ env:app.env.get(),
12
+ options:JSON.stringify(app.options)
13
+ });
14
+ }
15
+ }
16
+
17
+ }
@@ -0,0 +1,36 @@
1
+ const log4js=require('log4js')
2
+ /**
3
+ * 日志工具
4
+ * 外部调用 app.logger.info app.logger.error 等方法
5
+ */
6
+
7
+ module.exports=(app)=>{
8
+ let logger
9
+ if(app.env.isLocal()){
10
+ //打印在控制台即可
11
+ logger=console;
12
+ }else{
13
+ log4js.configure({
14
+ //把日志输出并落地到磁盘(日志落盘)
15
+ appenders:{
16
+ console:{
17
+ type:'console',
18
+ },
19
+ //日志文件切分
20
+ dateFile:{
21
+ type:'dateFile',
22
+ filename:'./logs/application.log',
23
+ pattern:'yyyy-MM-dd',
24
+ },
25
+ },
26
+ categories:{
27
+ default:{
28
+ appenders:['console','dateFile'],
29
+ level:'trace',
30
+ }
31
+ }
32
+ })
33
+ logger=log4js.getLogger()
34
+ }
35
+ return logger
36
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * API参数验证合法性中间件
3
+ */
4
+ const Ajv=require('ajv')
5
+ const ajv=new Ajv()
6
+ module.exports=(app)=>{
7
+ const $schema= 'http://json-schema.org/draft-07/schema#'
8
+ return async (ctx,next)=>{
9
+ //只对API请求进行参数验证
10
+ if(ctx.path.indexOf('/api')<0){
11
+ return await next()
12
+ }
13
+
14
+ //获取请求参数
15
+ const {body,query,headers}=ctx.request
16
+ const {params,path,method}=ctx
17
+
18
+ app.logger.info(`[${method}] ${path} body:${JSON.stringify(body)}`)
19
+ app.logger.info(`[${method}] ${path} params:${JSON.stringify(params)}`)
20
+ app.logger.info(`[${method}] ${path} query:${JSON.stringify(query)}`)
21
+ app.logger.info(`[${method}] ${path} headers:${JSON.stringify(headers)}`)
22
+
23
+ const schema=app.routerSchemas[path]?.[method.toLowerCase()]
24
+ if(!schema){
25
+ return await next()
26
+ }
27
+ let valid=true
28
+
29
+ //ajv校验器
30
+ let validate
31
+
32
+ //校验params
33
+ if(valid && params &&schema.params){
34
+ schema.params.$schema=$schema
35
+ validate=ajv.compile(schema.params) //编译校验器
36
+ valid=validate(params)
37
+ }
38
+
39
+
40
+ //校验headers
41
+ if(valid && headers &&schema.headers){
42
+ schema.headers.$schema=$schema
43
+ validate=ajv.compile(schema.headers) //编译校验器
44
+ valid=validate(headers)
45
+ }
46
+ //校验body
47
+ if(valid && body &&schema.body){
48
+ schema.body.$schema=$schema
49
+ validate=ajv.compile(schema.body) //编译校验器
50
+ valid=validate(body)
51
+ }
52
+ //校验query
53
+ if(valid && query &&schema.query){
54
+ schema.query.$schema=$schema
55
+ validate=ajv.compile(schema.query) //编译校验器
56
+ valid=validate(query)
57
+ }
58
+
59
+ if(!valid){
60
+ ctx.status=200
61
+ const firstError=validate.errors[0]
62
+ let errorText
63
+ if(firstError && firstError.keyword==='required' && firstError.params && firstError.params.missingProperty){
64
+ errorText=`data ${firstError.params.missingProperty} is required`
65
+ }else{
66
+ errorText=ajv.errorsText(validate.errors)
67
+ }
68
+ ctx.body={
69
+ code:442,
70
+ message:`request validate failed:${errorText}`,
71
+ success:false,
72
+ }
73
+ return
74
+ }
75
+
76
+ await next()
77
+ };
78
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * API签名验证合法性中间件
3
+ */
4
+ const md5=require('md5')
5
+ module.exports=(app)=>{
6
+ return async (ctx,next)=>{
7
+ //只对API请求做签名校验
8
+ if(ctx.path.indexOf('/api')<0){
9
+ return await next()
10
+ }
11
+
12
+ const {path,method}=ctx
13
+ const {headers}=ctx.request
14
+ const {s_sign:sSign,s_t:st}=headers
15
+
16
+ const signKey='fai3ihihfi43go53gjo53g53'
17
+ const signature=md5(`${signKey}_${st}`)
18
+ app.logger.info(`[${method} ${path}] signature:${signature}`)
19
+
20
+ if(!sSign || !st || signature!==sSign.toLowerCase() || Date.now()-st>1000*60){
21
+ ctx.status=200
22
+ ctx.body={
23
+ code:445,
24
+ msg:'签名不正确或请求超时',
25
+ success:false,
26
+ }
27
+ return
28
+ }
29
+ await next()
30
+ }
31
+ }