@huangzongxin/elpis 0.0.0-stage → 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 +58 -0
- package/.husky/pre-commit +4 -0
- package/.vscode/settings.json +5 -0
- package/README.md +183 -3
- package/app/controller/base.js +44 -0
- package/app/controller/project.js +62 -0
- package/app/controller/view.js +17 -0
- package/app/extend/logger.js +38 -0
- package/app/middleware/api-params-verify.js +71 -0
- package/app/middleware/api-sign-verify.js +35 -0
- package/app/middleware/error-handler.js +33 -0
- package/app/middleware/project-handler.js +26 -0
- package/app/middleware.js +41 -0
- package/app/pages/asserts/custom.css +13 -0
- package/app/pages/boot.js +46 -0
- package/app/pages/common/curl.js +82 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +16 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +103 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +41 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +39 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +113 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/creat-form.vue +73 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +103 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +112 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +123 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +77 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +16 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +107 -0
- package/app/pages/dashboard/dashboard.vue +86 -0
- package/app/pages/dashboard/entry.dashboard.js +42 -0
- package/app/pages/store/index.js +7 -0
- package/app/pages/store/menu.js +53 -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 +108 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +110 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +126 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +103 -0
- package/app/pages/widgets/schema-form/form-item-config.js +23 -0
- package/app/pages/widgets/schema-form/schema-form.vue +115 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +38 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +50 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +35 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +37 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +116 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +26 -0
- package/app/pages/widgets/schema-table/schema-table.vue +193 -0
- package/app/pages/widgets/sider-container/sider-container.vue +32 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +15 -0
- package/app/router/view.js +7 -0
- package/app/router-schema/project.js +31 -0
- package/app/service/base.js +14 -0
- package/app/service/project.js +43 -0
- package/app/view/entry.tpl +27 -0
- package/app/webpack/config/webpack.base.js +270 -0
- package/app/webpack/config/webpack.dev.js +57 -0
- package/app/webpack/config/webpack.prod.js +82 -0
- package/app/webpack/dev.js +55 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +22 -0
- package/config/config.default.js +4 -0
- package/docs/ELPIS-DSL.md +406 -0
- package/docs/dashboard-model.js +140 -0
- package/docs/webpack/345/267/245/347/250/213/345/214/226/345/256/236/350/267/265.md +313 -0
- package/elpis-core/env.js +17 -0
- package/elpis-core/index.js +101 -0
- package/elpis-core/loader/config.js +64 -0
- package/elpis-core/loader/controller.js +75 -0
- package/elpis-core/loader/extend.js +54 -0
- package/elpis-core/loader/middleware.js +76 -0
- package/elpis-core/loader/router-schema.js +57 -0
- package/elpis-core/loader/router.js +53 -0
- package/elpis-core/loader/service.js +72 -0
- package/index.html +0 -0
- package/index.js +39 -0
- package/model/index.js +107 -0
- package/package.json +91 -6
- package/study.md +9 -0
- package/test/controller/project.test.js +207 -0
package/.eslintignore
ADDED
package/.eslintrc
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ignorePatterns": ["docs/**/*.js"],
|
|
3
|
+
"extends": [
|
|
4
|
+
"plugin:vue/base",
|
|
5
|
+
"plugin:vue/recommended"
|
|
6
|
+
],
|
|
7
|
+
"plugins": ["vue"],
|
|
8
|
+
"env": {
|
|
9
|
+
"browser": true,
|
|
10
|
+
"node": true
|
|
11
|
+
},
|
|
12
|
+
"parserOptions": {
|
|
13
|
+
"ecmaVersion": 2020,
|
|
14
|
+
"sourceType": "module"
|
|
15
|
+
},
|
|
16
|
+
"rules": {
|
|
17
|
+
"no-unused-vars": [2, {"args": "none"}],
|
|
18
|
+
"strict": "off",
|
|
19
|
+
"valid-jsdoc": "off",
|
|
20
|
+
"jsdoc/require-param-description": "off",
|
|
21
|
+
"jsdoc/require-param-type": "off",
|
|
22
|
+
"jsdoc/check-param-names": "off",
|
|
23
|
+
"jsdoc/require-param": "off",
|
|
24
|
+
"jsdoc/check-tag-names": "off",
|
|
25
|
+
"linebreak-style": "off",
|
|
26
|
+
"array-bracket-spacing": "off",
|
|
27
|
+
"prefer-promise-reject-errors": "off",
|
|
28
|
+
"comma-dangle": "off",
|
|
29
|
+
"newline-per-chained-call": "off",
|
|
30
|
+
"no-loop-func": "off",
|
|
31
|
+
"no-empty": "off",
|
|
32
|
+
"no-else-return": "off",
|
|
33
|
+
"no-unneeded-ternary": "off",
|
|
34
|
+
"no-eval": "off",
|
|
35
|
+
"prefer-destructuring": "off",
|
|
36
|
+
"no-param-reassign": "off",
|
|
37
|
+
"max-len": "off",
|
|
38
|
+
"no-restricted-syntax": "off",
|
|
39
|
+
"no-plusplus": "off",
|
|
40
|
+
"no-useless-escape": "off",
|
|
41
|
+
"no-nested-ternary": "off",
|
|
42
|
+
"radix": "off",
|
|
43
|
+
"arrow-body-style": "off",
|
|
44
|
+
"arrow-parens": "off",
|
|
45
|
+
"vue/multi-word-component-names": "off",
|
|
46
|
+
"vue/valid-v-for": "off",
|
|
47
|
+
"vue/no-multiple-template-root": "off",
|
|
48
|
+
"vue/max-attributes-per-line": ["warn", {
|
|
49
|
+
"singleline": 3,
|
|
50
|
+
"multiline": 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
"globals": {
|
|
54
|
+
"$": true,
|
|
55
|
+
"axios": true,
|
|
56
|
+
"Vue": true
|
|
57
|
+
}
|
|
58
|
+
}
|
package/README.md
CHANGED
|
@@ -1,3 +1,183 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
model配置
|
|
2
|
+
{
|
|
3
|
+
mode: 'dashboard', // 模版类型,不同模版类型对应不一样的模版数据结构
|
|
4
|
+
name: '', // 名称
|
|
5
|
+
desc: '', // 描述
|
|
6
|
+
icon: '', // icon
|
|
7
|
+
homePage: '', // 首页(项目配置)
|
|
8
|
+
// 头部菜单
|
|
9
|
+
menu: [{
|
|
10
|
+
key: '', // 菜单唯一描述,
|
|
11
|
+
name: '', // 菜单名称
|
|
12
|
+
menuType: '', // 枚举值:group / module
|
|
13
|
+
|
|
14
|
+
// 当 menuType == group 时,可填
|
|
15
|
+
subMenu: [{
|
|
16
|
+
// 可递归 menuItem
|
|
17
|
+
}, ...],
|
|
18
|
+
|
|
19
|
+
// 当 menuType == module 时,可填
|
|
20
|
+
moduleType: '', // 枚举值:sider/iframe/custom/schema
|
|
21
|
+
|
|
22
|
+
// 当 moduleType == sider 时
|
|
23
|
+
siderConfig: {
|
|
24
|
+
menu: [{
|
|
25
|
+
// 可递归 menuItem(除 moduleType === sider)
|
|
26
|
+
}, ...]
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// 当 moduleType == iframe 时
|
|
30
|
+
iframeConfig: {
|
|
31
|
+
path: '', // iframe 路径
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// 当 moduleType == custom 时
|
|
35
|
+
customConfig: {
|
|
36
|
+
path: '', // 自定义路由路径
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// 当 moduleType == schema 时
|
|
40
|
+
schemaConfig: {
|
|
41
|
+
api: '', // 数据源API(遵循 RESTFUL 规范)
|
|
42
|
+
schema: { // 板块数据结构
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
key: {
|
|
46
|
+
...schema, // 标准 schema 配置
|
|
47
|
+
type: '', // 字段类型
|
|
48
|
+
label: '', // 字段的中文名
|
|
49
|
+
// 字段在 table 中的相关配置
|
|
50
|
+
tableOption: {
|
|
51
|
+
...elTableColumnConfig, // 标准 el-table-column 配置
|
|
52
|
+
toFixed: 0, // 保留小数点后几位
|
|
53
|
+
visible: true, // 默认为 true(false 时,表示不在表单中显示)
|
|
54
|
+
},
|
|
55
|
+
// 字段在 search-bar 中的相关配置
|
|
56
|
+
searchOption: {
|
|
57
|
+
...eleComponentConfig, // 标准 el-component-column 配置
|
|
58
|
+
comType: '', // 配置组件类型 input/select/.....
|
|
59
|
+
default: '', // 默认值
|
|
60
|
+
|
|
61
|
+
// comType === 'select'
|
|
62
|
+
enumList: [], // 下拉框可选项
|
|
63
|
+
|
|
64
|
+
// comType === 'dynamicSelect'
|
|
65
|
+
api: ''
|
|
66
|
+
},
|
|
67
|
+
// 字段在不同动态 component 中的相关配置,前缀对应 componentComfig 中的键值
|
|
68
|
+
// 如:componentConfig.createForm,这里对应 createFormOption
|
|
69
|
+
// 字段在 createForm 中相关配置
|
|
70
|
+
createFormOption: {
|
|
71
|
+
...eleComponentConfig, // 标准 el-component 配置
|
|
72
|
+
comType: '', // 控件类型 input/selct/input-number
|
|
73
|
+
visible: true, // 是否展示 (true/false),默认为 true
|
|
74
|
+
disabled: false, // 是否禁用 (true/false),默认为 false
|
|
75
|
+
default: '', // 默认值
|
|
76
|
+
|
|
77
|
+
// comType === 'select' 时生效
|
|
78
|
+
enumList: [] // 枚举列表
|
|
79
|
+
},
|
|
80
|
+
// 字段在 editForm 表单中的相关配置
|
|
81
|
+
editFormOption: {
|
|
82
|
+
...eleComponentConfig, // 标准 el-component 配置
|
|
83
|
+
comType: '', // 控件类型 input/selct/input-number
|
|
84
|
+
visible: true, // 是否展示 (true/false),默认为 true
|
|
85
|
+
disabled: false, // 是否禁用 (true/false),默认为 false
|
|
86
|
+
default: '', // 默认值
|
|
87
|
+
|
|
88
|
+
// comType === 'select' 时生效
|
|
89
|
+
enumList: [] // 枚举列表
|
|
90
|
+
},
|
|
91
|
+
detailPanelOption: {
|
|
92
|
+
...eleComponentConfig // 标准 el-component 配置
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
...
|
|
96
|
+
},
|
|
97
|
+
required: [], // 标记哪些字段是必填项
|
|
98
|
+
},
|
|
99
|
+
// table 相关配置
|
|
100
|
+
tableConfig: {
|
|
101
|
+
headerButtons: [{
|
|
102
|
+
label: '', // 按钮中文名
|
|
103
|
+
eventKey: '', // 按钮事件名
|
|
104
|
+
// 按钮事件具体配置
|
|
105
|
+
eventOption: {
|
|
106
|
+
// 当 eventKey === 'showComponent'
|
|
107
|
+
comName: '' // 组件名称
|
|
108
|
+
},
|
|
109
|
+
...elButtonConfig // 标准 el-button 配置
|
|
110
|
+
}, ...],
|
|
111
|
+
rowButtons: [{
|
|
112
|
+
label: '', // 按钮中文名
|
|
113
|
+
eventKey: '', // 按钮事件名
|
|
114
|
+
eventOption: {
|
|
115
|
+
// 当 eventKey === 'showComponent'
|
|
116
|
+
comName: '' // 组件名称
|
|
117
|
+
|
|
118
|
+
// 当 eventKey === 'remove'
|
|
119
|
+
params: {
|
|
120
|
+
// paramKey = 参数的键值
|
|
121
|
+
// rowValueKey = 参数值, 格式为 schema::tableKey,到 table 中找相应的字段
|
|
122
|
+
paramKey: rowValueKey
|
|
123
|
+
}
|
|
124
|
+
}, // 按钮事件具体配置
|
|
125
|
+
...elButtonConfig // 标准 el-button 配置
|
|
126
|
+
}, ...]
|
|
127
|
+
},
|
|
128
|
+
// search-bar 相关配置
|
|
129
|
+
searchConfig: {},
|
|
130
|
+
// 动态组件 相关配置
|
|
131
|
+
componentConfig: {
|
|
132
|
+
// create-form 表单相关配置
|
|
133
|
+
createForm: {
|
|
134
|
+
title: '', // 表单标题
|
|
135
|
+
saveBtnText: '', // 保存按钮文案
|
|
136
|
+
},
|
|
137
|
+
// edit-form 表单相关配置
|
|
138
|
+
editForm: {
|
|
139
|
+
mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
|
|
140
|
+
title: '', // 表单标题
|
|
141
|
+
saveBtnText: '', // 保存按钮文案
|
|
142
|
+
},
|
|
143
|
+
// detail-panel 相关配置
|
|
144
|
+
detailPanel: {
|
|
145
|
+
mainKey: '', // 表单主键,用于唯一标识要修改的数据对象
|
|
146
|
+
title: '', // 表单标题
|
|
147
|
+
}
|
|
148
|
+
// ...支持用户动态扩展
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}, ...]
|
|
153
|
+
}
|
|
154
|
+
服务端启动
|
|
155
|
+
const {
|
|
156
|
+
serverStart
|
|
157
|
+
} = require('@fsiaonma/elpis');
|
|
158
|
+
|
|
159
|
+
// 启动 elpis 服务
|
|
160
|
+
const app = serverStart({});
|
|
161
|
+
前端构建
|
|
162
|
+
const { frontendBuild } = require('@fsiaonma/elpis');
|
|
163
|
+
|
|
164
|
+
// 编译构建前端工程(签名不变)
|
|
165
|
+
// local → Vite createServer :9002
|
|
166
|
+
// production → Vite build → app/public/dist/prod
|
|
167
|
+
frontendBuild(process.env.\_ENV);
|
|
168
|
+
自定义页面扩展
|
|
169
|
+
在 app/pages/ 目录下写入口 entry.xxx.js
|
|
170
|
+
dashboard / custom-view 自定义页面扩展
|
|
171
|
+
在 app/pages/dashboard/xxxx 下写页面
|
|
172
|
+
dashboard / shcema-view / components 动态组件扩展
|
|
173
|
+
在 app/pages/dashboard/complex-view/schema-view/components 下写组件
|
|
174
|
+
配置到 app/pages/dashboard/complex-view/schema-view/components/component-config.js
|
|
175
|
+
schema-form 控件扩展
|
|
176
|
+
在 app/widgets/schema-form/complex-view 下写控件
|
|
177
|
+
配置到 app/widgets/schema-form/form-item-config.js
|
|
178
|
+
schema-search-bar 控件扩展
|
|
179
|
+
在 app/widgets/schema-search-bar/complex-view 下写控件
|
|
180
|
+
配置到 app/widgets/schema-search-bar/search-item-config.js
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module.exports = (app) => class BaseController {
|
|
2
|
+
/**
|
|
3
|
+
* 基础控制器
|
|
4
|
+
* conttroller 基类
|
|
5
|
+
* 统一收拢 controller 相关的公共方法
|
|
6
|
+
*/
|
|
7
|
+
constructor() {
|
|
8
|
+
this.app = app;
|
|
9
|
+
this.config = app.config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* API 处理成功响应统一返回结构
|
|
14
|
+
* @param {*} ctx koa 上下文
|
|
15
|
+
* @param {*} data 响应数据
|
|
16
|
+
* @param {*} metadata 元数据
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
success(ctx,data={}, metadata = {}) {
|
|
20
|
+
ctx.status = 200;
|
|
21
|
+
ctx.body = {
|
|
22
|
+
success:true,
|
|
23
|
+
data,
|
|
24
|
+
metadata
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* API 处理失败响应统一返回结构
|
|
31
|
+
* @param {*} ctx koa 上下文
|
|
32
|
+
* @param {*} message 错误消息
|
|
33
|
+
* @param {*} code 错误码
|
|
34
|
+
* @returns
|
|
35
|
+
*/
|
|
36
|
+
fail(ctx,message, code) {
|
|
37
|
+
ctx.body = {
|
|
38
|
+
success:false,
|
|
39
|
+
message,
|
|
40
|
+
code,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module.exports = (app) => {
|
|
2
|
+
const BaseController = require("./base")(app);
|
|
3
|
+
|
|
4
|
+
return class ProjectController extends BaseController {
|
|
5
|
+
/*
|
|
6
|
+
根据proj_key 获取项目配置
|
|
7
|
+
*/
|
|
8
|
+
get(ctx) {
|
|
9
|
+
const { proj_key: projKey } = ctx.request.query;
|
|
10
|
+
const { project: projectService } = app.service;
|
|
11
|
+
const projConfig = projectService.get(projKey);
|
|
12
|
+
if (!projConfig) {
|
|
13
|
+
this.fail(ctx, "获取项目异常", 50000);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
this.success(ctx, projConfig);
|
|
17
|
+
}
|
|
18
|
+
/*
|
|
19
|
+
获取当前projectKey 对应模型下的项目列表,(如果无projectKey,全量获取)
|
|
20
|
+
*/
|
|
21
|
+
getList(ctx) {
|
|
22
|
+
const { proj_key: projKey } = ctx.request.query;
|
|
23
|
+
const { project: projectService } = app.service;
|
|
24
|
+
const projectList = projectService.getList({ projKey });
|
|
25
|
+
|
|
26
|
+
const dtoProjectList = projectList.map((item) => {
|
|
27
|
+
const { modelKey, key, name, desc, homePage } = item;
|
|
28
|
+
return { modelKey, key, name, desc, homePage };
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.success(ctx, dtoProjectList);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
获取所有模型与项目的结构化数据
|
|
36
|
+
*/
|
|
37
|
+
async getModelList(ctx) {
|
|
38
|
+
const { project: projectService } = app.service;
|
|
39
|
+
const modleList = await projectService.getModelList();
|
|
40
|
+
//构造返回结果,只返回关键数据
|
|
41
|
+
const dtoModelList = modleList.reduce((preList, item) => {
|
|
42
|
+
const { model, project } = item;
|
|
43
|
+
//构造model关键数据
|
|
44
|
+
const { key, name, desc } = model;
|
|
45
|
+
const dtoModel = { key, name, desc };
|
|
46
|
+
//构造project 关键数据
|
|
47
|
+
const dtoProject = Object.keys(project).reduce((preObj, projKey) => {
|
|
48
|
+
const { key, name, desc, homePage } = project[projKey];
|
|
49
|
+
preObj[projKey] = { key, name, desc, homePage };
|
|
50
|
+
return preObj;
|
|
51
|
+
}, {});
|
|
52
|
+
|
|
53
|
+
preList.push({
|
|
54
|
+
model: dtoModel,
|
|
55
|
+
project: dtoProject,
|
|
56
|
+
});
|
|
57
|
+
return preList;
|
|
58
|
+
}, []);
|
|
59
|
+
this.success(ctx, dtoModelList);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = (app) => {
|
|
2
|
+
// 渲染模板
|
|
3
|
+
return class ViewController {
|
|
4
|
+
async renderPage(ctx) {
|
|
5
|
+
const { query, body } = ctx.request;
|
|
6
|
+
app.logger.info(`[ViewController] query: ${JSON.stringify(query)}`);
|
|
7
|
+
app.logger.info(`[ViewController] body: ${JSON.stringify(body)}`);
|
|
8
|
+
|
|
9
|
+
await ctx.render(`dist/entry.${ctx.params.page}`, {
|
|
10
|
+
projKey: ctx.query?.proj_key,
|
|
11
|
+
name: app.options.name,
|
|
12
|
+
env: app.env.get(),
|
|
13
|
+
options: JSON.stringify(app.options),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const log4js = require('log4js');
|
|
2
|
+
/**
|
|
3
|
+
* 日志记录器
|
|
4
|
+
* 外部调用 app.logger.info app.logger.error
|
|
5
|
+
* @returns 日志记录器实例
|
|
6
|
+
*/
|
|
7
|
+
module.exports = (app) => {
|
|
8
|
+
let logger ;
|
|
9
|
+
if(app.env.isLocal()){
|
|
10
|
+
// 打印在控制台即可
|
|
11
|
+
logger = console;
|
|
12
|
+
}else{
|
|
13
|
+
// 生产环境,八日至输出并落地到磁盘(日志落盘)
|
|
14
|
+
log4js.configure({
|
|
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: ['dateFile','console'],
|
|
29
|
+
level: 'trace'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
logger = log4js.getLogger();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return logger;
|
|
38
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const Ajv = require("ajv");
|
|
2
|
+
const ajv = new Ajv();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* API参数验证中间件,合法性校验
|
|
6
|
+
*/
|
|
7
|
+
module.exports = (app) => {
|
|
8
|
+
//定义 json-schema 校验规则
|
|
9
|
+
const $schema = "http://json-schema.org/draft-07/schema#";
|
|
10
|
+
return async (ctx, next) => {
|
|
11
|
+
// 只对api请求进行参数校验
|
|
12
|
+
if (ctx.path.indexOf("/api/") < 0) {
|
|
13
|
+
return await next();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 验证请求参数
|
|
17
|
+
const { query, body, headers } = ctx.request;
|
|
18
|
+
const { params, path, method } = ctx;
|
|
19
|
+
|
|
20
|
+
app.logger.info(`[${method}${path}] query: ${JSON.stringify(query)}`);
|
|
21
|
+
app.logger.info(`[${method}${path}] body: ${JSON.stringify(body)}`);
|
|
22
|
+
app.logger.info(`[${method}${path}] headers: ${JSON.stringify(headers)}`);
|
|
23
|
+
app.logger.info(`[${method}${path}] params: ${JSON.stringify(params)}`);
|
|
24
|
+
|
|
25
|
+
const schema = app.routerSchema[path]?.[method.toLowerCase()];
|
|
26
|
+
if (!schema) {
|
|
27
|
+
return await next();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let valid = true;
|
|
31
|
+
|
|
32
|
+
// ajv 校验请求参数 校验器
|
|
33
|
+
let validate;
|
|
34
|
+
|
|
35
|
+
// 校验 headers
|
|
36
|
+
if (valid && headers && schema.headers) {
|
|
37
|
+
schema.headers.$schema = $schema;
|
|
38
|
+
validate = ajv.compile(schema.headers);
|
|
39
|
+
valid = validate(headers);
|
|
40
|
+
}
|
|
41
|
+
// 校验 body
|
|
42
|
+
if (valid && body && schema.body) {
|
|
43
|
+
schema.body.$schema = $schema;
|
|
44
|
+
validate = ajv.compile(schema.body);
|
|
45
|
+
valid = validate(body);
|
|
46
|
+
}
|
|
47
|
+
// 校验 query
|
|
48
|
+
if (valid && query && schema.query) {
|
|
49
|
+
schema.query.$schema = $schema;
|
|
50
|
+
validate = ajv.compile(schema.query);
|
|
51
|
+
valid = validate(query);
|
|
52
|
+
}
|
|
53
|
+
// 校验 params
|
|
54
|
+
if (valid && params && schema.params) {
|
|
55
|
+
schema.params.$schema = $schema;
|
|
56
|
+
validate = ajv.compile(schema.params);
|
|
57
|
+
valid = validate(params);
|
|
58
|
+
}
|
|
59
|
+
if (!valid) {
|
|
60
|
+
ctx.status = 200;
|
|
61
|
+
ctx.body = {
|
|
62
|
+
success: false,
|
|
63
|
+
message: `request validate fail: ${ajv.errorsText(validate.errors)}`,
|
|
64
|
+
code: 442,
|
|
65
|
+
};
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await next();
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const md5 = require("md5");
|
|
2
|
+
/**
|
|
3
|
+
* API签名验证中间件,合法性校验
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = (app) => {
|
|
7
|
+
return async (ctx, next) => {
|
|
8
|
+
// 只对api请求进行校验
|
|
9
|
+
if (ctx.path.indexOf("/api") < 0) {
|
|
10
|
+
return await next();
|
|
11
|
+
}
|
|
12
|
+
const { path, method } = ctx;
|
|
13
|
+
const { headers } = ctx.request;
|
|
14
|
+
const { s_sign: sSgin, s_t: st } = headers;
|
|
15
|
+
|
|
16
|
+
const signKey = "ahjflkjcklvmlakdujsfwiujz123s"; //随便写的
|
|
17
|
+
const signature = md5(`${signKey}_${st}`);
|
|
18
|
+
app.logger.info(`[${method}${path}] signature: ${signature}`);
|
|
19
|
+
if (
|
|
20
|
+
!sSgin ||
|
|
21
|
+
!st ||
|
|
22
|
+
signature !== sSgin.toLowerCase() ||
|
|
23
|
+
Date.now() - st > 1000 * 600
|
|
24
|
+
) {
|
|
25
|
+
ctx.status = 200;
|
|
26
|
+
ctx.body = {
|
|
27
|
+
success: false,
|
|
28
|
+
message: "signature not correct or api request timeout !!",
|
|
29
|
+
code: 445,
|
|
30
|
+
};
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await next();
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时 错误处理中间件 ,兜底所有异常
|
|
3
|
+
* @param {*} app
|
|
4
|
+
* @returns
|
|
5
|
+
*/
|
|
6
|
+
module.exports = (app) => {
|
|
7
|
+
return async (ctx,next) => {
|
|
8
|
+
try {
|
|
9
|
+
await next();
|
|
10
|
+
} catch (err) {
|
|
11
|
+
// 异常处理
|
|
12
|
+
const {status,message,detail} =err
|
|
13
|
+
app.logger.info(JSON.stringify(err))
|
|
14
|
+
app.logger.error('[-- exception --]:',err)
|
|
15
|
+
app.logger.error('[-- exception --]:',status , message , detail)
|
|
16
|
+
|
|
17
|
+
if(message && message.indexOf('template not found') > -1){
|
|
18
|
+
// 进行页面重定向 301:永久重定向 302:临时重定向
|
|
19
|
+
ctx.status = 302;
|
|
20
|
+
ctx.redirect(`${app.options?.homePage}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const resBody = {
|
|
24
|
+
success:false,
|
|
25
|
+
code:50000,
|
|
26
|
+
message:"网络异常,请稍后重试",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ctx.status = 200;
|
|
30
|
+
ctx.body = resBody ;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* projectHandler 项目处理中间件
|
|
3
|
+
*/
|
|
4
|
+
module.exports = (app) => {
|
|
5
|
+
return async (ctx, next) => {
|
|
6
|
+
// 只对业务API 进行proj_key校验
|
|
7
|
+
if (ctx.path.indexOf("/api/proj/") < 0) {
|
|
8
|
+
return await next();
|
|
9
|
+
}
|
|
10
|
+
// 项目处理中间件
|
|
11
|
+
const { proj_key: projKey } = ctx.request.headers;
|
|
12
|
+
|
|
13
|
+
if (!projKey) {
|
|
14
|
+
ctx.status = 200;
|
|
15
|
+
ctx.body = {
|
|
16
|
+
success: false,
|
|
17
|
+
code: 446,
|
|
18
|
+
msg: "proj_key not found",
|
|
19
|
+
};
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
ctx.projKey = projKey;
|
|
23
|
+
|
|
24
|
+
await next();
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const koaNunjucks = require("koa-nunjucks-2");
|
|
3
|
+
|
|
4
|
+
module.exports = (app) => {
|
|
5
|
+
// 配置静态根目录
|
|
6
|
+
const koaStatic = require("koa-static");
|
|
7
|
+
app.use(koaStatic(path.resolve(process.cwd(), "./app/public")));
|
|
8
|
+
|
|
9
|
+
// 加载模板引擎
|
|
10
|
+
app.use(
|
|
11
|
+
koaNunjucks({
|
|
12
|
+
ext: "tpl",
|
|
13
|
+
path: path.resolve(process.cwd(), "./app/public"),
|
|
14
|
+
nunjucksConfig: {
|
|
15
|
+
noCache: true,
|
|
16
|
+
trimBlocks: true,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// 引入ctx.body 解析中间件
|
|
22
|
+
const BodyParser = require("koa-bodyparser");
|
|
23
|
+
app.use(
|
|
24
|
+
BodyParser({
|
|
25
|
+
formList: "1000mb",
|
|
26
|
+
enableTypes: ["json", "form-data", "text"],
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// 引入错误处理中间件
|
|
31
|
+
app.use(app.middlewares.errorHandler);
|
|
32
|
+
|
|
33
|
+
// 引入API签名验证中间件
|
|
34
|
+
app.use(app.middlewares.apiSignVerify);
|
|
35
|
+
|
|
36
|
+
// 引入API参数验证中间件
|
|
37
|
+
app.use(app.middlewares.apiParamsVerify);
|
|
38
|
+
|
|
39
|
+
// 引入项目处理中间件
|
|
40
|
+
app.use(app.middlewares.projectHandler);
|
|
41
|
+
};
|