@dmqweb/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 +59 -0
- package/.vscode/settings.json +15 -0
- package/README.md +198 -0
- package/app/controller/base.js +41 -0
- package/app/controller/project.js +98 -0
- package/app/controller/view.js +24 -0
- package/app/extend/logger.js +43 -0
- package/app/middleware/api-params-verify.js +89 -0
- package/app/middleware/api-sign-verify.js +47 -0
- package/app/middleware/error-handler.js +41 -0
- package/app/middleware/project-handler.js +27 -0
- package/app/middleware.js +40 -0
- package/app/pages/asserts/custom.css +12 -0
- package/app/pages/boot.js +59 -0
- package/app/pages/common/curl.js +88 -0
- package/app/pages/common/util.js +3 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +40 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +141 -0
- package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +43 -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 +146 -0
- package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +24 -0
- package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +118 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +177 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +157 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +150 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +113 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +134 -0
- package/app/pages/dashboard/dashboard.vue +127 -0
- package/app/pages/dashboard/entry.dashboard.js +46 -0
- package/app/pages/store/index.js +5 -0
- package/app/pages/store/menu.js +61 -0
- package/app/pages/store/project.js +13 -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 +144 -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 +166 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +144 -0
- package/app/pages/widgets/schema-form/form-item.config.js +24 -0
- package/app/pages/widgets/schema-form/schema-form.vue +144 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +57 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +77 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +51 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +58 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +138 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
- package/app/pages/widgets/schema-table/schema-table.vue +254 -0
- package/app/pages/widgets/sider-container/sider-container.vue +32 -0
- package/app/router/business.js +15 -0
- package/app/router/project.js +10 -0
- package/app/router/view.js +11 -0
- package/app/router-schema/business.js +82 -0
- package/app/router-schema/project.js +40 -0
- package/app/service/base.js +13 -0
- package/app/service/project.js +55 -0
- package/app/view/entry.tpl +27 -0
- package/app/webpack/config/blank.js +3 -0
- package/app/webpack/config/webpack.base.js +269 -0
- package/app/webpack/config/webpack.dev.js +61 -0
- package/app/webpack/config/webpack.prod.js +149 -0
- package/app/webpack/dev.js +58 -0
- package/app/webpack/prod.js +21 -0
- package/config/config.default.js +3 -0
- package/elpis-core/env.js +22 -0
- package/elpis-core/index.js +99 -0
- package/elpis-core/loader/config.js +51 -0
- package/elpis-core/loader/controller.js +75 -0
- package/elpis-core/loader/extend.js +54 -0
- package/elpis-core/loader/middleware.js +69 -0
- package/elpis-core/loader/router-schema.js +50 -0
- package/elpis-core/loader/router.js +52 -0
- package/elpis-core/loader/service.js +74 -0
- package/index.js +29 -0
- package/jsconfig.json +16 -0
- package/logs/applocation.log +3 -0
- package/model/index.js +119 -0
- package/package.json +93 -0
- package/test/controller/project.test.js +216 -0
package/.eslintignore
ADDED
package/.eslintrc
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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-parser",
|
|
14
|
+
"ecmaVersion": 2017,
|
|
15
|
+
"sourceType": "module",
|
|
16
|
+
"requireConfigFile": false,
|
|
17
|
+
"babelOptions": {
|
|
18
|
+
"presets": ["@babel/preset-env"] // 确保与项目 Babel 预设一致
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"rules": {
|
|
22
|
+
"no-unused-vars": [2, {"args": "none"}],
|
|
23
|
+
"strict": "off",
|
|
24
|
+
"valid-jsdoc": "off",
|
|
25
|
+
"jsdoc/require-param-description": "off",
|
|
26
|
+
"jsdoc/require-param-type": "off",
|
|
27
|
+
"jsdoc/check-param-names": "off",
|
|
28
|
+
"jsdoc/require-param": "off",
|
|
29
|
+
"jsdoc/check-tag-names": "off",
|
|
30
|
+
"linebreak-style": "off",
|
|
31
|
+
"array-bracket-spacing": "off",
|
|
32
|
+
"prefer-promise-reject-errors": "off",
|
|
33
|
+
"comma-dangle": "off",
|
|
34
|
+
"newline-per-chained-call": "off",
|
|
35
|
+
"no-loop-func": "off",
|
|
36
|
+
"no-empty": "off",
|
|
37
|
+
"no-else-return": "off",
|
|
38
|
+
"no-unneeded-ternary": "off",
|
|
39
|
+
"no-eval": "off",
|
|
40
|
+
"prefer-destructuring": "off",
|
|
41
|
+
"no-param-reassign": "off",
|
|
42
|
+
"max-len": "off",
|
|
43
|
+
"no-restricted-syntax": "off",
|
|
44
|
+
"no-plusplus": "off",
|
|
45
|
+
"no-useless-escape": "off",
|
|
46
|
+
"no-nested-ternary": "off",
|
|
47
|
+
"radix": "off",
|
|
48
|
+
"arrow-body-style": "off",
|
|
49
|
+
"arrow-parens": "off",
|
|
50
|
+
"vue/multi-word-component-names": "off",
|
|
51
|
+
"vue/valid-v-for": "off",
|
|
52
|
+
"vue/no-multiple-template-root": "off"
|
|
53
|
+
},
|
|
54
|
+
"globals": {
|
|
55
|
+
"$": true,
|
|
56
|
+
"axios": true,
|
|
57
|
+
"Vue": true
|
|
58
|
+
}
|
|
59
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Elpis
|
|
2
|
+
## 企业级全栈框架
|
|
3
|
+
## model配置
|
|
4
|
+
```js
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// dsl dashboard 模板配置
|
|
8
|
+
export default {
|
|
9
|
+
mode: 'dashboard', // 模板类型, 不同模板类型对应不一样的模板数据结构
|
|
10
|
+
name: '', // 名称;
|
|
11
|
+
desc: '', // 描述
|
|
12
|
+
icon: '', // 图标
|
|
13
|
+
homePage: '', // 首页(项目配置)
|
|
14
|
+
|
|
15
|
+
// 头部菜单
|
|
16
|
+
menu: [{
|
|
17
|
+
key: '', // 菜单唯一描述
|
|
18
|
+
name: '', // 菜单名称
|
|
19
|
+
menuType: '', // 枚举值 group 为有下拉子菜单 / module 为无子菜单
|
|
20
|
+
|
|
21
|
+
// 当menuType为group时,可填
|
|
22
|
+
subMenu: [{
|
|
23
|
+
// 可递归 menuItem
|
|
24
|
+
// 更多菜单项
|
|
25
|
+
}],
|
|
26
|
+
// 当menuType为module时,可填
|
|
27
|
+
moduleType: '', // 枚举值 sider 为侧边栏 /iframe 为第三方页面 / custom 为自定义页面 / schema 为配置式页面
|
|
28
|
+
|
|
29
|
+
// 当moduleType为sider时,可填
|
|
30
|
+
siderConfig: {
|
|
31
|
+
menu: [{
|
|
32
|
+
// 可递归 menuItem(除moduleType === sider 时)
|
|
33
|
+
}] // ... 更多菜单项
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// 当moduleType为iframe时,可填
|
|
37
|
+
iframeConfig: {
|
|
38
|
+
path: '' // iframe 路径
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// 当moduleType为custom时,可填
|
|
42
|
+
customConfig: {
|
|
43
|
+
path: '' // 自定义组件路径
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 当moduleType为schema时,可填
|
|
47
|
+
schemaConfig: {
|
|
48
|
+
api: '', // 数据源API (遵循 RESTFUL 规范)
|
|
49
|
+
// 板块数据结构
|
|
50
|
+
schema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: { // 板块属性
|
|
53
|
+
key: {
|
|
54
|
+
// 标准 schema 配置(占位)
|
|
55
|
+
type: '', // 字段类型
|
|
56
|
+
label: '', // 字段名称
|
|
57
|
+
// 字段在 table 中的相关配置
|
|
58
|
+
tableOption: {
|
|
59
|
+
// 标准 el-table-column 配置(占位)
|
|
60
|
+
toFixes: 2, // 数字类字段保留小数位数
|
|
61
|
+
visible: true // 是否在 表单 中显示
|
|
62
|
+
},
|
|
63
|
+
searchOption: {
|
|
64
|
+
// 标准 el-component-column 配置(占位)
|
|
65
|
+
comType: '', // 配置组件类型 input/select....
|
|
66
|
+
default: '', // 默认值
|
|
67
|
+
|
|
68
|
+
// 当 comType 为 select时
|
|
69
|
+
enumList: [], // 下拉框可选值
|
|
70
|
+
|
|
71
|
+
// 当 comType 为 dynamicSelect时
|
|
72
|
+
api: '',
|
|
73
|
+
},
|
|
74
|
+
// 字段在不同动态 component 中的相关配置, 前缀对应 componentConfig 中的键值
|
|
75
|
+
// 比如 componentConfig.createForm 这里就对应 createFormOption 的配置
|
|
76
|
+
// 字段在 createForm 中相关配置
|
|
77
|
+
createFormOption: {
|
|
78
|
+
// 标准 el-component-column 配置(占位)
|
|
79
|
+
comType: '', // 控件类型 input/select....
|
|
80
|
+
visible: true, // 是否在 表单 中显示 默认true
|
|
81
|
+
disabled: false, // 是否禁用
|
|
82
|
+
default: '', // 默认值
|
|
83
|
+
|
|
84
|
+
// 当 comType 为 select时
|
|
85
|
+
enumList: [], // 下拉框可选值
|
|
86
|
+
},
|
|
87
|
+
// 字段在 editForm 中相关配置
|
|
88
|
+
editFormOption: {
|
|
89
|
+
// 标准 el-component-column 配置(占位)
|
|
90
|
+
comType: '', // 控件类型 input/select....
|
|
91
|
+
visible: true, // 是否在 表单 中显示 默认true
|
|
92
|
+
disabled: false, // 是否禁用
|
|
93
|
+
default: '', // 默认值
|
|
94
|
+
},
|
|
95
|
+
// 字段在 detailPanel 中相关配置
|
|
96
|
+
detailPanelOption: {
|
|
97
|
+
// 标准 el-component-column 配置(占位)
|
|
98
|
+
comType: '', // 控件类型 input/select....
|
|
99
|
+
visible: true, // 是否在 表单 中显示 默认true
|
|
100
|
+
disabled: false, // 是否禁用
|
|
101
|
+
default: '', // 默认值
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
// ... 用户可扩展
|
|
105
|
+
},
|
|
106
|
+
required: [],
|
|
107
|
+
},
|
|
108
|
+
// 表单配置
|
|
109
|
+
tableConfig: {
|
|
110
|
+
headerButtons: [{ // 头部按钮组
|
|
111
|
+
label: '', // 按钮名称
|
|
112
|
+
eventKey: '', // 按钮事件名
|
|
113
|
+
eventOption: {
|
|
114
|
+
// 当eventKey === 'showComponent' 时,可填
|
|
115
|
+
conName: '', // 组件名称
|
|
116
|
+
}, // 按钮配置
|
|
117
|
+
// 标准 el-button 配置(占位)
|
|
118
|
+
}], // ... 更多按钮项
|
|
119
|
+
rowButtons: [{ // 行为按钮组
|
|
120
|
+
label: '',
|
|
121
|
+
eventKey: '',
|
|
122
|
+
eventOption: {
|
|
123
|
+
// 当eventKey === 'showComponent' 时,可填
|
|
124
|
+
conName: '', // 组件名称
|
|
125
|
+
|
|
126
|
+
// 当eventKey === 'remove' 时,可填
|
|
127
|
+
params: {
|
|
128
|
+
// paramKey = 参数键名
|
|
129
|
+
// rowValueKey = 参数值 当格式为 schema::tableKey 的时候,到 table中找到响应的字段
|
|
130
|
+
paramKey: 'rowValueKey'
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
// 标准 el-button 配置(占位)
|
|
134
|
+
}]
|
|
135
|
+
}, // table 配置
|
|
136
|
+
searchConfig: {}, // search-bar 配置
|
|
137
|
+
// 动态组件 相关配置
|
|
138
|
+
componentConfig: {
|
|
139
|
+
// create-form 表单相关配置
|
|
140
|
+
createForm: {
|
|
141
|
+
title: '', // 创建表单标题
|
|
142
|
+
saveBtnText: '', // 保存按钮名称
|
|
143
|
+
},
|
|
144
|
+
// edit-form 表单相关配置
|
|
145
|
+
editForm: {
|
|
146
|
+
mainKey: '', // 主键字段
|
|
147
|
+
title: '', // 编辑表单标题
|
|
148
|
+
saveBtnText: '', // 保存按钮名称
|
|
149
|
+
},
|
|
150
|
+
// detail-panel 表单相关配置
|
|
151
|
+
detailPanel: {
|
|
152
|
+
mainKey: '', // 主键字段
|
|
153
|
+
title: '', // 详情表单标题
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
// ... 支持用户动态扩展
|
|
157
|
+
}
|
|
158
|
+
}] // ... 更多菜单
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 服务端启动
|
|
163
|
+
```js
|
|
164
|
+
const { serverStart } = require("@dmqweb/elpis");
|
|
165
|
+
const app = serverStart({
|
|
166
|
+
name:"名称",
|
|
167
|
+
homePage:"/home"
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
## 自定义服务器
|
|
171
|
+
- router-schema
|
|
172
|
+
- router
|
|
173
|
+
- controller
|
|
174
|
+
- service
|
|
175
|
+
- extend
|
|
176
|
+
- config
|
|
177
|
+
## 前端构建
|
|
178
|
+
```js
|
|
179
|
+
const {frontendBuild} = require("@dmqweb/elpis");
|
|
180
|
+
frontendBuild(process.env._ENV);
|
|
181
|
+
```
|
|
182
|
+
## 自定义页面扩展
|
|
183
|
+
* 在`app/pages/`目录下写入口 entry.xxx.js
|
|
184
|
+
|
|
185
|
+
## dashboard / custom-view 自定义页面扩展
|
|
186
|
+
* 在 `app/pages/dashboard/xxx` 下写页面
|
|
187
|
+
|
|
188
|
+
## dashboard / schema-view / components 动态组件扩展
|
|
189
|
+
1. 在 `app/pages/dashboard/complex-view/schema-view/components` 下写组件
|
|
190
|
+
2. 配置到 `app/pages/dashboard/complex-view/schema-view/components/component-config.js`
|
|
191
|
+
|
|
192
|
+
## schema-form控件扩展
|
|
193
|
+
1. 在 `app/widgets/schema-form/complex-view` 下写控件
|
|
194
|
+
2. 配置到 `app/widgets/schema-form/form-item-config.js`
|
|
195
|
+
|
|
196
|
+
## schema-search-bar 控件扩展
|
|
197
|
+
1. 在 `app/widgets/schema-search-bar/complext-view` 下写控件
|
|
198
|
+
2. 配置到 `app/widgets/schema-search-bar/search-item-config.js`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module.exports = (app) => class BaseController {
|
|
2
|
+
/**
|
|
3
|
+
* controller 基类
|
|
4
|
+
* 统一收拢 controller 相关的公共方法
|
|
5
|
+
*/
|
|
6
|
+
constructor() {
|
|
7
|
+
this.app = app;
|
|
8
|
+
this.config = app.config;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* API 处理成功时统一返回结构
|
|
13
|
+
* @param {object} ctx 上下文
|
|
14
|
+
* @param {object} data 核心数据
|
|
15
|
+
* @param {object} metadata 附加数据
|
|
16
|
+
*/
|
|
17
|
+
success(ctx, data = {}, metadata = {}) {
|
|
18
|
+
ctx.status = 200; // 设置HTTP响应状态码为200,表示请求成功
|
|
19
|
+
// 设置响应体,返回统一格式的JSON数据
|
|
20
|
+
ctx.body = {
|
|
21
|
+
success: true,
|
|
22
|
+
data,
|
|
23
|
+
metadata
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* API 处理失败时统一返回结构
|
|
29
|
+
* @param {object} ctx 上下文
|
|
30
|
+
* @param {object} message 错误信息
|
|
31
|
+
* @param {object} code 错误码
|
|
32
|
+
*/
|
|
33
|
+
fail(ctx, message, code) {
|
|
34
|
+
// 设置响应体,返回统一格式的JSON数据
|
|
35
|
+
ctx.body = {
|
|
36
|
+
success: false,
|
|
37
|
+
message,
|
|
38
|
+
code
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module.exports = (app) => {
|
|
2
|
+
// 引入基础控制器类,用于继承基础方法如success、error等
|
|
3
|
+
const BaseController = require('./base')(app);
|
|
4
|
+
|
|
5
|
+
// 导出并创建项目控制器类,继承基础控制器
|
|
6
|
+
return class projectController extends BaseController {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 获取项目配置
|
|
10
|
+
* @param {object} ctx 上下文对象,包含请求和响应相关信息
|
|
11
|
+
* @param {string} ctx.request.query.proj_key 项目键值,用于指定要获取的项目配置
|
|
12
|
+
*/
|
|
13
|
+
async get(ctx) {
|
|
14
|
+
const {
|
|
15
|
+
proj_key: projKey
|
|
16
|
+
} = ctx.request.query;
|
|
17
|
+
|
|
18
|
+
const { project: projectService} = app.service;
|
|
19
|
+
const projConfig = await projectService.get(projKey);
|
|
20
|
+
|
|
21
|
+
if(!projConfig) {
|
|
22
|
+
this.fail(ctx, '项目获取异常', 5000);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 调用基础控制器的success方法,返回成功响应和项目配置数据
|
|
27
|
+
this.success(ctx,projConfig)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 获取项目列表
|
|
33
|
+
* 获取当前 projectKey 对应模型下的项目列表 (如果无 projectKey, 则获取所有项目列表)
|
|
34
|
+
*/
|
|
35
|
+
async getProjectList(ctx) {
|
|
36
|
+
const {
|
|
37
|
+
proj_key: projectKey
|
|
38
|
+
} = ctx.request.query;
|
|
39
|
+
|
|
40
|
+
// 从app.service中解构出project服务模块
|
|
41
|
+
const { project: projectService } = app.service
|
|
42
|
+
const projectList = await projectService.getProjectList({projectKey});
|
|
43
|
+
|
|
44
|
+
// 构造返回结果,只返回关键数据
|
|
45
|
+
const dtoProjectList = projectList.map(item => {
|
|
46
|
+
const { name, desc, homePage, key, modelKey} = item;
|
|
47
|
+
return {
|
|
48
|
+
name,
|
|
49
|
+
desc,
|
|
50
|
+
homePage,
|
|
51
|
+
key,
|
|
52
|
+
modelKey
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// 调用基础控制器的success方法,返回成功响应和项目列表数据
|
|
57
|
+
this.success(ctx,dtoProjectList)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 获取所有模型与项目的结构化数据
|
|
64
|
+
* @param {object} ctx 上下文对象,包含请求和响应相关信息
|
|
65
|
+
*/
|
|
66
|
+
async getModelList(ctx) {
|
|
67
|
+
// 从app.service中解构出project服务模块
|
|
68
|
+
const { project: projectService } = app.service
|
|
69
|
+
const modelList = await projectService.getModelList();
|
|
70
|
+
|
|
71
|
+
// 构造返回结果,只返回关键数据
|
|
72
|
+
const dtoModelList = modelList.reduce((preList, item) => {
|
|
73
|
+
const { model, project } = item
|
|
74
|
+
|
|
75
|
+
// 构造 model 关键数据
|
|
76
|
+
const {key, name, desc} = model
|
|
77
|
+
const dtoModel = {key, name, desc}
|
|
78
|
+
|
|
79
|
+
// 构造 project 关键数据
|
|
80
|
+
const dtoProject = Object.keys(project).reduce((pre, projKey) => {
|
|
81
|
+
const {key, name, desc, homePage} = project[projKey]
|
|
82
|
+
pre[projKey] = {key, name, desc, homePage}
|
|
83
|
+
return pre
|
|
84
|
+
}, {})
|
|
85
|
+
|
|
86
|
+
preList.push({
|
|
87
|
+
model: dtoModel,
|
|
88
|
+
project: dtoProject
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return preList
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
// 调用基础控制器的success方法,返回成功响应和项目列表数据
|
|
95
|
+
this.success(ctx,dtoModelList)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// 这个函数返回一个控制器类
|
|
2
|
+
module.exports = (app) => {
|
|
3
|
+
// 返回一个名为 viewController 的类
|
|
4
|
+
return class viewController {
|
|
5
|
+
/**
|
|
6
|
+
* 渲染页面
|
|
7
|
+
* @param {object} ctx 上下文
|
|
8
|
+
*/
|
|
9
|
+
async renderPage(ctx) {
|
|
10
|
+
app.logger.info(`[viewController] query: ${JSON.stringify(ctx.query)} `)
|
|
11
|
+
app.logger.info(`[viewController] params: ${JSON.stringify(ctx.params)} `)
|
|
12
|
+
// ctx.render() 是模板引擎提供的方法,用于渲染模板
|
|
13
|
+
// ctx.params.page 获取路由中的 :page 参数值
|
|
14
|
+
// 例如:如果访问 /view/page1,那么 ctx.params.page 就是 "page1"
|
|
15
|
+
// 最终会渲染 output/entry.page1 模板
|
|
16
|
+
await ctx.render(`dist/entry.${ctx.params.page}`,{
|
|
17
|
+
projKey: ctx.query?.proj_key,
|
|
18
|
+
name: app.options?.name,
|
|
19
|
+
env: app.env.get(),
|
|
20
|
+
options: JSON.stringify(app.options)
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const log4js = require('log4js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 日志工具
|
|
5
|
+
* 外部调用 app.logger.info app.logger.error
|
|
6
|
+
*/
|
|
7
|
+
module.exports = (app) => {
|
|
8
|
+
let logger;
|
|
9
|
+
|
|
10
|
+
// 根据运行环境选择不同的日志记录方式
|
|
11
|
+
if (app.env.isLocal()){
|
|
12
|
+
// 本地开发环境:直接使用控制台输出日志
|
|
13
|
+
logger = console;
|
|
14
|
+
}else {
|
|
15
|
+
// 生产或测试环境:使用 log4js 记录日志并保存到磁盘文件中
|
|
16
|
+
log4js.configure({
|
|
17
|
+
// 配置日志输出方式(追加器)
|
|
18
|
+
appenders: {
|
|
19
|
+
// 控制台输出
|
|
20
|
+
console:{
|
|
21
|
+
type: 'console'
|
|
22
|
+
},
|
|
23
|
+
// 文件输出,按日期切分日志文件
|
|
24
|
+
dateFile: {
|
|
25
|
+
type: 'dateFile',
|
|
26
|
+
filename: './logs/applocation.log', // 日志文件路径和基本名称
|
|
27
|
+
pattern: 'yyyy-MM-dd' // 按天切分文件的模式
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
// 配置日志分类和级别
|
|
31
|
+
categories: {
|
|
32
|
+
default: {
|
|
33
|
+
appenders: ['console', 'dateFile'],// 同时输出到控制台和文件
|
|
34
|
+
level: 'trace' // 记录所有级别的日志(trace 是最低级别)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// 获取默认分类的日志记录器实例
|
|
39
|
+
logger = log4js.getLogger();
|
|
40
|
+
}
|
|
41
|
+
// 返回配置好的日志记录器
|
|
42
|
+
return logger;
|
|
43
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const Ajv = require('ajv')
|
|
2
|
+
const ajv = new Ajv()
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* API参数验证中间件
|
|
6
|
+
* 用于验证API请求的参数是否符合预定义的JSON Schema规范
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = (app) => {
|
|
10
|
+
|
|
11
|
+
// JSON Schema标准版本定义
|
|
12
|
+
const $schema = "http://json-schema.org/draft-07/schema#"
|
|
13
|
+
|
|
14
|
+
// 返回中间件函数,处理每个请求
|
|
15
|
+
return async (ctx, next) => {
|
|
16
|
+
// 只对API请求进行参数验证,非API请求直接跳过
|
|
17
|
+
if (ctx.path.indexOf('/api/') < 0 ) {
|
|
18
|
+
return await next()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 获取请求的各个部分参数
|
|
22
|
+
const {body, query, headers} = ctx.request; // 请求体、查询参数、请求头
|
|
23
|
+
const {params, path, method} = ctx; // 路径参数、请求路径、请求方法
|
|
24
|
+
|
|
25
|
+
// 记录请求信息到日志,便于调试和监控
|
|
26
|
+
app.logger.info(`[${method} ${path}] body: ${JSON.stringify(body)}]`)
|
|
27
|
+
app.logger.info(`[${method} ${path}] query: ${JSON.stringify(query)}]`)
|
|
28
|
+
app.logger.info(`[${method} ${path}] params: ${JSON.stringify(params)}]`)
|
|
29
|
+
app.logger.info(`[${method} ${path}] headers: ${JSON.stringify(headers)}]`)
|
|
30
|
+
|
|
31
|
+
// 从应用的路由Schema配置中获取当前路径和方法对应的参数验证规则
|
|
32
|
+
const schema = app.routerSchema[path]?.[method.toLowerCase()]
|
|
33
|
+
|
|
34
|
+
// 如果没有定义验证规则,则跳过验证
|
|
35
|
+
if (!schema) {
|
|
36
|
+
return await next();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 验证结果标志,默认为true
|
|
40
|
+
let valid = true
|
|
41
|
+
|
|
42
|
+
// 用于存储ajv验证器实例
|
|
43
|
+
let validate;
|
|
44
|
+
|
|
45
|
+
// 按优先级顺序验证各部分参数: headers -> body -> query -> params
|
|
46
|
+
|
|
47
|
+
// 验证请求头(headers)参数
|
|
48
|
+
if (valid && headers && schema.headers) {
|
|
49
|
+
schema.headers.$schema = $schema // 设置JSON Schema版本
|
|
50
|
+
validate = ajv.compile(schema.headers) // 编译验证器
|
|
51
|
+
valid = validate(headers) // 执行验证
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 验证请求体(body)参数
|
|
55
|
+
if (valid && body && schema.body) {
|
|
56
|
+
schema.body.$schema = $schema
|
|
57
|
+
validate = ajv.compile(schema.body)
|
|
58
|
+
valid = validate(body)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 验证查询参数(query)参数
|
|
62
|
+
if (valid && query && schema.query) {
|
|
63
|
+
schema.query.$schema = $schema
|
|
64
|
+
validate = ajv.compile(schema.query)
|
|
65
|
+
valid = validate(query)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 验证路径参数(params)参数
|
|
69
|
+
if (valid && params && schema.params) {
|
|
70
|
+
schema.params.$schema = $schema
|
|
71
|
+
validate = ajv.compile(schema.params)
|
|
72
|
+
valid = validate(params)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 如果验证失败,返回错误响应
|
|
76
|
+
if (!valid){
|
|
77
|
+
ctx.status = 200; // HTTP状态码设为200
|
|
78
|
+
ctx.body = { // 返回错误信息
|
|
79
|
+
success: false, // 标记请求处理失败
|
|
80
|
+
message: `request validate fail ${ajv.errorsText(validate.errors)} `, // 错误详情
|
|
81
|
+
code: 442 // 自定义错误码
|
|
82
|
+
}
|
|
83
|
+
return // 中断后续处理流程
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 验证通过,继续执行后续中间件
|
|
87
|
+
await next()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const md5 = require("md5");
|
|
2
|
+
|
|
3
|
+
module.exports = (app) => {
|
|
4
|
+
return async (ctx, next) => {
|
|
5
|
+
// 只对API请求做签名校验
|
|
6
|
+
if (ctx.path.indexOf('/api') < 0 ){
|
|
7
|
+
return await next()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 获取请求路径、方法和请求头信息
|
|
11
|
+
const { path, method} = ctx;
|
|
12
|
+
const { headers} = ctx.request;
|
|
13
|
+
// 从请求头中获取签名和时间戳
|
|
14
|
+
const { s_sign: sSign, s_t: st} = headers;
|
|
15
|
+
|
|
16
|
+
// 如果是浏览器直接访问(没有签名信息),则跳过验证
|
|
17
|
+
if (!sSign && !st) {
|
|
18
|
+
await next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 服务端预设的签名密钥
|
|
23
|
+
const signKey = 'elpis-sign-key'
|
|
24
|
+
// 根据时间戳和密钥生成签名
|
|
25
|
+
const signature = md5(`${signKey}_${st}`)
|
|
26
|
+
app.logger.info(`[${method} ${path}] signature: ${signature}`)
|
|
27
|
+
|
|
28
|
+
// 验证签名的合法性
|
|
29
|
+
// 条件包括:
|
|
30
|
+
// 1. 必须提供签名(sSign)
|
|
31
|
+
// 2. 必须提供时间戳(st)
|
|
32
|
+
// 3. 生成的签名必须与客户端提供的签名一致
|
|
33
|
+
// 4. 时间戳不能超过10分钟(600 * 1000毫秒)
|
|
34
|
+
if (!sSign || !st || signature !== sSign.toLowerCase() || Date.now() - st > 600 * 1000 ) {
|
|
35
|
+
ctx.status = 200;
|
|
36
|
+
ctx.body = {
|
|
37
|
+
success: false,
|
|
38
|
+
message: 'signature not correct or api timeout!!',
|
|
39
|
+
code: 445
|
|
40
|
+
};
|
|
41
|
+
// 验证失败时直接返回,不继续执行后续中间件和路由处理
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// 签名验证通过,继续执行后续中间件
|
|
45
|
+
await next()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时异常错误处理,兜底使用异常
|
|
3
|
+
* @param {[object]} app koa 实例
|
|
4
|
+
*
|
|
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
|
+
|
|
14
|
+
// 记录错误日志,包括完整的错误对象和分解的错误信息
|
|
15
|
+
app.logger.info(JSON.stringify(err));
|
|
16
|
+
app.logger.error('[-- exception --]:', err);
|
|
17
|
+
app.logger.error('[-- exception --]:', status, message, detail);
|
|
18
|
+
|
|
19
|
+
// 特殊处理:如果错误信息中包含"template not found"(模板未找到)
|
|
20
|
+
// 则重定向到配置的主页
|
|
21
|
+
if (message && message.indexOf('template not found') > -1){
|
|
22
|
+
ctx.status = 302;
|
|
23
|
+
ctx.redirect(`${app.options?.homePage}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 默认的错误响应结构
|
|
28
|
+
const resBody = {
|
|
29
|
+
success: false,
|
|
30
|
+
code: 500,
|
|
31
|
+
message: '服务器异常',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 设置响应状态码为200(即使出现错误也返回200,错误信息放在body中
|
|
35
|
+
ctx.status = 200;
|
|
36
|
+
// 设置响应体,返回统一格式的错误信息
|
|
37
|
+
ctx.body = resBody;
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|