@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.
- package/.eslintignore +3 -0
- package/.eslintrc +55 -0
- package/.tmp-check.js +47 -0
- package/README.md +287 -0
- package/app/controller/base.js +37 -0
- package/app/controller/project.js +76 -0
- package/app/controller/view.js +17 -0
- package/app/extend/logger.js +36 -0
- package/app/middleware/api-params-verify.js +78 -0
- package/app/middleware/api-sign-verify.js +31 -0
- package/app/middleware/error-handler.js +36 -0
- package/app/middleware/project-handler.js +26 -0
- package/app/middleware.js +69 -0
- package/app/pages/asserts/custom.css +10 -0
- package/app/pages/boot.js +44 -0
- package/app/pages/common/curl.js +76 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +19 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +131 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +29 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-pannel/search-pannel.vue +36 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-pannel/table-pannel.vue +125 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +19 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +119 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +131 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +149 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +126 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +111 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +28 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +138 -0
- package/app/pages/dashboard/dashboard.vue +121 -0
- package/app/pages/dashboard/entry.dashboard.js +52 -0
- package/app/pages/dashboard/router.js +3 -0
- package/app/pages/dashboard/todo/todo.vue +10 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +71 -0
- package/app/pages/store/project.js +15 -0
- package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
- package/app/pages/widgets/header-container/asserts/logo.png +0 -0
- package/app/pages/widgets/header-container/header-container.vue +118 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +164 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +138 -0
- package/app/pages/widgets/schema-form/form-item-config.js +20 -0
- package/app/pages/widgets/schema-form/schema-form.vue +154 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +44 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +134 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
- package/app/pages/widgets/schema-table/schema-table.vue +277 -0
- package/app/pages/widgets/sider-container/sider-container.vue +42 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +7 -0
- package/app/router/view.js +7 -0
- package/app/router-schema/project.js +34 -0
- package/app/service/base.js +10 -0
- package/app/service/project.js +43 -0
- package/app/view/entry.tpl +31 -0
- package/app/webpack/config/webpack.base.js +224 -0
- package/app/webpack/config/webpack.dev.js +53 -0
- package/app/webpack/config/webpack.prod.js +104 -0
- package/app/webpack/dev.js +54 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +17 -0
- package/config/config.default.js +3 -0
- package/docs/dashboard-model.js +150 -0
- package/elpis-core/env.js +21 -0
- package/elpis-core/index.js +82 -0
- package/elpis-core/loader/config.js +58 -0
- package/elpis-core/loader/controller.js +55 -0
- package/elpis-core/loader/extend.js +42 -0
- package/elpis-core/loader/middleware.js +50 -0
- package/elpis-core/loader/router-schema.js +28 -0
- package/elpis-core/loader/router.js +42 -0
- package/elpis-core/loader/service.js +51 -0
- package/index.js +37 -0
- package/logs/application.log +50 -0
- package/logs/application.log.2026-06-13 +159 -0
- package/model/index.js +119 -0
- package/package.json +88 -0
- package/scripts/generate-cover.py +93 -0
- package/scripts/generate-diagrams.js +80 -0
- package/test/controller/project.test.js +218 -0
package/.eslintignore
ADDED
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
|
+
}
|