@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.
Files changed (81) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +59 -0
  3. package/.vscode/settings.json +15 -0
  4. package/README.md +198 -0
  5. package/app/controller/base.js +41 -0
  6. package/app/controller/project.js +98 -0
  7. package/app/controller/view.js +24 -0
  8. package/app/extend/logger.js +43 -0
  9. package/app/middleware/api-params-verify.js +89 -0
  10. package/app/middleware/api-sign-verify.js +47 -0
  11. package/app/middleware/error-handler.js +41 -0
  12. package/app/middleware/project-handler.js +27 -0
  13. package/app/middleware.js +40 -0
  14. package/app/pages/asserts/custom.css +12 -0
  15. package/app/pages/boot.js +59 -0
  16. package/app/pages/common/curl.js +88 -0
  17. package/app/pages/common/util.js +3 -0
  18. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +40 -0
  19. package/app/pages/dashboard/complex-view/header-view/header-view.vue +141 -0
  20. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +43 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +39 -0
  22. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +146 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +24 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +118 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +177 -0
  26. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +157 -0
  27. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +150 -0
  28. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +113 -0
  29. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
  30. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +134 -0
  31. package/app/pages/dashboard/dashboard.vue +127 -0
  32. package/app/pages/dashboard/entry.dashboard.js +46 -0
  33. package/app/pages/store/index.js +5 -0
  34. package/app/pages/store/menu.js +61 -0
  35. package/app/pages/store/project.js +13 -0
  36. package/app/pages/widgets/header-container/asserts/avatar.png +0 -0
  37. package/app/pages/widgets/header-container/asserts/logo.png +0 -0
  38. package/app/pages/widgets/header-container/header-container.vue +144 -0
  39. package/app/pages/widgets/schema-form/complex-view/input/input.vue +165 -0
  40. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +166 -0
  41. package/app/pages/widgets/schema-form/complex-view/select/select.vue +144 -0
  42. package/app/pages/widgets/schema-form/form-item.config.js +24 -0
  43. package/app/pages/widgets/schema-form/schema-form.vue +144 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +57 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +77 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +51 -0
  47. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +58 -0
  48. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +138 -0
  49. package/app/pages/widgets/schema-search-bar/search-item-config.js +27 -0
  50. package/app/pages/widgets/schema-table/schema-table.vue +254 -0
  51. package/app/pages/widgets/sider-container/sider-container.vue +32 -0
  52. package/app/router/business.js +15 -0
  53. package/app/router/project.js +10 -0
  54. package/app/router/view.js +11 -0
  55. package/app/router-schema/business.js +82 -0
  56. package/app/router-schema/project.js +40 -0
  57. package/app/service/base.js +13 -0
  58. package/app/service/project.js +55 -0
  59. package/app/view/entry.tpl +27 -0
  60. package/app/webpack/config/blank.js +3 -0
  61. package/app/webpack/config/webpack.base.js +269 -0
  62. package/app/webpack/config/webpack.dev.js +61 -0
  63. package/app/webpack/config/webpack.prod.js +149 -0
  64. package/app/webpack/dev.js +58 -0
  65. package/app/webpack/prod.js +21 -0
  66. package/config/config.default.js +3 -0
  67. package/elpis-core/env.js +22 -0
  68. package/elpis-core/index.js +99 -0
  69. package/elpis-core/loader/config.js +51 -0
  70. package/elpis-core/loader/controller.js +75 -0
  71. package/elpis-core/loader/extend.js +54 -0
  72. package/elpis-core/loader/middleware.js +69 -0
  73. package/elpis-core/loader/router-schema.js +50 -0
  74. package/elpis-core/loader/router.js +52 -0
  75. package/elpis-core/loader/service.js +74 -0
  76. package/index.js +29 -0
  77. package/jsconfig.json +16 -0
  78. package/logs/applocation.log +3 -0
  79. package/model/index.js +119 -0
  80. package/package.json +93 -0
  81. package/test/controller/project.test.js +216 -0
package/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ public/
3
+ docs/
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
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "oxc.enable": true,
3
+ "cSpell.words": [
4
+ "获取和modelitem里的key值相同的projectitem",
5
+ "chunkhash",
6
+ "daterange",
7
+ "elpis",
8
+ "happypack",
9
+ "mlist",
10
+ "sider",
11
+ "taobao",
12
+ "validata"
13
+ ],
14
+ "liveServer.settings.port": 5501
15
+ }
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
+ }