@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.
- package/.eslintignore +2 -0
- package/.eslintrc +55 -0
- package/README.md +218 -0
- package/app/controller/base.js +40 -0
- package/app/controller/project.js +81 -0
- package/app/controller/view.js +22 -0
- package/app/extend/logger.js +43 -0
- package/app/middleware/api-params-verify.js +73 -0
- package/app/middleware/api-sign-verify.js +49 -0
- package/app/middleware/error-handler.js +31 -0
- package/app/middleware/project-handler.js +26 -0
- package/app/middleware.js +44 -0
- package/app/pages/assets/custom.css +14 -0
- package/app/pages/boot.js +56 -0
- package/app/pages/common/curl.js +84 -0
- package/app/pages/common/index.css +3 -0
- package/app/pages/common/utils.js +1 -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 +126 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +35 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +120 -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/create-form.vue +87 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +100 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +122 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +161 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +95 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +19 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +135 -0
- package/app/pages/dashboard/dashboard.vue +86 -0
- package/app/pages/dashboard/entry.dashboard.js +48 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +68 -0
- package/app/pages/store/project.js +12 -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 +107 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +138 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +140 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +122 -0
- package/app/pages/widgets/schema-form/form-item-config.js +20 -0
- package/app/pages/widgets/schema-form/schema-form.vue +135 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +51 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +63 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +41 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +49 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +126 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +22 -0
- package/app/pages/widgets/schema-table/schema-table.vue +259 -0
- package/app/pages/widgets/sider-container/sider-container.vue +27 -0
- package/app/public/output/entry.page1.tpl +40 -0
- package/app/public/output/entry.page2.tpl +11 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +239 -0
- package/app/router/project.js +22 -0
- package/app/router/view.js +17 -0
- package/app/router-schema/project.js +34 -0
- package/app/service/base.js +15 -0
- package/app/service/project.js +59 -0
- package/app/view/entry.tpl +25 -0
- package/app/webpack/config/webpack.base.js +280 -0
- package/app/webpack/config/webpack.dev.js +57 -0
- package/app/webpack/config/webpack.prod.js +127 -0
- package/app/webpack/dev.js +63 -0
- package/app/webpack/libs/blank.js +1 -0
- package/app/webpack/prod.js +22 -0
- package/config/config.beta.js +1 -0
- package/config/config.default.js +3 -0
- package/config/config.prod.js +1 -0
- package/elpis-core/env.js +27 -0
- package/elpis-core/index.js +98 -0
- package/elpis-core/loader/config.js +58 -0
- package/elpis-core/loader/controller.js +93 -0
- package/elpis-core/loader/extend.js +63 -0
- package/elpis-core/loader/middleware.js +84 -0
- package/elpis-core/loader/router-schema.js +56 -0
- package/elpis-core/loader/router.js +50 -0
- package/elpis-core/loader/service.js +85 -0
- package/index.js +38 -0
- package/model/index.js +129 -0
- package/package.json +92 -0
- package/test/controller/project.test.js +214 -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/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
|
+
};
|