@misstalor17/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 +56 -0
- package/README.md +6 -0
- package/app/controller/base.js +43 -0
- package/app/controller/business.js +121 -0
- package/app/controller/project.js +75 -0
- package/app/controller/view.js +24 -0
- package/app/extend/logger.js +37 -0
- package/app/middleware/api-params-verify.js +70 -0
- package/app/middleware/api-sign-verify.js +31 -0
- package/app/middleware/error-handler.js +32 -0
- package/app/middleware/project-handler.js +28 -0
- package/app/middleware.js +44 -0
- package/app/pages/asserts/custom.css +12 -0
- package/app/pages/boot.js +45 -0
- package/app/pages/common/curl.js +96 -0
- package/app/pages/common/utils.js +2 -0
- package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu.vue +22 -0
- package/app/pages/dashboard/complex-view/header-view/header-view.vue +106 -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 +33 -0
- package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +105 -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 +85 -0
- package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +90 -0
- package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +113 -0
- package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +109 -0
- package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +66 -0
- package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +17 -0
- package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +116 -0
- package/app/pages/dashboard/dashboard.vue +96 -0
- package/app/pages/dashboard/entry.dashboard.js +45 -0
- package/app/pages/store/index.js +3 -0
- package/app/pages/store/menu.js +60 -0
- package/app/pages/store/project.js +17 -0
- package/app/pages/widgets/header-container/asserts/logo.png +0 -0
- package/app/pages/widgets/header-container/asserts/user.png +0 -0
- package/app/pages/widgets/header-container/header-container.vue +105 -0
- package/app/pages/widgets/schema-form/complex-view/input/input.vue +120 -0
- package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +121 -0
- package/app/pages/widgets/schema-form/complex-view/select/select.vue +106 -0
- package/app/pages/widgets/schema-form/form-item-config.js +20 -0
- package/app/pages/widgets/schema-form/schema-form.vue +122 -0
- package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +42 -0
- package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +55 -0
- package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +37 -0
- package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +41 -0
- package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +109 -0
- package/app/pages/widgets/schema-search-bar/search-item-config.js +23 -0
- package/app/pages/widgets/schema-table/schema.table.vue +191 -0
- package/app/pages/widgets/sider-container/sider-container.vue +34 -0
- package/app/public/static/logo.png +0 -0
- package/app/public/static/normalize.css +240 -0
- package/app/router/business.js +9 -0
- package/app/router/project.js +6 -0
- package/app/router/view.js +13 -0
- package/app/router-schema/business.js +82 -0
- package/app/router-schema/project.js +31 -0
- package/app/service/base.js +13 -0
- package/app/service/project.js +40 -0
- package/app/view/entry.tpl +25 -0
- package/app/webpack/config/webpack.base.js +204 -0
- package/app/webpack/config/webpack.dev.js +56 -0
- package/app/webpack/config/webpack.prod.js +116 -0
- package/app/webpack/dev.js +52 -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 +143 -0
- package/elpis-core/env.js +23 -0
- package/elpis-core/index.js +93 -0
- package/elpis-core/loader/config.js +52 -0
- package/elpis-core/loader/controller.js +61 -0
- package/elpis-core/loader/extend.js +52 -0
- package/elpis-core/loader/middleware.js +61 -0
- package/elpis-core/loader/router-schema.js +46 -0
- package/elpis-core/loader/router.js +43 -0
- package/elpis-core/loader/service.js +61 -0
- package/index.js +38 -0
- package/model/buiness/model.js +200 -0
- package/model/index.js +93 -0
- package/npminstall-debug.log +218 -0
- package/package.json +92 -0
- package/test/controller/project.test.js +194 -0
package/.eslintignore
ADDED
package/.eslintrc
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
}
|
|
56
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module.exports = (app) => class BaseController {
|
|
2
|
+
/**
|
|
3
|
+
* controller 基类
|
|
4
|
+
* 统一收拢 controller 相关的公共方法
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.app = app;
|
|
9
|
+
this.config = app.config
|
|
10
|
+
// this.service = app.service;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* API 处理成功时统一返回结构
|
|
15
|
+
* @params {onject} ctx 上下文
|
|
16
|
+
* @params {onject} data 核心数据
|
|
17
|
+
* @params {onject} metadata 附加数据
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
success(ctx, data = {}, metadata = {}) {
|
|
21
|
+
ctx.status = 200;
|
|
22
|
+
ctx.body = {
|
|
23
|
+
success: true,
|
|
24
|
+
data,
|
|
25
|
+
metadata
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* API 处理失败时统一返回结构
|
|
31
|
+
* @params {onject} ctx 上下文
|
|
32
|
+
* @params {onject} message 错误信息
|
|
33
|
+
* @params {onject} code 错误码
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
fail(ctx, message, code) {
|
|
37
|
+
ctx.body = {
|
|
38
|
+
success: false,
|
|
39
|
+
message,
|
|
40
|
+
code
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module.exports = (app) => {
|
|
2
|
+
const sleep = async (time) => {
|
|
3
|
+
return new Promise(resolve => {
|
|
4
|
+
setTimeout(() => {
|
|
5
|
+
resolve()
|
|
6
|
+
}, time)
|
|
7
|
+
})
|
|
8
|
+
}
|
|
9
|
+
const baseController = require('./base')(app)
|
|
10
|
+
|
|
11
|
+
return class BusinessController extends baseController {
|
|
12
|
+
async update(ctx) {
|
|
13
|
+
const {
|
|
14
|
+
product_id: productId,
|
|
15
|
+
product_name: productName,
|
|
16
|
+
price,
|
|
17
|
+
inventory
|
|
18
|
+
} = ctx.request.body
|
|
19
|
+
|
|
20
|
+
await sleep(500)
|
|
21
|
+
this.success(ctx, {
|
|
22
|
+
product_id: productId,
|
|
23
|
+
product_name: productName,
|
|
24
|
+
price,
|
|
25
|
+
inventory
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async create(ctx) {
|
|
32
|
+
const {
|
|
33
|
+
product_name: productName,
|
|
34
|
+
price,
|
|
35
|
+
inventory
|
|
36
|
+
} = ctx.request.body
|
|
37
|
+
await sleep(500)
|
|
38
|
+
this.success(ctx, {
|
|
39
|
+
product_id: Date.now(),
|
|
40
|
+
product_name: productName,
|
|
41
|
+
price,
|
|
42
|
+
inventory
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async remove(ctx) {
|
|
47
|
+
const { product_id: productId } = ctx.request.body
|
|
48
|
+
await sleep(500)
|
|
49
|
+
this.success(ctx, {
|
|
50
|
+
proj_key: ctx.projKey,
|
|
51
|
+
product_id: productId
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async get(ctx) {
|
|
56
|
+
const {
|
|
57
|
+
product_id: productId,
|
|
58
|
+
} = ctx.request.query
|
|
59
|
+
await sleep(500)
|
|
60
|
+
const productList = this.getProductList(ctx)
|
|
61
|
+
|
|
62
|
+
const productItem = productList.find(item => item.product_id === productId)
|
|
63
|
+
|
|
64
|
+
this.success(ctx,productItem)
|
|
65
|
+
}
|
|
66
|
+
async getList(ctx) {
|
|
67
|
+
const { page, size, product_name: productName } = ctx.request.query
|
|
68
|
+
await sleep(500)
|
|
69
|
+
|
|
70
|
+
let productList = this.getProductList(ctx)
|
|
71
|
+
|
|
72
|
+
if (productName && productName !== 'all') {
|
|
73
|
+
productList = productList.filter(item => item.product_name === productName)
|
|
74
|
+
}
|
|
75
|
+
this.success(ctx, productList, {
|
|
76
|
+
total: 3,
|
|
77
|
+
page,
|
|
78
|
+
size
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getProductEnumList(ctx) {
|
|
83
|
+
this.success(ctx, [{
|
|
84
|
+
label: '全部',
|
|
85
|
+
value: 'all',
|
|
86
|
+
}, {
|
|
87
|
+
label: `${ctx.projKey}-111`,
|
|
88
|
+
value: `${ctx.projKey}-111`,
|
|
89
|
+
}, {
|
|
90
|
+
label: `${ctx.projKey}-222`,
|
|
91
|
+
value: `${ctx.projKey}-222`,
|
|
92
|
+
}, {
|
|
93
|
+
label: `${ctx.projKey}-333`,
|
|
94
|
+
value: `${ctx.projKey}-333`,
|
|
95
|
+
}])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
getProductList(ctx) {
|
|
99
|
+
return [{
|
|
100
|
+
product_id: '1',
|
|
101
|
+
product_name: `${ctx.projKey}-111`,
|
|
102
|
+
price: 999,
|
|
103
|
+
inventory: 111,
|
|
104
|
+
create_time: '2024-06-08 18:00:00'
|
|
105
|
+
}, {
|
|
106
|
+
product_id: '2',
|
|
107
|
+
product_name: `${ctx.projKey}-222`,
|
|
108
|
+
price: 888,
|
|
109
|
+
inventory: 222,
|
|
110
|
+
create_time: '2024-06-08 18:00:00'
|
|
111
|
+
}, {
|
|
112
|
+
product_id: '3',
|
|
113
|
+
product_name: `${ctx.projKey}-333`,
|
|
114
|
+
price: 777,
|
|
115
|
+
inventory: 333,
|
|
116
|
+
create_time: '2024-06-08 18:00:00'
|
|
117
|
+
}]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 {
|
|
10
|
+
proj_key:projKey
|
|
11
|
+
} = ctx.request.query
|
|
12
|
+
|
|
13
|
+
const { project:projectService } = app.service
|
|
14
|
+
const projConfig = projectService.get(projKey)
|
|
15
|
+
|
|
16
|
+
if(!projConfig) {
|
|
17
|
+
this.fail(ctx,'获取项目异常',50000);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.success(ctx,projConfig)
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 获取当前 project 对应模型下的项目列表(如果无projectKey,全量获取)
|
|
24
|
+
* @param {object} ctx
|
|
25
|
+
*/
|
|
26
|
+
getList(ctx) {
|
|
27
|
+
const { proj_key: projKey } = ctx.request.query
|
|
28
|
+
|
|
29
|
+
const {project:projectService } = app.service;
|
|
30
|
+
const projectList = projectService.getList({projKey})
|
|
31
|
+
|
|
32
|
+
const dtoProjectList = projectList.map(item => {
|
|
33
|
+
const { modelKey,key,name,desc,homePage } = item
|
|
34
|
+
return { modelKey,key,name,desc,homePage }
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
this.success(ctx,dtoProjectList)
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取所有模型与项目的结构化数据
|
|
42
|
+
* @param {object} ctx
|
|
43
|
+
*/
|
|
44
|
+
async getModelList(ctx) {
|
|
45
|
+
const { project: projectService } = app.service
|
|
46
|
+
const modelList = await projectService.getModelList()
|
|
47
|
+
//构造返回结果,只返回关键数据
|
|
48
|
+
const dtoModelList = modelList.reduce((preList, item) => {
|
|
49
|
+
const { model, project } = item
|
|
50
|
+
|
|
51
|
+
//构造model关键数据
|
|
52
|
+
const { key, name, desc } = model
|
|
53
|
+
const dtoModel = { key, name, desc }
|
|
54
|
+
//构造project关键数据
|
|
55
|
+
const dtoProject = Object.keys(project).reduce((preObj, projKey) => {
|
|
56
|
+
const { key, name, desc, homePage } = project[projKey]
|
|
57
|
+
preObj[projKey] = { key, name, desc, homePage }
|
|
58
|
+
return preObj;
|
|
59
|
+
}, {})
|
|
60
|
+
|
|
61
|
+
//整合返回结构
|
|
62
|
+
preList.push({
|
|
63
|
+
model: dtoModel,
|
|
64
|
+
project: dtoProject
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return preList
|
|
68
|
+
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
this.success(ctx, dtoModelList)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module.exports = (app) => {
|
|
2
|
+
// 返回一个控制器类:ViewController
|
|
3
|
+
return class ViewController {
|
|
4
|
+
/**
|
|
5
|
+
* 渲染页面
|
|
6
|
+
* @param {object} ctx 上下文
|
|
7
|
+
*/
|
|
8
|
+
// 定义一个异步方法renderPage,接收ctx参数
|
|
9
|
+
async renderPage(ctx) {
|
|
10
|
+
// 核心逻辑:调用ctx.render()渲染模板
|
|
11
|
+
// 模板路径是动态拼接的:output/entry.${ctx.params.page}
|
|
12
|
+
const { query, params } = ctx.request
|
|
13
|
+
app.logger.info(`[ViewController] query: ${JSON.stringify(query)}`);
|
|
14
|
+
app.logger.info(`[ViewController] params: ${JSON.stringify(params)}`);
|
|
15
|
+
await ctx.render(`dist/entry.${ctx.params.page}`, {
|
|
16
|
+
projKey: ctx.query?.proj_key,
|
|
17
|
+
name: app.options?.name,
|
|
18
|
+
env: app.env.get(),
|
|
19
|
+
options: JSON.stringify(app.options)
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
if (app.env.isLocal()) {
|
|
11
|
+
//如果是本地环境,打印在控制台即可
|
|
12
|
+
logger = console
|
|
13
|
+
} else {
|
|
14
|
+
//如果不是,把日志输出并落地到磁盘(日志落盘)
|
|
15
|
+
log4js.configure({
|
|
16
|
+
appenders: {
|
|
17
|
+
console: {
|
|
18
|
+
type: 'console'
|
|
19
|
+
},
|
|
20
|
+
//日志文件切分
|
|
21
|
+
dateFile: {
|
|
22
|
+
type: 'dateFile',
|
|
23
|
+
filename: './log/application.log',
|
|
24
|
+
pattern: '.yyyy-MM-dd'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
categories: {
|
|
28
|
+
default: {
|
|
29
|
+
appenders: ['console', 'dateFile'],
|
|
30
|
+
level: 'trace'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
logger = log4js.getLogger();
|
|
35
|
+
}
|
|
36
|
+
return logger
|
|
37
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const Ajv = require('ajv')
|
|
2
|
+
const ajv = new Ajv()
|
|
3
|
+
/**
|
|
4
|
+
* API 参数校验
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = (app) => {
|
|
8
|
+
const $schema = "http://json-schema.org/draft-07/schema#"
|
|
9
|
+
//仅对以/api开头的接口请求进行签名验证,非 API 请求直接放行。
|
|
10
|
+
return async (ctx, next) => {
|
|
11
|
+
if (ctx.path.indexOf('/api/') < 0) {
|
|
12
|
+
return await next()
|
|
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} query: ${JSON.stringify(query)}`);
|
|
20
|
+
app.logger.info(`[${method}] ${path} params: ${JSON.stringify(params)}`);
|
|
21
|
+
app.logger.info(`[${method}] ${path} headers: ${JSON.stringify(headers)}`);
|
|
22
|
+
//从app.routerSchema中,根据当前请求的路径(path)和请求方法(method),加载对应的 JSON Schema 校验规则;
|
|
23
|
+
const schema = app.routerSchema[path]?.[method.toLowerCase()]
|
|
24
|
+
|
|
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
|
+
validate = ajv.compile(schema.headers)
|
|
38
|
+
valid = validate(headers)
|
|
39
|
+
}
|
|
40
|
+
//校验query
|
|
41
|
+
if (valid && query && schema.query) {
|
|
42
|
+
schema.query.$schema = $schema
|
|
43
|
+
validate = ajv.compile(schema.query)
|
|
44
|
+
valid = validate(query)
|
|
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
|
+
//校验params
|
|
53
|
+
if (valid && params && schema.params) {
|
|
54
|
+
schema.params.$schema = $schema
|
|
55
|
+
validate = ajv.compile(schema.params)
|
|
56
|
+
valid = validate(params)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!valid) {
|
|
60
|
+
ctx.status = 200
|
|
61
|
+
ctx.body = {
|
|
62
|
+
success: false,
|
|
63
|
+
message: `request vaildate fail: ${ajv.errorsText(validate.errors)}`,
|
|
64
|
+
code: 442
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await next()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const md5 = require('md5')
|
|
2
|
+
/**
|
|
3
|
+
* API签名的合法性
|
|
4
|
+
*/
|
|
5
|
+
module.exports = (app) => {
|
|
6
|
+
return async (ctx, next) => {
|
|
7
|
+
//仅对以/api开头的接口请求进行签名验证,非 API 请求直接放行。
|
|
8
|
+
if (ctx.path.indexOf('/api') < 0) {
|
|
9
|
+
return await next()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { path, method } = ctx
|
|
13
|
+
const headers = ctx.headers;
|
|
14
|
+
const { s_sign: sSign, s_t: st } = headers
|
|
15
|
+
|
|
16
|
+
const signKey = 'AShdahsdoahdsoanjsdoaiuhd13'// 前后端约定的「签名密钥」(需保密)
|
|
17
|
+
const signature = md5(`${signKey}_${st}`)// 服务端用「密钥+前端时间戳」生成MD5签名
|
|
18
|
+
app.logger.info(`[${method} ${path}] signature: ${signature}`)
|
|
19
|
+
|
|
20
|
+
if (!sSign || !st || signature !== sSign.toLowerCase() || Date.now() - st > 600000) {
|
|
21
|
+
ctx.status = 200
|
|
22
|
+
ctx.body = {
|
|
23
|
+
success: false,
|
|
24
|
+
message: 'signature not correct or api timeout!!',
|
|
25
|
+
code: 445
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await next()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时异常错误处理,兜底所有异常
|
|
3
|
+
* @param {object} app koa 实例
|
|
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('[--exception--]:', error)
|
|
14
|
+
app.logger.error('[--exception--]:', status, message, detail)
|
|
15
|
+
|
|
16
|
+
if (message && message.indexOf('template not found') > -1) {
|
|
17
|
+
//页面重定向
|
|
18
|
+
ctx.status = 302
|
|
19
|
+
ctx.redirect(`${app.options?.homePage}`)
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const resBody = {
|
|
24
|
+
success:false,
|
|
25
|
+
code:50000,
|
|
26
|
+
message:'网络异常,请稍后重试'
|
|
27
|
+
}
|
|
28
|
+
ctx.status = 200
|
|
29
|
+
ctx.body = resBody
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* projectHandler 相关项目处理内容
|
|
3
|
+
* @param {*} app
|
|
4
|
+
*/
|
|
5
|
+
module.exports = (app) => {
|
|
6
|
+
return async (ctx, next) => {
|
|
7
|
+
//只对业务 API 进行 proj——key 处理
|
|
8
|
+
if (ctx.path.indexOf('/api/proj/') < 0) {
|
|
9
|
+
return await next()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//获取projKey
|
|
13
|
+
const { proj_key: projKey } = ctx.request.headers;
|
|
14
|
+
if (!projKey) {
|
|
15
|
+
ctx.status = 200
|
|
16
|
+
ctx.body = {
|
|
17
|
+
success: false,
|
|
18
|
+
message: 'proj_key not found',
|
|
19
|
+
code: 446
|
|
20
|
+
}
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ctx.projKey = projKey
|
|
25
|
+
await next()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
// 导出一个函数,接收Koa的app实例作为参数
|
|
4
|
+
module.exports = (app) => {
|
|
5
|
+
//配置静态根目录
|
|
6
|
+
const koaStatic = require('koa-static')
|
|
7
|
+
app.use(koaStatic(path.resolve(process.cwd(), './app/public')))
|
|
8
|
+
//渲染模版引擎
|
|
9
|
+
// 引入koa-nunjucks-2中间件(Nunjucks模板引擎的Koa适配插件)
|
|
10
|
+
const koaNunjucks = require('koa-nunjucks-2');
|
|
11
|
+
// 给Koa应用注册这个中间件,并配置参数
|
|
12
|
+
app.use(koaNunjucks({
|
|
13
|
+
ext: 'tpl',// 配置模板文件的后缀为.tpl(即识别app/public下的*.tpl文件为模板)
|
|
14
|
+
// 配置模板文件的存放目录:
|
|
15
|
+
// process.cwd()是当前项目的根目录,path.resolve拼接成:项目根目录/app/public
|
|
16
|
+
path: path.resolve(process.cwd(), './app/public'),
|
|
17
|
+
// Nunjucks模板引擎的额外配置
|
|
18
|
+
nunjucksConfig: {
|
|
19
|
+
// 渲染时自动去除模板中多余的空格/换行(让输出的HTML更整洁)
|
|
20
|
+
trimBlocks: true,
|
|
21
|
+
// 关闭模板缓存(开发环境用,修改模板后不用重启服务就能生效)
|
|
22
|
+
noCache: true
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
//引入 ctx.body 解析中间件
|
|
27
|
+
const bodyParser = require('koa-bodyparser')
|
|
28
|
+
app.use(bodyParser({
|
|
29
|
+
formLimit: '1000mb',//设置表单数据的最大体积限制为 1000MB
|
|
30
|
+
enableTypes: ['form', 'json', 'text'] //指定中间件支持解析的请求体类型
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
//引入异常捕获中间件
|
|
34
|
+
app.use(app.middlewares.errorHandler);
|
|
35
|
+
|
|
36
|
+
//引入签名合法性校验
|
|
37
|
+
app.use(app.middlewares.apiSignVerify)
|
|
38
|
+
|
|
39
|
+
//引入API参数校验
|
|
40
|
+
app.use(app.middlewares.apiParamsVerify)
|
|
41
|
+
|
|
42
|
+
//引入项目处理中间件
|
|
43
|
+
app.use(app.middlewares.projectHandler)
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
|
|
3
|
+
//引入 element-plus
|
|
4
|
+
import ElementUI from 'element-plus'
|
|
5
|
+
import 'element-plus/dist/index.css'
|
|
6
|
+
import './asserts/custom.css'
|
|
7
|
+
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
8
|
+
import 'element-plus/theme-chalk/index.css'
|
|
9
|
+
|
|
10
|
+
import pinia from "$elpisStore";
|
|
11
|
+
|
|
12
|
+
import { createRouter, createWebHistory } from "vue-router";
|
|
13
|
+
/**
|
|
14
|
+
* vue 页面主入口,用于启动vue
|
|
15
|
+
* @params pageComponent vue 入口文件
|
|
16
|
+
* @params routes 路由列表
|
|
17
|
+
* @params libs 页面依赖的第三方包
|
|
18
|
+
*/
|
|
19
|
+
export default (pageComponent, { routes,libs } = {}) => {
|
|
20
|
+
const app = createApp(pageComponent)
|
|
21
|
+
//应用 ElementUI
|
|
22
|
+
app.use(ElementUI)
|
|
23
|
+
//引入pinia
|
|
24
|
+
app.use(pinia)
|
|
25
|
+
//引入第三方包
|
|
26
|
+
if(libs && libs.length){
|
|
27
|
+
for(let i = 0; i < libs.length;i++){
|
|
28
|
+
app.use(libs[i])
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//页面路由
|
|
32
|
+
if (routes && routes.length) {
|
|
33
|
+
const router = createRouter({
|
|
34
|
+
history: createWebHistory(),// 采用 history 模式
|
|
35
|
+
routes
|
|
36
|
+
})
|
|
37
|
+
app.use(router)
|
|
38
|
+
router.isReady().then(() => {
|
|
39
|
+
app.mount('#root')
|
|
40
|
+
})
|
|
41
|
+
} else {
|
|
42
|
+
app.mount('#root')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|