diff --git a/README.md b/README.md index 06ad886110afd97e9a3035d7b922174ebf4abe04..30711b35fee7e7e5f7f62cb37092236153521ac7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -> 用上diboot,告别常规SQL和CRUD,写的更少,性能更好! +> 用上diboot,告别常规SQL和CRUD,写的更少,性能更好! -> 新用户指南: [手把手来体验](https://www.diboot.com/guide/newer/bootstrap.html) 、[看视频了解我](https://www.bilibili.com/video/BV17P4y1p7L4) 、[如何做到高性能](https://www.bilibili.com/video/BV1tL411p7CD) +> 新用户指南: [手把手来体验](https://www.diboot.com/guide/beginner/bootstrap.html) 、[看视频了解 Diboot](https://www.bilibili.com/video/BV17P4y1p7L4) 、[了解Diboot如何做到高性能](https://www.bilibili.com/video/BV1tL411p7CD) # diboot - 基础组件化繁为简,高效工具以简驭繁
@@ -17,9 +17,9 @@ ![diboot平台组成结构图](https://www.diboot.com/structure.png) -> [diboot-cloud 微服务版本,看这里->](https://www.diboot.com/ent/service.html) +> [diboot-cloud 微服务版本,看这里->](https://www.diboot.com/guide/diboot-cloud/introduce.html) -> [diboot-workflow 工作流版本,看这里->](https://www.diboot.com/ent/service.html) +> [diboot-workflow 工作流版本,看这里->](https://www.diboot.com/guide/diboot-workflow/introduce.html) ## diboot基础组件 ### 1、 diboot-core: 精简优化内核:写的更少,性能更好 @@ -32,31 +32,31 @@ * 其他常用工具类、状态码、异常处理的更优实践封装 基于diboot-core的CRUD和常规关联的功能实现,代码量比传统Mybatis项目减少80%+,且性能更好更易维护。 -> 详细文档: [diboot-core文档](https://www.diboot.com/guide/diboot-core/introduce.html). +> 详细文档: [diboot-core文档](https://www.diboot.com/guide/diboot-core/introduce.html). ### 2、IAM 身份认证基础组件 及 配套VUE前端框架(diboot-antd-admin、diboot-element-admin) * 开箱即用的RBAC角色权限模型与预置组织人员岗位模型 -* 基于JWT的认证授权,支持申请token、刷新token、无状态认证 -* 简化的BindPermission注解,支持兼容shiro的简化权限绑定与自动鉴权 -* 自动提取需要验证的后端接口, 借助前端功能方便绑定前后端菜单按钮权限 +* 基于无状态token的认证授权,支持刷新token +* 简化的BindPermission注解,支持兼容shiro的简化权限配置与自动鉴权 +* 自动提取需要鉴权的后端接口, 借助前端功能方便配置菜单按钮权限 * 无缝适配redis,引入redis依赖即可启用shiro的redis缓存 * 支持基于注解的数据权限实现、简化的Log注解记录操作日志等 * 支持灵活的扩展能力(扩展多种登录方式、灵活替换用户实体类、自定义缓存等) -> 详细文档: [diboot-iam文档](https://www.diboot.com/guide/diboot-iam/introduce.html). +> 详细文档: [diboot-iam文档](https://www.diboot.com/guide/diboot-iam/introduce.html). ### 3、diboot-file 文件相关处理组件 * EasyExcel轻量封装,支持Java注解校验与@ExcelBind*注解实现字典及关联字段的name-value转换,并提供完善的校验错误提示 * 文件存储接口化,预置本地存储,简单扩展OSS、分布式存储等实现 * 封装常用的文件上传下载、图片压缩水印等常用处理 -> 详细文档: [diboot-file文档](https://www.diboot.com/guide/diboot-file/introduce.html). +> 详细文档: [diboot-file文档](https://www.diboot.com/guide/diboot-file/introduce.html). ### 4、diboot-scheduler 定时任务组件 * Quartz定时任务统一管理及日志的最佳实践封装 * @CollectThisJob注解提供定时任务定义,自动收集供前端选择 -> 详细文档: [diboot-scheduler文档](https://www.diboot.com/guide/diboot-scheduler/introduce.html). +> 详细文档: [diboot-scheduler文档](https://www.diboot.com/guide/diboot-scheduler/introduce.html). ### 5. diboot-message 消息通知组件 @@ -75,9 +75,9 @@ * 功能强大(数据结构与代码同步、前后端代码一键生成、前端面板组件编排) * 配置灵活(可按需配置生成代码路径及启用`Lombok`、`Swagger`等) * 代码标准(devtools标准化了数据结构定义与代码实现,降低维护成本) -* 支持多库(MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL) +* 支持多库(MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL、达梦、人大金仓) -> 详细文档: [diboot-devtools文档](https://www.diboot.com/guide/diboot-devtools/introduce.html). +> 详细文档: [diboot-devtools文档](https://www.diboot.com/guide/diboot-devtools/introduce.html). ## 捐助支持 捐助二维码 @@ -87,8 +87,8 @@ ## 技术交流 如遇diboot相关技术问题,欢迎加群交流: -* **VIP技术支持QQ群**(捐助/付费用户尊享): [931266830]() +* **VIP技术支持QQ群**(付费用户尊享): [931266830]() -* 技术交流QQ群: [731690096]() +* 技术交流QQ群: [731690096]() * 技术交流微信群 加: [wx20201024]() (备注diboot) diff --git a/diboot-admin-ui/.env.development b/diboot-admin-ui/.env.development new file mode 100644 index 0000000000000000000000000000000000000000..a435ff63ffad3c2dc73b0e9b3c8afb42fa6f36c0 --- /dev/null +++ b/diboot-admin-ui/.env.development @@ -0,0 +1,2 @@ +# BASE_URL +VITE_APP_BASE_URL=/api diff --git a/diboot-admin-ui/.env.production b/diboot-admin-ui/.env.production new file mode 100644 index 0000000000000000000000000000000000000000..a435ff63ffad3c2dc73b0e9b3c8afb42fa6f36c0 --- /dev/null +++ b/diboot-admin-ui/.env.production @@ -0,0 +1,2 @@ +# BASE_URL +VITE_APP_BASE_URL=/api diff --git a/diboot-admin-ui/.eslintignore b/diboot-admin-ui/.eslintignore new file mode 100644 index 0000000000000000000000000000000000000000..a4afa61ec603d8978b645ba64265a5740b653427 --- /dev/null +++ b/diboot-admin-ui/.eslintignore @@ -0,0 +1,17 @@ +node_modules +public +dist + +/bin +/docs + +.vscode +.idea + +*.sh +*.md +*.woff +*.ttf +.husky +.local +.eslintrc.js diff --git a/diboot-admin-ui/.eslintrc-auto-import.json b/diboot-admin-ui/.eslintrc-auto-import.json new file mode 100644 index 0000000000000000000000000000000000000000..82ea7b2fb1e13f8cbad0af5a99d78ea508c72b5f --- /dev/null +++ b/diboot-admin-ui/.eslintrc-auto-import.json @@ -0,0 +1,73 @@ +{ + "globals": { + "_": "readonly", + "acceptHMRUpdate": "readonly", + "api": "readonly", + "baseURL": "readonly", + "computed": "readonly", + "createApp": "readonly", + "createPinia": "readonly", + "customRef": "readonly", + "defineAsyncComponent": "readonly", + "defineComponent": "readonly", + "defineStore": "readonly", + "effectScope": "readonly", + "EffectScope": "readonly", + "ElMessage": "readonly", + "ElMessageBox": "readonly", + "ElNotification": "readonly", + "getActivePinia": "readonly", + "getCurrentInstance": "readonly", + "getCurrentScope": "readonly", + "h": "readonly", + "inject": "readonly", + "isReadonly": "readonly", + "isRef": "readonly", + "mapActions": "readonly", + "mapGetters": "readonly", + "mapState": "readonly", + "mapStores": "readonly", + "mapWritableState": "readonly", + "markRaw": "readonly", + "nextTick": "readonly", + "onActivated": "readonly", + "onBeforeMount": "readonly", + "onBeforeUnmount": "readonly", + "onBeforeUpdate": "readonly", + "onDeactivated": "readonly", + "onErrorCaptured": "readonly", + "onMounted": "readonly", + "onRenderTracked": "readonly", + "onRenderTriggered": "readonly", + "onScopeDispose": "readonly", + "onServerPrefetch": "readonly", + "onUnmounted": "readonly", + "onUpdated": "readonly", + "provide": "readonly", + "reactive": "readonly", + "readonly": "readonly", + "ref": "readonly", + "resolveComponent": "readonly", + "setActivePinia": "readonly", + "setMapStoreSuffix": "readonly", + "shallowReactive": "readonly", + "shallowReadonly": "readonly", + "shallowRef": "readonly", + "storeToRefs": "readonly", + "toRaw": "readonly", + "toRef": "readonly", + "toRefs": "readonly", + "triggerRef": "readonly", + "unref": "readonly", + "useAttrs": "readonly", + "useCssModule": "readonly", + "useCssVars": "readonly", + "useList": "readonly", + "useListDefault": "readonly", + "useRoute": "readonly", + "useRouter": "readonly", + "useSlots": "readonly", + "watch": "readonly", + "watchEffect": "readonly" + } +} \ No newline at end of file diff --git a/diboot-admin-ui/.eslintrc.js b/diboot-admin-ui/.eslintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..aa8bc3f400f167cfa38fc817f2aa4771355e9518 --- /dev/null +++ b/diboot-admin-ui/.eslintrc.js @@ -0,0 +1,35 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + 'vue/setup-compiler-macros': true + }, + parser: 'vue-eslint-parser', + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-recommended', + '@vue/eslint-config-typescript/recommended', + '@vue/eslint-config-prettier', + './.eslintrc-auto-import.json' + ], + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module' + }, + plugins: ['vue', '@typescript-eslint', 'prettier'], + globals: { + NodeJS: 'readonly' + }, + rules: { + 'comma-dangle': ['warn', 'only-multiline'], + 'vue/multi-word-component-names': 'off', + 'vue/html-self-closing': ['warn', { html: { void: 'always' } }], + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'prettier/prettier': 'warn', + 'arrow-body-style': 'off', + 'prefer-arrow-callback': 'off' + } +} diff --git a/diboot-admin-ui/.gitignore b/diboot-admin-ui/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a547bf36d8d11a4f89c59c144f24795749086dd1 --- /dev/null +++ b/diboot-admin-ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/diboot-admin-ui/.prettierignore b/diboot-admin-ui/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..d3e769730b07e967ec4cdcfeea572fc3ea27f2d1 --- /dev/null +++ b/diboot-admin-ui/.prettierignore @@ -0,0 +1,9 @@ +/node_modules/** +/public/* +/dist/* + +.local +.output.js + +**/*.svg +**/*.sh diff --git a/diboot-admin-ui/.prettierrc b/diboot-admin-ui/.prettierrc new file mode 100644 index 0000000000000000000000000000000000000000..78f239ac58497dc84f25ecc6d642182da0426846 --- /dev/null +++ b/diboot-admin-ui/.prettierrc @@ -0,0 +1,20 @@ +# 配置文档:https://prettier.io/docs/en/options.html + +# 声明结尾使用分号(默认true) +semi: false +# 每行代码长度(默认80) +printWidth: 120 +# 每个tab相当于多少个空格(默认2) +tabWidth: 2 +# 是否使用tab进行缩进(默认false) +useTabs: false +# 使用单引号(默认false) +singleQuote: true +# 对象字面量的大括号间使用空格(默认true) +bracketSpacing: true +# 包括单箭头函数参数周围的括号(默认always) +arrowParens: 'avoid' +# 尾部跟随逗号(默认 es5) +trailingComma: 'none' +# 自动换行(默认 preserve) +proseWrap: 'never' diff --git a/diboot-admin-ui/.vscode/extensions.json b/diboot-admin-ui/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..3dc5b08bcdc96b1536eec0cff46a39d54235aa12 --- /dev/null +++ b/diboot-admin-ui/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["johnsoncodehk.volar"] +} diff --git a/diboot-admin-ui/README.md b/diboot-admin-ui/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e432516724c1a7ea0e7391f4b675a2dd8d1661cc --- /dev/null +++ b/diboot-admin-ui/README.md @@ -0,0 +1,16 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/diboot-admin-ui/mock/_prodServer.ts b/diboot-admin-ui/mock/_prodServer.ts new file mode 100644 index 0000000000000000000000000000000000000000..f93f0ed9cd55bddf844b85aa79003e4cc6e41c24 --- /dev/null +++ b/diboot-admin-ui/mock/_prodServer.ts @@ -0,0 +1,15 @@ +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' + +const modules = import.meta.globEager('./**/*.ts') + +const mockModules: unknown[] = [] +Object.keys(modules) + .filter(path => !path.includes('/_')) // Ignore files and directories starting with _ + .forEach(key => mockModules.push(...modules[key].default)) + +/** + * Used in a production environment. Need to manually import all modules + */ +export function setupProdMockServer() { + createProdMockServer(mockModules) +} diff --git a/diboot-admin-ui/mock/_util.ts b/diboot-admin-ui/mock/_util.ts new file mode 100644 index 0000000000000000000000000000000000000000..794073c694f9be79edaeaac50c56d843d3f10e9a --- /dev/null +++ b/diboot-admin-ui/mock/_util.ts @@ -0,0 +1,132 @@ +import { Recordable } from 'vite-plugin-mock' +import Mock from 'mockjs' + +// 分页 +export const pagination = (pageNo: number, pageSize: number, array: T[]): T[] => { + const offset = (pageNo - 1) * Number(pageSize) + return offset + Number(pageSize) >= array.length + ? array.slice(offset, array.length) + : array.slice(offset, offset + Number(pageSize)) +} + +/** + * 接口请求 + */ +export interface ApiRequest { + url: string + body: B + query: Q + headers: H +} + +const resultJson = (code: number, msg: string, data?: unknown, ext = {}) => + Mock.mock({ + ...ext, + code, + data, + msg + }) + +/** + * 通用数据返回 + */ +export const JsonResult = { + /** + * 操作成功 + */ + OK(data?: unknown, msg = '操作成功') { + return resultJson(0, msg, data) + }, + + /** + * 数据分页 + */ + PAGINATION(page: number, pageSize: number, list: T[] = []) { + page = page ? page : 1 + pageSize = pageSize ? pageSize : 20 + return resultJson(0, '操作成功', pagination(page, pageSize, list), { + page: { + pageIndex: Number(page), + pageSize: Number(pageSize), + totalCount: list.length + } + }) + }, + + /** + * 部分成功(一般用于批量处理场景,只处理筛选后的合法数据) + */ + WARN_PARTIAL_SUCCESS(msg?: string) { + return resultJson(1001, '部分成功' + (msg ? `:${msg}` : '')) + }, + + /** + * 有潜在的性能问题 + */ + WARN_PERFORMANCE_ISSUE(msg?: string) { + return resultJson(1002, '潜在的性能问题' + (msg ? `:${msg}` : '')) + }, + + /** + * 传入参数不对 + */ + FAIL_INVALID_PARAM(msg?: string) { + return resultJson(4000, '请求参数不匹配' + (msg ? `:${msg}` : '')) + }, + + /** + * Token无效或已过期 + */ + FAIL_INVALID_TOKEN(msg?: string) { + return resultJson(4001, 'Token无效或已过期' + (msg ? `:${msg}` : '')) + }, + + /** + * 没有权限执行该操作 + */ + FAIL_NO_PERMISSION(msg?: string) { + return resultJson(4003, '无权执行该操作' + (msg ? `:${msg}` : '')) + }, + + /** + * 请求资源不存在 + */ + FAIL_NOT_FOUND(msg?: string) { + return resultJson(4004, '请求资源不存在' + (msg ? `:${msg}` : '')) + }, + + /** + * 数据校验不通过 + */ + FAIL_VALIDATION(msg?: string) { + return resultJson(4005, '数据校验不通过' + (msg ? `:${msg}` : '')) + }, + + /** + * 操作执行失败 + */ + FAIL_OPERATION(msg?: string) { + return resultJson(4006, '操作执行失败' + (msg ? `:${msg}` : '')) + }, + + /** + * 请求连接超时 + */ + FAIL_REQUEST_TIMEOUT(msg?: string) { + return resultJson(4008, '请求连接超时' + (msg ? `:${msg}` : '')) + }, + + /** + * 认证不通过(用户名密码错误等认证失败场景) + */ + FAIL_AUTHENTICATION(msg?: string) { + return resultJson(4009, '认证不通过' + (msg ? `:${msg}` : '')) + }, + + /** + * 系统异常 + */ + FAIL_EXCEPTION(msg?: string) { + return resultJson(5000, '系统异常' + (msg ? `:${msg}` : '')) + } +} diff --git a/diboot-admin-ui/mock/auth/index.ts b/diboot-admin-ui/mock/auth/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..73f623a80d3c549d9a4b36ba93ff4f556b26285c --- /dev/null +++ b/diboot-admin-ui/mock/auth/index.ts @@ -0,0 +1,144 @@ +import { MockMethod } from 'vite-plugin-mock' +import { JsonResult, ApiRequest } from '../_util' +import { Random } from 'mockjs' +import * as Element from '@element-plus/icons-vue' + +const baseUrl = '/api/auth' + +export default [ + { + url: `${baseUrl}/captcha`, + timeout: Random.natural(50, 100), + method: 'get', + // response: () => Random.dataImage('200x100', 'Diboot') + rawResponse: (req, res) => { + res.setHeader('Content-Type', 'image/gif') + res.setHeader('Pragma', 'No-cache') + res.setHeader('Cache-Control', 'no-cache') + res.write(Random.dataImage('200x100', 'Diboot')) + } + }, + { + url: `${baseUrl}/login`, + timeout: Random.natural(50, 300), + method: 'post', + response: ({ body }: ApiRequest) => { + if (body.username === 'admin' && body.password === '123456') { + return JsonResult.OK({ token: Random.string('lower', 32, 32) }) + } + return JsonResult.FAIL_OPERATION('用户名或密码错误') + } + }, + { + url: `${baseUrl}/userInfo`, + timeout: Random.natural(50, 300), + method: 'get', + response: ({ headers }: ApiRequest) => { + const token = headers.authorization + if (token && token.length >= 32) { + const name = Random.cname() + return JsonResult.OK({ + realname: name, + email: Random.email(), + avatar: Random.image('50x50', Random.color(), Random.color(), name[0]), + roles: [Random.pick(['admin', 'develop', 'test'])] + }) + } + return JsonResult.FAIL_INVALID_TOKEN() + } + }, + { + url: `${baseUrl}/logout`, + timeout: Random.natural(50, 300), + method: 'post', + response: () => { + return JsonResult.OK() + } + }, + { + url: `${baseUrl}/ping`, + timeout: Random.natural(50, 300), + method: 'get', + rawResponse: (req, res) => { + const token = req.headers.authorization + if (token && token.length >= 32) res.setHeader('Authorization', Random.string('lower', 32, 32)) + res.end() + } + }, + { + url: `${baseUrl}/menu`, + timeout: Random.natural(50, 300), + method: 'get', + response: () => { + return JsonResult.OK(authMenu) + } + } +] as MockMethod[] + +// 随机按钮权限 +const permission = '@pick(["detail", "create", "update", "delete", "import", "export"])' +// 随机图标 +const icon = `Element:@pick(${Object.keys(Element)})` + +// 授权菜单 +const authMenu = [ + { + path: '/demo', + name: 'Demo', + meta: { title: 'Demo', icon, componentName: 'Layout' }, + 'children|20': [ + { + path: `hello@string('number', 5)`, + name: 'Hello-@increment', + meta: { + title: 'Hello-@increment', + componentName: 'Dashboard', + icon, + sort: '@natural', + keepAlive: false, + hollow: '@boolean', + hideFooter: '@boolean', + permissions: [permission, permission, permission] + } + } + ] + }, + { + path: '/system', + name: 'System', + redirect: '/system/iamResourcePermission', + meta: { title: '系统管理', icon: 'Element:SetUp', componentName: 'Layout' }, + children: [ + { + path: 'iamResourcePermission', + name: 'iamResourcePermission-@increment', + meta: { + title: '资源权限管理', + componentName: 'IamResourcePermissionList', + sort: '@natural', + keepAlive: false, + hollow: '@boolean', + hideFooter: '@boolean' + } + }, + { + path: 'role', + name: 'Role', + meta: { + title: '角色管理', + componentName: 'RoleList', + sort: 3 + } + }, + { + path: 'dictionary', + name: 'Dictionary', + meta: { + title: 'Dictionary', + componentName: 'DictionaryList', + keepAlive: false + } + } + ] + } +] diff --git a/diboot-admin-ui/mock/system/dictionary.ts b/diboot-admin-ui/mock/system/dictionary.ts new file mode 100644 index 0000000000000000000000000000000000000000..eaa246ac6f6edf73818f1f6fa58d9e7aa81df708 --- /dev/null +++ b/diboot-admin-ui/mock/system/dictionary.ts @@ -0,0 +1,139 @@ +import { MockMethod } from 'vite-plugin-mock' +import { JsonResult } from '../_util' +import { Random } from 'mockjs' + +const baseUrl = '/api/dictionary' + +export default [ + { + url: `${baseUrl}/list`, + timeout: Random.natural(50, 300), + method: 'get', + response: () => { + return JsonResult.OK(dictionaryDataMap.list) + } + }, + { + url: `${baseUrl}/detail`, + timeout: Random.natural(50, 300), + method: 'get', + response: () => { + return JsonResult.OK(dictionaryDataMap.detail) + } + } +] as MockMethod[] + +const dictionaryDataMap = { + list: [ + { + id: '1', + type: 'GENDER', + itemName: '用户性别', + itemValue: '', + description: '用户性别数据字典', + createTime: '2022-05-11', + children: [ + { + id: '2', + type: 'GENDER', + itemName: '男', + itemValue: 'M', + description: '男性', + createTime: '2022-05-11' + }, + { + id: '3', + type: 'GENDER', + itemName: '女', + itemValue: 'F', + description: '女性', + createTime: '2022-05-11' + } + ] + }, + { + id: '4', + type: 'ACCOUNT_STATUS', + itemName: '账号状态', + itemValue: '', + description: '用户账号状态信息', + createTime: '2022-05-11', + children: [ + { + id: '5', + type: 'ACCOUNT_STATUS', + itemName: '有效', + itemValue: 'A', + description: '有效', + createTime: '2022-05-11' + }, + { + id: '6', + type: 'ACCOUNT_STATUS', + itemName: '无效', + itemValue: 'I', + description: '无效', + createTime: '2022-05-11' + }, + { + id: '7', + type: 'ACCOUNT_STATUS', + itemName: '锁定', + itemValue: 'L', + description: '锁定', + createTime: '2022-05-11' + }, + { + id: '8', + type: 'ACCOUNT_STATUS', + itemName: '停用', + itemValue: 'S', + description: '停用', + createTime: '2022-05-11' + } + ] + } + ], + detail: { + id: '4', + type: 'ACCOUNT_STATUS', + itemName: '账号状态', + itemValue: '', + description: '用户账号状态信息', + createTime: '2022-05-11', + children: [ + { + id: '5', + type: 'ACCOUNT_STATUS', + itemName: '有效', + itemValue: 'A', + description: '有效', + createTime: '2022-05-11' + }, + { + id: '6', + type: 'ACCOUNT_STATUS', + itemName: '无效', + itemValue: 'I', + description: '无效', + createTime: '2022-05-11' + }, + { + id: '7', + type: 'ACCOUNT_STATUS', + itemName: '锁定', + itemValue: 'L', + description: '锁定', + createTime: '2022-05-11' + }, + { + id: '8', + type: 'ACCOUNT_STATUS', + itemName: '停用', + itemValue: 'S', + description: '停用', + createTime: '2022-05-11' + } + ] + } +} diff --git a/diboot-admin-ui/mock/system/role.ts b/diboot-admin-ui/mock/system/role.ts new file mode 100644 index 0000000000000000000000000000000000000000..2df6e2b3249172db4b9f56c8023dd9e9e4717b49 --- /dev/null +++ b/diboot-admin-ui/mock/system/role.ts @@ -0,0 +1,94 @@ +import { MockMethod } from 'vite-plugin-mock' +import { JsonResult, ApiRequest } from '../_util' +import { Random } from 'mockjs' +import type { Role } from '@/views/system/role/type' + +const baseUrl = '/api/role' + +const deleteDataIds: string[] = [] + +const dataList: Role[] = Array.from({ length: 50 }).map((_, index) => { + const id = String(50 - index) + return { + id, + name: '角色' + id, + code: 'role' + id, + description: '@csentence', + createTime: '@datetime', + updateTime: '@datetime' + } +}) + +export default [ + { + url: `${baseUrl}/list`, + timeout: Random.natural(50, 300), + method: 'get', + response: ({ query }: ApiRequest) => { + return JsonResult.PAGINATION( + query.pageIndex, + query.pageSize, + dataList + .filter(e => !deleteDataIds.includes(e.id)) + .filter(e => e.name.match(query.name) && e.code.match(query.code)) + ) + } + }, + { + url: `${baseUrl}/:id`, + timeout: Random.natural(50, 300), + method: 'get', + response: ({ query }: ApiRequest) => { + return JsonResult.OK(dataList.filter(e => e.id === query.id)) + } + }, + { + url: `${baseUrl}`, + timeout: Random.natural(50, 300), + method: 'post', + response: ({ body }: ApiRequest) => { + return JsonResult.OK(!!dataList.unshift(body)) + } + }, + { + url: `${baseUrl}/:id`, + timeout: Random.natural(50, 300), + method: 'put', + response: ({ body, query }: ApiRequest) => { + return JsonResult.OK( + !!dataList.splice( + dataList.findIndex(e => e.id === query.id), + 1, + body + ) + ) + } + }, + { + url: `${baseUrl}/:id`, + timeout: Random.natural(50, 300), + method: 'delete', + response: ({ query }: ApiRequest) => { + deleteDataIds.push(query.id) + return JsonResult.OK() + } + }, + { + url: `${baseUrl}/cancelDeleted`, + timeout: Random.natural(50, 300), + method: 'patch', + response: ({ body }: ApiRequest>) => { + deleteDataIds.splice(0, deleteDataIds.length, ...deleteDataIds.filter(e => !body.includes(e))) + return JsonResult.OK() + } + }, + { + url: `${baseUrl}/batchDelete`, + timeout: Random.natural(50, 300), + method: 'post', + response: ({ body }: ApiRequest>) => { + deleteDataIds.push(...body) + return JsonResult.OK() + } + } +] as MockMethod[] diff --git a/diboot-admin-ui/mock/utils/relatedData.ts b/diboot-admin-ui/mock/utils/relatedData.ts new file mode 100644 index 0000000000000000000000000000000000000000..5cf4418bb582a5a2ff377061e7f03ae99efd72bf --- /dev/null +++ b/diboot-admin-ui/mock/utils/relatedData.ts @@ -0,0 +1,49 @@ +import { MockMethod } from 'vite-plugin-mock' +import { JsonResult } from '../_util' +import { Random } from 'mockjs' + +const baseUrl = '/api' + +export default [ + { + url: `${baseUrl}/common/attachMore`, + timeout: Random.natural(50, 100), + method: 'post', + response: () => { + return JsonResult.OK({ + genderOptions: [ + { + label: '女', + value: 'F' + }, + { + label: '男', + value: 'M' + } + ], + iamUserOptions: [ + { + label: '超级管理员', + value: 10000, + ext: '000' + } + ] + }) + } + }, + { + url: `${baseUrl}/iamResource/attachMore`, + timeout: Random.natural(50, 100), + method: 'get', + response: () => { + return JsonResult.OK({ + iamResourceOptions: [ + { + label: '测试权限', + value: 10000 + } + ] + }) + } + } +] as MockMethod[] diff --git a/diboot-admin-ui/package.json b/diboot-admin-ui/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2bdeac6d6234e2fff7bad63148d12103b34eaed1 --- /dev/null +++ b/diboot-admin-ui/package.json @@ -0,0 +1,51 @@ +{ + "name": "diboot-admin-ui", + "private": true, + "version": "3.0.0", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "@element-plus/icons-vue": "^1.1.4", + "@vueuse/core": "^8.5.0", + "axios": "^0.27.2", + "element-plus": "^2.2.0", + "fuse.js": "^6.6.2", + "lodash": "^4.17.21", + "mockjs": "^1.1.0", + "nprogress": "^0.2.0", + "pinia": "^2.0.14", + "pinia-plugin-persist": "^1.0.0", + "qs": "^6.10.3", + "vue": "^3.2.33", + "vue-clipboard3": "^2.0.0", + "vue-fuse": "^4.1.1", + "vue-router": "^4.0.15", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@types/lodash": "^4.14.182", + "@types/mockjs": "^1.0.6", + "@types/node": "^17.0.34", + "@types/nprogress": "^0.2.0", + "@types/qs": "^6.9.7", + "@vitejs/plugin-vue": "^2.3.3", + "@vue/eslint-config-prettier": "^7.0.0", + "@vue/eslint-config-typescript": "^10.0.0", + "eslint": "^8.15.0", + "eslint-plugin-vue": "^8.7.1", + "prettier": "2.6.2", + "sass": "^1.51.0", + "typescript": "^4.6.4", + "unplugin-auto-import": "^0.7.1", + "unplugin-vue-components": "^0.19.5", + "vite": "^2.9.9", + "vite-plugin-eslint": "^1.6.0", + "vite-plugin-mock": "^2.9.6", + "vite-plugin-vue-setup-extend": "^0.4.0", + "vue-tsc": "^0.34.15" + } +} diff --git a/diboot-admin-ui/pnpm-lock.yaml b/diboot-admin-ui/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a486968935e6b243ea6c9db6b3b507ac3fb1c65e --- /dev/null +++ b/diboot-admin-ui/pnpm-lock.yaml @@ -0,0 +1,2932 @@ +lockfileVersion: 5.3 + +specifiers: + '@element-plus/icons-vue': ^1.1.4 + '@types/lodash': ^4.14.182 + '@types/mockjs': ^1.0.6 + '@types/node': ^17.0.34 + '@types/nprogress': ^0.2.0 + '@types/qs': ^6.9.7 + '@vitejs/plugin-vue': ^2.3.3 + '@vue/eslint-config-prettier': ^7.0.0 + '@vue/eslint-config-typescript': ^10.0.0 + '@vueuse/core': ^8.5.0 + axios: ^0.27.2 + element-plus: ^2.2.0 + eslint: ^8.15.0 + eslint-plugin-vue: ^8.7.1 + fuse.js: ^6.6.2 + lodash: ^4.17.21 + mockjs: ^1.1.0 + nprogress: ^0.2.0 + pinia: ^2.0.14 + pinia-plugin-persist: ^1.0.0 + prettier: 2.6.2 + qs: ^6.10.3 + sass: ^1.51.0 + typescript: ^4.6.4 + unplugin-auto-import: ^0.7.1 + unplugin-vue-components: ^0.19.5 + vite: ^2.9.9 + vite-plugin-eslint: ^1.6.0 + vite-plugin-mock: ^2.9.6 + vite-plugin-vue-setup-extend: ^0.4.0 + vue: ^3.2.33 + vue-clipboard3: ^2.0.0 + vue-fuse: ^4.1.1 + vue-router: ^4.0.15 + vue-tsc: ^0.34.15 + vuedraggable: ^4.1.0 + +dependencies: + '@element-plus/icons-vue': registry.npmmirror.com/@element-plus/icons-vue/1.1.4_vue@3.2.33 + '@vueuse/core': registry.npmmirror.com/@vueuse/core/8.5.0_vue@3.2.33 + axios: registry.npmmirror.com/axios/0.27.2 + element-plus: registry.npmmirror.com/element-plus/2.2.0_vue@3.2.33 + fuse.js: registry.npmmirror.com/fuse.js/6.6.2 + lodash: registry.npmmirror.com/lodash/4.17.21 + mockjs: registry.npmmirror.com/mockjs/1.1.0 + nprogress: registry.npmmirror.com/nprogress/0.2.0 + pinia: registry.npmmirror.com/pinia/2.0.14_typescript@4.6.4+vue@3.2.33 + pinia-plugin-persist: registry.npmmirror.com/pinia-plugin-persist/1.0.0_pinia@2.0.14+vue@3.2.33 + qs: registry.npmmirror.com/qs/6.10.3 + vue: registry.npmmirror.com/vue/3.2.33 + vue-clipboard3: registry.npmmirror.com/vue-clipboard3/2.0.0 + vue-fuse: registry.npmmirror.com/vue-fuse/4.1.1_fuse.js@6.6.2+vue@3.2.33 + vue-router: registry.npmmirror.com/vue-router/4.0.15_vue@3.2.33 + vuedraggable: registry.npmmirror.com/vuedraggable/4.1.0_vue@3.2.33 + +devDependencies: + '@types/lodash': registry.npmmirror.com/@types/lodash/4.14.182 + '@types/mockjs': registry.npmmirror.com/@types/mockjs/1.0.6 + '@types/node': registry.npmmirror.com/@types/node/17.0.34 + '@types/nprogress': registry.npmmirror.com/@types/nprogress/0.2.0 + '@types/qs': registry.npmmirror.com/@types/qs/6.9.7 + '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/2.3.3_vite@2.9.9+vue@3.2.33 + '@vue/eslint-config-prettier': registry.npmmirror.com/@vue/eslint-config-prettier/7.0.0_eslint@8.15.0+prettier@2.6.2 + '@vue/eslint-config-typescript': registry.npmmirror.com/@vue/eslint-config-typescript/10.0.0_7f105dc3ebd31cec885fdbbd30d5cc4c + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-plugin-vue: registry.npmmirror.com/eslint-plugin-vue/8.7.1_eslint@8.15.0 + prettier: registry.npmmirror.com/prettier/2.6.2 + sass: registry.npmmirror.com/sass/1.51.0 + typescript: registry.npmmirror.com/typescript/4.6.4 + unplugin-auto-import: registry.npmmirror.com/unplugin-auto-import/0.7.1_@vueuse+core@8.5.0+vite@2.9.9 + unplugin-vue-components: registry.npmmirror.com/unplugin-vue-components/0.19.5_vite@2.9.9+vue@3.2.33 + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + vite-plugin-eslint: registry.npmmirror.com/vite-plugin-eslint/1.6.0_eslint@8.15.0+vite@2.9.9 + vite-plugin-mock: registry.npmmirror.com/vite-plugin-mock/2.9.6_mockjs@1.1.0+vite@2.9.9 + vite-plugin-vue-setup-extend: registry.npmmirror.com/vite-plugin-vue-setup-extend/0.4.0_vite@2.9.9 + vue-tsc: registry.npmmirror.com/vue-tsc/0.34.15_typescript@4.6.4 + +packages: + + registry.npmmirror.com/@antfu/utils/0.5.2: + resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@antfu/utils/-/utils-0.5.2.tgz} + name: '@antfu/utils' + version: 0.5.2 + dev: true + + registry.npmmirror.com/@babel/parser/7.17.12: + resolution: {integrity: sha512-FLzHmN9V3AJIrWfOpvRlZCeVg/WLdicSnTMsLur6uDj9TT8ymUlG9XxURdW/XvuygK+2CW0poOJABdA4m/YKxA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@babel/parser/-/parser-7.17.12.tgz} + name: '@babel/parser' + version: 7.17.12 + engines: {node: '>=6.0.0'} + hasBin: true + + registry.npmmirror.com/@ctrl/tinycolor/3.4.1: + resolution: {integrity: sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.4.1.tgz} + name: '@ctrl/tinycolor' + version: 3.4.1 + engines: {node: '>=10'} + dev: false + + registry.npmmirror.com/@element-plus/icons-vue/1.1.4_vue@3.2.33: + resolution: {integrity: sha512-Iz/nHqdp1sFPmdzRwHkEQQA3lKvoObk8azgABZ81QUOpW9s/lUyQVUSh0tNtEPZXQlKwlSh7SPgoVxzrE0uuVQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-1.1.4.tgz} + id: registry.npmmirror.com/@element-plus/icons-vue/1.1.4 + name: '@element-plus/icons-vue' + version: 1.1.4 + peerDependencies: + vue: ^3.2.0 + dependencies: + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/@eslint/eslintrc/1.2.3: + resolution: {integrity: sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-1.2.3.tgz} + name: '@eslint/eslintrc' + version: 1.2.3 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: registry.npmmirror.com/ajv/6.12.6 + debug: registry.npmmirror.com/debug/4.3.4 + espree: registry.npmmirror.com/espree/9.3.2 + globals: registry.npmmirror.com/globals/13.15.0 + ignore: registry.npmmirror.com/ignore/5.2.0 + import-fresh: registry.npmmirror.com/import-fresh/3.3.0 + js-yaml: registry.npmmirror.com/js-yaml/4.1.0 + minimatch: registry.npmmirror.com/minimatch/3.1.2 + strip-json-comments: registry.npmmirror.com/strip-json-comments/3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@floating-ui/core/0.6.2: + resolution: {integrity: sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@floating-ui/core/-/core-0.6.2.tgz} + name: '@floating-ui/core' + version: 0.6.2 + dev: false + + registry.npmmirror.com/@floating-ui/dom/0.4.5: + resolution: {integrity: sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.4.5.tgz} + name: '@floating-ui/dom' + version: 0.4.5 + dependencies: + '@floating-ui/core': registry.npmmirror.com/@floating-ui/core/0.6.2 + dev: false + + registry.npmmirror.com/@humanwhocodes/config-array/0.9.5: + resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz} + name: '@humanwhocodes/config-array' + version: 0.9.5 + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': registry.npmmirror.com/@humanwhocodes/object-schema/1.2.1 + debug: registry.npmmirror.com/debug/4.3.4 + minimatch: registry.npmmirror.com/minimatch/3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz} + name: '@humanwhocodes/object-schema' + version: 1.2.1 + dev: true + + registry.npmmirror.com/@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz} + name: '@nodelib/fs.scandir' + version: 2.1.5 + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': registry.npmmirror.com/@nodelib/fs.stat/2.0.5 + run-parallel: registry.npmmirror.com/run-parallel/1.2.0 + dev: true + + registry.npmmirror.com/@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz} + name: '@nodelib/fs.stat' + version: 2.0.5 + engines: {node: '>= 8'} + dev: true + + registry.npmmirror.com/@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz} + name: '@nodelib/fs.walk' + version: 1.2.8 + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': registry.npmmirror.com/@nodelib/fs.scandir/2.1.5 + fastq: registry.npmmirror.com/fastq/1.13.0 + dev: true + + registry.npmmirror.com/@rollup/plugin-node-resolve/13.3.0: + resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz} + name: '@rollup/plugin-node-resolve' + version: 13.3.0 + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^2.42.0 + dependencies: + '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/3.1.0 + '@types/resolve': registry.npmmirror.com/@types/resolve/1.17.1 + deepmerge: registry.npmmirror.com/deepmerge/4.2.2 + is-builtin-module: registry.npmmirror.com/is-builtin-module/3.1.0 + is-module: registry.npmmirror.com/is-module/1.0.0 + resolve: registry.npmmirror.com/resolve/1.22.0 + dev: true + + registry.npmmirror.com/@rollup/pluginutils/3.1.0: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz} + name: '@rollup/pluginutils' + version: 3.1.0 + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': registry.npmmirror.com/@types/estree/0.0.39 + estree-walker: registry.npmmirror.com/estree-walker/1.0.1 + picomatch: registry.npmmirror.com/picomatch/2.3.1 + dev: true + + registry.npmmirror.com/@rollup/pluginutils/4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz} + name: '@rollup/pluginutils' + version: 4.2.1 + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: registry.npmmirror.com/estree-walker/2.0.2 + picomatch: registry.npmmirror.com/picomatch/2.3.1 + dev: true + + registry.npmmirror.com/@sxzz/popperjs-es/2.11.7: + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz} + name: '@sxzz/popperjs-es' + version: 2.11.7 + dev: false + + registry.npmmirror.com/@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz} + name: '@types/estree' + version: 0.0.39 + dev: true + + registry.npmmirror.com/@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.11.tgz} + name: '@types/json-schema' + version: 7.0.11 + dev: true + + registry.npmmirror.com/@types/lodash-es/4.17.6: + resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.6.tgz} + name: '@types/lodash-es' + version: 4.17.6 + dependencies: + '@types/lodash': registry.npmmirror.com/@types/lodash/4.14.182 + dev: false + + registry.npmmirror.com/@types/lodash/4.14.182: + resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.182.tgz} + name: '@types/lodash' + version: 4.14.182 + + registry.npmmirror.com/@types/mockjs/1.0.6: + resolution: {integrity: sha512-Yu5YlqbYZyqsd6LjO4e8ONJDN9pTSnciHDcRP4teNOh/au2b8helFhgRx+3w8xsTFEnwr9jtfTVJbAx+eYmlHA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/mockjs/-/mockjs-1.0.6.tgz} + name: '@types/mockjs' + version: 1.0.6 + dev: true + + registry.npmmirror.com/@types/node/17.0.34: + resolution: {integrity: sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-17.0.34.tgz} + name: '@types/node' + version: 17.0.34 + dev: true + + registry.npmmirror.com/@types/nprogress/0.2.0: + resolution: {integrity: sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/nprogress/-/nprogress-0.2.0.tgz} + name: '@types/nprogress' + version: 0.2.0 + dev: true + + registry.npmmirror.com/@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz} + name: '@types/qs' + version: 6.9.7 + dev: true + + registry.npmmirror.com/@types/resolve/1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/resolve/-/resolve-1.17.1.tgz} + name: '@types/resolve' + version: 1.17.1 + dependencies: + '@types/node': registry.npmmirror.com/@types/node/17.0.34 + dev: true + + registry.npmmirror.com/@typescript-eslint/eslint-plugin/5.24.0_b7082f82c31c8938795d79ce8f1e88a5: + resolution: {integrity: sha512-6bqFGk6wa9+6RrU++eLknKyDqXU1Oc8nyoLu5a1fU17PNRJd9UBr56rMF7c4DRaRtnarlkQ4jwxUbvBo8cNlpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.24.0.tgz} + id: registry.npmmirror.com/@typescript-eslint/eslint-plugin/5.24.0 + name: '@typescript-eslint/eslint-plugin' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': registry.npmmirror.com/@typescript-eslint/parser/5.24.0_eslint@8.15.0+typescript@4.6.4 + '@typescript-eslint/scope-manager': registry.npmmirror.com/@typescript-eslint/scope-manager/5.24.0 + '@typescript-eslint/type-utils': registry.npmmirror.com/@typescript-eslint/type-utils/5.24.0_eslint@8.15.0+typescript@4.6.4 + '@typescript-eslint/utils': registry.npmmirror.com/@typescript-eslint/utils/5.24.0_eslint@8.15.0+typescript@4.6.4 + debug: registry.npmmirror.com/debug/4.3.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + functional-red-black-tree: registry.npmmirror.com/functional-red-black-tree/1.0.1 + ignore: registry.npmmirror.com/ignore/5.2.0 + regexpp: registry.npmmirror.com/regexpp/3.2.0 + semver: registry.npmmirror.com/semver/7.3.7 + tsutils: registry.npmmirror.com/tsutils/3.21.0_typescript@4.6.4 + typescript: registry.npmmirror.com/typescript/4.6.4 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@typescript-eslint/parser/5.24.0_eslint@8.15.0+typescript@4.6.4: + resolution: {integrity: sha512-4q29C6xFYZ5B2CXqSBBdcS0lPyfM9M09DoQLtHS5kf+WbpV8pBBhHDLNhXfgyVwFnhrhYzOu7xmg02DzxeF2Uw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.24.0.tgz} + id: registry.npmmirror.com/@typescript-eslint/parser/5.24.0 + name: '@typescript-eslint/parser' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': registry.npmmirror.com/@typescript-eslint/scope-manager/5.24.0 + '@typescript-eslint/types': registry.npmmirror.com/@typescript-eslint/types/5.24.0 + '@typescript-eslint/typescript-estree': registry.npmmirror.com/@typescript-eslint/typescript-estree/5.24.0_typescript@4.6.4 + debug: registry.npmmirror.com/debug/4.3.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + typescript: registry.npmmirror.com/typescript/4.6.4 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@typescript-eslint/scope-manager/5.24.0: + resolution: {integrity: sha512-WpMWipcDzGmMzdT7NtTjRXFabx10WleLUGrJpuJLGaxSqpcyq5ACpKSD5VE40h2nz3melQ91aP4Du7lh9FliCA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.24.0.tgz} + name: '@typescript-eslint/scope-manager' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': registry.npmmirror.com/@typescript-eslint/types/5.24.0 + '@typescript-eslint/visitor-keys': registry.npmmirror.com/@typescript-eslint/visitor-keys/5.24.0 + dev: true + + registry.npmmirror.com/@typescript-eslint/type-utils/5.24.0_eslint@8.15.0+typescript@4.6.4: + resolution: {integrity: sha512-uGi+sQiM6E5CeCZYBXiaIvIChBXru4LZ1tMoeKbh1Lze+8BO9syUG07594C4lvN2YPT4KVeIupOJkVI+9/DAmQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.24.0.tgz} + id: registry.npmmirror.com/@typescript-eslint/type-utils/5.24.0 + name: '@typescript-eslint/type-utils' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/utils': registry.npmmirror.com/@typescript-eslint/utils/5.24.0_eslint@8.15.0+typescript@4.6.4 + debug: registry.npmmirror.com/debug/4.3.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + tsutils: registry.npmmirror.com/tsutils/3.21.0_typescript@4.6.4 + typescript: registry.npmmirror.com/typescript/4.6.4 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@typescript-eslint/types/5.24.0: + resolution: {integrity: sha512-Tpg1c3shTDgTmZd3qdUyd+16r/pGmVaVEbLs+ufuWP0EruVbUiEOmpBBQxBb9a8iPRxi8Rb2oiwOxuZJzSq11A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.24.0.tgz} + name: '@typescript-eslint/types' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + registry.npmmirror.com/@typescript-eslint/typescript-estree/5.24.0_typescript@4.6.4: + resolution: {integrity: sha512-zcor6vQkQmZAQfebSPVwUk/FD+CvnsnlfKXYeQDsWXRF+t7SBPmIfNia/wQxCSeu1h1JIjwV2i9f5/DdSp/uDw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.24.0.tgz} + id: registry.npmmirror.com/@typescript-eslint/typescript-estree/5.24.0 + name: '@typescript-eslint/typescript-estree' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': registry.npmmirror.com/@typescript-eslint/types/5.24.0 + '@typescript-eslint/visitor-keys': registry.npmmirror.com/@typescript-eslint/visitor-keys/5.24.0 + debug: registry.npmmirror.com/debug/4.3.4 + globby: registry.npmmirror.com/globby/11.1.0 + is-glob: registry.npmmirror.com/is-glob/4.0.3 + semver: registry.npmmirror.com/semver/7.3.7 + tsutils: registry.npmmirror.com/tsutils/3.21.0_typescript@4.6.4 + typescript: registry.npmmirror.com/typescript/4.6.4 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/@typescript-eslint/utils/5.24.0_eslint@8.15.0+typescript@4.6.4: + resolution: {integrity: sha512-K05sbWoeCBJH8KXu6hetBJ+ukG0k2u2KlgD3bN+v+oBKm8adJqVHpSSLHNzqyuv0Lh4GVSAUgZ5lB4icmPmWLw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.24.0.tgz} + id: registry.npmmirror.com/@typescript-eslint/utils/5.24.0 + name: '@typescript-eslint/utils' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': registry.npmmirror.com/@types/json-schema/7.0.11 + '@typescript-eslint/scope-manager': registry.npmmirror.com/@typescript-eslint/scope-manager/5.24.0 + '@typescript-eslint/types': registry.npmmirror.com/@typescript-eslint/types/5.24.0 + '@typescript-eslint/typescript-estree': registry.npmmirror.com/@typescript-eslint/typescript-estree/5.24.0_typescript@4.6.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-scope: registry.npmmirror.com/eslint-scope/5.1.1 + eslint-utils: registry.npmmirror.com/eslint-utils/3.0.0_eslint@8.15.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + registry.npmmirror.com/@typescript-eslint/visitor-keys/5.24.0: + resolution: {integrity: sha512-qzGwSXMyMnogcAo+/2fU+jhlPPVMXlIH2PeAonIKjJSoDKl1+lJVvG5Z5Oud36yU0TWK2cs1p/FaSN5J2OUFYA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.24.0.tgz} + name: '@typescript-eslint/visitor-keys' + version: 5.24.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': registry.npmmirror.com/@typescript-eslint/types/5.24.0 + eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/3.3.0 + dev: true + + registry.npmmirror.com/@vitejs/plugin-vue/2.3.3_vite@2.9.9+vue@3.2.33: + resolution: {integrity: sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz} + id: registry.npmmirror.com/@vitejs/plugin-vue/2.3.3 + name: '@vitejs/plugin-vue' + version: 2.3.3 + engines: {node: '>=12.0.0'} + peerDependencies: + vite: ^2.5.10 + vue: ^3.2.25 + dependencies: + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + vue: registry.npmmirror.com/vue/3.2.33 + dev: true + + registry.npmmirror.com/@volar/code-gen/0.34.15: + resolution: {integrity: sha512-g30glPo5N9bJocf1NBt802UcmqgZ3UtPst9b/Tangj+zR+K2RV5S2Un/suR6ZRiETXtg3nmrUcCgsTSJ6PC29A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@volar/code-gen/-/code-gen-0.34.15.tgz} + name: '@volar/code-gen' + version: 0.34.15 + dependencies: + '@volar/source-map': registry.npmmirror.com/@volar/source-map/0.34.15 + dev: true + + registry.npmmirror.com/@volar/source-map/0.34.15: + resolution: {integrity: sha512-Y3sENK/kqsgD7Vtve6gq6/Dor6JuoJWR+s9iwHcHTcA4VDkJnJRGHcvP8S3SVBsWl7T9qtlnvH3WCbFj7WlXrw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@volar/source-map/-/source-map-0.34.15.tgz} + name: '@volar/source-map' + version: 0.34.15 + dev: true + + registry.npmmirror.com/@volar/vue-code-gen/0.34.15: + resolution: {integrity: sha512-GglGsHxPPb7mW2v//5MUrkzxAO68YEIL5bRwpZD0Cp9np34keQdd1SHB5DXdoyU38cnfHJWjBlqCYpTnz2CR/w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@volar/vue-code-gen/-/vue-code-gen-0.34.15.tgz} + name: '@volar/vue-code-gen' + version: 0.34.15 + dependencies: + '@volar/code-gen': registry.npmmirror.com/@volar/code-gen/0.34.15 + '@volar/source-map': registry.npmmirror.com/@volar/source-map/0.34.15 + '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.33 + '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + dev: true + + registry.npmmirror.com/@volar/vue-typescript/0.34.15: + resolution: {integrity: sha512-7jwhYl1NQB0uYgTO74x+OBSD4SPF7bI3m1KFQ98Wt/NOTXr57YcUyOkDBImcTKRLX3PHG9ex6OfT7u3jiZ2Zzg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@volar/vue-typescript/-/vue-typescript-0.34.15.tgz} + name: '@volar/vue-typescript' + version: 0.34.15 + dependencies: + '@volar/code-gen': registry.npmmirror.com/@volar/code-gen/0.34.15 + '@volar/source-map': registry.npmmirror.com/@volar/source-map/0.34.15 + '@volar/vue-code-gen': registry.npmmirror.com/@volar/vue-code-gen/0.34.15 + '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.33 + '@vue/reactivity': registry.npmmirror.com/@vue/reactivity/3.2.33 + dev: true + + registry.npmmirror.com/@vue/compiler-core/3.2.33: + resolution: {integrity: sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz} + name: '@vue/compiler-core' + version: 3.2.33 + dependencies: + '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.12 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + estree-walker: registry.npmmirror.com/estree-walker/2.0.2 + source-map: registry.npmmirror.com/source-map/0.6.1 + + registry.npmmirror.com/@vue/compiler-dom/3.2.33: + resolution: {integrity: sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz} + name: '@vue/compiler-dom' + version: 3.2.33 + dependencies: + '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + + registry.npmmirror.com/@vue/compiler-sfc/3.2.33: + resolution: {integrity: sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz} + name: '@vue/compiler-sfc' + version: 3.2.33 + dependencies: + '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.12 + '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.33 + '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.33 + '@vue/compiler-ssr': registry.npmmirror.com/@vue/compiler-ssr/3.2.33 + '@vue/reactivity-transform': registry.npmmirror.com/@vue/reactivity-transform/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + estree-walker: registry.npmmirror.com/estree-walker/2.0.2 + magic-string: registry.npmmirror.com/magic-string/0.25.9 + postcss: registry.npmmirror.com/postcss/8.4.13 + source-map: registry.npmmirror.com/source-map/0.6.1 + + registry.npmmirror.com/@vue/compiler-ssr/3.2.33: + resolution: {integrity: sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz} + name: '@vue/compiler-ssr' + version: 3.2.33 + dependencies: + '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + + registry.npmmirror.com/@vue/devtools-api/6.1.4: + resolution: {integrity: sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz} + name: '@vue/devtools-api' + version: 6.1.4 + dev: false + + registry.npmmirror.com/@vue/eslint-config-prettier/7.0.0_eslint@8.15.0+prettier@2.6.2: + resolution: {integrity: sha512-/CTc6ML3Wta1tCe1gUeO0EYnVXfo3nJXsIhZ8WJr3sov+cGASr6yuiibJTL6lmIBm7GobopToOuB3B6AWyV0Iw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz} + id: registry.npmmirror.com/@vue/eslint-config-prettier/7.0.0 + name: '@vue/eslint-config-prettier' + version: 7.0.0 + peerDependencies: + eslint: '>= 7.28.0' + prettier: '>= 2.0.0' + dependencies: + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-config-prettier: registry.npmmirror.com/eslint-config-prettier/8.5.0_eslint@8.15.0 + eslint-plugin-prettier: registry.npmmirror.com/eslint-plugin-prettier/4.0.0_440b30a60bbe5bb6e3ad0057150b2782 + prettier: registry.npmmirror.com/prettier/2.6.2 + dev: true + + registry.npmmirror.com/@vue/eslint-config-typescript/10.0.0_7f105dc3ebd31cec885fdbbd30d5cc4c: + resolution: {integrity: sha512-F94cL8ug3FaYXlCfU5/wiGjk1qeadmoBpRGAOBq+qre3Smdupa59dd6ZJrsfRODpsMPyTG7330juMDsUvpZ3Rw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz} + id: registry.npmmirror.com/@vue/eslint-config-typescript/10.0.0 + name: '@vue/eslint-config-typescript' + version: 10.0.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + eslint-plugin-vue: ^8.0.1 + dependencies: + '@typescript-eslint/eslint-plugin': registry.npmmirror.com/@typescript-eslint/eslint-plugin/5.24.0_b7082f82c31c8938795d79ce8f1e88a5 + '@typescript-eslint/parser': registry.npmmirror.com/@typescript-eslint/parser/5.24.0_eslint@8.15.0+typescript@4.6.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-plugin-vue: registry.npmmirror.com/eslint-plugin-vue/8.7.1_eslint@8.15.0 + vue-eslint-parser: registry.npmmirror.com/vue-eslint-parser/8.3.0_eslint@8.15.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + registry.npmmirror.com/@vue/reactivity-transform/3.2.33: + resolution: {integrity: sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz} + name: '@vue/reactivity-transform' + version: 3.2.33 + dependencies: + '@babel/parser': registry.npmmirror.com/@babel/parser/7.17.12 + '@vue/compiler-core': registry.npmmirror.com/@vue/compiler-core/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + estree-walker: registry.npmmirror.com/estree-walker/2.0.2 + magic-string: registry.npmmirror.com/magic-string/0.25.9 + + registry.npmmirror.com/@vue/reactivity/3.2.33: + resolution: {integrity: sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.33.tgz} + name: '@vue/reactivity' + version: 3.2.33 + dependencies: + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + + registry.npmmirror.com/@vue/runtime-core/3.2.33: + resolution: {integrity: sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz} + name: '@vue/runtime-core' + version: 3.2.33 + dependencies: + '@vue/reactivity': registry.npmmirror.com/@vue/reactivity/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + dev: false + + registry.npmmirror.com/@vue/runtime-dom/3.2.33: + resolution: {integrity: sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz} + name: '@vue/runtime-dom' + version: 3.2.33 + dependencies: + '@vue/runtime-core': registry.npmmirror.com/@vue/runtime-core/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + csstype: registry.npmmirror.com/csstype/2.6.20 + dev: false + + registry.npmmirror.com/@vue/server-renderer/3.2.33_vue@3.2.33: + resolution: {integrity: sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz} + id: registry.npmmirror.com/@vue/server-renderer/3.2.33 + name: '@vue/server-renderer' + version: 3.2.33 + peerDependencies: + vue: 3.2.33 + dependencies: + '@vue/compiler-ssr': registry.npmmirror.com/@vue/compiler-ssr/3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/@vue/shared/3.2.33: + resolution: {integrity: sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vue/shared/-/shared-3.2.33.tgz} + name: '@vue/shared' + version: 3.2.33 + + registry.npmmirror.com/@vueuse/core/8.5.0_vue@3.2.33: + resolution: {integrity: sha512-VEJ6sGNsPlUp0o9BGda2YISvDZbhWJSOJu5zlp2TufRGVrLcYUKr31jyFEOj6RXzG3k/H4aCYeZyjpItfU8glw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/core/-/core-8.5.0.tgz} + id: registry.npmmirror.com/@vueuse/core/8.5.0 + name: '@vueuse/core' + version: 8.5.0 + peerDependencies: + '@vue/composition-api': ^1.1.0 + vue: ^2.6.0 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue: + optional: true + dependencies: + '@vueuse/metadata': registry.npmmirror.com/@vueuse/metadata/8.5.0 + '@vueuse/shared': registry.npmmirror.com/@vueuse/shared/8.5.0_vue@3.2.33 + vue: registry.npmmirror.com/vue/3.2.33 + vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.33 + dev: false + + registry.npmmirror.com/@vueuse/metadata/8.5.0: + resolution: {integrity: sha512-WxsD+Cd+bn+HcjpY6Dl9FJ8ywTRTT9pTwk3bCQpzEhXVYAyNczKDSahk50fCfIJKeWHhyI4B2+/ZEOxQAkUr0g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/metadata/-/metadata-8.5.0.tgz} + name: '@vueuse/metadata' + version: 8.5.0 + dev: false + + registry.npmmirror.com/@vueuse/shared/8.5.0_vue@3.2.33: + resolution: {integrity: sha512-qKG+SZb44VvGD4dU5cQ63z4JE2Yk39hQUecR0a9sEdJA01cx+XrxAvFKJfPooxwoiqalAVw/ktWK6xbyc/jS3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@vueuse/shared/-/shared-8.5.0.tgz} + id: registry.npmmirror.com/@vueuse/shared/8.5.0 + name: '@vueuse/shared' + version: 8.5.0 + peerDependencies: + '@vue/composition-api': ^1.1.0 + vue: ^2.6.0 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue: + optional: true + dependencies: + vue: registry.npmmirror.com/vue/3.2.33 + vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.33 + dev: false + + registry.npmmirror.com/acorn-jsx/5.3.2_acorn@8.7.1: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz} + id: registry.npmmirror.com/acorn-jsx/5.3.2 + name: acorn-jsx + version: 5.3.2 + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: registry.npmmirror.com/acorn/8.7.1 + dev: true + + registry.npmmirror.com/acorn/8.7.1: + resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/acorn/-/acorn-8.7.1.tgz} + name: acorn + version: 8.7.1 + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + registry.npmmirror.com/ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz} + name: ajv + version: 6.12.6 + dependencies: + fast-deep-equal: registry.npmmirror.com/fast-deep-equal/3.1.3 + fast-json-stable-stringify: registry.npmmirror.com/fast-json-stable-stringify/2.1.0 + json-schema-traverse: registry.npmmirror.com/json-schema-traverse/0.4.1 + uri-js: registry.npmmirror.com/uri-js/4.4.1 + dev: true + + registry.npmmirror.com/ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz} + name: ansi-regex + version: 5.0.1 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz} + name: ansi-styles + version: 4.3.0 + engines: {node: '>=8'} + dependencies: + color-convert: registry.npmmirror.com/color-convert/2.0.1 + dev: true + + registry.npmmirror.com/anymatch/3.1.2: + resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/anymatch/-/anymatch-3.1.2.tgz} + name: anymatch + version: 3.1.2 + engines: {node: '>= 8'} + dependencies: + normalize-path: registry.npmmirror.com/normalize-path/3.0.0 + picomatch: registry.npmmirror.com/picomatch/2.3.1 + dev: true + + registry.npmmirror.com/argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz} + name: argparse + version: 2.0.1 + dev: true + + registry.npmmirror.com/array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz} + name: array-union + version: 2.1.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/async-validator/4.1.1: + resolution: {integrity: sha512-p4DO/JXwjs8klJyJL8Q2oM4ks5fUTze/h5k10oPPKMiLe1fj3G1QMzPHNmN1Py4ycOk7WlO2DcGXv1qiESJCZA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/async-validator/-/async-validator-4.1.1.tgz} + name: async-validator + version: 4.1.1 + dev: false + + registry.npmmirror.com/asynckit/0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz} + name: asynckit + version: 0.4.0 + dev: false + + registry.npmmirror.com/axios/0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/axios/-/axios-0.27.2.tgz} + name: axios + version: 0.27.2 + dependencies: + follow-redirects: registry.npmmirror.com/follow-redirects/1.15.0 + form-data: registry.npmmirror.com/form-data/4.0.0 + transitivePeerDependencies: + - debug + dev: false + + registry.npmmirror.com/balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz} + name: balanced-match + version: 1.0.2 + dev: true + + registry.npmmirror.com/binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz} + name: binary-extensions + version: 2.2.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/boolbase/1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz} + name: boolbase + version: 1.0.0 + dev: true + + registry.npmmirror.com/brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz} + name: brace-expansion + version: 1.1.11 + dependencies: + balanced-match: registry.npmmirror.com/balanced-match/1.0.2 + concat-map: registry.npmmirror.com/concat-map/0.0.1 + dev: true + + registry.npmmirror.com/brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz} + name: brace-expansion + version: 2.0.1 + dependencies: + balanced-match: registry.npmmirror.com/balanced-match/1.0.2 + dev: true + + registry.npmmirror.com/braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz} + name: braces + version: 3.0.2 + engines: {node: '>=8'} + dependencies: + fill-range: registry.npmmirror.com/fill-range/7.0.1 + dev: true + + registry.npmmirror.com/builtin-modules/3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz} + name: builtin-modules + version: 3.3.0 + engines: {node: '>=6'} + dev: true + + registry.npmmirror.com/call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz} + name: call-bind + version: 1.0.2 + dependencies: + function-bind: registry.npmmirror.com/function-bind/1.1.1 + get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 + dev: false + + registry.npmmirror.com/callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz} + name: callsites + version: 3.1.0 + engines: {node: '>=6'} + dev: true + + registry.npmmirror.com/chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz} + name: chalk + version: 4.1.2 + engines: {node: '>=10'} + dependencies: + ansi-styles: registry.npmmirror.com/ansi-styles/4.3.0 + supports-color: registry.npmmirror.com/supports-color/7.2.0 + dev: true + + registry.npmmirror.com/chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz} + name: chokidar + version: 3.5.3 + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: registry.npmmirror.com/anymatch/3.1.2 + braces: registry.npmmirror.com/braces/3.0.2 + glob-parent: registry.npmmirror.com/glob-parent/5.1.2 + is-binary-path: registry.npmmirror.com/is-binary-path/2.1.0 + is-glob: registry.npmmirror.com/is-glob/4.0.3 + normalize-path: registry.npmmirror.com/normalize-path/3.0.0 + readdirp: registry.npmmirror.com/readdirp/3.6.0 + optionalDependencies: + fsevents: registry.npmmirror.com/fsevents/2.3.2 + dev: true + + registry.npmmirror.com/clipboard/2.0.11: + resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz} + name: clipboard + version: 2.0.11 + dependencies: + good-listener: registry.npmmirror.com/good-listener/1.2.2 + select: registry.npmmirror.com/select/1.1.2 + tiny-emitter: registry.npmmirror.com/tiny-emitter/2.1.0 + dev: false + + registry.npmmirror.com/color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz} + name: color-convert + version: 2.0.1 + engines: {node: '>=7.0.0'} + dependencies: + color-name: registry.npmmirror.com/color-name/1.1.4 + dev: true + + registry.npmmirror.com/color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz} + name: color-name + version: 1.1.4 + dev: true + + registry.npmmirror.com/combined-stream/1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz} + name: combined-stream + version: 1.0.8 + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: registry.npmmirror.com/delayed-stream/1.0.0 + dev: false + + registry.npmmirror.com/commander/9.2.0: + resolution: {integrity: sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/commander/-/commander-9.2.0.tgz} + name: commander + version: 9.2.0 + engines: {node: ^12.20.0 || >=14} + dev: false + + registry.npmmirror.com/concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz} + name: concat-map + version: 0.0.1 + dev: true + + registry.npmmirror.com/connect/3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/connect/-/connect-3.7.0.tgz} + name: connect + version: 3.7.0 + engines: {node: '>= 0.10.0'} + dependencies: + debug: registry.npmmirror.com/debug/2.6.9 + finalhandler: registry.npmmirror.com/finalhandler/1.1.2 + parseurl: registry.npmmirror.com/parseurl/1.3.3 + utils-merge: registry.npmmirror.com/utils-merge/1.0.1 + dev: true + + registry.npmmirror.com/cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz} + name: cross-spawn + version: 7.0.3 + engines: {node: '>= 8'} + dependencies: + path-key: registry.npmmirror.com/path-key/3.1.1 + shebang-command: registry.npmmirror.com/shebang-command/2.0.0 + which: registry.npmmirror.com/which/2.0.2 + dev: true + + registry.npmmirror.com/cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz} + name: cssesc + version: 3.0.0 + engines: {node: '>=4'} + hasBin: true + dev: true + + registry.npmmirror.com/csstype/2.6.20: + resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/csstype/-/csstype-2.6.20.tgz} + name: csstype + version: 2.6.20 + dev: false + + registry.npmmirror.com/dayjs/1.11.2: + resolution: {integrity: sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dayjs/-/dayjs-1.11.2.tgz} + name: dayjs + version: 1.11.2 + dev: false + + registry.npmmirror.com/debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz} + name: debug + version: 2.6.9 + dependencies: + ms: registry.npmmirror.com/ms/2.0.0 + dev: true + + registry.npmmirror.com/debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz} + name: debug + version: 4.3.4 + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: registry.npmmirror.com/ms/2.1.2 + dev: true + + registry.npmmirror.com/deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz} + name: deep-is + version: 0.1.4 + dev: true + + registry.npmmirror.com/deepmerge/4.2.2: + resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/deepmerge/-/deepmerge-4.2.2.tgz} + name: deepmerge + version: 4.2.2 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/delayed-stream/1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz} + name: delayed-stream + version: 1.0.0 + engines: {node: '>=0.4.0'} + dev: false + + registry.npmmirror.com/delegate/3.2.0: + resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz} + name: delegate + version: 3.2.0 + dev: false + + registry.npmmirror.com/dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz} + name: dir-glob + version: 3.0.1 + engines: {node: '>=8'} + dependencies: + path-type: registry.npmmirror.com/path-type/4.0.0 + dev: true + + registry.npmmirror.com/doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz} + name: doctrine + version: 3.0.0 + engines: {node: '>=6.0.0'} + dependencies: + esutils: registry.npmmirror.com/esutils/2.0.3 + dev: true + + registry.npmmirror.com/ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz} + name: ee-first + version: 1.1.1 + dev: true + + registry.npmmirror.com/element-plus/2.2.0_vue@3.2.33: + resolution: {integrity: sha512-zxmAFEAa1T/n09rR+NozXcWl5CjaFtqoaxhFSafag0dgc90tgEHitDXfegdFAl4ahugdNTqu9aLzngx3VhDAtA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.2.0.tgz} + id: registry.npmmirror.com/element-plus/2.2.0 + name: element-plus + version: 2.2.0 + peerDependencies: + vue: ^3.2.0 + dependencies: + '@ctrl/tinycolor': registry.npmmirror.com/@ctrl/tinycolor/3.4.1 + '@element-plus/icons-vue': registry.npmmirror.com/@element-plus/icons-vue/1.1.4_vue@3.2.33 + '@floating-ui/dom': registry.npmmirror.com/@floating-ui/dom/0.4.5 + '@popperjs/core': registry.npmmirror.com/@sxzz/popperjs-es/2.11.7 + '@types/lodash': registry.npmmirror.com/@types/lodash/4.14.182 + '@types/lodash-es': registry.npmmirror.com/@types/lodash-es/4.17.6 + '@vueuse/core': registry.npmmirror.com/@vueuse/core/8.5.0_vue@3.2.33 + async-validator: registry.npmmirror.com/async-validator/4.1.1 + dayjs: registry.npmmirror.com/dayjs/1.11.2 + escape-html: registry.npmmirror.com/escape-html/1.0.3 + lodash: registry.npmmirror.com/lodash/4.17.21 + lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-unified: registry.npmmirror.com/lodash-unified/1.0.2_da03a4540fbd16bbaafbb96724306afd + memoize-one: registry.npmmirror.com/memoize-one/6.0.0 + normalize-wheel-es: registry.npmmirror.com/normalize-wheel-es/1.1.2 + vue: registry.npmmirror.com/vue/3.2.33 + transitivePeerDependencies: + - '@vue/composition-api' + dev: false + + registry.npmmirror.com/encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz} + name: encodeurl + version: 1.0.2 + engines: {node: '>= 0.8'} + dev: true + + registry.npmmirror.com/esbuild-android-64/0.14.39: + resolution: {integrity: sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz} + name: esbuild-android-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-android-arm64/0.14.39: + resolution: {integrity: sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz} + name: esbuild-android-arm64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-darwin-64/0.14.39: + resolution: {integrity: sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz} + name: esbuild-darwin-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-darwin-arm64/0.14.39: + resolution: {integrity: sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz} + name: esbuild-darwin-arm64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-freebsd-64/0.14.39: + resolution: {integrity: sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz} + name: esbuild-freebsd-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-freebsd-arm64/0.14.39: + resolution: {integrity: sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz} + name: esbuild-freebsd-arm64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-32/0.14.39: + resolution: {integrity: sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz} + name: esbuild-linux-32 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-64/0.14.39: + resolution: {integrity: sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz} + name: esbuild-linux-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-arm/0.14.39: + resolution: {integrity: sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz} + name: esbuild-linux-arm + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-arm64/0.14.39: + resolution: {integrity: sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz} + name: esbuild-linux-arm64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-mips64le/0.14.39: + resolution: {integrity: sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz} + name: esbuild-linux-mips64le + version: 0.14.39 + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-ppc64le/0.14.39: + resolution: {integrity: sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz} + name: esbuild-linux-ppc64le + version: 0.14.39 + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-riscv64/0.14.39: + resolution: {integrity: sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz} + name: esbuild-linux-riscv64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-linux-s390x/0.14.39: + resolution: {integrity: sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz} + name: esbuild-linux-s390x + version: 0.14.39 + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-netbsd-64/0.14.39: + resolution: {integrity: sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz} + name: esbuild-netbsd-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-openbsd-64/0.14.39: + resolution: {integrity: sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz} + name: esbuild-openbsd-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-sunos-64/0.14.39: + resolution: {integrity: sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz} + name: esbuild-sunos-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-32/0.14.39: + resolution: {integrity: sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz} + name: esbuild-windows-32 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-64/0.14.39: + resolution: {integrity: sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz} + name: esbuild-windows-64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild-windows-arm64/0.14.39: + resolution: {integrity: sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz} + name: esbuild-windows-arm64 + version: 0.14.39 + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/esbuild/0.11.3: + resolution: {integrity: sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild/-/esbuild-0.11.3.tgz} + name: esbuild + version: 0.11.3 + hasBin: true + requiresBuild: true + dev: true + + registry.npmmirror.com/esbuild/0.14.39: + resolution: {integrity: sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esbuild/-/esbuild-0.14.39.tgz} + name: esbuild + version: 0.14.39 + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + esbuild-android-64: registry.npmmirror.com/esbuild-android-64/0.14.39 + esbuild-android-arm64: registry.npmmirror.com/esbuild-android-arm64/0.14.39 + esbuild-darwin-64: registry.npmmirror.com/esbuild-darwin-64/0.14.39 + esbuild-darwin-arm64: registry.npmmirror.com/esbuild-darwin-arm64/0.14.39 + esbuild-freebsd-64: registry.npmmirror.com/esbuild-freebsd-64/0.14.39 + esbuild-freebsd-arm64: registry.npmmirror.com/esbuild-freebsd-arm64/0.14.39 + esbuild-linux-32: registry.npmmirror.com/esbuild-linux-32/0.14.39 + esbuild-linux-64: registry.npmmirror.com/esbuild-linux-64/0.14.39 + esbuild-linux-arm: registry.npmmirror.com/esbuild-linux-arm/0.14.39 + esbuild-linux-arm64: registry.npmmirror.com/esbuild-linux-arm64/0.14.39 + esbuild-linux-mips64le: registry.npmmirror.com/esbuild-linux-mips64le/0.14.39 + esbuild-linux-ppc64le: registry.npmmirror.com/esbuild-linux-ppc64le/0.14.39 + esbuild-linux-riscv64: registry.npmmirror.com/esbuild-linux-riscv64/0.14.39 + esbuild-linux-s390x: registry.npmmirror.com/esbuild-linux-s390x/0.14.39 + esbuild-netbsd-64: registry.npmmirror.com/esbuild-netbsd-64/0.14.39 + esbuild-openbsd-64: registry.npmmirror.com/esbuild-openbsd-64/0.14.39 + esbuild-sunos-64: registry.npmmirror.com/esbuild-sunos-64/0.14.39 + esbuild-windows-32: registry.npmmirror.com/esbuild-windows-32/0.14.39 + esbuild-windows-64: registry.npmmirror.com/esbuild-windows-64/0.14.39 + esbuild-windows-arm64: registry.npmmirror.com/esbuild-windows-arm64/0.14.39 + dev: true + + registry.npmmirror.com/escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz} + name: escape-html + version: 1.0.3 + + registry.npmmirror.com/escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz} + name: escape-string-regexp + version: 4.0.0 + engines: {node: '>=10'} + dev: true + + registry.npmmirror.com/eslint-config-prettier/8.5.0_eslint@8.15.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz} + id: registry.npmmirror.com/eslint-config-prettier/8.5.0 + name: eslint-config-prettier + version: 8.5.0 + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: registry.npmmirror.com/eslint/8.15.0 + dev: true + + registry.npmmirror.com/eslint-plugin-prettier/4.0.0_440b30a60bbe5bb6e3ad0057150b2782: + resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz} + id: registry.npmmirror.com/eslint-plugin-prettier/4.0.0 + name: eslint-plugin-prettier + version: 4.0.0 + engines: {node: '>=6.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-config-prettier: registry.npmmirror.com/eslint-config-prettier/8.5.0_eslint@8.15.0 + prettier: registry.npmmirror.com/prettier/2.6.2 + prettier-linter-helpers: registry.npmmirror.com/prettier-linter-helpers/1.0.0 + dev: true + + registry.npmmirror.com/eslint-plugin-vue/8.7.1_eslint@8.15.0: + resolution: {integrity: sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz} + id: registry.npmmirror.com/eslint-plugin-vue/8.7.1 + name: eslint-plugin-vue + version: 8.7.1 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + dependencies: + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-utils: registry.npmmirror.com/eslint-utils/3.0.0_eslint@8.15.0 + natural-compare: registry.npmmirror.com/natural-compare/1.4.0 + nth-check: registry.npmmirror.com/nth-check/2.0.1 + postcss-selector-parser: registry.npmmirror.com/postcss-selector-parser/6.0.10 + semver: registry.npmmirror.com/semver/7.3.7 + vue-eslint-parser: registry.npmmirror.com/vue-eslint-parser/8.3.0_eslint@8.15.0 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz} + name: eslint-scope + version: 5.1.1 + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: registry.npmmirror.com/esrecurse/4.3.0 + estraverse: registry.npmmirror.com/estraverse/4.3.0 + dev: true + + registry.npmmirror.com/eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz} + name: eslint-scope + version: 7.1.1 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: registry.npmmirror.com/esrecurse/4.3.0 + estraverse: registry.npmmirror.com/estraverse/5.3.0 + dev: true + + registry.npmmirror.com/eslint-utils/3.0.0_eslint@8.15.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz} + id: registry.npmmirror.com/eslint-utils/3.0.0 + name: eslint-utils + version: 3.0.0 + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/2.1.0 + dev: true + + registry.npmmirror.com/eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz} + name: eslint-visitor-keys + version: 2.1.0 + engines: {node: '>=10'} + dev: true + + registry.npmmirror.com/eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz} + name: eslint-visitor-keys + version: 3.3.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + registry.npmmirror.com/eslint/8.15.0: + resolution: {integrity: sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eslint/-/eslint-8.15.0.tgz} + name: eslint + version: 8.15.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': registry.npmmirror.com/@eslint/eslintrc/1.2.3 + '@humanwhocodes/config-array': registry.npmmirror.com/@humanwhocodes/config-array/0.9.5 + ajv: registry.npmmirror.com/ajv/6.12.6 + chalk: registry.npmmirror.com/chalk/4.1.2 + cross-spawn: registry.npmmirror.com/cross-spawn/7.0.3 + debug: registry.npmmirror.com/debug/4.3.4 + doctrine: registry.npmmirror.com/doctrine/3.0.0 + escape-string-regexp: registry.npmmirror.com/escape-string-regexp/4.0.0 + eslint-scope: registry.npmmirror.com/eslint-scope/7.1.1 + eslint-utils: registry.npmmirror.com/eslint-utils/3.0.0_eslint@8.15.0 + eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/3.3.0 + espree: registry.npmmirror.com/espree/9.3.2 + esquery: registry.npmmirror.com/esquery/1.4.0 + esutils: registry.npmmirror.com/esutils/2.0.3 + fast-deep-equal: registry.npmmirror.com/fast-deep-equal/3.1.3 + file-entry-cache: registry.npmmirror.com/file-entry-cache/6.0.1 + functional-red-black-tree: registry.npmmirror.com/functional-red-black-tree/1.0.1 + glob-parent: registry.npmmirror.com/glob-parent/6.0.2 + globals: registry.npmmirror.com/globals/13.15.0 + ignore: registry.npmmirror.com/ignore/5.2.0 + import-fresh: registry.npmmirror.com/import-fresh/3.3.0 + imurmurhash: registry.npmmirror.com/imurmurhash/0.1.4 + is-glob: registry.npmmirror.com/is-glob/4.0.3 + js-yaml: registry.npmmirror.com/js-yaml/4.1.0 + json-stable-stringify-without-jsonify: registry.npmmirror.com/json-stable-stringify-without-jsonify/1.0.1 + levn: registry.npmmirror.com/levn/0.4.1 + lodash.merge: registry.npmmirror.com/lodash.merge/4.6.2 + minimatch: registry.npmmirror.com/minimatch/3.1.2 + natural-compare: registry.npmmirror.com/natural-compare/1.4.0 + optionator: registry.npmmirror.com/optionator/0.9.1 + regexpp: registry.npmmirror.com/regexpp/3.2.0 + strip-ansi: registry.npmmirror.com/strip-ansi/6.0.1 + strip-json-comments: registry.npmmirror.com/strip-json-comments/3.1.1 + text-table: registry.npmmirror.com/text-table/0.2.0 + v8-compile-cache: registry.npmmirror.com/v8-compile-cache/2.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/espree/9.3.2: + resolution: {integrity: sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/espree/-/espree-9.3.2.tgz} + name: espree + version: 9.3.2 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: registry.npmmirror.com/acorn/8.7.1 + acorn-jsx: registry.npmmirror.com/acorn-jsx/5.3.2_acorn@8.7.1 + eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/3.3.0 + dev: true + + registry.npmmirror.com/esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esquery/-/esquery-1.4.0.tgz} + name: esquery + version: 1.4.0 + engines: {node: '>=0.10'} + dependencies: + estraverse: registry.npmmirror.com/estraverse/5.3.0 + dev: true + + registry.npmmirror.com/esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz} + name: esrecurse + version: 4.3.0 + engines: {node: '>=4.0'} + dependencies: + estraverse: registry.npmmirror.com/estraverse/5.3.0 + dev: true + + registry.npmmirror.com/estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz} + name: estraverse + version: 4.3.0 + engines: {node: '>=4.0'} + dev: true + + registry.npmmirror.com/estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz} + name: estraverse + version: 5.3.0 + engines: {node: '>=4.0'} + dev: true + + registry.npmmirror.com/estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz} + name: estree-walker + version: 1.0.1 + dev: true + + registry.npmmirror.com/estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz} + name: estree-walker + version: 2.0.2 + + registry.npmmirror.com/esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz} + name: esutils + version: 2.0.3 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} + name: fast-deep-equal + version: 3.1.3 + dev: true + + registry.npmmirror.com/fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-diff/-/fast-diff-1.2.0.tgz} + name: fast-diff + version: 1.2.0 + dev: true + + registry.npmmirror.com/fast-glob/3.2.11: + resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.11.tgz} + name: fast-glob + version: 3.2.11 + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': registry.npmmirror.com/@nodelib/fs.stat/2.0.5 + '@nodelib/fs.walk': registry.npmmirror.com/@nodelib/fs.walk/1.2.8 + glob-parent: registry.npmmirror.com/glob-parent/5.1.2 + merge2: registry.npmmirror.com/merge2/1.4.1 + micromatch: registry.npmmirror.com/micromatch/4.0.5 + dev: true + + registry.npmmirror.com/fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz} + name: fast-json-stable-stringify + version: 2.1.0 + dev: true + + registry.npmmirror.com/fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz} + name: fast-levenshtein + version: 2.0.6 + dev: true + + registry.npmmirror.com/fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fastq/-/fastq-1.13.0.tgz} + name: fastq + version: 1.13.0 + dependencies: + reusify: registry.npmmirror.com/reusify/1.0.4 + dev: true + + registry.npmmirror.com/file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz} + name: file-entry-cache + version: 6.0.1 + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: registry.npmmirror.com/flat-cache/3.0.4 + dev: true + + registry.npmmirror.com/fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz} + name: fill-range + version: 7.0.1 + engines: {node: '>=8'} + dependencies: + to-regex-range: registry.npmmirror.com/to-regex-range/5.0.1 + dev: true + + registry.npmmirror.com/finalhandler/1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/finalhandler/-/finalhandler-1.1.2.tgz} + name: finalhandler + version: 1.1.2 + engines: {node: '>= 0.8'} + dependencies: + debug: registry.npmmirror.com/debug/2.6.9 + encodeurl: registry.npmmirror.com/encodeurl/1.0.2 + escape-html: registry.npmmirror.com/escape-html/1.0.3 + on-finished: registry.npmmirror.com/on-finished/2.3.0 + parseurl: registry.npmmirror.com/parseurl/1.3.3 + statuses: registry.npmmirror.com/statuses/1.5.0 + unpipe: registry.npmmirror.com/unpipe/1.0.0 + dev: true + + registry.npmmirror.com/flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/flat-cache/-/flat-cache-3.0.4.tgz} + name: flat-cache + version: 3.0.4 + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: registry.npmmirror.com/flatted/3.2.5 + rimraf: registry.npmmirror.com/rimraf/3.0.2 + dev: true + + registry.npmmirror.com/flatted/3.2.5: + resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/flatted/-/flatted-3.2.5.tgz} + name: flatted + version: 3.2.5 + dev: true + + registry.npmmirror.com/follow-redirects/1.15.0: + resolution: {integrity: sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.0.tgz} + name: follow-redirects + version: 1.15.0 + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + registry.npmmirror.com/form-data/4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz} + name: form-data + version: 4.0.0 + engines: {node: '>= 6'} + dependencies: + asynckit: registry.npmmirror.com/asynckit/0.4.0 + combined-stream: registry.npmmirror.com/combined-stream/1.0.8 + mime-types: registry.npmmirror.com/mime-types/2.1.35 + dev: false + + registry.npmmirror.com/fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz} + name: fs.realpath + version: 1.0.0 + dev: true + + registry.npmmirror.com/fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz} + name: fsevents + version: 2.3.2 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + registry.npmmirror.com/function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz} + name: function-bind + version: 1.1.1 + + registry.npmmirror.com/functional-red-black-tree/1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz} + name: functional-red-black-tree + version: 1.0.1 + dev: true + + registry.npmmirror.com/fuse.js/6.6.2: + resolution: {integrity: sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fuse.js/-/fuse.js-6.6.2.tgz} + name: fuse.js + version: 6.6.2 + engines: {node: '>=10'} + dev: false + + registry.npmmirror.com/get-intrinsic/1.1.1: + resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz} + name: get-intrinsic + version: 1.1.1 + dependencies: + function-bind: registry.npmmirror.com/function-bind/1.1.1 + has: registry.npmmirror.com/has/1.0.3 + has-symbols: registry.npmmirror.com/has-symbols/1.0.3 + dev: false + + registry.npmmirror.com/glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz} + name: glob-parent + version: 5.1.2 + engines: {node: '>= 6'} + dependencies: + is-glob: registry.npmmirror.com/is-glob/4.0.3 + dev: true + + registry.npmmirror.com/glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz} + name: glob-parent + version: 6.0.2 + engines: {node: '>=10.13.0'} + dependencies: + is-glob: registry.npmmirror.com/is-glob/4.0.3 + dev: true + + registry.npmmirror.com/glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz} + name: glob + version: 7.2.3 + dependencies: + fs.realpath: registry.npmmirror.com/fs.realpath/1.0.0 + inflight: registry.npmmirror.com/inflight/1.0.6 + inherits: registry.npmmirror.com/inherits/2.0.4 + minimatch: registry.npmmirror.com/minimatch/3.1.2 + once: registry.npmmirror.com/once/1.4.0 + path-is-absolute: registry.npmmirror.com/path-is-absolute/1.0.1 + dev: true + + registry.npmmirror.com/globals/13.15.0: + resolution: {integrity: sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globals/-/globals-13.15.0.tgz} + name: globals + version: 13.15.0 + engines: {node: '>=8'} + dependencies: + type-fest: registry.npmmirror.com/type-fest/0.20.2 + dev: true + + registry.npmmirror.com/globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz} + name: globby + version: 11.1.0 + engines: {node: '>=10'} + dependencies: + array-union: registry.npmmirror.com/array-union/2.1.0 + dir-glob: registry.npmmirror.com/dir-glob/3.0.1 + fast-glob: registry.npmmirror.com/fast-glob/3.2.11 + ignore: registry.npmmirror.com/ignore/5.2.0 + merge2: registry.npmmirror.com/merge2/1.4.1 + slash: registry.npmmirror.com/slash/3.0.0 + dev: true + + registry.npmmirror.com/good-listener/1.2.2: + resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz} + name: good-listener + version: 1.2.2 + dependencies: + delegate: registry.npmmirror.com/delegate/3.2.0 + dev: false + + registry.npmmirror.com/has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz} + name: has-flag + version: 4.0.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz} + name: has-symbols + version: 1.0.3 + engines: {node: '>= 0.4'} + dev: false + + registry.npmmirror.com/has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has/-/has-1.0.3.tgz} + name: has + version: 1.0.3 + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: registry.npmmirror.com/function-bind/1.1.1 + + registry.npmmirror.com/ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz} + name: ignore + version: 5.2.0 + engines: {node: '>= 4'} + dev: true + + registry.npmmirror.com/immutable/4.0.0: + resolution: {integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/immutable/-/immutable-4.0.0.tgz} + name: immutable + version: 4.0.0 + dev: true + + registry.npmmirror.com/import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz} + name: import-fresh + version: 3.3.0 + engines: {node: '>=6'} + dependencies: + parent-module: registry.npmmirror.com/parent-module/1.0.1 + resolve-from: registry.npmmirror.com/resolve-from/4.0.0 + dev: true + + registry.npmmirror.com/imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz} + name: imurmurhash + version: 0.1.4 + engines: {node: '>=0.8.19'} + dev: true + + registry.npmmirror.com/inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz} + name: inflight + version: 1.0.6 + dependencies: + once: registry.npmmirror.com/once/1.4.0 + wrappy: registry.npmmirror.com/wrappy/1.0.2 + dev: true + + registry.npmmirror.com/inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz} + name: inherits + version: 2.0.4 + dev: true + + registry.npmmirror.com/is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz} + name: is-binary-path + version: 2.1.0 + engines: {node: '>=8'} + dependencies: + binary-extensions: registry.npmmirror.com/binary-extensions/2.2.0 + dev: true + + registry.npmmirror.com/is-builtin-module/3.1.0: + resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-builtin-module/-/is-builtin-module-3.1.0.tgz} + name: is-builtin-module + version: 3.1.0 + engines: {node: '>=6'} + dependencies: + builtin-modules: registry.npmmirror.com/builtin-modules/3.3.0 + dev: true + + registry.npmmirror.com/is-core-module/2.9.0: + resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz} + name: is-core-module + version: 2.9.0 + dependencies: + has: registry.npmmirror.com/has/1.0.3 + dev: true + + registry.npmmirror.com/is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz} + name: is-extglob + version: 2.1.1 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz} + name: is-glob + version: 4.0.3 + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: registry.npmmirror.com/is-extglob/2.1.1 + dev: true + + registry.npmmirror.com/is-module/1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz} + name: is-module + version: 1.0.0 + dev: true + + registry.npmmirror.com/is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz} + name: is-number + version: 7.0.0 + engines: {node: '>=0.12.0'} + dev: true + + registry.npmmirror.com/isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz} + name: isexe + version: 2.0.0 + dev: true + + registry.npmmirror.com/js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz} + name: js-yaml + version: 4.1.0 + hasBin: true + dependencies: + argparse: registry.npmmirror.com/argparse/2.0.1 + dev: true + + registry.npmmirror.com/json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz} + name: json-schema-traverse + version: 0.4.1 + dev: true + + registry.npmmirror.com/json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz} + name: json-stable-stringify-without-jsonify + version: 1.0.1 + dev: true + + registry.npmmirror.com/levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz} + name: levn + version: 0.4.1 + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: registry.npmmirror.com/prelude-ls/1.2.1 + type-check: registry.npmmirror.com/type-check/0.4.0 + dev: true + + registry.npmmirror.com/local-pkg/0.4.1: + resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.1.tgz} + name: local-pkg + version: 0.4.1 + engines: {node: '>=14'} + dev: true + + registry.npmmirror.com/lodash-es/4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz} + name: lodash-es + version: 4.17.21 + dev: false + + registry.npmmirror.com/lodash-unified/1.0.2_da03a4540fbd16bbaafbb96724306afd: + resolution: {integrity: sha512-OGbEy+1P+UT26CYi4opY4gebD8cWRDxAT6MAObIVQMiqYdxZr1g3QHWCToVsm31x2NkLS4K3+MC2qInaRMa39g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.2.tgz} + id: registry.npmmirror.com/lodash-unified/1.0.2 + name: lodash-unified + version: 1.0.2 + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + dependencies: + '@types/lodash-es': registry.npmmirror.com/@types/lodash-es/4.17.6 + lodash: registry.npmmirror.com/lodash/4.17.21 + lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + dev: false + + registry.npmmirror.com/lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz} + name: lodash.merge + version: 4.6.2 + dev: true + + registry.npmmirror.com/lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz} + name: lodash + version: 4.17.21 + + registry.npmmirror.com/lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz} + name: lru-cache + version: 6.0.0 + engines: {node: '>=10'} + dependencies: + yallist: registry.npmmirror.com/yallist/4.0.0 + dev: true + + registry.npmmirror.com/magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz} + name: magic-string + version: 0.25.9 + dependencies: + sourcemap-codec: registry.npmmirror.com/sourcemap-codec/1.4.8 + + registry.npmmirror.com/magic-string/0.26.2: + resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.26.2.tgz} + name: magic-string + version: 0.26.2 + engines: {node: '>=12'} + dependencies: + sourcemap-codec: registry.npmmirror.com/sourcemap-codec/1.4.8 + dev: true + + registry.npmmirror.com/memoize-one/6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz} + name: memoize-one + version: 6.0.0 + dev: false + + registry.npmmirror.com/merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz} + name: merge2 + version: 1.4.1 + engines: {node: '>= 8'} + dev: true + + registry.npmmirror.com/micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz} + name: micromatch + version: 4.0.5 + engines: {node: '>=8.6'} + dependencies: + braces: registry.npmmirror.com/braces/3.0.2 + picomatch: registry.npmmirror.com/picomatch/2.3.1 + dev: true + + registry.npmmirror.com/mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz} + name: mime-db + version: 1.52.0 + engines: {node: '>= 0.6'} + dev: false + + registry.npmmirror.com/mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz} + name: mime-types + version: 2.1.35 + engines: {node: '>= 0.6'} + dependencies: + mime-db: registry.npmmirror.com/mime-db/1.52.0 + dev: false + + registry.npmmirror.com/minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz} + name: minimatch + version: 3.1.2 + dependencies: + brace-expansion: registry.npmmirror.com/brace-expansion/1.1.11 + dev: true + + registry.npmmirror.com/minimatch/5.1.0: + resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-5.1.0.tgz} + name: minimatch + version: 5.1.0 + engines: {node: '>=10'} + dependencies: + brace-expansion: registry.npmmirror.com/brace-expansion/2.0.1 + dev: true + + registry.npmmirror.com/mockjs/1.1.0: + resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/mockjs/-/mockjs-1.1.0.tgz} + name: mockjs + version: 1.1.0 + hasBin: true + dependencies: + commander: registry.npmmirror.com/commander/9.2.0 + dev: false + + registry.npmmirror.com/ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz} + name: ms + version: 2.0.0 + dev: true + + registry.npmmirror.com/ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz} + name: ms + version: 2.1.2 + dev: true + + registry.npmmirror.com/nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz} + name: nanoid + version: 3.3.4 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + registry.npmmirror.com/natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz} + name: natural-compare + version: 1.4.0 + dev: true + + registry.npmmirror.com/normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz} + name: normalize-path + version: 3.0.0 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/normalize-wheel-es/1.1.2: + resolution: {integrity: sha512-scX83plWJXYH1J4+BhAuIHadROzxX0UBF3+HuZNY2Ks8BciE7tSTQ+5JhTsvzjaO0/EJdm4JBGrfObKxFf3Png==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.1.2.tgz} + name: normalize-wheel-es + version: 1.1.2 + dev: false + + registry.npmmirror.com/nprogress/0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz} + name: nprogress + version: 0.2.0 + dev: false + + registry.npmmirror.com/nth-check/2.0.1: + resolution: {integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/nth-check/-/nth-check-2.0.1.tgz} + name: nth-check + version: 2.0.1 + dependencies: + boolbase: registry.npmmirror.com/boolbase/1.0.0 + dev: true + + registry.npmmirror.com/object-inspect/1.12.0: + resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.0.tgz} + name: object-inspect + version: 1.12.0 + dev: false + + registry.npmmirror.com/on-finished/2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/on-finished/-/on-finished-2.3.0.tgz} + name: on-finished + version: 2.3.0 + engines: {node: '>= 0.8'} + dependencies: + ee-first: registry.npmmirror.com/ee-first/1.1.1 + dev: true + + registry.npmmirror.com/once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/once/-/once-1.4.0.tgz} + name: once + version: 1.4.0 + dependencies: + wrappy: registry.npmmirror.com/wrappy/1.0.2 + dev: true + + registry.npmmirror.com/optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/optionator/-/optionator-0.9.1.tgz} + name: optionator + version: 0.9.1 + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: registry.npmmirror.com/deep-is/0.1.4 + fast-levenshtein: registry.npmmirror.com/fast-levenshtein/2.0.6 + levn: registry.npmmirror.com/levn/0.4.1 + prelude-ls: registry.npmmirror.com/prelude-ls/1.2.1 + type-check: registry.npmmirror.com/type-check/0.4.0 + word-wrap: registry.npmmirror.com/word-wrap/1.2.3 + dev: true + + registry.npmmirror.com/parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz} + name: parent-module + version: 1.0.1 + engines: {node: '>=6'} + dependencies: + callsites: registry.npmmirror.com/callsites/3.1.0 + dev: true + + registry.npmmirror.com/parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz} + name: parseurl + version: 1.3.3 + engines: {node: '>= 0.8'} + dev: true + + registry.npmmirror.com/path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz} + name: path-is-absolute + version: 1.0.1 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz} + name: path-key + version: 3.1.1 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz} + name: path-parse + version: 1.0.7 + dev: true + + registry.npmmirror.com/path-to-regexp/6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz} + name: path-to-regexp + version: 6.2.1 + dev: true + + registry.npmmirror.com/path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz} + name: path-type + version: 4.0.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz} + name: picocolors + version: 1.0.0 + + registry.npmmirror.com/picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz} + name: picomatch + version: 2.3.1 + engines: {node: '>=8.6'} + dev: true + + registry.npmmirror.com/pinia-plugin-persist/1.0.0_pinia@2.0.14+vue@3.2.33: + resolution: {integrity: sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz} + id: registry.npmmirror.com/pinia-plugin-persist/1.0.0 + name: pinia-plugin-persist + version: 1.0.0 + peerDependencies: + '@vue/composition-api': ^1.0.0 + pinia: ^2.0.0 + vue: ^2.0.0 || >=3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + pinia: registry.npmmirror.com/pinia/2.0.14_typescript@4.6.4+vue@3.2.33 + vue: registry.npmmirror.com/vue/3.2.33 + vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.33 + dev: false + + registry.npmmirror.com/pinia/2.0.14_typescript@4.6.4+vue@3.2.33: + resolution: {integrity: sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pinia/-/pinia-2.0.14.tgz} + id: registry.npmmirror.com/pinia/2.0.14 + name: pinia + version: 2.0.14 + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + dependencies: + '@vue/devtools-api': registry.npmmirror.com/@vue/devtools-api/6.1.4 + typescript: registry.npmmirror.com/typescript/4.6.4 + vue: registry.npmmirror.com/vue/3.2.33 + vue-demi: registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.33 + dev: false + + registry.npmmirror.com/postcss-selector-parser/6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz} + name: postcss-selector-parser + version: 6.0.10 + engines: {node: '>=4'} + dependencies: + cssesc: registry.npmmirror.com/cssesc/3.0.0 + util-deprecate: registry.npmmirror.com/util-deprecate/1.0.2 + dev: true + + registry.npmmirror.com/postcss/8.4.13: + resolution: {integrity: sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.4.13.tgz} + name: postcss + version: 8.4.13 + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: registry.npmmirror.com/nanoid/3.3.4 + picocolors: registry.npmmirror.com/picocolors/1.0.0 + source-map-js: registry.npmmirror.com/source-map-js/1.0.2 + + registry.npmmirror.com/prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz} + name: prelude-ls + version: 1.2.1 + engines: {node: '>= 0.8.0'} + dev: true + + registry.npmmirror.com/prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz} + name: prettier-linter-helpers + version: 1.0.0 + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: registry.npmmirror.com/fast-diff/1.2.0 + dev: true + + registry.npmmirror.com/prettier/2.6.2: + resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prettier/-/prettier-2.6.2.tgz} + name: prettier + version: 2.6.2 + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + registry.npmmirror.com/punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz} + name: punycode + version: 2.1.1 + engines: {node: '>=6'} + dev: true + + registry.npmmirror.com/qs/6.10.3: + resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/qs/-/qs-6.10.3.tgz} + name: qs + version: 6.10.3 + engines: {node: '>=0.6'} + dependencies: + side-channel: registry.npmmirror.com/side-channel/1.0.4 + dev: false + + registry.npmmirror.com/queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz} + name: queue-microtask + version: 1.2.3 + dev: true + + registry.npmmirror.com/readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz} + name: readdirp + version: 3.6.0 + engines: {node: '>=8.10.0'} + dependencies: + picomatch: registry.npmmirror.com/picomatch/2.3.1 + dev: true + + registry.npmmirror.com/regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz} + name: regexpp + version: 3.2.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz} + name: resolve-from + version: 4.0.0 + engines: {node: '>=4'} + dev: true + + registry.npmmirror.com/resolve/1.22.0: + resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/resolve/-/resolve-1.22.0.tgz} + name: resolve + version: 1.22.0 + hasBin: true + dependencies: + is-core-module: registry.npmmirror.com/is-core-module/2.9.0 + path-parse: registry.npmmirror.com/path-parse/1.0.7 + supports-preserve-symlinks-flag: registry.npmmirror.com/supports-preserve-symlinks-flag/1.0.0 + dev: true + + registry.npmmirror.com/reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz} + name: reusify + version: 1.0.4 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz} + name: rimraf + version: 3.0.2 + hasBin: true + dependencies: + glob: registry.npmmirror.com/glob/7.2.3 + dev: true + + registry.npmmirror.com/rollup/2.73.0: + resolution: {integrity: sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/rollup/-/rollup-2.73.0.tgz} + name: rollup + version: 2.73.0 + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: registry.npmmirror.com/fsevents/2.3.2 + dev: true + + registry.npmmirror.com/run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz} + name: run-parallel + version: 1.2.0 + dependencies: + queue-microtask: registry.npmmirror.com/queue-microtask/1.2.3 + dev: true + + registry.npmmirror.com/sass/1.51.0: + resolution: {integrity: sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sass/-/sass-1.51.0.tgz} + name: sass + version: 1.51.0 + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + chokidar: registry.npmmirror.com/chokidar/3.5.3 + immutable: registry.npmmirror.com/immutable/4.0.0 + source-map-js: registry.npmmirror.com/source-map-js/1.0.2 + dev: true + + registry.npmmirror.com/select/1.1.2: + resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/select/-/select-1.1.2.tgz} + name: select + version: 1.1.2 + dev: false + + registry.npmmirror.com/semver/7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz} + name: semver + version: 7.3.7 + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: registry.npmmirror.com/lru-cache/6.0.0 + dev: true + + registry.npmmirror.com/shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz} + name: shebang-command + version: 2.0.0 + engines: {node: '>=8'} + dependencies: + shebang-regex: registry.npmmirror.com/shebang-regex/3.0.0 + dev: true + + registry.npmmirror.com/shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz} + name: shebang-regex + version: 3.0.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/side-channel/-/side-channel-1.0.4.tgz} + name: side-channel + version: 1.0.4 + dependencies: + call-bind: registry.npmmirror.com/call-bind/1.0.2 + get-intrinsic: registry.npmmirror.com/get-intrinsic/1.1.1 + object-inspect: registry.npmmirror.com/object-inspect/1.12.0 + dev: false + + registry.npmmirror.com/slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz} + name: slash + version: 3.0.0 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/sortablejs/1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz} + name: sortablejs + version: 1.14.0 + dev: false + + registry.npmmirror.com/source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz} + name: source-map-js + version: 1.0.2 + engines: {node: '>=0.10.0'} + + registry.npmmirror.com/source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz} + name: source-map + version: 0.6.1 + engines: {node: '>=0.10.0'} + + registry.npmmirror.com/sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz} + name: sourcemap-codec + version: 1.4.8 + + registry.npmmirror.com/statuses/1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz} + name: statuses + version: 1.5.0 + engines: {node: '>= 0.6'} + dev: true + + registry.npmmirror.com/strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz} + name: strip-ansi + version: 6.0.1 + engines: {node: '>=8'} + dependencies: + ansi-regex: registry.npmmirror.com/ansi-regex/5.0.1 + dev: true + + registry.npmmirror.com/strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz} + name: strip-json-comments + version: 3.1.1 + engines: {node: '>=8'} + dev: true + + registry.npmmirror.com/supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz} + name: supports-color + version: 7.2.0 + engines: {node: '>=8'} + dependencies: + has-flag: registry.npmmirror.com/has-flag/4.0.0 + dev: true + + registry.npmmirror.com/supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz} + name: supports-preserve-symlinks-flag + version: 1.0.0 + engines: {node: '>= 0.4'} + dev: true + + registry.npmmirror.com/text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz} + name: text-table + version: 0.2.0 + dev: true + + registry.npmmirror.com/tiny-emitter/2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz} + name: tiny-emitter + version: 2.1.0 + dev: false + + registry.npmmirror.com/to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz} + name: to-regex-range + version: 5.0.1 + engines: {node: '>=8.0'} + dependencies: + is-number: registry.npmmirror.com/is-number/7.0.0 + dev: true + + registry.npmmirror.com/tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz} + name: tslib + version: 1.14.1 + dev: true + + registry.npmmirror.com/tsutils/3.21.0_typescript@4.6.4: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz} + id: registry.npmmirror.com/tsutils/3.21.0 + name: tsutils + version: 3.21.0 + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: registry.npmmirror.com/tslib/1.14.1 + typescript: registry.npmmirror.com/typescript/4.6.4 + dev: true + + registry.npmmirror.com/type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz} + name: type-check + version: 0.4.0 + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: registry.npmmirror.com/prelude-ls/1.2.1 + dev: true + + registry.npmmirror.com/type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz} + name: type-fest + version: 0.20.2 + engines: {node: '>=10'} + dev: true + + registry.npmmirror.com/typescript/4.6.4: + resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/typescript/-/typescript-4.6.4.tgz} + name: typescript + version: 4.6.4 + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + registry.npmmirror.com/unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz} + name: unpipe + version: 1.0.0 + engines: {node: '>= 0.8'} + dev: true + + registry.npmmirror.com/unplugin-auto-import/0.7.1_@vueuse+core@8.5.0+vite@2.9.9: + resolution: {integrity: sha512-9865OV9eP99PNxHR2mtTDExeN01m4M9boT5U2BtIwsU1wDRsaFIYWLwcCBEjvXzXfTTC2NNMskhHGVAMfL2WgA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.7.1.tgz} + id: registry.npmmirror.com/unplugin-auto-import/0.7.1 + name: unplugin-auto-import + version: 0.7.1 + engines: {node: '>=14'} + peerDependencies: + '@vueuse/core': '*' + peerDependenciesMeta: + '@vueuse/core': + optional: true + dependencies: + '@antfu/utils': registry.npmmirror.com/@antfu/utils/0.5.2 + '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/4.2.1 + '@vueuse/core': registry.npmmirror.com/@vueuse/core/8.5.0_vue@3.2.33 + local-pkg: registry.npmmirror.com/local-pkg/0.4.1 + magic-string: registry.npmmirror.com/magic-string/0.26.2 + resolve: registry.npmmirror.com/resolve/1.22.0 + unplugin: registry.npmmirror.com/unplugin/0.6.3_vite@2.9.9 + transitivePeerDependencies: + - esbuild + - rollup + - vite + - webpack + dev: true + + registry.npmmirror.com/unplugin-vue-components/0.19.5_vite@2.9.9+vue@3.2.33: + resolution: {integrity: sha512-cIC+PdQEXmG+B1gmZGk4hws2xP+00C6pg3FD6ixEgRyW+WF+QXQW/60pc+hUhtDYs1PFE+23K3NY7yvYTnDDTA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.19.5.tgz} + id: registry.npmmirror.com/unplugin-vue-components/0.19.5 + name: unplugin-vue-components + version: 0.19.5 + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@babel/traverse': ^7.15.4 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@babel/traverse': + optional: true + dependencies: + '@antfu/utils': registry.npmmirror.com/@antfu/utils/0.5.2 + '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/4.2.1 + chokidar: registry.npmmirror.com/chokidar/3.5.3 + debug: registry.npmmirror.com/debug/4.3.4 + fast-glob: registry.npmmirror.com/fast-glob/3.2.11 + local-pkg: registry.npmmirror.com/local-pkg/0.4.1 + magic-string: registry.npmmirror.com/magic-string/0.26.2 + minimatch: registry.npmmirror.com/minimatch/5.1.0 + resolve: registry.npmmirror.com/resolve/1.22.0 + unplugin: registry.npmmirror.com/unplugin/0.6.3_vite@2.9.9 + vue: registry.npmmirror.com/vue/3.2.33 + transitivePeerDependencies: + - esbuild + - rollup + - supports-color + - vite + - webpack + dev: true + + registry.npmmirror.com/unplugin/0.6.3_vite@2.9.9: + resolution: {integrity: sha512-CoW88FQfCW/yabVc4bLrjikN9HC8dEvMU4O7B6K2jsYMPK0l6iAnd9dpJwqGcmXJKRCU9vwSsy653qg+RK0G6A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/unplugin/-/unplugin-0.6.3.tgz} + id: registry.npmmirror.com/unplugin/0.6.3 + name: unplugin + version: 0.6.3 + peerDependencies: + esbuild: '>=0.13' + rollup: ^2.50.0 + vite: ^2.3.0 + webpack: 4 || 5 + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + dependencies: + chokidar: registry.npmmirror.com/chokidar/3.5.3 + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + webpack-sources: registry.npmmirror.com/webpack-sources/3.2.3 + webpack-virtual-modules: registry.npmmirror.com/webpack-virtual-modules/0.4.3 + dev: true + + registry.npmmirror.com/uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz} + name: uri-js + version: 4.4.1 + dependencies: + punycode: registry.npmmirror.com/punycode/2.1.1 + dev: true + + registry.npmmirror.com/util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz} + name: util-deprecate + version: 1.0.2 + dev: true + + registry.npmmirror.com/utils-merge/1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz} + name: utils-merge + version: 1.0.1 + engines: {node: '>= 0.4.0'} + dev: true + + registry.npmmirror.com/v8-compile-cache/2.3.0: + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz} + name: v8-compile-cache + version: 2.3.0 + dev: true + + registry.npmmirror.com/vite-plugin-eslint/1.6.0_eslint@8.15.0+vite@2.9.9: + resolution: {integrity: sha512-knoNx2jksnqSO645OGgA++xQigTfA+CgvoeJV6554y5DOc+mXbwUm4oR6ijycsD5IrPMDEnwj3NqnFTgjAAJCQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite-plugin-eslint/-/vite-plugin-eslint-1.6.0.tgz} + id: registry.npmmirror.com/vite-plugin-eslint/1.6.0 + name: vite-plugin-eslint + version: 1.6.0 + peerDependencies: + eslint: '>=7' + vite: ^2.0.0 + dependencies: + '@rollup/pluginutils': registry.npmmirror.com/@rollup/pluginutils/4.2.1 + eslint: registry.npmmirror.com/eslint/8.15.0 + rollup: registry.npmmirror.com/rollup/2.73.0 + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + dev: true + + registry.npmmirror.com/vite-plugin-mock/2.9.6_mockjs@1.1.0+vite@2.9.9: + resolution: {integrity: sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite-plugin-mock/-/vite-plugin-mock-2.9.6.tgz} + id: registry.npmmirror.com/vite-plugin-mock/2.9.6 + name: vite-plugin-mock + version: 2.9.6 + engines: {node: '>=12.0.0'} + peerDependencies: + mockjs: '>=1.1.0' + vite: '>=2.0.0' + dependencies: + '@rollup/plugin-node-resolve': registry.npmmirror.com/@rollup/plugin-node-resolve/13.3.0 + '@types/mockjs': registry.npmmirror.com/@types/mockjs/1.0.6 + chalk: registry.npmmirror.com/chalk/4.1.2 + chokidar: registry.npmmirror.com/chokidar/3.5.3 + connect: registry.npmmirror.com/connect/3.7.0 + debug: registry.npmmirror.com/debug/4.3.4 + esbuild: registry.npmmirror.com/esbuild/0.11.3 + fast-glob: registry.npmmirror.com/fast-glob/3.2.11 + mockjs: registry.npmmirror.com/mockjs/1.1.0 + path-to-regexp: registry.npmmirror.com/path-to-regexp/6.2.1 + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + transitivePeerDependencies: + - rollup + - supports-color + dev: true + + registry.npmmirror.com/vite-plugin-vue-setup-extend/0.4.0_vite@2.9.9: + resolution: {integrity: sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite-plugin-vue-setup-extend/-/vite-plugin-vue-setup-extend-0.4.0.tgz} + id: registry.npmmirror.com/vite-plugin-vue-setup-extend/0.4.0 + name: vite-plugin-vue-setup-extend + version: 0.4.0 + peerDependencies: + vite: '>=2.0.0' + dependencies: + '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.33 + magic-string: registry.npmmirror.com/magic-string/0.25.9 + vite: registry.npmmirror.com/vite/2.9.9_sass@1.51.0 + dev: true + + registry.npmmirror.com/vite/2.9.9_sass@1.51.0: + resolution: {integrity: sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vite/-/vite-2.9.9.tgz} + id: registry.npmmirror.com/vite/2.9.9 + name: vite + version: 2.9.9 + engines: {node: '>=12.2.0'} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + dependencies: + esbuild: registry.npmmirror.com/esbuild/0.14.39 + postcss: registry.npmmirror.com/postcss/8.4.13 + resolve: registry.npmmirror.com/resolve/1.22.0 + rollup: registry.npmmirror.com/rollup/2.73.0 + sass: registry.npmmirror.com/sass/1.51.0 + optionalDependencies: + fsevents: registry.npmmirror.com/fsevents/2.3.2 + dev: true + + registry.npmmirror.com/vue-clipboard3/2.0.0: + resolution: {integrity: sha512-Q9S7dzWGax7LN5iiSPcu/K1GGm2gcBBlYwmMsUc5/16N6w90cbKow3FnPmPs95sungns4yvd9/+JhbAznECS2A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-clipboard3/-/vue-clipboard3-2.0.0.tgz} + name: vue-clipboard3 + version: 2.0.0 + dependencies: + clipboard: registry.npmmirror.com/clipboard/2.0.11 + dev: false + + registry.npmmirror.com/vue-demi/0.11.4_vue@3.2.33: + resolution: {integrity: sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-demi/-/vue-demi-0.11.4.tgz} + id: registry.npmmirror.com/vue-demi/0.11.4 + name: vue-demi + version: 0.11.4 + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/vue-demi/0.12.5_vue@3.2.33: + resolution: {integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz} + id: registry.npmmirror.com/vue-demi/0.12.5 + name: vue-demi + version: 0.12.5 + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/vue-eslint-parser/8.3.0_eslint@8.15.0: + resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz} + id: registry.npmmirror.com/vue-eslint-parser/8.3.0 + name: vue-eslint-parser + version: 8.3.0 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: registry.npmmirror.com/debug/4.3.4 + eslint: registry.npmmirror.com/eslint/8.15.0 + eslint-scope: registry.npmmirror.com/eslint-scope/7.1.1 + eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/3.3.0 + espree: registry.npmmirror.com/espree/9.3.2 + esquery: registry.npmmirror.com/esquery/1.4.0 + lodash: registry.npmmirror.com/lodash/4.17.21 + semver: registry.npmmirror.com/semver/7.3.7 + transitivePeerDependencies: + - supports-color + dev: true + + registry.npmmirror.com/vue-fuse/4.1.1_fuse.js@6.6.2+vue@3.2.33: + resolution: {integrity: sha512-bhuTiniVK3HmTcxvksrzPgDWPFTEDYiWbJa01E7yT2lRPtE4BVb5cXUBUmqtb5rCkDOsLgMh1n9mRlHQImYNkA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-fuse/-/vue-fuse-4.1.1.tgz} + id: registry.npmmirror.com/vue-fuse/4.1.1 + name: vue-fuse + version: 4.1.1 + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + fuse.js: ^6.4.6 + vue: ^3.2.20 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + fuse.js: registry.npmmirror.com/fuse.js/6.6.2 + vue: registry.npmmirror.com/vue/3.2.33 + vue-demi: registry.npmmirror.com/vue-demi/0.11.4_vue@3.2.33 + dev: false + + registry.npmmirror.com/vue-router/4.0.15_vue@3.2.33: + resolution: {integrity: sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-router/-/vue-router-4.0.15.tgz} + id: registry.npmmirror.com/vue-router/4.0.15 + name: vue-router + version: 4.0.15 + peerDependencies: + vue: ^3.2.0 + dependencies: + '@vue/devtools-api': registry.npmmirror.com/@vue/devtools-api/6.1.4 + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/vue-tsc/0.34.15_typescript@4.6.4: + resolution: {integrity: sha512-xRNaOpg/UCHnRcz9vOdbIjB7uCQ0mifHpqNaejAho7em4WLOzNdJx4R9HMJrqWek44keg7AblIiwM+86QfXx9g==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-tsc/-/vue-tsc-0.34.15.tgz} + id: registry.npmmirror.com/vue-tsc/0.34.15 + name: vue-tsc + version: 0.34.15 + hasBin: true + peerDependencies: + typescript: '*' + dependencies: + '@volar/vue-typescript': registry.npmmirror.com/@volar/vue-typescript/0.34.15 + typescript: registry.npmmirror.com/typescript/4.6.4 + dev: true + + registry.npmmirror.com/vue/3.2.33: + resolution: {integrity: sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue/-/vue-3.2.33.tgz} + name: vue + version: 3.2.33 + dependencies: + '@vue/compiler-dom': registry.npmmirror.com/@vue/compiler-dom/3.2.33 + '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.33 + '@vue/runtime-dom': registry.npmmirror.com/@vue/runtime-dom/3.2.33 + '@vue/server-renderer': registry.npmmirror.com/@vue/server-renderer/3.2.33_vue@3.2.33 + '@vue/shared': registry.npmmirror.com/@vue/shared/3.2.33 + dev: false + + registry.npmmirror.com/vuedraggable/4.1.0_vue@3.2.33: + resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz} + id: registry.npmmirror.com/vuedraggable/4.1.0 + name: vuedraggable + version: 4.1.0 + peerDependencies: + vue: ^3.0.1 + dependencies: + sortablejs: registry.npmmirror.com/sortablejs/1.14.0 + vue: registry.npmmirror.com/vue/3.2.33 + dev: false + + registry.npmmirror.com/webpack-sources/3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz} + name: webpack-sources + version: 3.2.3 + engines: {node: '>=10.13.0'} + dev: true + + registry.npmmirror.com/webpack-virtual-modules/0.4.3: + resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz} + name: webpack-virtual-modules + version: 0.4.3 + dev: true + + registry.npmmirror.com/which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/which/-/which-2.0.2.tgz} + name: which + version: 2.0.2 + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: registry.npmmirror.com/isexe/2.0.0 + dev: true + + registry.npmmirror.com/word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz} + name: word-wrap + version: 1.2.3 + engines: {node: '>=0.10.0'} + dev: true + + registry.npmmirror.com/wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz} + name: wrappy + version: 1.0.2 + dev: true + + registry.npmmirror.com/yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz} + name: yallist + version: 4.0.0 + dev: true diff --git a/diboot-admin-ui/public/favicon.ico b/diboot-admin-ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 Binary files /dev/null and b/diboot-admin-ui/public/favicon.ico differ diff --git a/diboot-admin-ui/src/App.vue b/diboot-admin-ui/src/App.vue new file mode 100644 index 0000000000000000000000000000000000000000..a92e5364db7ab7fe369a7d7cd63518b66654a273 --- /dev/null +++ b/diboot-admin-ui/src/App.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/diboot-admin-ui/src/assets/icon/TextFontSize.svg b/diboot-admin-ui/src/assets/icon/TextFontSize.svg new file mode 100644 index 0000000000000000000000000000000000000000..e09eb4a0066aaa780a1c7b28cc613823442f069e --- /dev/null +++ b/diboot-admin-ui/src/assets/icon/TextFontSize.svg @@ -0,0 +1 @@ + diff --git a/diboot-admin-ui/src/assets/logo.png b/diboot-admin-ui/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..186302198e8d93fdc3145a79d148f5e7d0aacb3a Binary files /dev/null and b/diboot-admin-ui/src/assets/logo.png differ diff --git a/diboot-admin-ui/src/components/icon/index.vue b/diboot-admin-ui/src/components/icon/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..aae5fd89faf78555fa4e3aa4c47afe4dc21605ad --- /dev/null +++ b/diboot-admin-ui/src/components/icon/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/diboot-admin-ui/src/components/icon/select.vue b/diboot-admin-ui/src/components/icon/select.vue new file mode 100644 index 0000000000000000000000000000000000000000..84ec74093a920f8827c878531aa02e3d5d024352 --- /dev/null +++ b/diboot-admin-ui/src/components/icon/select.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/diboot-admin-ui/src/config/iconLibrary.ts b/diboot-admin-ui/src/config/iconLibrary.ts new file mode 100644 index 0000000000000000000000000000000000000000..542e6c066df78c23a5a9b08656098c8fe11b71b7 --- /dev/null +++ b/diboot-admin-ui/src/config/iconLibrary.ts @@ -0,0 +1,29 @@ +import * as Element from '@element-plus/icons-vue' + +/** + * 加载 @/assets/icon 目录下所有图标(svg|vue) + */ +const iconSvgFiles = import.meta.glob('@/assets/icon/**/*.svg', { as: 'raw' }) +const iconVueFiles = import.meta.globEager('@/assets/icon/**/*.vue') + +/** + * 构建本地图标 + */ +const Local = Object.keys(iconSvgFiles).reduce((all: Record, path: string) => { + const name = path.replace(/.*icon\/(.*)\.svg/, '$1') + all[name] = defineComponent({ + name, + render: () => h('i', { innerHTML: iconSvgFiles[path], style: { display: 'inline-flex' } }) + }) + return all +}, {}) +Object.keys(iconVueFiles).reduce((all: Record, path: string) => { + const name = path.replace(/.*icon\/(.*)\.vue/, '$1') + all[name] = { name, ...iconVueFiles[path].default } + return all +}, Local) + +/** + * 导出所有图标资源 + */ +export default { Element, Local } as Record> diff --git a/diboot-admin-ui/src/directives/index.ts b/diboot-admin-ui/src/directives/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b0ae82f7f4ee163a36b9a1f17434ebc57e17b23 --- /dev/null +++ b/diboot-admin-ui/src/directives/index.ts @@ -0,0 +1,7 @@ +import { App } from 'vue' +import { hasRole, hasPermission } from './permission' + +export default (app: App) => { + app.directive('hasRole', hasRole) + app.directive('hasPermission', hasPermission) +} diff --git a/diboot-admin-ui/src/directives/permission/index.ts b/diboot-admin-ui/src/directives/permission/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..40a839ecc4c1140891de2f5237b512e6ae61eef3 --- /dev/null +++ b/diboot-admin-ui/src/directives/permission/index.ts @@ -0,0 +1,24 @@ +import type { Directive } from 'vue' +import { checkPermission, checkRole } from '@/utils/permission' + +/** + * 检查权限 + * + * @modifiers not 取反 + * @modifiers all 全部 + */ +export const hasPermission: Directive> = (el, binding) => { + const { not, all } = binding.modifiers + if (!checkPermission(binding.value, not, all)) el.remove() +} + +/** + * 检查角色 + * + * @modifiers not 取反 + * @modifiers all 全部 + */ +export const hasRole: Directive> = (el, binding) => { + const { not, all } = binding.modifiers + if (!checkRole(binding.value, not, all)) el.remove() +} diff --git a/diboot-admin-ui/src/hooks/list.ts b/diboot-admin-ui/src/hooks/list.ts new file mode 100644 index 0000000000000000000000000000000000000000..29c57d07044883dcf4a5e43097de3e61ac332bf2 --- /dev/null +++ b/diboot-admin-ui/src/hooks/list.ts @@ -0,0 +1,255 @@ +import { ElNotification } from 'element-plus' +import { Ref } from 'vue' + +import { reactive } from 'vue' + +type HookOptions = { + pageLoader?: BaseListPageLoader + options?: ListOptions + baseApi?: string + autoLoad?: boolean +} + +export interface Pagination { + pageSize: number + current: number + total: number + showSizeChanger: boolean + pageSizeOptions: string[] +} + +export interface ListOptions { + baseUrl?: string + // 主键字段名 + primaryKey?: string + // 请求接口基础路径 + baseApi?: string + // 列表数据接口 + listApi?: string + // 删除接口 + deleteApiPrefix?: string + // 导出接口 + exportApi?: string + // 自定义参数(不被查询表单重置和改变的参数) + customQueryParam?: Record + // 与查询条件绑定的参数(会被查询表单重置和改变的参数) + queryParam?: Record + // 日期区间选择配置 + dateRangeQuery?: Record + // 高级搜索 展开/关闭 + advanced?: boolean + // 列表数据 + list?: T[] + // 是否将children转化为_children + childrenConvert?: boolean + // 是否从mixin中自动获取初始的列表数据 + getListFromMixin?: boolean + // 标记加载状态 + loading?: boolean + // 标记导出 + exportLoadingData?: boolean + // 是否允许撤回删除 + allowCanceledDelete?: boolean + // 是否重新加载 + reload?: boolean + // 当前激活value + currentPrimaryValue?: string | number + // 分页数据 + pagination?: Pagination +} + +export class BaseListPageLoader { + public options: ListOptions = reactive({ + // 主键字段名 + primaryKey: 'id', + // 请求接口基础路径 + baseApi: '/', + // 列表数据接口 + listApi: '', + // 删除接口 + deleteApiPrefix: '', + // 导出接口 + exportApi: '', + // 自定义参数(不被查询表单重置和改变的参数) + customQueryParam: {}, + // 与查询条件绑定的参数(会被查询表单重置和改变的参数) + queryParam: {}, + // 日期区间选择配置 + dateRangeQuery: {}, + // 高级搜索 展开/关闭 + advanced: false, + // 列表数据 + list: [], + // 是否将children转化为_children + childrenConvert: true, + // 是否从mixin中自动获取初始的列表数据 + getListFromMixin: true, + // 标记加载状态 + loading: false, + // 标记导出 + exportLoadingData: false, + // 是否允许撤回删除 + allowCanceledDelete: false, + // 是否重新加载 + reload: false, + // 当前激活value + currentPrimaryValue: '', + // 分页数据 + pagination: { + pageSize: 10, + current: 1, + total: 0, + showSizeChanger: true, + pageSizeOptions: ['10', '20', '30', '50', '100'] + } + }) + /** + * 搜索,查询第一页 + */ + public onSearch() { + if (this.options?.pagination) this.options.pagination.current = 1 + this.getList() + } + + /** + * 防抖搜索(防止重复触发) + */ + public onDebounceSearch = _.debounce(() => { + this.onSearch() + }, 300) + + public toggleLoading() { + if (this.options) this.options.loading = true + } + /** + * get请求获取列表 + * @returns {Promise} + */ + public async getList() { + try { + this.toggleLoading() + const res = await api.get( + this.options?.listApi ? this.options?.listApi : `${this.options?.baseApi}/list`, + this.buildQueryParam() + ) + if (res.code === 0) { + if (this.options) this.options.list = this.listFilter(res.data) + if (res.page) { + if (this.options?.pagination) { + this.options.pagination.pageSize = res.page.pageSize + this.options.pagination.current = res.page.pageIndex + this.options.pagination.total = res.page.totalCount ? Number(res.page.totalCount) : 0 + } + } + } else { + ElNotification.error({ + title: '获取列表数据失败', + message: res.msg + }) + } + } catch (e) { + ElNotification.error({ + title: '获取列表数据失败', + message: e as string + }) + } finally { + this.toggleLoading() + console.log(this.options?.loading) + } + } + /** + * 列表过滤器 + * @param list + * @returns {*} + */ + public listFilter(list: T[] | undefined) { + if (!list || list.length === 0) { + return [] + } + return list + } + /** + * 构建查询参数 + */ + public buildQueryParam() { + this.dateRange2queryParam() + // 进行前置处理,获取查询条件初始值 + let tempQueryParam = this.beforeBuildQueryParam() + // 合并自定义查询参数 + _.merge(tempQueryParam, this.options?.customQueryParam) + // 合并搜索参数 + _.merge(tempQueryParam, this.options?.queryParam) + // 进行后置处理,可用于改造查询条件(用于列表页扩展等场景) + tempQueryParam = this.afterBuildQueryParam(tempQueryParam) + return tempQueryParam + } + /** + * 查询条件前置处理 + */ + beforeBuildQueryParam(): object { + return {} + } + /** + * 查询条件后置处理 + */ + afterBuildQueryParam(queryParam: object): object { + return {} + } + clearQueryParam(): void { + this.options.queryParam = {} + } + public onReset(): void { + this.clearQueryParam() + this.getList() + } + /** + * 构建区间查询参数 + */ + dateRange2queryParam() { + const dateRangeQuery = this.options?.dateRangeQuery || {} + for (const [key, value] of Object.entries(dateRangeQuery)) { + if (this.options != null && this.options.queryParam != null) { + this.options.queryParam[`${key}Begin`] = value[0] + this.options.queryParam[`${key}End`] = value[1] + } + } + } +} + +type Results = { + pageLoader: BaseListPageLoader + queryParam: Ref<{ [p: string]: any } | undefined> | undefined + dataList: Ref | undefined + loading: Ref | undefined + pagination: Ref | undefined +} + +export default function (options: HookOptions) { + let { pageLoader, autoLoad } = options + const { baseApi, options: loaderOptions } = options + pageLoader = pageLoader || new BaseListPageLoader() + // 赋值所有选项参数 + if (loaderOptions != null) { + for (const [key, value] of Object.entries(loaderOptions)) { + pageLoader.options[key as keyof ListOptions] = value + } + } + // 赋值自定义参数 + if (baseApi != null) { + pageLoader.options.baseApi = baseApi + } + autoLoad = autoLoad == null ? true : autoLoad + onMounted(() => { + if (autoLoad) { + pageLoader?.onSearch() + } + }) + const { list: dataList, queryParam, loading, pagination } = toRefs(pageLoader.options) + return { + pageLoader, + queryParam, + dataList, + loading, + pagination + } +} diff --git a/diboot-admin-ui/src/hooks/list_default.ts b/diboot-admin-ui/src/hooks/list_default.ts new file mode 100644 index 0000000000000000000000000000000000000000..618ec702eddf6582b58e39db369848c79d1066ad --- /dev/null +++ b/diboot-admin-ui/src/hooks/list_default.ts @@ -0,0 +1,218 @@ +import { ElButton } from 'element-plus' + +export interface ListOption { + // 请求接口基础路径 + baseApi: string + // 列表数据接口 + listApi?: string + // 自定义参数(不被查询表单重置和改变的参数) + initQueryParam?: QueryParam + // 重建查询条件 + rebuildQuery?: (query: QueryParam) => QueryParam +} + +export type QueryParam = Partial & Record + +export interface Pagination { + pageSize: number + current: number + total: number +} + +/** + * 列表操作 + * + * @param option + */ +export default (option: ListOption & DeleteOption) => { + // 标记加载状态 + const loading = ref(false) + + const dataList: Array = reactive([]) + + const pagination: Partial = reactive({}) + + const queryParam: QueryParam = reactive(_.cloneDeep(option.initQueryParam ?? {})) + + const dateRangeQuery: Record = reactive({}) + + /** + * 构建查询参数 + */ + const buildQueryParam = () => { + // 合并自定义查询 + const tempQueryParam: QueryParam = _.cloneDeep(queryParam) + // 合并分页参数 + tempQueryParam.pageIndex = pagination.current + tempQueryParam.pageSize = pagination.pageSize + // 合并日期范围查询参数 + for (const [key, value] of Object.entries(dateRangeQuery)) { + tempQueryParam[`${key}Begin`] = value[0] + tempQueryParam[`${key}End`] = value[1] + } + // TODO 日期格式化 + + // 改造查询条件(用于列表页扩展) + return option.rebuildQuery ? option.rebuildQuery(tempQueryParam) : tempQueryParam + } + + /** + * 获取数据列表 + */ + const getList = () => { + loading.value = true + api + .get>(option.listApi ? option.listApi : `${option.baseApi}/list`, buildQueryParam()) + .then(res => { + dataList.splice(0, dataList.length) + if (res.data) dataList.push(...(res.data ?? [])) + pagination.pageSize = res.page?.pageSize + pagination.current = res.page?.pageIndex + pagination.total = res.page?.totalCount ? Number(res.page.totalCount) : 0 + }) + .catch(err => { + ElNotification.error({ + title: '获取列表数据失败', + message: err.msg || err.message + }) + }) + .finally(() => (loading.value = false)) + } + + /** + * 搜索,查询第一页 + */ + const onSearch = _.debounce(() => { + pagination.current = 1 + getList() + }, 300) + + /** + * 重置筛选条件 + */ + const resetFilter = () => { + Object.keys(dateRangeQuery).forEach(key => delete dateRangeQuery[key]) + Object.keys(queryParam).forEach(key => delete queryParam[key]) + Object.assign(queryParam, _.cloneDeep(option.initQueryParam ?? {})) + onSearch() + } + + // 删除 + const del = useDelete({ deleteCallback: getList, ...option }) + + return { + queryParam, + dateRangeQuery, + onSearch, + resetFilter, + getList, + loading, + dataList, + pagination, + ...del + } +} + +// 删除数据 +export interface DeleteOption { + // 请求接口基础路径 + baseApi: string + // 删除数据接口前缀 + deleteApiPrefix?: string + // 是否允许撤回删除 (默认允许) + allowCanceledDelete?: boolean + // 删除回调 + deleteCallback?: () => void +} + +/** + * 删除数据 + * + * @param option + */ +export const useDelete = (option: DeleteOption) => { + /** + * 删除数据 + * + * @param id + */ + const remove = (id: string) => { + ElMessageBox.confirm('确认删除该数据吗?', '删除', { type: 'warning' }) + .then(() => { + api + .delete(`${option.baseApi}${option.deleteApiPrefix ?? ''}/${id}`) + .then(() => removeSuccessHandler([id])) + .catch(err => { + ElMessage.error(err.msg || err.message || '删除失败') + }) + }) + .catch(() => null) + } + + /** + * 批量删除数据 + * + * @param ids + */ + const batchRemove = (ids: Array) => { + if (!(ids && ids.length)) { + ElMessage.warning('未选择数据') + return + } + + ElMessageBox.confirm('确认删除已选数据吗?', '批量删除', { type: 'warning' }) + .then(() => { + api + .post(`${option.baseApi}/batchDelete`, ids) + .then(() => removeSuccessHandler(ids)) + .catch(err => { + ElMessage.error(err.msg || err.message || '删除失败') + }) + }) + .catch(() => null) + } + + /** + * 删除成功处理 + * + * @param ids + */ + const removeSuccessHandler = (ids: Array) => { + if (option.deleteCallback) option.deleteCallback() + + if (option.allowCanceledDelete === false) { + ElMessage.success('数据删除成功') + return + } + + // 支持撤回的删除成功提示 + const message = ElMessage.success({ + type: 'success', + message: h('span', null, [ + h('span', { style: { color: 'var(--el-color-success)' } }, '数据删除成功'), + h( + ElButton, + { + bg: true, + text: true, + type: 'primary', + style: { height: '25px', position: 'absolute', right: '13px' }, + onClick: () => { + message.close() + api + .patch(`${option.baseApi}/cancelDeleted`, ids) + .then(() => { + ElMessage.success('撤回成功') + if (option.deleteCallback) option.deleteCallback() + }) + .catch(() => ElMessage.error('撤回失败')) + } + }, + { default: () => '撤回' } + ) + ]) + }) + } + + return { remove, batchRemove } +} diff --git a/diboot-admin-ui/src/layout/footer/index.vue b/diboot-admin-ui/src/layout/footer/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..e28280d56d0cadd82a74fcd9fc8b8ac08cdfaf65 --- /dev/null +++ b/diboot-admin-ui/src/layout/footer/index.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/header/index.vue b/diboot-admin-ui/src/layout/header/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..385a46bc292e7ddef6413aa0a6c02df8b654d2fa --- /dev/null +++ b/diboot-admin-ui/src/layout/header/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/header/menuSearch.vue b/diboot-admin-ui/src/layout/header/menuSearch.vue new file mode 100644 index 0000000000000000000000000000000000000000..754b41b42dc58ab85f9820b8f8bcd0adccea55db --- /dev/null +++ b/diboot-admin-ui/src/layout/header/menuSearch.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/index.vue b/diboot-admin-ui/src/layout/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..95b96acf35156f856eda75bde3978169be718a13 --- /dev/null +++ b/diboot-admin-ui/src/layout/index.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/main/index.vue b/diboot-admin-ui/src/layout/main/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..cdfc5f4eaa60bfad87d77036b7b93d92070649f4 --- /dev/null +++ b/diboot-admin-ui/src/layout/main/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/memu/index.vue b/diboot-admin-ui/src/layout/memu/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..3fa33e4054be190fde406ec3a6559710739a7421 --- /dev/null +++ b/diboot-admin-ui/src/layout/memu/index.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/memu/subMenu.vue b/diboot-admin-ui/src/layout/memu/subMenu.vue new file mode 100644 index 0000000000000000000000000000000000000000..c50718734577c63fa29fa76c531c7cc9ff81eeab --- /dev/null +++ b/diboot-admin-ui/src/layout/memu/subMenu.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/setting/index.vue b/diboot-admin-ui/src/layout/setting/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..9e0b679312fbf58e9588fde7163f00a89cf37fd3 --- /dev/null +++ b/diboot-admin-ui/src/layout/setting/index.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/diboot-admin-ui/src/layout/tabs/index.vue b/diboot-admin-ui/src/layout/tabs/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..d89cad4c29d0a2514a103fd40923da8210340195 --- /dev/null +++ b/diboot-admin-ui/src/layout/tabs/index.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/diboot-admin-ui/src/main.ts b/diboot-admin-ui/src/main.ts new file mode 100644 index 0000000000000000000000000000000000000000..e328b38a06ce48c51c851be62d9e844794e0fd10 --- /dev/null +++ b/diboot-admin-ui/src/main.ts @@ -0,0 +1,18 @@ +import App from './App.vue' +import router from './router' +import pinia from './store' +import directives from './directives' + +const app = createApp(App) +app.use(router) +app.use(pinia) +app.mount('#app') + +app.use(directives) + +import '@/styles/index.scss' +import 'nprogress/nprogress.css' + +import 'element-plus/es/components/message/style/css' +import 'element-plus/es/components/message-box/style/css' +import 'element-plus/es/components/notification/style/css' diff --git a/diboot-admin-ui/src/router/index.ts b/diboot-admin-ui/src/router/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..05e3240073199cd9998e341268cd64eac6b54dd8 --- /dev/null +++ b/diboot-admin-ui/src/router/index.ts @@ -0,0 +1,91 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw, RouterView } from 'vue-router' +import { createRouterGuard } from '@/router/router-guards' + +/** + * constantRoutes + * a base page that does not have permission requirements + * all roles can be accessed + */ +export const constantRoutes: RouteRecordRaw[] = [ + { + path: '/redirect/:path(.*)*', + name: 'Redirect', + meta: { hidden: true, ignoreAuth: true }, + redirect: to => { + const path = to.params.path + return { path: `/${Array.isArray(path) ? path.join('/') : path}`, query: to.query, replace: true } + } + }, + { + path: '/exception', + name: 'Exception', + redirect: '/exception/404', + component: RouterView, + meta: { title: 'Exception', hidden: true, ignoreAuth: true }, + children: [ + { + path: '404', + name: '404', + component: () => import('@/views/exception/404.vue'), + meta: { title: '404', ignoreAuth: true } + }, + { + path: '500', + name: '500', + component: () => import('@/views/exception/500.vue'), + meta: { title: '500', ignoreAuth: true } + } + ] + }, + { + path: '/:path(.*)*', + name: 'ErrorPage', + meta: { hidden: true, ignoreAuth: true }, + redirect: to => { + return { name: '404', query: { path: to.path }, replace: true } + } + }, + { + path: '/login', + name: 'Login', + component: () => import('@/views/login/index.vue'), + meta: { hidden: true, ignoreAuth: true } + }, + { + path: '/', + name: 'Home', + redirect: '/dashboard', + component: () => import('@/layout/index.vue'), + children: [ + { + path: 'dashboard', + name: 'Dashboard', + component: () => import('@/views/dashboard/index.vue'), + meta: { title: '仪表盘', icon: 'Element:Odometer', affixTab: true, sort: -1 } + } + ] + } +] + +/** + * 创建路由 + */ +const createAppRouter = () => + createRouter({ + history: createWebHashHistory(import.meta.env.BASE_URL), // hash 模式 + // history: createWebHistory(import.meta.env.BASE_URL), // HTML5 模式 + routes: constantRoutes + }) + +const router = createAppRouter() + +// 添加路由守卫等 +createRouterGuard(router) + +export default router + +// 重置路由 +export const resetRouter = () => { + router.currentRoute.value.matched = createAppRouter().currentRoute.value.matched + router.go(0) +} diff --git a/diboot-admin-ui/src/router/router-guards.ts b/diboot-admin-ui/src/router/router-guards.ts new file mode 100644 index 0000000000000000000000000000000000000000..598777eed9f2aac1d1532ed29a884242e23fe9aa --- /dev/null +++ b/diboot-admin-ui/src/router/router-guards.ts @@ -0,0 +1,62 @@ +import { isNavigationFailure, Router, RouteRecordRaw } from 'vue-router' +import nProgress from 'nprogress' +import useAuthStore from '@/store/auth' +import { buildAsyncRoutes } from '@/utils/route' +import auth from '@/utils/auth' + +/** + * 路由守卫函数 + * @param router - 路由实例 + */ +export function createRouterGuard(router: Router) { + router.beforeEach(async (to, from) => { + nProgress.start() + // 未登录 + if (auth.getToken()) { + const userStore = useAuthStore() + // 已加载完基本数据 + if (userStore.info) { + return + } + + try { + await userStore.getInfo() + + // 加载异步路由 + const res = await api.get>('/auth/menu') + if (res.data?.length) { + buildAsyncRoutes(res.data).forEach(e => router.addRoute(e)) + } + + const redirectPath = ((to.name === '404' && to.query.path) || from.query.redirect || to.path) as string + const redirect = decodeURIComponent(redirectPath) + return to.path === redirect ? { ...to, replace: true } : { path: redirect } + } catch (e) { + // 获取数据异常 + console.error('动态加载授权路由失败', e) + } + } + + // You can access without permissions. You need to set the routing meta.ignoreAuth to true + if (to.meta.ignoreAuth) { + return + } + + // redirect login page + const redirectData = { + name: 'Login', + replace: true, + query: to.query + } + redirectData.query.redirect = to.path + return redirectData + }) + + router.afterEach((to, from, failure) => { + if (isNavigationFailure(failure)) { + nProgress.remove() + return + } + nProgress.done() + }) +} diff --git a/diboot-admin-ui/src/router/typings.d.ts b/diboot-admin-ui/src/router/typings.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..00e3347ad1b651681dca20260bb59892132e56fe --- /dev/null +++ b/diboot-admin-ui/src/router/typings.d.ts @@ -0,0 +1,21 @@ +import 'vue-router' + +declare module 'vue-router' { + interface RouteMeta { + title?: string + icon?: string + module?: string + componentName?: string + permissions?: Array + sort?: number + affixTab?: boolean + ignoreAuth?: boolean + keepAlive?: boolean + hidden?: boolean + hollow?: boolean + hideFooter?: boolean + borderless?: boolean + openNewWindow?: boolean + hideBreadcrumb?: boolean + } +} diff --git a/diboot-admin-ui/src/store/app.ts b/diboot-admin-ui/src/store/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5e31385959353b13fe02829202810293b9c65d6 --- /dev/null +++ b/diboot-admin-ui/src/store/app.ts @@ -0,0 +1,27 @@ +export interface IAppStore { + enableTabs: boolean + enableFooter: boolean + globalSize: 'large' | 'default' | 'small' + layout: 'default' | 'dock' | 'menu' | 'topNav' + colorPrimary?: string +} + +export default defineStore('app', { + state: (): IAppStore => { + return { + enableTabs: true, + enableFooter: true, + globalSize: 'default', + layout: 'default', + colorPrimary: undefined + } + }, + persist: { + enabled: true, + strategies: [ + { + storage: localStorage + } + ] + } +}) diff --git a/diboot-admin-ui/src/store/auth.ts b/diboot-admin-ui/src/store/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef6ec90b0a4599aea11783ee4e31a60db07749d3 --- /dev/null +++ b/diboot-admin-ui/src/store/auth.ts @@ -0,0 +1,64 @@ +import auth from '@/utils/auth' +import { resetRouter } from '@/router' + +export interface IAuthStore { + realname: string + avatar?: string + roles: Array + info?: IUserInfo +} + +export interface IUserInfo extends Record { + realname?: string + avatar?: string + roles: Array +} + +export default defineStore('auth', { + state: () => { + return { + realname: '', + avatar: undefined, + roles: [], + info: undefined + } + }, + actions: { + login(account: unknown) { + return new Promise((resolve, reject) => { + api + .post<{ token: string }>('/auth/login', account) + .then(res => { + if (res.data) { + auth.setToken(res.data.token) + resolve(res.data) + } + }) + .catch(err => { + ElMessage.error(err.message || err.msg || '稍后重试') + reject() + }) + }) + }, + getInfo: async function () { + try { + const res = await api.get<{ realname: string; avatar: string; roles: Array }>('/auth/userInfo') + this.info = res.data + this.avatar = `${res.data?.avatar}` + this.realname = `${res.data?.realname}` + this.roles = res.data?.roles ?? [] + } catch (e) { + throw new Error('获取登录者信息异常') + } + }, + async logout() { + try { + await api.post('/auth/logout') + } finally { + auth.clearToken() + this.$reset() + resetRouter() + } + } + } +}) diff --git a/diboot-admin-ui/src/store/index.ts b/diboot-admin-ui/src/store/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1fd816d024230dbcce52de30759aec099d163c4 --- /dev/null +++ b/diboot-admin-ui/src/store/index.ts @@ -0,0 +1,8 @@ +import piniaPersist from 'pinia-plugin-persist' + +const pinia = createPinia() + +// 持久化插件 +pinia.use(piniaPersist) + +export default pinia diff --git a/diboot-admin-ui/src/store/viewTabs.ts b/diboot-admin-ui/src/store/viewTabs.ts new file mode 100644 index 0000000000000000000000000000000000000000..9064cd4ef174d5bc29220931f8334441c3b3b55b --- /dev/null +++ b/diboot-admin-ui/src/store/viewTabs.ts @@ -0,0 +1,89 @@ +import { RouteLocationNormalized, Router } from 'vue-router' + +export type IViewTabsStore = { + tabList: RouteLocationNormalized[] // 页签 +} + +// 是否存在激活的 tab +const isPresenceActivation = (list: RouteLocationNormalized[], router: Router) => { + const currentRouteName = router.currentRoute.value.name + return list.some(item => item.name === currentRouteName) +} + +// 跳转到指定 tab,或最后一个 tab 或 Home +const goTabOrLastOrHome = (list: RouteLocationNormalized[], router: Router, index = -1) => { + const length = list.length + if (index > -1 && index < length) router.push(list[index].fullPath).finally() + else if (length === 0) router.push({ name: 'Home' }).finally() + else router.push(list[length - 1].fullPath).finally() +} + +// 查找 Tab 函数 +const findTabFu = (route: RouteLocationNormalized) => (item: RouteLocationNormalized) => item.name == route.name + +export default defineStore({ + id: 'view-tabs', + state: (): IViewTabsStore => ({ + tabList: [] + }), + getters: { + // 缓存的视图组件名称列表 + cachedViews(): string[] { + return this.tabList + .filter(e => e.meta.keepAlive != false && e.meta.componentName) + .map(e => e.meta.componentName) as string[] + } + }, + actions: { + // 初始化标签页 + initTabs(routes: RouteLocationNormalized[]) { + this.tabList = routes + }, + // 添加(存在则替换) + addTab(route: RouteLocationNormalized) { + const index = this.tabList.findIndex(findTabFu(route)) + if (index === -1) this.tabList.push(route) + else this.tabList.splice(index, 1, route) + if (!route.meta.title) route.meta.title = 'no-title' + return index === -1 ? this.tabList.length - 1 : index + }, + // 更新 + updateTabTitle(route: RouteLocationNormalized, title: string) { + const find = this.tabList.find(findTabFu(route)) + if (find) find.meta.title = title + else return false + }, + // 关闭指定前页 + closeTab(route: RouteLocationNormalized, router: Router) { + const findIndex = this.tabList.findIndex(findTabFu(route)) + this.tabList.splice(findIndex, 1) + if (router.currentRoute.value.name === route.name) goTabOrLastOrHome(this.tabList, router, findIndex) + }, + // 关闭左侧 + closeLeftTabs(route: RouteLocationNormalized, router: Router) { + const discard = this.tabList.splice(0, this.tabList.findIndex(findTabFu(route))) + if (isPresenceActivation(discard, router)) goTabOrLastOrHome(this.tabList, router) + }, + // 关闭右侧 + closeRightTabs(route: RouteLocationNormalized, router: Router) { + const discard = this.tabList.splice(this.tabList.findIndex(findTabFu(route)) + 1) + if (isPresenceActivation(discard, router)) goTabOrLastOrHome(this.tabList, router) + }, + // 关闭其他 + closeOtherTabs(route: RouteLocationNormalized, router: Router) { + if (router.currentRoute.value.name === route.name) router.push(route.fullPath).finally() + this.tabList = [route] + }, + // 关闭全部 + closeAllTabs(router: Router) { + let isPresenceActivation = false + const currentRouteName = router.currentRoute.value.name + //保留固定路由 + this.tabList = this.tabList.filter(item => { + if (!isPresenceActivation) isPresenceActivation = item.name === currentRouteName + return item.meta.affixTab ?? false + }) + if (isPresenceActivation) goTabOrLastOrHome(this.tabList, router) + } + } +}) diff --git a/diboot-admin-ui/src/styles/index.scss b/diboot-admin-ui/src/styles/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..081c50cdad7501c4cebc047283989e94df854ca1 --- /dev/null +++ b/diboot-admin-ui/src/styles/index.scss @@ -0,0 +1,5 @@ +//:root { +// .el-dialog { +// --el-dialog-margin-top: 10vh; +// } +//} diff --git a/diboot-admin-ui/src/styles/theme/dark.scss b/diboot-admin-ui/src/styles/theme/dark.scss new file mode 100644 index 0000000000000000000000000000000000000000..8679505241d71df7b7e6e58f0fa80a90fae71ba9 --- /dev/null +++ b/diboot-admin-ui/src/styles/theme/dark.scss @@ -0,0 +1,12 @@ +// Custom dark color scheme +@forward 'element-plus/theme-chalk/src/dark/var.scss' with ( + $colors: ( + 'success': ( 'base': #21ba45 ), + 'warning': ( 'base': #f2711c ), + 'danger': ( 'base': #db2828 ), + 'error': ( 'base': #db2828 ), + ), +); + +// import dark theme +@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *; diff --git a/diboot-admin-ui/src/styles/theme/index.scss b/diboot-admin-ui/src/styles/theme/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..caa04de6b0f4687667fcac1223d1439d1e5b651f --- /dev/null +++ b/diboot-admin-ui/src/styles/theme/index.scss @@ -0,0 +1,2 @@ +@use 'light'; +@use 'dark'; diff --git a/diboot-admin-ui/src/styles/theme/light.scss b/diboot-admin-ui/src/styles/theme/light.scss new file mode 100644 index 0000000000000000000000000000000000000000..0e4c8bf740807c74bc7d00b918a7c6f2ae1e0791 --- /dev/null +++ b/diboot-admin-ui/src/styles/theme/light.scss @@ -0,0 +1,12 @@ +// Custom light color scheme(Share light color schemes when dark color schemes are not overridden) +@forward 'element-plus/theme-chalk/src/common/var.scss' with ( + // default color scheme + $colors: ( + 'primary': ( 'base': #409eff ), + 'success': ( 'base': #67c23a ), + 'warning': ( 'base': #e6a23c ), + 'danger': ( 'base': #f56c6c ), + 'error': ( 'base': #f56c6c ), + 'info': ( 'base': #909399 ), + ), +); diff --git a/diboot-admin-ui/src/utils/auth.ts b/diboot-admin-ui/src/utils/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..50911ab914c3b116db9c7e376df896413cc273da --- /dev/null +++ b/diboot-admin-ui/src/utils/auth.ts @@ -0,0 +1,15 @@ +const TOKEN_KEY = 'token' + +export const AUTH_HEADER_KEY = 'Authorization' + +export default { + getToken(): string | null { + return localStorage.getItem(TOKEN_KEY) + }, + setToken(token: string) { + localStorage.setItem(TOKEN_KEY, token) + }, + clearToken() { + localStorage.removeItem(TOKEN_KEY) + } +} diff --git a/diboot-admin-ui/src/utils/permission.ts b/diboot-admin-ui/src/utils/permission.ts new file mode 100644 index 0000000000000000000000000000000000000000..25835706ad2549384d5d665482d9b78470c9cd24 --- /dev/null +++ b/diboot-admin-ui/src/utils/permission.ts @@ -0,0 +1,44 @@ +import useAuthStore from '@/store/auth' +import router from '@/router' + +/** + * 角色权限校验 + * + * @param {string | Array} value 校验值 + * @param {boolean} not 取反 + * @param {boolean} all 全部 + * @returns {Boolean} + */ +export function checkRole(value: string | Array, not = false, all = false) { + if (value && value.length) { + const roles = useAuthStore().roles ?? [] + const permissionRoles = value instanceof Array ? value : [value] + const findFn = (role: string) => roles.includes(role) + const exist = all ? permissionRoles.every(findFn) : permissionRoles.some(findFn) + return not ? !exist : exist + } else { + console.error(`need roles!`) + return false + } +} + +/** + * 字符权限校验 + * + * @param {string | Array} value 校验值 + * @param {boolean} not 取反 + * @param {boolean} all 全部 + * @returns {Boolean} + */ +export function checkPermission(value: string | Array, not = false, all = false) { + if (value && value.length) { + const permissions = router.currentRoute.value.meta?.permissions ?? [] + const permissionList = value instanceof Array ? value : [value] + const findFn = (permission: string) => permissions.includes(permission) + const exist = all ? permissionList.every(findFn) : permissionList.some(findFn) + return not ? !exist : exist + } else { + console.error(`need permissions!`) + return false + } +} diff --git a/diboot-admin-ui/src/utils/relatedData.ts b/diboot-admin-ui/src/utils/relatedData.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd7a13f07a97cafa539a41bdbe9aa1c11dbb43cf --- /dev/null +++ b/diboot-admin-ui/src/utils/relatedData.ts @@ -0,0 +1,40 @@ +import { api } from '@/utils/request' +export interface RelatedDataConfig { + target: string // 对象名 / 字典名 + cache?: boolean // 数据是否缓存 TODO + module?: string // 模块名(用于cloud指定module,默认当前module) + alias?: string // 指定别名(默认为:target的小驼峰+Options) + label?: string // 对象的属性名,查询作为label的属性名称 + value?: string // 对象的属性名,,需要查询作为value的属性名称 + ext?: string // 对象的属性名,需要查询作为ext的属性名称 (扩展值,一般用于特殊情景) + condition?: Record | null> // 查询条件 +} +export interface ILabelValue { + label: string + value: string + ext?: unknown +} +export type LabelValue = { [label: string]: ILabelValue[] } +/** + * useLoadRelatedData 请求 + * @param relatedDataConfigList + * @param baseApi 设置api,表示发起个性化请求 + */ +export const useLoadRelatedData = (relatedDataConfigList: RelatedDataConfig[], baseApi?: string): LabelValue => { + const more = reactive({}) + const requestList = [] + // add 通用接口 + relatedDataConfigList && + relatedDataConfigList.length > 0 && + requestList.push(api.post('/common/attachMore', relatedDataConfigList)) + // add 个性化接口 + baseApi && requestList.push(api.get(`${baseApi}/attachMore`)) + // send api and set more + requestList.length > 0 && + Promise.all(requestList).then(results => { + results.forEach(result => { + Object.assign(more, result.code === 0 ? result.data : {}) + }) + }) + return more +} diff --git a/diboot-admin-ui/src/utils/request.ts b/diboot-admin-ui/src/utils/request.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8cabeb40726bd7eb43269c95f4ed53fa501cfab --- /dev/null +++ b/diboot-admin-ui/src/utils/request.ts @@ -0,0 +1,214 @@ +import axios, { AxiosRequestHeaders, AxiosResponse } from 'axios' +import auth, { AUTH_HEADER_KEY } from './auth' +import router from '@/router' +import qs from 'qs' + +// baseURL +const BASE_URL = import.meta.env.VITE_APP_BASE_URL +// 创建 axios 实例 +const service = axios.create({ + // API 请求的默认前缀 + baseURL: BASE_URL, + timeout: 30_000 // 请求超时时间 +}) + +// 添加请求拦截器 +service.interceptors.request.use(config => { + // 让每个请求携带自定义 token 请根据实际情况自行修改 + if (auth.getToken()) (config.headers as AxiosRequestHeaders)[AUTH_HEADER_KEY] = 'Bearer ' + auth.getToken() + + // 只针对get方式进行序列化 + if (config.method === 'get') config.paramsSerializer = params => qs.stringify(params, { arrayFormat: 'repeat' }) + + return config +}) + +// 添加响应拦截器 +service.interceptors.response.use( + response => { + // 检查是否携带有新的token + const newToken = response.headers[AUTH_HEADER_KEY] + if (newToken) auth.setToken(newToken) + + // 如果请求成功,则重置心跳定时器 + if (response.status === 200) resetPingTimer() + + // 如果返回的自定义状态码为 4001, 则token过期,需要清理掉token并跳转至登录页面重新登录 + if (response.data && response.data.code === 4001) { + auth.clearToken() + router.push({ name: 'Login' }).finally() + throw new Error('登录过期,请重新登录') + } + + return response + }, + error => { + let message + if (error && error.response && error.response.status) { + switch (error.response.status) { + case 500: + message = '服务器好像开小差了,重试下吧!' + break + case 400: + message = '提交数据出错' + break + case 401: + message = '没有权限' + break + case 403: + message = '无权访问' + break + case 404: + message = '请求资源不存在' + break + default: + message = '网络可能出现问题' + } + console.error(message) + } + return Promise.reject(error) + } +) + +// token 自动刷新(发送心跳)的时间间隔(分钟) +const TOKEN_REFRESH_EXPIRE = 10 +// 心跳计时器 +let pingTimer: NodeJS.Timeout +resetPingTimer() + +/** + * 重置心跳定时器 + */ +function resetPingTimer() { + clearTimeout(pingTimer) + pingTimer = setTimeout(() => { + service.get('/auth/ping').then() + resetPingTimer() + }, TOKEN_REFRESH_EXPIRE * 60 * 1000) +} + +interface ApiData { + code: number + msg: string + data?: T + page?: Pagination +} + +interface Pagination { + pageIndex: number + pageSize: number + totalCount: number +} + +/** + * 请求拆包 + * @param request 请求 + */ +function unpack(request: Promise>>): Promise> { + return new Promise((resolve, reject) => { + request + .then(res => { + // 操作成功时(code = 0)【其他情况自行调整】 + if (res.data.code === 0) { + resolve(res.data) + } else { + reject(res.data) + } + }) + .catch(err => { + reject(err) + }) + }) +} + +const api = { + get(url: string, params?: unknown) { + return unpack(service.get, AxiosResponse>, unknown>(url, { params })) + }, + post(url: string, data?: unknown) { + return unpack( + service.post, AxiosResponse>, unknown>(url, JSON.stringify(data), { + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } + }) + ) + }, + put(url: string, data?: unknown) { + return unpack( + service.put, AxiosResponse>, unknown>(url, JSON.stringify(data), { + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } + }) + ) + }, + patch(url: string, data?: unknown) { + return unpack( + service.patch, AxiosResponse>, unknown>(url, JSON.stringify(data), { + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } + }) + ) + }, + delete(url: string, params?: unknown) { + return unpack( + service.delete, AxiosResponse>, unknown>(url, { + params, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json;charset=UTF-8' + }, + withCredentials: true + }) + ) + }, + /** + * 上传文件接口 + * + * @param url + * @param formData + */ + upload(url: string, formData: FormData) { + return unpack(service.post, AxiosResponse>, unknown>(url, formData)) + }, + /** + * GET下载文件 + * + * @param url + * @param params + */ + download(url: string, params?: unknown) { + return unpack( + service.get, AxiosResponse>, unknown>(url, { + responseType: 'arraybuffer', + params, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json;charset=UTF-8' + }, + withCredentials: true + }) + ) + }, + /** + * POST下载文件(常用于提交json数据下载文件) + * @param url + * @param data + */ + postDownload(url: string, data?: unknown) { + return unpack( + service.post, AxiosResponse>, unknown>(url, JSON.stringify(data), { + responseType: 'arraybuffer', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json;charset=UTF-8' + }, + withCredentials: true + }) + ) + } +} + +export { BASE_URL as baseURL, service as axios, api } diff --git a/diboot-admin-ui/src/utils/route.ts b/diboot-admin-ui/src/utils/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..de70f8906ddd9b510b4820e67b6798b2b270ac5a --- /dev/null +++ b/diboot-admin-ui/src/utils/route.ts @@ -0,0 +1,124 @@ +import { RouteRecord, RouteRecordName, RouteRecordRaw } from 'vue-router' +import Layout from '@/layout/index.vue' + +const RouterView = defineComponent({ + name: 'RouteView', + render: () => h('router-view') +}) + +/** + * 视图组件 + */ +export const viewComponents = Object.values(import.meta.globEager('@/views/**/*.{vue,tsx,jsx}')) + .map(e => e.default) + .filter(e => e.name) + .reduce( + (components, view) => { + components[view.name] = view + return components + }, + { Layout } + ) + +/** + * 构建异步路由 + * + * @param asyncRoutes + */ +export const buildAsyncRoutes = (asyncRoutes: RouteRecordRaw[]) => { + // 获取组件 + const resolveComponent = (name: string) => { + if (viewComponents[name]) return viewComponents[name] + throw new Error(`Unknown component '${name}'. Is it located under 'views' with extension?`) + } + + // 构建完整路径 + const buildFullPath = (path: string, parentPath = '/') => + /^\//.exec(path) ? path : `${parentPath === '/' ? '' : parentPath}/${path}` + + // 路由排序 + const routeSort = (e1: RouteRecordRaw, e2: RouteRecordRaw) => (e1.meta?.sort ?? 0) - (e2.meta?.sort ?? 0) + + /** + * 构建路由 + * + * @param routes + * @param parentPath + */ + function buildRouter(routes: RouteRecordRaw[], parentPath?: string) { + return routes.filter(route => { + const fullPath = buildFullPath(route.path, parentPath) + if (route.children?.length && (route.children = buildRouter(route.children.sort(routeSort), fullPath)).length) { + const componentName = route.meta?.componentName + if (componentName) { + route.component = resolveComponent(componentName) + } else { + route.component = RouterView + } + // 父级目录重定向首个子菜单 + route.redirect = buildFullPath(route.children[0].path, fullPath) + } else { + delete route.children + if (route.meta?.componentName) { + route.component = resolveComponent(route.meta?.componentName) + } else return false + } + return true + }) + } + + return buildRouter(asyncRoutes) +} + +/** + * 获取菜单Tree + */ +export const getMenuTree = () => { + const routes = useRouter().getRoutes() + const routeTree: RouteRecord[] = [] + + const findByNameAndCollect = (name: RouteRecordName | undefined, arr: RouteRecord[]) => { + if (!name) return + const index = routeTree.findIndex(e => e.name === name) + const find = index > -1 ? routeTree.splice(index, 1)[0] : routes.find(e => e.name === name) + find && arr.push(find) + } + + for (const route of routes) { + const children = route.children + if (!route.meta.title && children.length === 1) { + findByNameAndCollect(children[0].name, routeTree) + } else if (children.length) { + const newChildren: RouteRecord[] = [] + children.forEach(child => findByNameAndCollect(child.name, newChildren)) + route.children = newChildren + routeTree.push(route) + } else { + routeTree.push(route) + } + } + + // 菜单排序 + const menuSort = (e1: RouteRecordRaw, e2: RouteRecordRaw) => (e1.meta?.sort ?? 0) - (e2.meta?.sort ?? 0) + + // 过滤菜单(隐藏菜单、无 title 时减少层级) + function filterMenu(routeTree: RouteRecordRaw[]) { + const routes: RouteRecordRaw[] = [] + for (const route of routeTree.sort(menuSort)) { + if (route.meta?.hidden) continue + if (!route.children || route.children.length === 0) { + routes.push(route) + } else if (!route.meta?.title && route.children.length === 1) { + const child = route.children[0] + child.path = route.redirect as string + routes.push(child) + } else { + route.children = filterMenu(route.children) + route.children.length && routes.push(route) + } + } + return routes + } + + return filterMenu(routeTree) +} diff --git a/diboot-admin-ui/src/utils/theme.ts b/diboot-admin-ui/src/utils/theme.ts new file mode 100644 index 0000000000000000000000000000000000000000..441347c96557d4563451391a5a0adad7f974e573 --- /dev/null +++ b/diboot-admin-ui/src/utils/theme.ts @@ -0,0 +1,35 @@ +import { useCssVar, useDark, useToggle } from '@vueuse/core' + +// 主题 (是否是黑暗的) +export const isDark = useDark() +// 切换主题 +export const toggleTheme = useToggle(isDark) + +// 主题色配置 +const pre = '--el-color-primary' +// 主题色 +export const colorPrimary = useCssVar(pre) +// 颜色混合(参考 sass @function mix) +const mix = (color1: string, color2: string, weight: number) => { + weight = Math.max(Math.min(Number(weight), 1), 0) + const unit = (color: string, index: number) => parseInt(color.substring(index, index + 2), 16) + const unitColor = (index: number) => + `0${Math.round(unit(color1, index) * weight + unit(color2, index) * (1 - weight)).toString(16)}`.slice(-2) + return `#${unitColor(1)}${unitColor(3)}${unitColor(5)}` +} +// 混入从主题色 +const mixColorPrimary = (color: string, light: string, dark: string) => { + for (let i = 1; i < 10; i += 1) { + useCssVar(`${pre}-light-${i}`).value = mix(light, color, i * 0.1) + } + useCssVar(`${pre}-dark-2`).value = mix(dark, color, 0.2) +} +// 设置从主题色 +const subColorPrimary = (color: string, isDark: boolean) => { + const bgColor = useCssVar('--el-bg-color').value.trim() + mixColorPrimary(color, bgColor, isDark ? '#ffffff' : '#000000') +} +// 监听主题色变化 +watch(colorPrimary, value => subColorPrimary(value, isDark.value)) +// 监听主题变化,随着改变主题色 +watch(isDark, value => nextTick(() => subColorPrimary(colorPrimary.value.trim(), value))) diff --git a/diboot-admin-ui/src/views/dashboard/index.vue b/diboot-admin-ui/src/views/dashboard/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..e4a71b06bd497380fa97e1f4a819394749e5042c --- /dev/null +++ b/diboot-admin-ui/src/views/dashboard/index.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/diboot-admin-ui/src/views/exception/404.vue b/diboot-admin-ui/src/views/exception/404.vue new file mode 100644 index 0000000000000000000000000000000000000000..33990bf2a51d50d37633b26ffa88a9715886e4b4 --- /dev/null +++ b/diboot-admin-ui/src/views/exception/404.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/diboot-admin-ui/src/views/exception/500.vue b/diboot-admin-ui/src/views/exception/500.vue new file mode 100644 index 0000000000000000000000000000000000000000..e0b8651ba7daedd0b220907572f74acaa19848f6 --- /dev/null +++ b/diboot-admin-ui/src/views/exception/500.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/diboot-admin-ui/src/views/login/index.vue b/diboot-admin-ui/src/views/login/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..342632763b2efd46fbdad969ea0cd4329d2e939a --- /dev/null +++ b/diboot-admin-ui/src/views/login/index.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/diboot-admin-ui/src/views/system/dictionary/list.vue b/diboot-admin-ui/src/views/system/dictionary/list.vue new file mode 100644 index 0000000000000000000000000000000000000000..dadebe0b06af6928536a5928fe3743f6fbbb778d --- /dev/null +++ b/diboot-admin-ui/src/views/system/dictionary/list.vue @@ -0,0 +1,46 @@ + + diff --git a/diboot-admin-ui/src/views/system/iamResourcePermission/list.vue b/diboot-admin-ui/src/views/system/iamResourcePermission/list.vue new file mode 100644 index 0000000000000000000000000000000000000000..d807013ad3ba3e386561e2f3ed8c6c6f418b6a59 --- /dev/null +++ b/diboot-admin-ui/src/views/system/iamResourcePermission/list.vue @@ -0,0 +1,84 @@ + + + diff --git a/diboot-admin-ui/src/views/system/role/list.vue b/diboot-admin-ui/src/views/system/role/list.vue new file mode 100644 index 0000000000000000000000000000000000000000..af5bcc2023f4d68e50432c3ee6349af29ea42f46 --- /dev/null +++ b/diboot-admin-ui/src/views/system/role/list.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/diboot-admin-ui/src/views/system/role/type.ts b/diboot-admin-ui/src/views/system/role/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..15a1314c7befa67378f99b254b744f001f32eb2a --- /dev/null +++ b/diboot-admin-ui/src/views/system/role/type.ts @@ -0,0 +1,8 @@ +export interface Role { + id: string + name: string + code: string + description: string + createTime: string + updateTime: string +} diff --git a/diboot-admin-ui/tsconfig.json b/diboot-admin-ui/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..6bd0cab59527ed44c56517d4c72506c68e4ff38c --- /dev/null +++ b/diboot-admin-ui/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": [ + "esnext", + "dom" + ], + "baseUrl": ".", + "paths": { + "@/*": [ + "src/*" + ], + "#/*": [ + "types/*" + ] + }, + "types": [ + "vite/client", + "element-plus/global" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "types/**/*.ts", + "types/**/*.d.ts" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/diboot-admin-ui/tsconfig.node.json b/diboot-admin-ui/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..e993792cb12c9d8223f5a731f2d662ab553180f2 --- /dev/null +++ b/diboot-admin-ui/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/diboot-admin-ui/types/auto-imports.d.ts b/diboot-admin-ui/types/auto-imports.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfa667641249e7a5f1cc24b4feb1911f21b70b07 --- /dev/null +++ b/diboot-admin-ui/types/auto-imports.d.ts @@ -0,0 +1,74 @@ +// Generated by 'unplugin-auto-import' +// We suggest you to commit this file into source control +declare global { + const _: typeof import('lodash') + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const api: typeof import('@/utils/request')['api'] + const baseURL: typeof import('@/utils/request')['baseURL'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const EffectScope: typeof import('vue')['EffectScope'] + const ElMessage: typeof import('element-plus')['ElMessage'] + const ElMessageBox: typeof import('element-plus')['ElMessageBox'] + const ElNotification: typeof import('element-plus')['ElNotification'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useList: typeof import('@/hooks/list')['default'] + const useListDefault: typeof import('@/hooks/list_default')['default'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] +} +export {} diff --git a/diboot-admin-ui/types/components.d.ts b/diboot-admin-ui/types/components.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..74a7a1ae00057d27d4326336c5af91cbc1c9eef0 --- /dev/null +++ b/diboot-admin-ui/types/components.d.ts @@ -0,0 +1,54 @@ +// generated by unplugin-vue-components +// We suggest you to commit this file into source control +// Read more: https://github.com/vuejs/vue-next/pull/3399 +import '@vue/runtime-core' + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElAside: typeof import('element-plus/es')['ElAside'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCol: typeof import('element-plus/es')['ElCol'] + ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElContainer: typeof import('element-plus/es')['ElContainer'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDivider: typeof import('element-plus/es')['ElDivider'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElHeader: typeof import('element-plus/es')['ElHeader'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElMain: typeof import('element-plus/es')['ElMain'] + ElMenu: typeof import('element-plus/es')['ElMenu'] + ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElRow: typeof import('element-plus/es')['ElRow'] + ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSpace: typeof import('element-plus/es')['ElSpace'] + ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + Icon: typeof import('@/components/icon/index.vue')['default'] + IconSelect: typeof import('@/components/icon/select.vue')['default'] + Loading: typeof import('element-plus/es')['ElLoadingDirective'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} + +export {} diff --git a/diboot-admin-ui/types/env.d.ts b/diboot-admin-ui/types/env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..355b4447411315fb7261666cb5734b73694a4b4a --- /dev/null +++ b/diboot-admin-ui/types/env.d.ts @@ -0,0 +1,14 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly VITE_PORT: string + readonly VITE_OPEN: string + readonly VITE_APP_BASE_URL: string +} diff --git a/diboot-admin-ui/vite.config.ts b/diboot-admin-ui/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..9935501525ebabdeff0f2b378912e448c47772f6 --- /dev/null +++ b/diboot-admin-ui/vite.config.ts @@ -0,0 +1,79 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import VueSetupExtend from 'vite-plugin-vue-setup-extend' +import eslintPlugin from 'vite-plugin-eslint' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import { viteMockServe } from 'vite-plugin-mock' +import { fileURLToPath, URL } from 'url' + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + return { + plugins: [ + vue(), + VueSetupExtend(), + eslintPlugin(), + AutoImport({ + // 解析器 + resolvers: [ElementPlusResolver({ importStyle: 'sass' })], + // 自动导入Api + imports: [ + 'vue', + 'vue-router', + 'pinia', + { lodash: [['*', '_']] }, + { 'element-plus': ['ElMessage', 'ElMessageBox', 'ElNotification'] }, + { '@/utils/request': ['api', 'baseURL'] }, + { '@/hooks/list': [['default', 'useList']] }, + { '@/hooks/list_default': [['default', 'useListDefault']] } + ], + // 为true时在项目根目录自动创建 + dts: 'types/auto-imports.d.ts', + // 启用 eslint + eslintrc: { enabled: true, globalsPropValue: 'readonly' } + }), + Components({ + // 解析器 + resolvers: [ElementPlusResolver({ importStyle: 'sass' })], + // 自动加载的组件目录,默认值为 ['src/components'] + dirs: ['src/components'], + // 组件名称包含目录,防止同名组件冲突 + directoryAsNamespace: true, + // 指定类型声明文件,为true时在项目根目录创建 + dts: 'types/components.d.ts', + // 导入路径变换 + importPathTransform: path => path.replace(/^.+\/src/g, '@') + }), + viteMockServe({ + // 忽略以_开头的文件及目录 + ignore: /^_|\/_/, + // 开发打包开关(默认开启) + localEnabled: command === 'serve', + // 生产打包开关(默认不打包) + prodEnabled: command !== 'serve', + // 注入代码(用于生产需要mock) + injectCode: ` + import { setupProdMockServer } from '../mock/_prodServer'; + + setupProdMockServer(); + ` + }) + ], + css: { + preprocessorOptions: { + scss: { + // javascriptEnabled: true, + additionalData: `@use "@/styles/theme/index.scss" as *;` + } + } + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + '#': fileURLToPath(new URL('./types', import.meta.url)) + } + } + } +}) diff --git a/diboot-core-starter/pom.xml b/diboot-core-starter/pom.xml index 0f9f9385a7af63d586db45a81ded47dc19a8f1ff..b147da86371ab3eaa26d9f77dffdc5b90e9f3fef 100644 --- a/diboot-core-starter/pom.xml +++ b/diboot-core-starter/pom.xml @@ -7,11 +7,11 @@ com.diboot diboot-root - 2.5.0 + 2.6.0 diboot-core-spring-boot-starter - 2.5.0 + 2.6.0 jar diboot core starter project @@ -56,5 +56,13 @@ junit test + diff --git a/diboot-core-starter/src/main/java/com/diboot/core/cache/DynamicRedisCacheManager.java b/diboot-core-starter/src/main/java/com/diboot/core/cache/DynamicRedisCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..40d52de8ae360c3857758a0771af38459a6f5efb --- /dev/null +++ b/diboot-core-starter/src/main/java/com/diboot/core/cache/DynamicRedisCacheManager.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.data.redis.cache.RedisCacheManager; + +/** + * 动态数据Redis缓存 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/17 + * Copyright © diboot.com + */ +@Slf4j +public class DynamicRedisCacheManager implements BaseCacheManager{ + + private RedisCacheManager redisCacheManager; + + public DynamicRedisCacheManager(RedisCacheManager redisCacheManager) { + this.redisCacheManager = redisCacheManager; + } + + @Override + public T getCacheObj(String cacheName, Object objKey, Class tClass) { + Cache cache = redisCacheManager.getCache(cacheName); + return cache != null? cache.get(objKey, tClass) : null; + } + + @Override + public String getCacheString(String cacheName, Object objKey) { + return getCacheObj(cacheName, objKey, String.class); + } + + @Override + public void putCacheObj(String cacheName, Object objKey, Object obj) { + Cache cache = redisCacheManager.getCache(cacheName); + cache.put(objKey, obj); + } + + public void putCacheObj(String cacheName, Object objKey, Object obj, int expireMinutes) { + // 暂不支持redis按cache设置不同过期时间 + this.putCacheObj(cacheName, objKey, obj); + } + + @Override + public void removeCacheObj(String cacheName, Object objKey) { + Cache cache = redisCacheManager.getCache(cacheName); + cache.evict(objKey); + } + + @Override + public boolean isUninitializedCache(String cacheName) { + return false; + } + + @Override + public void clearOutOfDateData(String cacheName) { + } + +} \ No newline at end of file diff --git a/diboot-core-starter/src/main/java/com/diboot/core/starter/CoreAutoConfig.java b/diboot-core-starter/src/main/java/com/diboot/core/starter/CoreAutoConfig.java index 28ad1186f601c9d7f57f7a7d765d510770105c9e..ca79e4a6268a086d538d461cd1544462604df63b 100644 --- a/diboot-core-starter/src/main/java/com/diboot/core/starter/CoreAutoConfig.java +++ b/diboot-core-starter/src/main/java/com/diboot/core/starter/CoreAutoConfig.java @@ -17,11 +17,11 @@ package com.diboot.core.starter; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.diboot.core.converter.*; import com.diboot.core.data.ProtectFieldHandler; import com.diboot.core.data.encrypt.ProtectInterceptor; import com.diboot.core.util.ContextHelper; import com.diboot.core.util.D; -import com.diboot.core.util.DateConverter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; @@ -43,6 +43,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.scheduling.annotation.EnableAsync; @@ -172,7 +174,11 @@ public class CoreAutoConfig implements WebMvcConfigurer { */ @Override public void addFormatters(FormatterRegistry registry) { - registry.addConverter(new DateConverter()); + registry.addConverter(new Date2LocalDateConverter()); + registry.addConverter(new Date2LocalDateTimeConverter()); + registry.addConverter(new String2DateConverter()); + registry.addConverter(new String2BooleanConverter()); + registry.addConverter(new Timestamp2LocalDateTimeConverter()); } } diff --git a/diboot-core-starter/src/main/resources/META-INF/sql/init-core-dm.sql b/diboot-core-starter/src/main/resources/META-INF/sql/init-core-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..9e5271ffcfeb0e49fffea3ffd1b324d538962dd4 --- /dev/null +++ b/diboot-core-starter/src/main/resources/META-INF/sql/init-core-dm.sql @@ -0,0 +1,38 @@ +-- 字典表 +create table ${SCHEMA}.dictionary ( + id BIGINT identity ( 10000,1) primary key, + parent_id BIGINT default 0 not null, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(150), + type VARCHAR(150) not null, + item_name VARCHAR(300) not null, + item_value VARCHAR(300), + description VARCHAR(300), + extdata VARCHAR(600), + sort_id SMALLINT default 99 not null, + is_deletable BIT default 1 not null, + is_editable BIT default 1 not null, + is_deleted BIT default 0 not null, + create_time DATETIME null +); +-- 添加备注 +comment on column ${SCHEMA}.dictionary.id is 'ID'; +comment on column ${SCHEMA}.dictionary.parent_id is '父ID'; +comment on column ${SCHEMA}.dictionary.tenant_id is '租户ID'; +comment on column ${SCHEMA}.dictionary.app_module is '应用模块'; +comment on column ${SCHEMA}.dictionary.type is '字典类型'; +comment on column ${SCHEMA}.dictionary.item_name is '显示名'; +comment on column ${SCHEMA}.dictionary.item_value is '存储值'; +comment on column ${SCHEMA}.dictionary.description is '备注'; +comment on column ${SCHEMA}.dictionary.extdata is '扩展JSON'; +comment on column ${SCHEMA}.dictionary.sort_id is '排序号'; +comment on column ${SCHEMA}.dictionary.is_editable is '是否可改'; +comment on column ${SCHEMA}.dictionary.is_deletable is '是否可删'; +comment on column ${SCHEMA}.dictionary.is_deleted is '删除标记'; +comment on column ${SCHEMA}.dictionary.create_time is '创建时间'; + +comment on table ${SCHEMA}.dictionary is '数据字典'; + +-- 创建索引 +create index idx_directory on ${SCHEMA}.dictionary (type, item_value); +create index idx_directory_tenant on ${SCHEMA}.dictionary(tenant_id); \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/TestCountBinder.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestCountBinder.java new file mode 100644 index 0000000000000000000000000000000000000000..05e96c6640c403064781244ca861ddea0033af69 --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestCountBinder.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.binder; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.diboot.core.binding.Binder; +import com.diboot.core.util.JSON; +import com.diboot.core.util.V; +import diboot.core.test.StartupApplication; +import diboot.core.test.binder.entity.Department; +import diboot.core.test.binder.entity.User; +import diboot.core.test.binder.service.DepartmentService; +import diboot.core.test.binder.service.UserService; +import diboot.core.test.binder.vo.*; +import diboot.core.test.config.SpringMvcConfig; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +/** + * 测试子项count计数绑定 + * @author mazc@dibo.ltd + * @version v2.0 + * @date 2019/06/22 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) +public class TestCountBinder { + + @Autowired + UserService userService; + + @Autowired + DepartmentService departmentService; + + /** + * 验证直接关联的绑定 + */ + @Test + public void testSimpleBinder(){ + // 加载测试数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(Department::getId, 10001L, 10003L); + List entityList = departmentService.list(queryWrapper); + // 自动绑定 + List voList = Binder.convertAndBindRelations(entityList, CountSimpleVO.class); + // 验证绑定结果 + Assert.assertTrue(V.notEmpty(voList)); + for(CountSimpleVO vo : voList){ + // 验证直接关联的绑定 + Assert.assertTrue(vo.getChildrenCount() > 0); + Assert.assertTrue(vo.getChildrenIds().size() == vo.getChildrenCount()); + } + } + + /** + * 验证通过中间表间接关联的绑定 + */ + @Test + public void testComplexBinder(){ + // 加载测试数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(User::getId, 1001L, 1002L); + List userList = userService.getEntityList(queryWrapper); + // 自动绑定 + List voList = Binder.convertAndBindRelations(userList, EntityListComplexVO.class); + // 验证绑定结果 + Assert.assertTrue(V.notEmpty(voList)); + for(EntityListComplexVO vo : voList){ + // 验证通过中间表间接关联的绑定 + Assert.assertTrue(vo.getRoleCount() > 0); + Assert.assertTrue(vo.getRoleCount() == vo.getRoleCodes().size()); + System.out.println(JSON.stringify(vo)); + } + } + +} \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/TestEntityListBinder.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestEntityListBinder.java index 22971328eaa461139e4c8ef5e8a2727526581c93..aa60c715d4fbcae50770fca1ecb05669e6e18be6 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/TestEntityListBinder.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestEntityListBinder.java @@ -172,7 +172,7 @@ public class TestEntityListBinder { // 验证通过中间表间接关联的绑定 if(vo.getManagerId().equals(1001L)){ Assert.assertTrue(vo.getManagerPhotos().size() == 1); - Assert.assertEquals(2, vo.getManagerPhotoList().size()); + Assert.assertEquals(1, vo.getManagerPhotoList().size()); } else{ Assert.assertTrue(vo.getManagerPhotos().size() == 2); diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/TestLeadExtConditionBinder.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestLeadExtConditionBinder.java new file mode 100644 index 0000000000000000000000000000000000000000..8ba5a02f0db6331dd7128a7fd0bc5cfd14fb7951 --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/TestLeadExtConditionBinder.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.binder; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.diboot.core.binding.Binder; +import com.diboot.core.util.JSON; +import com.diboot.core.util.V; +import diboot.core.test.StartupApplication; +import diboot.core.test.binder.entity.User; +import diboot.core.test.binder.service.UserService; +import diboot.core.test.binder.vo.FieldBinderVO; +import diboot.core.test.binder.vo.UserExtVO; +import diboot.core.test.config.SpringMvcConfig; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +/** + * 测试主表扩展条件 + * @author JerryMa + * @version v2.6.0 + * @date 2022/6/2 + * Copyright © diboot.com + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) +public class TestLeadExtConditionBinder { + @Autowired + UserService userService; + + @Test + public void testBinder(){ + // 加载测试数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + List userList = userService.getEntityList(queryWrapper); + // 自动绑定 + List voList = Binder.convertAndBindRelations(userList, UserExtVO.class); + // 验证绑定结果 + Assert.assertTrue(V.notEmpty(voList)); + for(UserExtVO vo : voList){ + if(vo.getGender().equals("F")){ + Assert.assertNotNull(vo.getDeptName()); + Assert.assertNotNull(vo.getDeptNameLike()); + Assert.assertNotNull(vo.getDeptNameIn()); + } + else { + Assert.assertNull(vo.getDeptName()); + Assert.assertNull(vo.getDeptNameLike()); + Assert.assertNull(vo.getDeptNameIn()); + } + if(vo.getDepartmentId().equals(10002l)) { + Assert.assertNotNull(vo.getDeptId()); + Assert.assertNotNull(vo.getRoleCodes()); + } + else{ + Assert.assertNull(vo.getDeptId()); + } + System.out.println(JSON.stringify(vo)); + } + } + +} diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/DepartmentDTO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/DepartmentDTO.java index 46f15c60e7efcd2a7e313745d9b9d99e3de41e7c..7679ff3445fd472a509b3928ebdb4322a0b3b463 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/DepartmentDTO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/DepartmentDTO.java @@ -57,6 +57,7 @@ public class DepartmentDTO implements Serializable { // 多绑定or连接 @BindQuery(comparison = Comparison.CONTAINS, field = "name") @BindQuery(comparison = Comparison.STARTSWITH, field = "`character`") + //@BindQuery(comparison = Comparison.STARTSWITH, field = "character") @BindQuery(comparison = Comparison.ENDSWITH, entity = Organization.class, field = "name", condition = "this.org_id=id") private String search; diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/UserDTO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/UserDTO.java index 7dee679439994f91ab4a6f3f782362675cd948c4..ef993e6fc003ca62f33d731a0c72c8eb6296a6ff 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/UserDTO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/dto/UserDTO.java @@ -58,4 +58,7 @@ public class UserDTO extends User { // LEFT JOIN user_role r3m ON self.id = r3m.user_id // LEFT JOIN role r3 ON r3m.role_id = r3.id + @BindQuery(entity = Role.class, field = "id", condition = "this.id=user_role.user_id AND user_role.role_id=id") + private Long roleId; + } \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/Department.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/Department.java index 6576346efe4d9bbd171ae905e4e7b139e218306c..03abb0876f098985eee7278cb4cfa1aa50099bc5 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/Department.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/Department.java @@ -18,6 +18,7 @@ package diboot.core.test.binder.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.diboot.core.binding.query.BindQuery; import com.diboot.core.binding.query.Comparison; +import com.diboot.core.binding.query.Strategy; import com.diboot.core.data.access.DataAccessCheckpoint; import com.diboot.core.entity.BaseEntity; import lombok.Getter; @@ -36,6 +37,7 @@ import lombok.experimental.Accessors; public class Department extends BaseEntity { private static final long serialVersionUID = -4849732665419794547L; + @BindQuery(comparison = Comparison.EQ, strategy = Strategy.INCLUDE_NULL) @TableField private Long parentId; @@ -43,7 +45,7 @@ public class Department extends BaseEntity { @DataAccessCheckpoint() private Long orgId; - @BindQuery(comparison = Comparison.CONTAINS) + @BindQuery(comparison = Comparison.STARTSWITH) @TableField private String name; diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestRegion.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestRegion.java new file mode 100644 index 0000000000000000000000000000000000000000..6a09f189ffddaa30d7c8aa22b50ecd9f2e0145cc --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestRegion.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.binder.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.diboot.core.entity.BaseEntity; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 地区,测试uuid + * @author wangyl@dibo.ltd + * @version v2.0 + * @date 2021/08/27 + */ +@Getter @Setter @Accessors(chain = true) +public class TestRegion extends BaseEntity { + private static final long serialVersionUID = -1391001660726027259L; + + // 废弃默认主键 + @TableField(exist = false) + private Long id; + // 声明新主键uuid + @TableId(type = IdType.ASSIGN_UUID) + private String uuid; + + /** + * 名称 + */ + private String name; + + /** + * 层级 + */ + private Integer level; + + /** + * 名称 + */ + private String code; + + /** + * 父级id + */ + private String parentId; + + /** + * 子节点 + */ + private List children; +} \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestUploadFile.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestUploadFile.java index 28fe12671325cbb794ef19d7232512588c37df27..3dc596789d88b58b8dc2a9fdf2562136d3443f52 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestUploadFile.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/TestUploadFile.java @@ -26,6 +26,7 @@ import lombok.experimental.Accessors; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; /** * file基础父类 @@ -70,4 +71,7 @@ public class TestUploadFile extends BaseEntity { @TableField private String accessUrl; + + @TableField(exist = false) + private LocalDateTime localDateTime; } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/User.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/User.java index ff0dd3a661554bf576625a02be9cbc58580b737b..43c7712b954f162dc35695e242232a7cacced234 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/User.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/entity/User.java @@ -16,6 +16,7 @@ package diboot.core.test.binder.entity; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; import com.diboot.core.data.copy.Accept; import com.diboot.core.entity.BaseEntity; import com.diboot.core.util.D; @@ -35,6 +36,7 @@ import java.util.Date; @Getter @Setter @Accessors(chain = true) +//@TableName("\"USER\"") public class User extends BaseEntity { private static final long serialVersionUID = 3050761344045195972L; @@ -48,7 +50,7 @@ public class User extends BaseEntity { @TableField private String gender; - @JsonFormat(pattern = D.FORMAT_DATE_Y4MD) + //@JsonFormat(pattern = D.FORMAT_DATE_Y4MD) private Date birthdate; @JsonFormat(pattern = D.FORMAT_DATE_Y4MD) diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/ComplexSplitVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/ComplexSplitVO.java index af1158689126ded436f7eed7b574096f645b6dd3..9706824ede2e3f045287f9b26c51dbf9d45f2d89 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/ComplexSplitVO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/ComplexSplitVO.java @@ -41,15 +41,20 @@ public class ComplexSplitVO extends Organization { // ,拆分的id值绑定 (中间表1-n主键绑定) @BindEntityList(entity = TestUploadFile.class, condition="this.manager_id=user.id AND user.`character`=uuid", splitBy= Cons.SEPARATOR_COMMA) +// @BindEntityList(entity = TestUploadFile.class, condition="this.manager_id=\"USER\".id AND \"USER\".character=uuid", +// splitBy= Cons.SEPARATOR_COMMA) private List managerPhotos; // ,拆分的id值绑定 @BindFieldList(entity = TestUploadFile.class, field = "fileName", condition="this.manager_id=user.id AND user.`character`=uuid", splitBy= Cons.SEPARATOR_COMMA) +// @BindFieldList(entity = TestUploadFile.class, field = "fileName", +// condition="this.manager_id=\"USER\".id AND \"USER\".character=uuid", splitBy= Cons.SEPARATOR_COMMA) private List managerPhotoNames; // 中间表1-n非主键绑定 @BindEntityList(entity = TestUploadFile.class, condition="this.manager_id=user.id AND user.id=rel_obj_id AND rel_obj_type = 'IamUser'") + //@BindEntityList(entity = TestUploadFile.class, condition="this.manager_id=\"USER\".id AND \"USER\".id=rel_obj_id AND rel_obj_type = 'IamUser'") private List managerPhotoList; } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/CountSimpleVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/CountSimpleVO.java new file mode 100644 index 0000000000000000000000000000000000000000..091099f4148b5a765f93373e75755767f12310e1 --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/CountSimpleVO.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.binder.vo; + +import com.diboot.core.binding.annotation.BindCount; +import com.diboot.core.binding.annotation.BindFieldList; +import diboot.core.test.binder.entity.Department; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * @author mazc@dibo.ltd + * @version v2.0 + * @date 2019/1/5 + */ +@Getter +@Setter +@Accessors(chain = true) +public class CountSimpleVO extends Department { + private static final long serialVersionUID = -362116388664907924L; + + // 直接关联多个Entity + @BindCount(entity = Department.class, condition = "this.id=parent_id") + private Long childrenCount; + + // 1-n 关联,取单个属性 + @BindFieldList(entity = Department.class, field = "id", condition = "this.id=parent_id") + private List childrenIds; + +} \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/EntityListComplexVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/EntityListComplexVO.java index 6ba20d8fee7bd22e97415c5913c6e6b17f36e27b..7a62c648117c58b306912711b5aa92bc682eb08f 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/EntityListComplexVO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/EntityListComplexVO.java @@ -15,6 +15,7 @@ */ package diboot.core.test.binder.vo; +import com.diboot.core.binding.annotation.BindCount; import com.diboot.core.binding.annotation.BindEntityList; import com.diboot.core.binding.annotation.BindFieldList; import diboot.core.test.binder.entity.Role; @@ -44,6 +45,9 @@ public class EntityListComplexVO extends User { @BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id AND user_role.user_id>1") private List roleList; + @BindCount(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id") + private Integer roleCount; + // 支持通过中间表的多-多Entity的单个属性集 @BindFieldList(entity = Role.class, field = "code", condition="this.id=user_role.user_id AND user_role.role_id=id") private List roleCodes; diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/SimpleSplitVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/SimpleSplitVO.java index 7786651eda20b4cdd08bbcecd93ca4388e3f6bec..299817abab769dcbdc5782b16418c02a735cc8d5 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/SimpleSplitVO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/SimpleSplitVO.java @@ -40,11 +40,13 @@ public class SimpleSplitVO extends Department { // ,拆分的id值绑定 @BindEntityList(entity = User.class, condition="this.`character`=id", splitBy= Cons.SEPARATOR_COMMA) + //@BindEntityList(entity = User.class, condition="this.character=id", splitBy= Cons.SEPARATOR_COMMA) private List managers; // ,拆分的id值绑定 @BindFieldList(entity = User.class, field = "username", condition="this.`character`=id", splitBy= Cons.SEPARATOR_COMMA) + //@BindFieldList(entity = User.class, field = "username", condition="this.character=id", splitBy= Cons.SEPARATOR_COMMA) private List managerNames; } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserEscVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserEscVO.java index 91433ef56ef24cfaa6511a3c32d04a4525dd6356..1bb5b7e236e916c15361953f8dcefa5549b7728f 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserEscVO.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserEscVO.java @@ -40,8 +40,10 @@ public class UserEscVO extends User { private String deptCharacter; @BindField(entity = Department.class, field="name", condition="this.`character`=`character`") + //@BindField(entity = Department.class, field="name", condition="this.character=character") private String deptName; @BindField(entity = Organization.class, field="name", condition="this.`character`=department.`character` AND department.org_id=id") + //@BindField(entity = Organization.class, field="name", condition="this.character=department.character AND department.org_id=id") private String orgName; } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserExtVO.java b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserExtVO.java new file mode 100644 index 0000000000000000000000000000000000000000..be86b4e4cfad65617e93d68a1ac3646d38b32b3b --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/binder/vo/UserExtVO.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.binder.vo; + +import com.diboot.core.binding.annotation.BindField; +import com.diboot.core.binding.annotation.BindFieldList; +import com.diboot.core.binding.query.BindQuery; +import com.diboot.core.binding.query.Comparison; +import com.diboot.core.binding.query.Strategy; +import diboot.core.test.binder.entity.Department; +import diboot.core.test.binder.entity.Organization; +import diboot.core.test.binder.entity.Role; +import diboot.core.test.binder.entity.User; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * User DTO + * @author mazc@dibo.ltd + * @version v2.0 + * @date 2018/12/27 + */ +@Getter +@Setter +public class UserExtVO extends User { + + // 字段关联+主从表附属条件 + @BindField(entity= Department.class, field = "name", condition="this.department_id=id AND this.gender='F' AND parent_id > 0") + private String deptName; + + // 字段关联+主从表附属条件 + @BindField(entity= Department.class, field = "name", condition="this.department_id=id AND this.gender like 'F%' AND parent_id > 0") + private String deptNameLike; + + // 字段关联+主从表附属条件 + @BindField(entity= Department.class, field = "name", condition="this.department_id=id AND this.gender IN('F', 'U') AND parent_id > 0") + private String deptNameIn; + + // 字段关联 + @BindField(entity= Department.class, field = "id", condition="this.department_id=id AND parent_id >= 0 AND this.department_id=10002") // + private Long deptId; + + @BindFieldList(entity = Role.class, field = "code", condition = "this.id=user_role.user_id AND user_role.role_id=id AND this.department_id IN (10002,10001)") + private List roleCodes; + +} \ No newline at end of file diff --git a/diboot-core-starter/src/test/java/diboot/core/test/config/SpringMvcConfig.java b/diboot-core-starter/src/test/java/diboot/core/test/config/SpringMvcConfig.java index 4904092030aa244c7db01e7a5cdd8f055c8749ad..a6f5fc89ceff9fc16149ef205378a0db86c21fdd 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/config/SpringMvcConfig.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/config/SpringMvcConfig.java @@ -17,18 +17,28 @@ package diboot.core.test.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.diboot.core.converter.*; import com.diboot.core.handler.DataAccessControlInterceptor; +import com.diboot.core.util.ContextHelper; import com.diboot.core.util.D; -import com.diboot.core.util.DateConverter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -39,6 +49,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.math.BigInteger; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -61,42 +75,78 @@ public class SpringMvcConfig implements WebMvcConfigurer { @Value("${spring.jackson.time-zone:GMT+8}") private String defaultTimeZone; + @Value("${spring.jackson.default-property-inclusion:NON_NULL}") + private JsonInclude.Include defaultPropertyInclusion; + /** - * 覆盖Jackson转换 - **/ - @Override - public void extendMessageConverters(List> converters) { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - ObjectMapper objectMapper = converter.getObjectMapper(); - // Long转换成String避免JS超长问题 - SimpleModule simpleModule = new SimpleModule(); + * 默认配置 ObjectMapper, 并允许用户覆盖 + * + * @return Jackson2ObjectMapperBuilderCustomizer + */ + @Bean + @ConditionalOnMissingBean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { + return builder -> { + // Long转换成String避免JS超长问题 + builder.serializerByType(Long.class, ToStringSerializer.instance); + builder.serializerByType(Long.TYPE, ToStringSerializer.instance); + builder.serializerByType(BigInteger.class, ToStringSerializer.instance); - // 不显示为null的字段 - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - simpleModule.addSerializer(Long.class, ToStringSerializer.instance); - simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); - simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance); + // 支持java8时间类型 + // LocalDateTime + DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern(D.FORMAT_DATETIME_Y4MDHMS); + builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(localDateTimeFormatter)); + builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(localDateTimeFormatter)); + // LocalDate + DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern(D.FORMAT_DATE_Y4MD); + builder.serializerByType(LocalDate.class, new LocalDateSerializer(localDateFormatter)); + builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(localDateFormatter)); + // LocalTime + DateTimeFormatter localTimeFormatter = DateTimeFormatter.ofPattern(D.FORMAT_TIME_HHmmss); + builder.serializerByType(LocalTime.class, new LocalTimeSerializer(localTimeFormatter)); + builder.deserializerByType(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter)); - objectMapper.registerModule(simpleModule); - // 时间格式化 - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.setTimeZone(TimeZone.getTimeZone(defaultTimeZone)); - SimpleDateFormat dateFormat = new SimpleDateFormat(defaultDatePattern) { - @Override - public Date parse(String dateStr) { - return D.fuzzyConvert(dateStr); - } + // 设置序列化包含策略 + builder.serializationInclusion(defaultPropertyInclusion); + // 时间格式化 + builder.failOnUnknownProperties(false); + builder.timeZone(TimeZone.getTimeZone(defaultTimeZone)); + SimpleDateFormat dateFormat = new SimpleDateFormat(defaultDatePattern) { + @Override + public Date parse(String dateStr) { + return D.fuzzyConvert(dateStr); + } + }; + builder.dateFormat(dateFormat); }; - objectMapper.setDateFormat(dateFormat); - // 设置格式化内容 - converter.setObjectMapper(objectMapper); + } - converters.add(0, converter); + @Bean + @ConditionalOnMissingBean + public HttpMessageConverters jacksonHttpMessageConverters() { + return new HttpMessageConverters(jacksonMessageConverter()); + } + + @Bean + @ConditionalOnMissingBean + public MappingJackson2HttpMessageConverter jacksonMessageConverter(){ + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + // 优先使用全局默认ObjectMapper, 保证ObjectMapper全局配置相同 + ObjectMapper objectMapper = ContextHelper.getBean(ObjectMapper.class); + if (objectMapper == null) { + objectMapper = converter.getObjectMapper(); + } + converter.setObjectMapper(objectMapper); + return converter; } @Override public void addFormatters(FormatterRegistry registry) { - registry.addConverter(new DateConverter()); + registry.addConverter(new Date2LocalDateConverter()); + registry.addConverter(new Date2LocalDateTimeConverter()); + registry.addConverter(new String2DateConverter()); + registry.addConverter(new String2BooleanConverter()); + registry.addConverter(new Timestamp2LocalDateTimeConverter()); } /** diff --git a/diboot-core-starter/src/test/java/diboot/core/test/query/TestJoinQuery.java b/diboot-core-starter/src/test/java/diboot/core/test/query/TestJoinQuery.java index 3bc441c76fd506f07e73659bea708081253eeba5..4f1ffde54f5094f62e1a7d8262a5187d26a487a5 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/query/TestJoinQuery.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/query/TestJoinQuery.java @@ -27,6 +27,7 @@ import diboot.core.test.binder.dto.UserDTO; import diboot.core.test.binder.entity.Department; import diboot.core.test.binder.entity.User; import diboot.core.test.binder.service.DepartmentService; +import diboot.core.test.binder.service.UserService; import diboot.core.test.binder.vo.DepartmentVO; import diboot.core.test.config.SpringMvcConfig; import org.apache.ibatis.jdbc.SQL; @@ -56,6 +57,9 @@ public class TestJoinQuery { @Autowired DepartmentService departmentService; + @Autowired + UserService userService; + @Test public void testDateCompaire(){ Department example = departmentService.list(null).get(0); @@ -210,6 +214,21 @@ public class TestJoinQuery { Assert.assertTrue(builderResultList.size() == 1); } + /** + * 测试有中间表的动态sql join + */ + @Test + public void testDynamicSqlQueryWithMiddleTable2() { + // 初始化DTO,测试不涉及关联的情况 + UserDTO dto = new UserDTO(); + dto.setRoleId(101L); + List builderResultList = QueryBuilder.toDynamicJoinQueryWrapper(dto).queryList(User.class); + Assert.assertTrue(builderResultList.size() == 1); + QueryWrapper queryWrapper = QueryBuilder.toQueryWrapper(dto); + List userList = userService.getEntityList(queryWrapper, new Pagination()); + Assert.assertTrue(userList.size() > 0); + } + @Test public void testBindQueryGroup(){ DepartmentDTO departmentDTO = new DepartmentDTO(); @@ -227,6 +246,24 @@ public class TestJoinQuery { Assert.assertTrue(list.size() > 5); } + + /** + * 测试空值和null + */ + @Test + public void testNullEmptyQuery() { + Department entity = new Department(); + entity.setName("测试组"); + entity.setOrgId(100001L); + QueryWrapper queryWrapper = QueryBuilder.toQueryWrapper(entity); + System.out.println(queryWrapper.getExpression()); + Assert.assertTrue(queryWrapper.getSqlSegment().contains("parent_id IS NULL")); + + entity.setParentId(10001L); + queryWrapper = QueryBuilder.toQueryWrapper(entity); + Assert.assertTrue(queryWrapper.getSqlSegment().contains("parent_id = #{")); + } + @Test public void test(){ String sql = buildCheckDeletedColSql("test"); diff --git a/diboot-core-starter/src/test/java/diboot/core/test/service/BaseServiceTest.java b/diboot-core-starter/src/test/java/diboot/core/test/service/BaseServiceTest.java index b2ac5946a2dc0f20f76eacc5d291d4cefc3c8e30..d44bccc7d40b6ec3459b3a707b06302fafa9185e 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/service/BaseServiceTest.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/service/BaseServiceTest.java @@ -36,6 +36,7 @@ import diboot.core.test.binder.entity.CcCityInfo; import diboot.core.test.binder.entity.Department; import diboot.core.test.binder.entity.User; import diboot.core.test.binder.entity.UserRole; +import diboot.core.test.binder.service.CcCityInfoService; import diboot.core.test.binder.service.DepartmentService; import diboot.core.test.binder.service.UserService; import diboot.core.test.binder.vo.SimpleDictionaryVO; @@ -299,13 +300,6 @@ public class BaseServiceTest { Assert.assertTrue(exists); } - @Test - public void testContextHelper(){ - String database = ContextHelper.getDatabaseType(); - System.out.println(database); - Assert.assertTrue(database.equals("mysql") || database.equals("oracle")); - } - @Test public void testGetValuesOfField(){ QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -450,13 +444,17 @@ public class BaseServiceTest { .select("sum(id) as count"); Map map = dictionaryService.getMap(queryWrapper); Assert.assertTrue(map!=null); - Assert.assertTrue(map.get("count") != null); + Assert.assertTrue(MapUtils.getIgnoreCase(map, "count") != null); + Assert.assertTrue(MapUtils.getIgnoreCase(map, "COUNT") != null); } @Test public void tesExecuteMultipleUpdateSqls(){ List sqls = new ArrayList<>(); Long dictId = 20000l; + if(ContextHelper.getDatabaseType().equals("dm")) { + sqls.add("SET IDENTITY_INSERT dictionary ON"); + } sqls.add("INSERT INTO dictionary(id, parent_id, type, item_name) VALUES("+dictId+", 0, 'TEST', '')"); sqls.add("DELETE FROM dictionary WHERE id=20000 AND is_deleted=1"); boolean success = SqlFileInitializer.executeMultipleUpdateSqlsWithTransaction(sqls); @@ -504,4 +502,10 @@ public class BaseServiceTest { Assert.assertTrue(dictionaries.size() == 0); } + @Test + public void testDelete(){ + CcCityInfo cityInfo = ContextHelper.getBean(CcCityInfoService.class).list().get(0); + //ContextHelper.getBean(CcCityInfoService.class).removeById(cityInfo.getId()); + } + } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/service/EnhancedConversionService2.java b/diboot-core-starter/src/test/java/diboot/core/test/service/EnhancedConversionService2.java new file mode 100644 index 0000000000000000000000000000000000000000..d07f718d796e84ad72a00cb416d574415ce96950 --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/service/EnhancedConversionService2.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.service; + +import com.diboot.core.converter.*; +import org.springframework.context.annotation.Primary; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.stereotype.Component; + +/** + * 扩展的转换service 测试覆盖 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +@Primary +@Component +public class EnhancedConversionService2 extends EnhancedConversionService { + + public EnhancedConversionService2(){ + super(); + //添加扩展 + //addConverter(new String2DateConverter()); + } + +} diff --git a/diboot-core-starter/src/test/java/diboot/core/test/util/BeanUtilsTest.java b/diboot-core-starter/src/test/java/diboot/core/test/util/BeanUtilsTest.java index aa0b856d4ae553c7ae25c396c60eccd4c692fc49..c4fda8cf6d5f3a9ca360d65a12a8385699aaaf51 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/util/BeanUtilsTest.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/util/BeanUtilsTest.java @@ -18,18 +18,24 @@ package diboot.core.test.util; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.service.IService; import com.diboot.core.binding.cache.BindingCacheManager; +import com.diboot.core.config.Cons; import com.diboot.core.entity.Dictionary; import com.diboot.core.service.DictionaryService; import com.diboot.core.util.BeanUtils; import com.diboot.core.util.JSON; +import com.diboot.core.util.S; +import com.diboot.core.util.V; import com.diboot.core.vo.DictionaryVO; import com.sun.management.OperatingSystemMXBean; import diboot.core.test.StartupApplication; +import diboot.core.test.binder.entity.TestRegion; +import diboot.core.test.binder.entity.TestUploadFile; import diboot.core.test.binder.entity.User; import diboot.core.test.config.SpringMvcConfig; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.BeanWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; @@ -37,6 +43,7 @@ import org.springframework.test.context.junit4.SpringRunner; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; +import java.sql.Timestamp; import java.util.*; /** @@ -105,13 +112,13 @@ public class BeanUtilsTest { @Test public void testDateCopy(){ - //User user1 = new User().setUsername("test").setGender("").setBirthdate(new Date()).setLocalDatetime(LocalDate.now()); User user2 = new User(); Map map = new HashMap<>(); map.put("username", "test"); map.put("birthdate", "1980-10-12"); map.put("localDatetime", new Date()); - + BeanUtils.setProperty(user2, "birthdate", "1980-10-12"); + Assert.assertTrue(user2.getBirthdate() != null); BeanUtils.bindProperties(user2, map); Assert.assertTrue(user2.getLocalDatetime() != null); System.out.println(JSON.stringify(user2)); @@ -224,7 +231,7 @@ public class BeanUtilsTest { Assert.assertEquals(list.get(0).getChildren().size(), 5); list = BeanUtils.convertList(dictionaryList, DictionaryVO.class); - list = BeanUtils.buildTree(list, 0, "parentId", "children"); + list = BeanUtils.buildTree(list, 0, Cons.FieldName.id.name()); Assert.assertEquals(list.size(), 1); Assert.assertEquals(list.get(0).getChildren().size(), 5); @@ -243,4 +250,105 @@ public class BeanUtilsTest { } } + @Test + public void testBuildTreeWithUUID(){ + // 准备节点数据 + List regionList = new ArrayList<>(); + TestRegion province1 = new TestRegion().setUuid(S.newUuid()).setName("江苏省").setLevel(1).setCode("JS"); + regionList.add(province1); + TestRegion province2 = new TestRegion().setUuid(S.newUuid()).setName("浙江省").setLevel(1).setCode("ZJ"); + regionList.add(province2); + + TestRegion city1 = new TestRegion().setUuid(S.newUuid()).setName("南京市").setLevel(2).setCode("NJ").setParentId(province1.getUuid()); + regionList.add(city1); + TestRegion city2 = new TestRegion().setUuid(S.newUuid()).setName("苏州市").setLevel(2).setCode("SZ").setParentId(province1.getUuid()); + regionList.add(city2); + TestRegion city3 = new TestRegion().setUuid(S.newUuid()).setName("杭州市").setLevel(2).setCode("HZ").setParentId(province2.getUuid()); + regionList.add(city3); + + TestRegion area1 = new TestRegion().setUuid(S.newUuid()).setName("建邺区").setLevel(3).setCode("JY").setParentId(city1.getUuid()); + regionList.add(area1); + TestRegion area2 = new TestRegion().setUuid(S.newUuid()).setName("工业园区").setLevel(3).setCode("SIP").setParentId(city2.getUuid()); + regionList.add(area2); + TestRegion area3 = new TestRegion().setUuid(S.newUuid()).setName("姑苏区").setLevel(3).setCode("GS").setParentId(city2.getUuid()); + regionList.add(area3); + + // 构建树形结构 + List list = BeanUtils.buildTree(regionList, null, "uuid"); + //BeanUtils.buildTree(regionList, null, "uuid", Cons.FieldName.parentId.name(), Cons.FieldName.children.name()); + + // 检测结果 + Assert.assertEquals(list.size(), 2); + for(TestRegion region : list) { + if(region.getCode().equals("JS")) { + Assert.assertEquals(region.getChildren().size(), 2); + for(TestRegion city : region.getChildren()) { + if(city.getCode().equals("NJ")) { + Assert.assertEquals(city.getChildren().size(), 1); + } + else{ + Assert.assertEquals(city.getChildren().size(), 2); + } + } + } + else { + Assert.assertEquals(region.getChildren().size(), 1); + Assert.assertNull(region.getChildren().get(0).getChildren()); + } + } + } + + @Test + public void testSetProperty(){ + TestUploadFile object = new TestUploadFile(); + BeanUtils.setProperty(object, "id", 123l); + BeanUtils.setProperty(object, "storagePath", "/test/xxx"); + Assert.assertTrue(object.getStoragePath() != null); + BeanUtils.setProperty(object, "createTime", new Timestamp(System.currentTimeMillis())); + Assert.assertTrue(object.getCreateTime() != null); + BeanUtils.setProperty(object, "createTime", new Date()); + Assert.assertTrue(object.getCreateTime() != null); + BeanUtils.setProperty(object, "localDateTime", new Timestamp(System.currentTimeMillis())); + Assert.assertTrue(object.getLocalDateTime() != null); + } + + /** + * 测试属性赋值的性能优化 + */ + @Test + public void testSetPropertyOptimize(){ + User user2 = new User(); + long begin = System.currentTimeMillis(); + for(int i=0; i< 10000; i++){ + BeanUtils.setProperty(user2, "username", "test"); + BeanUtils.setProperty(user2, "birthdate", "1980-10-12"); + BeanUtils.setProperty(user2, "localDatetime", new Date()); + } + long end = System.currentTimeMillis(); + long takes1 = (end - begin); + begin = System.currentTimeMillis(); + + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(user2); + for(int i=0; i< 10000; i++){ + beanWrapper.setPropertyValue("username", "test"); + beanWrapper.setPropertyValue("birthdate", "1980-10-12"); + beanWrapper.setPropertyValue("localDatetime", new Date()); + } + end = System.currentTimeMillis(); + long takes2 = (end - begin); + System.out.println(takes1 + " ms , after: " + takes2 + " ms") ; + Assert.assertTrue(takes2 < takes1); + } + + @Test + public void testConvertType() { + Collection list = new ArrayList<>(); + list.add("123"); + list.add("234"); + + Collection list2 = BeanUtils.convertIdValuesToType(list, Long.class); + Assert.assertTrue(list2.size() == 2); + Assert.assertTrue(V.equals(list2.iterator().next(), 123l)); + } + } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/util/CacheTest.java b/diboot-core-starter/src/test/java/diboot/core/test/util/CacheTest.java index 785b2b573bf3cd365b2c8aed357da8db6d48da91..5cd5896241ebd4c5e911d9adaab8cc8e8532cd4b 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/util/CacheTest.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/util/CacheTest.java @@ -42,14 +42,16 @@ public class CacheTest { Assert.assertTrue(dictionaryMapCache.get("G").getType().equals("GENDER")); boolean isExpired = cacheManager.isExpired(CACHE_TMPL, "version"); Assert.assertFalse(isExpired); - /*try{ + try{ Thread.sleep(60*1000); } catch (Exception e){ e.printStackTrace(); } + dictionaryMapCache = cacheManager.getCacheObj(CACHE_TMPL, "version", Map.class); + Assert.assertTrue(dictionaryMapCache == null); isExpired = cacheManager.isExpired(CACHE_TMPL, "version"); - Assert.assertTrue(isExpired);*/ + Assert.assertTrue(isExpired); } } diff --git a/diboot-core-starter/src/test/java/diboot/core/test/util/ContextHelperTest.java b/diboot-core-starter/src/test/java/diboot/core/test/util/ContextHelperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1c1226dc78488ecebc751843fe121ed546d85f58 --- /dev/null +++ b/diboot-core-starter/src/test/java/diboot/core/test/util/ContextHelperTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.core.test.util; + +import com.diboot.core.service.BaseService; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.ContextHelper; +import diboot.core.test.StartupApplication; +import diboot.core.test.binder.entity.DemoTest; +import diboot.core.test.binder.service.DemoTestService; +import diboot.core.test.binder.service.impl.DemoTestServiceImpl; +import diboot.core.test.config.SpringMvcConfig; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * ContextHelper单元测试 + * @author mazc@dibo.ltd + * @version 1.0 + * @date 2022/06/22 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) +public class ContextHelperTest { + + @Test + public void testDatabaseType(){ + String dbType = ContextHelper.getDatabaseType(); + Assert.assertTrue(dbType.equals("mysql") || dbType.equals("dm")); + + BaseService baseService = ContextHelper.getBaseServiceByEntity(DemoTest.class); + Assert.assertTrue(BeanUtils.getTargetClass(baseService).getName().equals(DemoTestServiceImpl.class.getName())); + + } + +} diff --git a/diboot-core-starter/src/test/java/diboot/core/test/util/JsonTest.java b/diboot-core-starter/src/test/java/diboot/core/test/util/JsonTest.java index 5b33ce5d7b9573e71deee87bc6d986c34f3a4cd6..50cea70c490be67db449996e367e3242bbfd3ced 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/util/JsonTest.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/util/JsonTest.java @@ -20,10 +20,16 @@ import com.diboot.core.util.JSON; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Pagination; import com.diboot.core.vo.PagingJsonResult; +import diboot.core.test.StartupApplication; import diboot.core.test.binder.entity.Role; import diboot.core.test.binder.entity.User; +import diboot.core.test.config.SpringMvcConfig; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.Date; @@ -35,6 +41,9 @@ import java.util.List; * @version 1.0 * @date 2021/01/22 */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) public class JsonTest { @Test @@ -51,7 +60,6 @@ public class JsonTest { Assert.assertTrue("1988-09-12".equals(D.convert2DateString(user2.getBirthdate()))); } - @Test public void testJsonConvert(){ Role role = new Role(); diff --git a/diboot-core-starter/src/test/java/diboot/core/test/util/VTest.java b/diboot-core-starter/src/test/java/diboot/core/test/util/VTest.java index 39fd7db0259b7d320542dd2f9aa9a7da36a233e5..b134ff2848c02b68474ebb14bba22f76ced295b0 100644 --- a/diboot-core-starter/src/test/java/diboot/core/test/util/VTest.java +++ b/diboot-core-starter/src/test/java/diboot/core/test/util/VTest.java @@ -20,11 +20,18 @@ import com.diboot.core.util.BeanUtils; import com.diboot.core.util.JSON; import com.diboot.core.util.S; import com.diboot.core.util.V; +import diboot.core.test.StartupApplication; +import diboot.core.test.config.SpringMvcConfig; import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -34,6 +41,9 @@ import java.util.List; * @version 1.0 * @date 2019/06/02 */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) public class VTest { @Test @@ -94,7 +104,7 @@ public class VTest { Assert.assertTrue(V.equals(field.getType().getName(), "java.util.Date")); Dictionary dictionary = BeanUtils.cloneBean(new Dictionary()); - Dictionary dictionary1 = JSON.parseObject("{}", Dictionary.class); + Dictionary dictionary1 = JSON.parseObject("{\"parentId\":0}", Dictionary.class); Assert.assertTrue(V.equals(dictionary.getClass(), dictionary1.getClass())); field = BeanUtils.extractField(Dictionary.class, "isDeletable"); @@ -133,4 +143,12 @@ public class VTest { Assert.assertTrue(isValidCol); } + @Test + public void testContains(){ + List IGNORE_FIELDS = Arrays.asList("id", "is_deleted"); + Assert.assertTrue(V.contains(IGNORE_FIELDS, "is_deleted")); + Assert.assertTrue(V.containsIgnoreCase(IGNORE_FIELDS, "IS_DELETED")); + Assert.assertTrue(V.notContainsIgnoreCase(IGNORE_FIELDS, "DELETED")); + } + } diff --git a/diboot-core-starter/src/test/resources/application.properties b/diboot-core-starter/src/test/resources/application.properties index a030edc62bf22ce836fdd1a28da8dbe7026ad0a9..6adc59da6124eea815d0ae02f5117a37a1dd719d 100644 --- a/diboot-core-starter/src/test/resources/application.properties +++ b/diboot-core-starter/src/test/resources/application.properties @@ -1,12 +1,19 @@ server.port=8080 server.servlet.context-path=/api -#datasource config +# Mysql datasource config spring.datasource.url=jdbc:mysql://localhost:3306/playground?characterEncoding=utf8&serverTimezone=GMT%2B8 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=diboot spring.datasource.password=123456 spring.datasource.hikari.maximum-pool-size=5 -spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver + +# DM datasource config +#spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver +#spring.datasource.url=jdbc:dm://localhost:3652?schema=DIBOOT_PLAYGROUND&characterEncoding=utf8&serverTimezone=GMT%2B8 +#spring.datasource.username=SYSDBA +#spring.datasource.password=123456 +#spring.datasource.hikari.maximum-pool-size=5 spring.main.allow-bean-definition-overriding=true diff --git a/diboot-core-starter/src/test/resources/unittest-dm.sql b/diboot-core-starter/src/test/resources/unittest-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..ab06b46253255abc9e1371c6472c7185a84395b9 --- /dev/null +++ b/diboot-core-starter/src/test/resources/unittest-dm.sql @@ -0,0 +1,186 @@ +--set schema diboot_unittest; + +-- create schema diboot_example collate utf8_general_ci; +-- 建表 +-- 字典表 +create table dictionary ( + id BIGINT primary key, + parent_id BIGINT default 0 not null, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(150), + type VARCHAR(150) not null, + item_name VARCHAR(300) not null, + item_value VARCHAR(300), + description VARCHAR(300), + extdata VARCHAR(600), + sort_id SMALLINT default 99 not null, + is_deletable BIT default 1 not null, + is_editable BIT default 1 not null, + is_deleted BIT default 0 not null, + create_time DATETIME null +); + +create table department +( + id BIGINT primary key, + parent_id bigint default 0 not null, + org_id bigint not null , + name varchar(50) not null, + extdata varchar(100) null , + character varchar(100) null , + is_deleted BIT default 0 not null , + create_time datetime null +); + +create table organization +( + id INT identity ( 10000,1) primary key, + parent_id int default 0 not null, + name varchar(100) not null, + telphone varchar(20) null, + manager_id bigint not null, + is_deleted BIT default 0 not null, + create_time datetime default CURRENT_TIMESTAMP not null +); + +create table role +( + id INT identity ( 10000,1) primary key, + name varchar(20) null, + code varchar(20) null, + is_deleted BIT default 0 null, + create_time datetime default CURRENT_TIMESTAMP null +); + +create table "USER" +( + id INT identity ( 10000,1) primary key, + department_id int default 0 not null, + username varchar(20) null, + gender varchar(20) null, + birthdate date null, + character varchar(100) null, + is_deleted BIT default 0 null, + create_time datetime default CURRENT_TIMESTAMP null, + local_datetime datetime null +); + +create table user_role +( + user_type varchar(20) not null, + user_id int not null, + role_id int not null, + primary key (user_id, role_id) +); + +create table cc_city_info +( + id INT identity ( 10000,1) primary key, + parent_id int null, + region_id int not null, + region_name varchar(100) null +); + +CREATE TABLE db_goods_goods_info ( + goods_id bigint DEFAULT NULL, + goods_nm varchar(10) DEFAULT NULL, + create_ts datetime default CURRENT_TIMESTAMP null, + update_ts datetime null, + is_del BIT DEFAULT 0 +); + +CREATE TABLE db_purchase_rel_plan_goods ( + rel_id bigint DEFAULT NULL, + purchase_form_plan_id bigint DEFAULT NULL, + goods_id bigint DEFAULT NULL, + create_ts datetime default CURRENT_TIMESTAMP null, + update_ts datetime null, + is_del BIT DEFAULT 0 +); + +CREATE TABLE db_purchase_form_plan ( + purchase_form_plan_id bigint DEFAULT NULL, + name varchar(100) DEFAULT NULL, + is_del BIT DEFAULT 0, + create_ts datetime default CURRENT_TIMESTAMP null, + update_ts datetime null +); + +-- 上传文件表 + +CREATE TABLE test_upload_file ( + uuid varchar(32) NOT NULL primary key, + rel_obj_type varchar(50) DEFAULT NULL, + rel_obj_id varchar(32) DEFAULT NULL, + rel_obj_field varchar(50) DEFAULT NULL, + file_name varchar(100) NOT NULL, + storage_path varchar(200) NOT NULL, + access_url varchar(200) NULL, + file_type varchar(20) DEFAULT NULL, + is_deleted BIT default 0 not null, + create_time datetime default CURRENT_TIMESTAMP not null +); + +-- playground.demo_test definition + +CREATE TABLE demo_test ( + id bigint identity ( 10000,1) primary key, + is_deleted BIT NOT NULL DEFAULT 0, + name varchar(100) NOT NULL, + age bigint NOT NULL, + id_card varchar(100) DEFAULT NULL, + mobile_phone varchar(100) DEFAULT NULL, + email varchar(100) DEFAULT NULL, + sex varchar(100) DEFAULT NULL, + birthday date DEFAULT NULL, + sss_img varchar(200) DEFAULT NULL, + mmm_img varchar(1000) DEFAULT NULL , + data_file varchar(1000) DEFAULT NULL , + file_test varchar(200) DEFAULT NULL , + create_by bigint DEFAULT '0' , + create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + update_time datetime NULL DEFAULT CURRENT_TIMESTAMP +); + +-- playground.demo_test_join definition + +CREATE TABLE demo_test_join ( + id bigint identity ( 10000,1) primary key, + demo_test_id bigint DEFAULT NULL, + name varchar(100) DEFAULT NULL , + email varchar(100) DEFAULT NULL , + is_deleted BIT NOT NULL DEFAULT 0, + create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- 初始化样例数据 +INSERT INTO department (id, parent_id, org_id, name, character) VALUES (10001, 0, 100001, '产品部', 'WW'), (10002, 10001, 100001, '研发组', '1001'), (10003, 10001, 100001, '测试组', '1001,1002'), + (10004, 10001, 100001, '市场部', '1001,1002'), (10005, 10003, 100001, '自动化测试', null), (10006, 10003, 100001, '功能测试', null); +INSERT INTO dictionary (id, parent_id, app_module, type, item_name, item_value) VALUES (1, 0, '', 'GENDER', '性别', null), (2, 1, '', 'GENDER', '男', 'M'), (3, 1, '', 'GENDER', '女', 'F'); +SET IDENTITY_INSERT organization ON; +INSERT INTO organization (id, parent_id, name, telphone, manager_id) VALUES (100001, 0, '苏州帝博', '0512-62988949', 1001), (100002, 0, '成都帝博', '028-62988949', 1002); +SET IDENTITY_INSERT role ON; +INSERT INTO role (id, name, code) VALUES (101, '管理员', 'ADMIN'), (102, '操作员', 'OPERATOR'); +SET IDENTITY_INSERT "USER" ON; +INSERT INTO "USER" (id, department_id, username, gender, character) VALUES (1001, 10002, '张三', 'M', 'test123456'), (1002, 10002, '李四', 'F', 'test123456,test234567'), (1003, 10001, '王五', 'M', 'WW'); +INSERT INTO user_role (user_type, user_id, role_id) VALUES ('SysUser', 1001, 101),('SysUser', 1001, 102),('OrgUser', 1002, 102); +SET IDENTITY_INSERT cc_city_info ON; +INSERT INTO cc_city_info (id, parent_id, region_id, region_name) VALUES (10000, 0, 10000, '江苏省'), (10010, 10000, 10010, '苏州市'), (10020, 10010, 10020, '园区'); +INSERT INTO db_goods_goods_info (goods_id, goods_nm, is_del) VALUES(1001, 'abcde', 0), (1002, 'abcd', 0); +INSERT INTO db_purchase_rel_plan_goods(rel_id, purchase_form_plan_id, goods_id, is_del)VALUES(1, 1, 1001, 0), (2, 1, 1002, 0); +INSERT INTO db_purchase_form_plan(purchase_form_plan_id, name, is_del)VALUES(1, '5月份采购计划', 0); +INSERT INTO test_upload_file(uuid, rel_obj_type, rel_obj_id, file_name, access_url, storage_path) +values ('test123456', 'IamUser', 1001, '123456.jpg', 'http://www.baidu.com', '/temp'), + ('test234567', 'IamUser', 1001, '234567.jpg', 'http://www.baidu.com', '/temp'); +SET IDENTITY_INSERT demo_test ON; +INSERT INTO demo_test +(id, is_deleted, name, age, id_card, mobile_phone, email, sex, birthday, sss_img, mmm_img, data_file, file_test, create_by) +VALUES(10000074, 0, '666', 666, 'kdt3kHX4lAEshODwXMaqAg==', 'dI7AKyanaPYiWL0c5CfuPQ==', 'kdt3kHX4lAEshODwXMaqAg==', NULL, NULL, NULL, NULL, NULL, NULL, 0), + (10000075, 0, '1', 111, 'kBzIU3XgeowKFQnyeGZfZZHbd5B1+JQBLITg8FzGqgI=', 'AYdMZnNVJwYmpHh8VmC11A==', '5tEbe9Q9hti2Z0spAE5fsA==', NULL, NULL, NULL, NULL, NULL, NULL, 0); +SET IDENTITY_INSERT demo_test_join ON; +INSERT INTO demo_test_join (id, demo_test_id, name, email) +VALUES(10000081, 10000074, '111', 'L9vrF7wJBKbbLRZChI33WA=='), + (10000084, 10000074, '114', 'A2Y6CZ4tv0V7QxPbq9EYkg=='), + (10000089, 10000075, '119', 'VVnnuR7fXzsIHCMilEsgLw=='), + (10000091, 10000075, '112', 'BqAefnSadfixkCebXakUDg=='), + (10000093, 10000075, '114', 'A2Y6CZ4tv0V7QxPbq9EYkg=='); diff --git a/diboot-core-starter/src/test/resources/init-mysql.sql b/diboot-core-starter/src/test/resources/unittest-mysql.sql similarity index 99% rename from diboot-core-starter/src/test/resources/init-mysql.sql rename to diboot-core-starter/src/test/resources/unittest-mysql.sql index ea741edb150173b1996d182d4aab84195cf8c570..80cece06068c9f68fa6ff65cedd82e0dc5e9bbd4 100644 --- a/diboot-core-starter/src/test/resources/init-mysql.sql +++ b/diboot-core-starter/src/test/resources/unittest-mysql.sql @@ -155,7 +155,7 @@ CREATE TABLE `demo_test_join` ( ) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关联测试'; -- 初始化样例数据 -INSERT INTO department (id, parent_id, org_id, name, `character`) VALUES (10001, 0, 100001, '产品部', 'WW'), (10002, 10001, 100001, '研发组', '1001'), (10003, 10001, 100001, '测试组', '1001,1001'), +INSERT INTO department (id, parent_id, org_id, name, `character`) VALUES (10001, 0, 100001, '产品部', 'WW'), (10002, 10001, 100001, '研发组', '1001'), (10003, 10001, 100001, '测试组', '1001,1002'), (10004, 10001, 100001, '市场部', '1001,1002'), (10005, 10003, 100001, '自动化测试', null), (10006, 10003, 100001, '功能测试', null); INSERT INTO dictionary (id, parent_id, app_module, type, item_name, item_value) VALUES (1, 0, '', 'GENDER', '性别', null), (2, 1, '', 'GENDER', '男', 'M'), (3, 1, '', 'GENDER', '女', 'F'); INSERT INTO organization (id, parent_id, name, telphone, manager_id) VALUES (100001, 0, '苏州帝博', '0512-62988949', 1001), (100002, 0, '成都帝博', '028-62988949', 1002); diff --git a/diboot-core/pom.xml b/diboot-core/pom.xml index e3ea9cd8afd0bc16b32eaef2ddedb12e975b1c9e..22d0b45d4dbf40079be0f5fd8e6636ea48b9558d 100644 --- a/diboot-core/pom.xml +++ b/diboot-core/pom.xml @@ -7,11 +7,11 @@ com.diboot diboot-root - 2.5.0 + 2.6.0 diboot-core - 2.5.0 + 2.6.0 jar diboot core project @@ -60,7 +60,7 @@ org.springframework.cloud spring-cloud-openfeign-core - 3.1.1 + 3.1.2 provided @@ -114,4 +114,4 @@ - \ No newline at end of file + diff --git a/diboot-core/src/main/java/com/diboot/core/binding/JoinsBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/JoinsBinder.java index 5a54ce750fabc1672da8544ebdaf2855b4dbcf52..43c00eaf75bffa9b86bd288b589c5215b96a0aff 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/JoinsBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/JoinsBinder.java @@ -34,6 +34,7 @@ import com.diboot.core.util.S; import com.diboot.core.util.V; import com.diboot.core.vo.Pagination; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanWrapper; import java.lang.reflect.Field; import java.util.*; @@ -165,10 +166,11 @@ public class JoinsBinder { E entityInst = entityClazz.newInstance(); BeanUtils.bindProperties(entityInst, fieldValueMap); if (protectFieldHandler != null) { + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(entityInst); ParserCache.getProtectFieldList(entityClazz).forEach(fieldName -> { String value = BeanUtils.getStringProperty(entityInst, fieldName); if (value != null) { - BeanUtils.setProperty(entityInst, fieldName, protectFieldHandler.decrypt(entityClazz,fieldName,value)); + beanWrapper.setPropertyValue(fieldName, protectFieldHandler.decrypt(entityClazz,fieldName,value)); } }); } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/QueryBuilder.java b/diboot-core/src/main/java/com/diboot/core/binding/QueryBuilder.java index 5933fb68ee158c8ca172ba88313e40d0e0c82397..de940ce417e8b5377bfdb9c64b70705cc9f93185 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/QueryBuilder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/QueryBuilder.java @@ -199,14 +199,17 @@ public class QueryBuilder { Class clazz = getClass.apply(query); String fieldName = getFieldName.apply(query, entry.getKey()); if (ParserCache.getProtectFieldList(clazz).contains(fieldName)) { - buildQuery(queryWrapper.or(), Comparison.EQ, columnName, protectFieldHandler.encrypt(clazz, fieldName, value.toString())); + buildQuery(queryWrapper.or(), bindQuery, columnName, protectFieldHandler.encrypt(clazz, fieldName, value.toString())); continue; } } - buildQuery(queryWrapper.or(), bindQuery.comparison(), columnName, value); + buildQuery(queryWrapper.or(), bindQuery, columnName, value); } }); } else { + if(query == null && V.isEmpty(value)) { + continue; + } if (ignoreEmpty.test(value, query)) { continue; } @@ -215,11 +218,11 @@ public class QueryBuilder { Class clazz = getClass.apply(query); String fieldName = getFieldName.apply(query, entry.getKey()); if (ParserCache.getProtectFieldList(clazz).contains(fieldName)) { - buildQuery(wrapper, Comparison.EQ, columnName, protectFieldHandler.encrypt(clazz, fieldName, value.toString())); + buildQuery(wrapper, query, columnName, protectFieldHandler.encrypt(clazz, fieldName, value.toString())); continue; } } - buildQuery(wrapper, query != null ? query.comparison() : Comparison.EQ, columnName, value); + buildQuery(wrapper, query, columnName, value); } } return wrapper; @@ -229,14 +232,20 @@ public class QueryBuilder { * 建立条件 * * @param wrapper 条件包装器 - * @param comparison 比较类型 + * @param bindQuery 注解 * @param columnName 列名 * @param value 值 */ - private static void buildQuery(QueryWrapper wrapper, Comparison comparison, String columnName, Object value) { + private static void buildQuery(QueryWrapper wrapper, BindQuery bindQuery, String columnName, Object value) { + Comparison comparison = bindQuery != null ? bindQuery.comparison() : Comparison.EQ; switch (comparison) { case EQ: - wrapper.eq(columnName, value); + if(value == null && bindQuery != null && bindQuery.strategy().equals(Strategy.INCLUDE_NULL)) { + wrapper.isNull(columnName); + } + else{ + wrapper.eq(columnName, value); + } break; case IN: if (value.getClass().isArray()) { @@ -252,6 +261,20 @@ public class QueryBuilder { log.warn("字段类型错误:IN仅支持List及数组."); } break; + case NOT_IN: + if (value.getClass().isArray()) { + Object[] valueArray = (Object[]) value; + if (valueArray.length == 1) { + wrapper.ne(columnName, valueArray[0]); + } else if (valueArray.length >= 2) { + wrapper.notIn(columnName, valueArray); + } + } else if (value instanceof Collection) { + wrapper.notIn(!((Collection) value).isEmpty(), columnName, (Collection) value); + } else { + log.warn("字段类型错误:NOT_IN仅支持List及数组."); + } + break; case CONTAINS: case LIKE: wrapper.like(columnName, value); @@ -360,7 +383,8 @@ public class QueryBuilder { if (field.isAnnotationPresent(TableLogic.class) && V.equals(false, value)) { continue; } - if (value != null) { + BindQuery bindQuery = field.getAnnotation(BindQuery.class); + if (value != null || (bindQuery != null && bindQuery.strategy().equals(Strategy.INCLUDE_NULL))) { resultMap.put(fieldName, new FieldAndValue(field, value)); } } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/RelationsBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/RelationsBinder.java index cc7b59bfea4b456839f122f423e615365a9bdcfb..f753c62b275e5d87186a9fe20ad0b193eac53612 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/RelationsBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/RelationsBinder.java @@ -163,6 +163,15 @@ public class RelationsBinder { binderFutures.add(bindFieldFuture); } } + // 绑定count子项计数 + List countAnnoList = bindAnnotationGroup.getBindCountAnnotations(); + if(countAnnoList != null){ + for(FieldAnnotation anno : countAnnoList){ + // 绑定关联对象count计数 + CompletableFuture bindCountFuture = parallelBindingManager.doBindingCount(voList, anno); + binderFutures.add(bindCountFuture); + } + } // 执行绑定 CompletableFuture.allOf(binderFutures.toArray(new CompletableFuture[0])).join(); // 深度绑定 diff --git a/diboot-core/src/main/java/com/diboot/core/binding/annotation/BindCount.java b/diboot-core/src/main/java/com/diboot/core/binding/annotation/BindCount.java new file mode 100644 index 0000000000000000000000000000000000000000..b2f6a8f7b72a6bab905ddfa2247880992db6d4ec --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/binding/annotation/BindCount.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.binding.annotation; + +import java.lang.annotation.*; + +/** + * 绑定子项的条目计数 + * @author JerryMa + * @version v2.6.0 + * @date 2022/6/23 + * Copyright © diboot.com + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface BindCount { + /*** + * 绑定的Entity类 + * @return + */ + Class entity(); + + /*** + * JOIN连接条件 + * @return + */ + String condition(); +} \ No newline at end of file diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/BaseBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/BaseBinder.java index a71c59aeb8bf53f0b15bb461631b794d7a0e7176..66015409e0967e94614f6c8a91bb626c0813a866 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/BaseBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/BaseBinder.java @@ -23,8 +23,10 @@ import com.diboot.core.binding.binder.remote.RemoteBindDTO; import com.diboot.core.binding.cache.BindingCacheManager; import com.diboot.core.binding.helper.ResultAssembler; import com.diboot.core.binding.helper.WrapperHelper; +import com.diboot.core.binding.parser.FieldComparison; import com.diboot.core.binding.parser.MiddleTable; import com.diboot.core.binding.parser.PropInfo; +import com.diboot.core.binding.query.Comparison; import com.diboot.core.config.BaseConfig; import com.diboot.core.exception.InvalidUsageException; import com.diboot.core.service.BaseService; @@ -51,6 +53,10 @@ public abstract class BaseBinder { * VO注解对象中的join on对象列名集合 */ protected List annoObjJoinCols; + /*** + * VO注解对象中的join on对象附加过滤条件 + */ + protected List annoObjJoinFieldComparisons; /*** * DO对象中的关联join on对象列名集合 */ @@ -174,6 +180,29 @@ public abstract class BaseBinder { return this; } + /** + * join连接条件的附加条件,指定当前VO的取值方法和关联entity的取值方法 + * @param annoObjectFieldKey 当前VO的取值属性名 + * @param eqFilterConsVal 常量条件值 + * @return + */ + public BaseBinder joinOnFieldComparison(String annoObjectFieldKey, Comparison comparison, Object eqFilterConsVal){ + if(annoObjectFieldKey != null && eqFilterConsVal != null){ + if(annoObjJoinFieldComparisons == null){ + annoObjJoinFieldComparisons = new ArrayList<>(4); + } + String fieldName = this.annoObjPropInfo.getFieldByColumn(annoObjectFieldKey); + if(fieldName == null && this.annoObjPropInfo.getFieldToColumnMap().containsKey(annoObjectFieldKey)) { + fieldName = annoObjectFieldKey; + } + if(fieldName == null) { + throw new InvalidUsageException("字段/列 "+ annoObjectFieldKey +" 不存在"); + } + annoObjJoinFieldComparisons.add(new FieldComparison(fieldName, comparison, eqFilterConsVal)); + } + return this; + } + public BaseBinder andEQ(String fieldName, Object value){ queryWrapper.eq(toRefObjColumn(fieldName), formatValue(fieldName, value)); return this; @@ -283,10 +312,10 @@ public abstract class BaseBinder { protected void buildQueryWrapperJoinOn() { for (int i = 0; i < annoObjJoinCols.size(); i++) { String annoObjJoinOnCol = annoObjJoinCols.get(i); - List annoObjectJoinOnList = BeanUtils.collectToList(annoObjectList, toAnnoObjField(annoObjJoinOnCol)); + List annoObjectJoinOnList = BeanUtils.collectToList(getMatchedAnnoObjectList(), toAnnoObjField(annoObjJoinOnCol)); // 构建查询条件 - String refObjJoinOnCol = refObjJoinCols.get(i); if (V.notEmpty(annoObjectJoinOnList)) { + String refObjJoinOnCol = refObjJoinCols.get(i); List unpackAnnoObjectJoinOnList = V.notEmpty(this.splitBy) ? ResultAssembler.unpackValueList(annoObjectJoinOnList, this.splitBy) : annoObjectJoinOnList; queryWrapper.in(refObjJoinOnCol, unpackAnnoObjectJoinOnList); @@ -297,6 +326,14 @@ public abstract class BaseBinder { } } + /** + * 获取匹配的 + * @return + */ + protected List getMatchedAnnoObjectList() { + return filterObjectList(annoObjectList, annoObjJoinFieldComparisons); + } + /** * 简化select列,仅select必需列 */ @@ -322,6 +359,7 @@ public abstract class BaseBinder { * @param queryWrapper * @return */ + @Deprecated protected List> getMapList(Wrapper queryWrapper) { if(referencedService instanceof BaseService){ return ((BaseService)referencedService).getMapList(queryWrapper); @@ -513,4 +551,82 @@ public abstract class BaseBinder { return value; } + /*** + * 筛选list + * @param objectList + * @param filterConditions 附加过滤条件 + * @param + * @return + */ + private List filterObjectList(List objectList, List filterConditions) { + if(V.isEmpty(objectList)){ + return Collections.emptyList(); + } + if(V.isEmpty(filterConditions)) { + return objectList; + } + List newObjectList = new ArrayList<>(); + for(E object : objectList){ + boolean matched = true; + for(FieldComparison fieldCompare : filterConditions) { + Object fieldValue = BeanUtils.getProperty(object, fieldCompare.getFieldName()); + if(Comparison.EQ.name().equals(fieldCompare.getComparison().name())){ + if(!V.fuzzyEqual(fieldValue, fieldCompare.getValue())) { + matched = false; + break; + } + } + else if(Comparison.NOT_EQ.name().equals(fieldCompare.getComparison().name())) { + if(V.fuzzyEqual(fieldValue, fieldCompare.getValue())) { + matched = false; + break; + } + } + else if(Comparison.CONTAINS.name().equals(fieldCompare.getComparison().name())){ + if(!S.valueOf(fieldValue).contains((String)fieldCompare.getValue())) { + matched = false; + break; + } + } + else if(Comparison.IN.name().equals(fieldCompare.getComparison().name())) { + List inValues = (List)fieldCompare.getValue(); + boolean in = false; + for(Object value : inValues) { + if(V.fuzzyEqual(fieldValue, value)) { + in = true; + break; + } + } + if(!in) { + matched = false; + break; + } + } + else if(Comparison.NOT_IN.name().equals(fieldCompare.getComparison().name())) { + List notInValues = (List)fieldCompare.getValue(); + boolean notIn = true; + for(Object value : notInValues) { + if(V.fuzzyEqual(fieldValue, value)) { + notIn = false; + break; + } + } + if(!notIn) { + matched = false; + break; + } + } + else{ + log.warn("暂不支持此类型对比: {}", fieldCompare.getComparison().name()); + } + } + if(!matched) { + log.debug("{} 不匹配 {}, 忽略收集该字段", object, filterConditions); + continue; + } + newObjectList.add(object); + } + return newObjectList; + } + } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/CountBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/CountBinder.java new file mode 100644 index 0000000000000000000000000000000000000000..be1c6a6b69a2c4dc0e546eff8b256ab2ab9c8ec0 --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/CountBinder.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.binding.binder; + +import com.diboot.core.binding.annotation.BindCount; +import com.diboot.core.binding.binder.remote.RemoteBindingManager; +import com.diboot.core.binding.cache.BindingCacheManager; +import com.diboot.core.binding.helper.ResultAssembler; +import com.diboot.core.config.Cons; +import com.diboot.core.exception.InvalidUsageException; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.S; +import com.diboot.core.util.V; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 关联子项count计数绑定 + * @author mazc@dibo.ltd + * @version v2.6.0 + * @date 2022/06/23 + */ +public class CountBinder extends EntityListBinder { + private static final Logger log = LoggerFactory.getLogger(CountBinder.class); + + /*** + * 构造方法 + * @param annotation + * @param voList + */ + public CountBinder(BindCount annotation, List voList){ + super(annotation.entity(), voList); + } + + + @Override + public void bind() { + if(V.isEmpty(annoObjectList)){ + return; + } + if(V.isEmpty(refObjJoinCols)){ + throw new InvalidUsageException("调用错误:无法从condition中解析出字段关联."); + } + Map valueListCountMap; + if(middleTable == null){ + this.simplifySelectColumns(); + super.buildQueryWrapperJoinOn(); + // 查询条件为空时不进行查询 + if (queryWrapper.isEmptyOfNormal()) { + return; + } + List entityList = null; + // 查询entity列表: List + if(V.isEmpty(this.module)){ + // 本地查询获取匹配结果的entityList + entityList = getEntityList(queryWrapper); + } + else{ + // 远程调用获取 + entityList = RemoteBindingManager.fetchEntityList(module, remoteBindDTO, referencedEntityClass); + } + if(V.notEmpty(entityList)){ + valueListCountMap = this.buildMatchKey2ListCountMap(entityList); + ResultAssembler.bindPropValue(annoObjectField, super.getMatchedAnnoObjectList(), getAnnoObjJoinFlds(), valueListCountMap, null); + } + } + else{ + if(refObjJoinCols.size() > 1){ + throw new InvalidUsageException(NOT_SUPPORT_MSG); + } + // 提取注解条件中指定的对应的列表 + Map trunkObjCol2ValuesMap = super.buildTrunkObjCol2ValuesMap(); + Map middleTableResultMap = middleTable.executeOneToManyQuery(trunkObjCol2ValuesMap); + if(V.isEmpty(middleTableResultMap)){ + return; + } + valueListCountMap = new HashMap<>(); + for(Map.Entry entry : middleTableResultMap.entrySet()){ + // List + List annoObjFKList = entry.getValue(); + if(V.isEmpty(annoObjFKList)){ + continue; + } + Integer count = entry.getValue() != null? entry.getValue().size() : 0; + valueListCountMap.put(entry.getKey(), count); + } + // 绑定结果 + ResultAssembler.bindPropValue(annoObjectField, super.getMatchedAnnoObjectList(), getAnnoObjJoinFlds(), valueListCountMap, null); + } + } + + /** + * 简化select列,仅select主键 + */ + @Override + protected void simplifySelectColumns() { + List selectColumns = new ArrayList<>(8); + String idCol = BindingCacheManager.getPropInfoByClass(referencedEntityClass).getIdColumn(); + selectColumns.add(idCol); + selectColumns.addAll(refObjJoinCols); + String[] selectColsArray = S.toStringArray(selectColumns); + if(remoteBindDTO != null){ + remoteBindDTO.setSelectColumns(selectColsArray); + } + this.queryWrapper.select(selectColsArray); + } + + + /** + * 构建匹配key-count目标的map + * @param list + * @return + */ + private Map buildMatchKey2ListCountMap(List list){ + Map key2TargetCountMap = new HashMap<>(list.size()); + StringBuilder sb = new StringBuilder(); + for(T entity : list){ + sb.setLength(0); + for(int i=0; i 0){ + sb.append(Cons.SEPARATOR_COMMA); + } + sb.append(pkValue); + } + // 查找匹配Key + String matchKey = sb.toString(); + // 获取list + Integer entityCount = key2TargetCountMap.get(matchKey); + if(entityCount == null){ + entityCount = 0; + } + entityCount++; + key2TargetCountMap.put(matchKey, entityCount); + } + sb.setLength(0); + return key2TargetCountMap; + } + +} \ No newline at end of file diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityBinder.java index 9cf91f06b4fbf3e271a4065fda46a37f77ae72b7..f68009e783ed42ff34903711dc5c1e23b0779d96 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityBinder.java @@ -121,7 +121,7 @@ public class EntityBinder extends BaseBinder { } if(V.notEmpty(entityList)){ Map valueEntityMap = this.buildMatchKey2EntityMap(entityList); - ResultAssembler.bindPropValue(annoObjectField, annoObjectList, getAnnoObjJoinFlds(), valueEntityMap, null); + ResultAssembler.bindPropValue(annoObjectField, super.getMatchedAnnoObjectList(), getAnnoObjJoinFlds(), valueEntityMap, null); } } // 通过中间表关联Entity @@ -174,7 +174,7 @@ public class EntityBinder extends BaseBinder { } } // 绑定结果 - ResultAssembler.bindEntityPropValue(annoObjectField, annoObjectList, middleTable.getTrunkObjColMapping(), valueEntityMap, getAnnoObjColumnToFieldMap()); + ResultAssembler.bindEntityPropValue(annoObjectField, super.getMatchedAnnoObjectList(), middleTable.getTrunkObjColMapping(), valueEntityMap, getAnnoObjColumnToFieldMap()); } } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityListBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityListBinder.java index ec1d8720bd931f857ed9224b373b6cb0942c88a7..9df5025bb6a3cabbbc6c3274502f5bcec485aac6 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityListBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/EntityListBinder.java @@ -54,6 +54,15 @@ public class EntityListBinder extends EntityBinder { } } + /*** + * 构造方法 + * @param entityClass + * @param voList + */ + public EntityListBinder(Class entityClass, List voList){ + super(entityClass, voList); + } + @Override public void bind() { if(V.isEmpty(annoObjectList)){ @@ -64,7 +73,7 @@ public class EntityListBinder extends EntityBinder { } Map valueEntityListMap = new HashMap<>(); if(middleTable == null){ - super.simplifySelectColumns(); + this.simplifySelectColumns(); super.buildQueryWrapperJoinOn(); // 查询条件为空时不进行查询 if (queryWrapper.isEmptyOfNormal()) { @@ -85,7 +94,7 @@ public class EntityListBinder extends EntityBinder { if(V.notEmpty(entityList)){ valueEntityListMap = this.buildMatchKey2EntityListMap(entityList); } - ResultAssembler.bindPropValue(annoObjectField, annoObjectList, getAnnoObjJoinFlds(), valueEntityListMap, this.splitBy); + ResultAssembler.bindPropValue(annoObjectField, super.getMatchedAnnoObjectList(), getAnnoObjJoinFlds(), valueEntityListMap, this.splitBy); } else{ if(refObjJoinCols.size() > 1){ @@ -97,7 +106,7 @@ public class EntityListBinder extends EntityBinder { if(V.isEmpty(middleTableResultMap)){ return; } - super.simplifySelectColumns(); + this.simplifySelectColumns(); // 收集查询结果values集合 List entityIdList = extractIdValueFromMap(middleTableResultMap); if(V.notEmpty(this.splitBy)){ @@ -157,7 +166,7 @@ public class EntityListBinder extends EntityBinder { valueEntityListMap.put(entry.getKey(), valueList); } // 绑定结果 - ResultAssembler.bindEntityPropValue(annoObjectField, annoObjectList, middleTable.getTrunkObjColMapping(), valueEntityListMap, getAnnoObjColumnToFieldMap()); + ResultAssembler.bindEntityPropValue(annoObjectField, super.getMatchedAnnoObjectList(), middleTable.getTrunkObjColMapping(), valueEntityListMap, getAnnoObjColumnToFieldMap()); } } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldBinder.java index f09bcf715e3d91bfa1f7ac8fa02094ab8e3bdcd3..80c621ed89e07f46a1ca63f90c78fc71c4dd8f53 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldBinder.java @@ -22,6 +22,7 @@ import com.diboot.core.exception.InvalidUsageException; import com.diboot.core.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanWrapper; import java.util.*; import java.util.stream.Collectors; @@ -104,30 +105,30 @@ public class FieldBinder extends BaseBinder { } // 直接关联 if(middleTable == null){ - List> mapList = null; this.simplifySelectColumns(); super.buildQueryWrapperJoinOn(); // 查询条件为空时不进行查询 if (queryWrapper.isEmptyOfNormal()) { return; } + List entityList = null; if(V.isEmpty(this.module)){ - // 本地查询获取匹配结果的mapList - mapList = getMapList(queryWrapper); + // 本地查询获取匹配结果的entityList + entityList = getEntityList(queryWrapper); } else{ // 远程调用获取 - mapList = RemoteBindingManager.fetchMapList(module, remoteBindDTO); + entityList = RemoteBindingManager.fetchEntityList(module, remoteBindDTO, referencedEntityClass); } - if(V.isEmpty(mapList)){ + if(V.isEmpty(entityList)){ return; } - // 将结果list转换成map - Map> key2DataMap = this.buildMatchKey2ResultMap(mapList); + // 将结果list转换成entityMap + Map key2EntityMap = this.buildMatchKey2EntityMap(entityList); // 遍历list并赋值 - for(Object annoObject : annoObjectList){ + for(Object annoObject : super.getMatchedAnnoObjectList()){ String matchKey = buildMatchKey(annoObject); - setFieldValueToTrunkObj(key2DataMap, annoObject, matchKey); + setFieldValueToTrunkObj(key2EntityMap, annoObject, matchKey); } } else{ @@ -147,26 +148,26 @@ public class FieldBinder extends BaseBinder { // 构建查询条件 String refObjJoinOnCol = refObjJoinCols.get(0); // 获取匹配结果的mapList - List> mapList = null; + List entityList = null; if(V.isEmpty(this.module)){ - // 本地查询获取匹配结果的mapList queryWrapper.in(refObjJoinOnCol, refObjValues); - mapList = getMapList(queryWrapper); + // 本地查询获取匹配结果的entityList + entityList = getEntityList(queryWrapper); } else{ // 远程调用获取 remoteBindDTO.setRefJoinCol(refObjJoinOnCol).setInConditionValues(refObjValues); - mapList = RemoteBindingManager.fetchMapList(module, remoteBindDTO); + entityList = RemoteBindingManager.fetchEntityList(module, remoteBindDTO, referencedEntityClass); } - if(V.isEmpty(mapList)){ + if(V.isEmpty(entityList)){ return; } - // 将结果list转换成map - Map> key2DataMap = this.buildMatchKey2ResultMap(mapList); + // 将结果list转换成entityMap + Map key2EntityMap = this.buildMatchKey2EntityMap(entityList); // 遍历list并赋值 - for(Object annoObject : annoObjectList){ + for(Object annoObject : super.getMatchedAnnoObjectList()){ String matchKey = buildMatchKey(annoObject, middleTableResultMap); - setFieldValueToTrunkObj(key2DataMap, annoObject, matchKey); + setFieldValueToTrunkObj(key2EntityMap, annoObject, matchKey); } } @@ -174,36 +175,37 @@ public class FieldBinder extends BaseBinder { /** * 设置字段值 - * @param key2DataMap + * @param key2EntityMap * @param annoObject * @param matchKey */ - private void setFieldValueToTrunkObj(Map> key2DataMap, Object annoObject, String matchKey) { - Map relationMap = key2DataMap.get(matchKey); - if (relationMap != null) { + private void setFieldValueToTrunkObj(Map key2EntityMap, Object annoObject, String matchKey) { + T relationEntity = key2EntityMap.get(matchKey); + if (relationEntity != null) { + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(annoObject); for (int i = 0; i < annoObjectSetterPropNameList.size(); i++) { - Object valObj = getValueIgnoreKeyCase(relationMap, toRefObjColumn(referencedGetterFieldNameList.get(i))); - BeanUtils.setProperty(annoObject, annoObjectSetterPropNameList.get(i), valObj); + Object valObj = BeanUtils.getProperty(relationEntity, referencedGetterFieldNameList.get(i)); + beanWrapper.setPropertyValue(annoObjectSetterPropNameList.get(i), valObj); } } } /** - * 构建匹配key-map目标的map - * @param mapList + * 构建匹配key-entity目标的map + * @param entityList * @return */ - protected Map> buildMatchKey2ResultMap(List> mapList){ - Map> key2TargetMap = new HashMap<>(mapList.size()); - for(Map map : mapList){ + protected Map buildMatchKey2EntityMap(List entityList){ + Map key2TargetMap = new HashMap<>(entityList.size()); + for(T entity : entityList){ List joinOnValues = new ArrayList<>(refObjJoinCols.size()); for(String refObjJoinOnCol : refObjJoinCols){ - Object valObj = getValueIgnoreKeyCase(map, refObjJoinOnCol); + Object valObj = BeanUtils.getProperty(entity, toRefObjField(refObjJoinOnCol)); joinOnValues.add(S.valueOf(valObj)); } String matchKey = S.join(joinOnValues); if(matchKey != null){ - key2TargetMap.put(matchKey, map); + key2TargetMap.put(matchKey, entity); } } return key2TargetMap; @@ -242,7 +244,7 @@ public class FieldBinder extends BaseBinder { String fieldValue = BeanUtils.getStringProperty(annoObject, getterField); // 通过中间结果Map转换得到OrgId if(V.notEmpty(middleTableResultMap)){ - Object value = middleTableResultMap.get(fieldValue); + Object value = getValueIgnoreKeyCase(middleTableResultMap, fieldValue); fieldValue = String.valueOf(value); } if(appendComma){ diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldListBinder.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldListBinder.java index 86f8ee3094f6fc253093edc799b437cd5c000ae7..bf092498569782317d7120d3df3c941088578b68 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldListBinder.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/FieldListBinder.java @@ -24,6 +24,7 @@ import com.diboot.core.util.BeanUtils; import com.diboot.core.util.V; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanWrapper; import java.util.ArrayList; import java.util.HashMap; @@ -90,7 +91,7 @@ public class FieldListBinder extends FieldBinder { valueEntityListMap = this.buildMatchKey2FieldListMap(entityList); } // 遍历list并赋值 - ResultAssembler.bindFieldListPropValue(annoObjectList, getAnnoObjJoinFlds(), valueEntityListMap, + ResultAssembler.bindFieldListPropValue(super.getMatchedAnnoObjectList(), getAnnoObjJoinFlds(), valueEntityListMap, annoObjectSetterPropNameList, referencedGetterFieldNameList, this.splitBy); } // 通过中间表关联 @@ -158,7 +159,7 @@ public class FieldListBinder extends FieldBinder { valueEntityListMap.put(entry.getKey(), valueList); } // 遍历list并赋值 - bindPropValue(annoObjectList, middleTable.getTrunkObjColMapping(), valueEntityListMap); + bindPropValue(super.getMatchedAnnoObjectList(), middleTable.getTrunkObjColMapping(), valueEntityListMap); } } @@ -193,9 +194,10 @@ public class FieldListBinder extends FieldBinder { List entityList = valueMatchMap.get(sb.toString()); if(entityList != null){ // 赋值 + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(object); for(int i = 0; i< annoObjectSetterPropNameList.size(); i++){ List valObjList = BeanUtils.collectToList(entityList, referencedGetterFieldNameList.get(i)); - BeanUtils.setProperty(object, annoObjectSetterPropNameList.get(i), valObjList); + beanWrapper.setPropertyValue(annoObjectSetterPropNameList.get(i), valObjList); } } } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/parallel/ParallelBindingManager.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/parallel/ParallelBindingManager.java index 0a079bbae087353c9ec3d8201f6048d930cf48ea..c5a31db62393cc1592ca44c9e7bac103dffd47ef 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/parallel/ParallelBindingManager.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/parallel/ParallelBindingManager.java @@ -134,6 +134,22 @@ public class ParallelBindingManager { return doBinding(binder, annotation.condition()); } + /*** + * 绑定count计数 + * @param voList + * @param fieldAnnotation + */ + @Async + public CompletableFuture doBindingCount(List voList, FieldAnnotation fieldAnnotation) { + BindCount annotation = (BindCount) fieldAnnotation.getAnnotation(); + // 绑定关联对象entity + CountBinder binder = new CountBinder(annotation, voList); + // 构建binder + binder.set(fieldAnnotation.getFieldName(), fieldAnnotation.getFieldClass()); + // 解析条件并且执行绑定 + return doBinding(binder, annotation.condition()); + } + /** * 绑定表关联数据 * @param binder diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindQueryExecutor.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindQueryExecutor.java index d90138a61327b0c6519f86bb192a5cfd1b905847..74eca9870c862cd9d17692b8c2c57f72e533beb3 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindQueryExecutor.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindQueryExecutor.java @@ -18,19 +18,19 @@ package com.diboot.core.binding.binder.remote; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.IService; +import com.diboot.core.binding.cache.BindingCacheManager; import com.diboot.core.binding.helper.WrapperHelper; +import com.diboot.core.binding.parser.PropInfo; import com.diboot.core.config.BaseConfig; import com.diboot.core.service.BaseService; +import com.diboot.core.util.BeanUtils; import com.diboot.core.util.ContextHelper; import com.diboot.core.util.JSON; import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import lombok.extern.slf4j.Slf4j; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; /** * 远程绑定查询执行器 @@ -61,17 +61,20 @@ public class RemoteBindQueryExecutor { if(inConditionValues == null){ return JsonResult.OK(); } + if (inConditionValues.isEmpty()) { + return JsonResult.OK(Collections.emptyList()); + } + // 关联主键列名 + String refJoinCol = remoteBindDTO.getRefJoinCol(); + PropInfo propInfo = BindingCacheManager.getPropInfoByClass(entityClass); + Class idFieldType = propInfo.getFieldTypeByColumn(refJoinCol); + Collection formatInValues = BeanUtils.convertIdValuesToType(inConditionValues, idFieldType); // 构建queryWrpper QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.setEntityClass(entityClass); queryWrapper.select(remoteBindDTO.getSelectColumns()); // 构建查询条件 - String refJoinCol = remoteBindDTO.getRefJoinCol(); - if (inConditionValues.isEmpty()) { - return JsonResult.OK(Collections.emptyList()); - } else { - queryWrapper.in(refJoinCol, inConditionValues); - } + queryWrapper.in(refJoinCol, formatInValues); queryWrapper.and(V.notEmpty(remoteBindDTO.getAdditionalConditions()), e -> remoteBindDTO.getAdditionalConditions().forEach(e::apply)); // 排序 WrapperHelper.buildOrderBy(queryWrapper, remoteBindDTO.getOrderBy(), e -> e); diff --git a/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindingManager.java b/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindingManager.java index 8eb3c17c8c12d4b2bf37fb222b473fb5cb34f51d..b767bc8ec5183695df81387cc477ffeb81d2e53b 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindingManager.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/binder/remote/RemoteBindingManager.java @@ -17,8 +17,8 @@ package com.diboot.core.binding.binder.remote; import com.diboot.core.util.ContextHelper; import com.diboot.core.util.JSON; +import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; -import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FeignClientBuilder; @@ -46,27 +46,6 @@ public class RemoteBindingManager { */ private static FeignClientBuilder feignClientBuilder; - /** - * 从远程接口抓取 Map List - * @param module - * @param remoteBindDTO - * @return - */ - public static List> fetchMapList(String module, RemoteBindDTO remoteBindDTO){ - remoteBindDTO.setResultType("Map"); - RemoteBindingProvider bindingProvider = getRemoteBindingProvider(module); - JsonResult jsonResult = bindingProvider.loadBindingData(remoteBindDTO); - if(jsonResult.isOK()){ - log.debug("获取到绑定数据: {}", jsonResult.getData()); - List> mapList = JSON.parseObject(jsonResult.getData(), new TypeReference>>(){}); - return mapList; - } - else{ - log.warn("获取绑定数据失败: {}", jsonResult.getMsg()); - return Collections.EMPTY_LIST; - } - } - /** * 从远程接口抓取 Entity List * @param module @@ -79,7 +58,7 @@ public class RemoteBindingManager { remoteBindDTO.setResultType("Entity"); RemoteBindingProvider bindingProvider = getRemoteBindingProvider(module); JsonResult jsonResult = bindingProvider.loadBindingData(remoteBindDTO); - if(jsonResult.isOK()){ + if(V.equals(jsonResult.getCode(), 0)){ log.debug("获取到绑定数据: {}", jsonResult.getData()); List entityList = JSON.parseArray(jsonResult.getData(), entityClass); return entityList; diff --git a/diboot-core/src/main/java/com/diboot/core/binding/cache/BindingCacheManager.java b/diboot-core/src/main/java/com/diboot/core/binding/cache/BindingCacheManager.java index 21d37b706c8e7d5c0b544f16ef29c11b202bd9c2..c05c5d4a3b465dc4d293462f062d1933c89cca53 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/cache/BindingCacheManager.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/cache/BindingCacheManager.java @@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.diboot.core.binding.parser.EntityInfoCache; import com.diboot.core.binding.parser.PropInfo; import com.diboot.core.cache.StaticMemoryCacheManager; +import com.diboot.core.config.Cons; import com.diboot.core.util.BeanUtils; import com.diboot.core.util.ContextHelper; import com.diboot.core.util.S; @@ -41,6 +42,7 @@ import java.util.*; * @version v2.2.1 * @date 2021/04/17 */ +@SuppressWarnings({"JavaDoc","rawtypes", "unchecked"}) @Slf4j public class BindingCacheManager { /** @@ -72,7 +74,7 @@ public class BindingCacheManager { */ private static final String CACHE_NAME_CLASS_NAME2FLDMAP = "CLASS_NAME2FLDMAP"; - private static StaticMemoryCacheManager getCacheManager(){ + private static synchronized StaticMemoryCacheManager getCacheManager(){ if(cacheManager == null){ cacheManager = new StaticMemoryCacheManager( CACHE_NAME_CLASS_ENTITY, @@ -90,7 +92,7 @@ public class BindingCacheManager { * @param tableName * @return */ - public static EntityInfoCache getEntityInfoByTable(String tableName){ + public static synchronized EntityInfoCache getEntityInfoByTable(String tableName){ initEntityInfoCache(); return getCacheManager().getCacheObj(CACHE_NAME_TABLE_ENTITY, tableName, EntityInfoCache.class); } @@ -100,7 +102,7 @@ public class BindingCacheManager { * @param entityClazz * @return */ - public static EntityInfoCache getEntityInfoByClass(Class entityClazz){ + public static synchronized EntityInfoCache getEntityInfoByClass(Class entityClazz){ initEntityInfoCache(); return getCacheManager().getCacheObj(CACHE_NAME_CLASS_ENTITY, entityClazz.getName(), EntityInfoCache.class); } @@ -110,7 +112,7 @@ public class BindingCacheManager { * @param beanClazz * @return */ - public static PropInfo getPropInfoByClass(Class beanClazz){ + public static synchronized PropInfo getPropInfoByClass(Class beanClazz){ PropInfo propInfo = getCacheManager().getCacheObj(CACHE_NAME_CLASS_PROP, beanClazz.getName(), PropInfo.class); if(propInfo == null){ propInfo = initPropInfoCache(beanClazz); @@ -123,7 +125,7 @@ public class BindingCacheManager { * @param tableName * @return */ - public static PropInfo getPropInfoByTable(String tableName){ + public static synchronized PropInfo getPropInfoByTable(String tableName){ Class entityClass = getEntityClassByTable(tableName); if(entityClass != null){ return getPropInfoByClass(entityClass); @@ -136,7 +138,7 @@ public class BindingCacheManager { * @param tableName * @return */ - public static Class getEntityClassByTable(String tableName){ + public static synchronized Class getEntityClassByTable(String tableName){ EntityInfoCache entityInfoCache = getEntityInfoByTable(tableName); return entityInfoCache != null? entityInfoCache.getEntityClass() : null; } @@ -147,7 +149,7 @@ public class BindingCacheManager { * @param classSimpleName * @return */ - public static Class getEntityClassBySimpleName(String classSimpleName){ + public static synchronized Class getEntityClassBySimpleName(String classSimpleName){ initEntityInfoCache(); return getCacheManager().getCacheObj(CACHE_NAME_ENTITYNAME_CLASS, classSimpleName, Class.class); } @@ -157,7 +159,7 @@ public class BindingCacheManager { * @param table * @return */ - public static BaseMapper getMapperByTable(String table){ + public static synchronized BaseMapper getMapperByTable(String table){ EntityInfoCache entityInfoCache = getEntityInfoByTable(table); if(entityInfoCache != null){ return entityInfoCache.getBaseMapper(); @@ -170,7 +172,7 @@ public class BindingCacheManager { * @param entityClazz * @return */ - public static BaseMapper getMapperByClass(Class entityClazz){ + public static synchronized BaseMapper getMapperByClass(Class entityClazz){ EntityInfoCache entityInfoCache = getEntityInfoByClass(entityClazz); if(entityInfoCache != null){ return entityInfoCache.getBaseMapper(); @@ -183,10 +185,10 @@ public class BindingCacheManager { * @param beanClazz * @return */ - public static List getFields(Class beanClazz){ + public static synchronized List getFields(Class beanClazz){ List fields = getCacheManager().getCacheObj(CACHE_NAME_CLASS_FIELDS, beanClazz.getName(), List.class); if(fields == null){ - fields = initClassFields(beanClazz, null); + fields = BeanUtils.extractAllFields(beanClazz); getCacheManager().putCacheObj(CACHE_NAME_CLASS_FIELDS, beanClazz.getName(), fields); } return fields; @@ -197,11 +199,11 @@ public class BindingCacheManager { * @param beanClazz * @return */ - public static List getFields(Class beanClazz, Class annotation){ - String key = S.join(beanClazz.getName(), annotation.getName()); + public static synchronized List getFields(Class beanClazz, Class annotation){ + String key = S.joinWith(Cons.SEPARATOR_COMMA, beanClazz.getName(), annotation.getName()); List fields = getCacheManager().getCacheObj(CACHE_NAME_CLASS_FIELDS, key, List.class); if(fields == null){ - fields = initClassFields(beanClazz, annotation); + fields = BeanUtils.extractFields(beanClazz, annotation); getCacheManager().putCacheObj(CACHE_NAME_CLASS_FIELDS, key, fields); } return fields; @@ -225,31 +227,29 @@ public class BindingCacheManager { /** * 初始化 */ - private static void initEntityInfoCache(){ + private static void initEntityInfoCache() { StaticMemoryCacheManager cacheManager = getCacheManager(); - if(cacheManager.isUninitializedCache(CACHE_NAME_CLASS_ENTITY) == false){ + if (cacheManager.isUninitializedCache(CACHE_NAME_CLASS_ENTITY) == false) { return; } // 初始化有service的entity缓存 Map serviceMap = ContextHelper.getApplicationContext().getBeansOfType(IService.class); Set uniqueEntitySet = new HashSet<>(); - if(V.notEmpty(serviceMap)){ - for(Map.Entry entry : serviceMap.entrySet()){ + if (V.notEmpty(serviceMap)) { + for (Map.Entry entry : serviceMap.entrySet()) { Class entityClass = BeanUtils.getGenericityClass(entry.getValue(), 1); - if(entityClass != null){ + if (entityClass != null) { IService entityIService = entry.getValue(); - if(uniqueEntitySet.contains(entityClass.getName())){ - if(entityIService.getClass().getAnnotation(Primary.class) != null){ + if (uniqueEntitySet.contains(entityClass.getName())) { + if (entityIService.getClass().getAnnotation(Primary.class) != null) { EntityInfoCache entityInfoCache = cacheManager.getCacheObj(CACHE_NAME_CLASS_ENTITY, entityClass.getName(), EntityInfoCache.class); - if(entityInfoCache != null){ + if (entityInfoCache != null) { entityInfoCache.setService(entry.getKey()); } - } - else{ + } else { log.warn("Entity: {} 存在多个service实现类,可能导致调用实例与预期不一致!", entityClass.getName()); } - } - else{ + } else { EntityInfoCache entityInfoCache = new EntityInfoCache(entityClass, entry.getKey()); cacheManager.putCacheObj(CACHE_NAME_CLASS_ENTITY, entityClass.getName(), entityInfoCache); cacheManager.putCacheObj(CACHE_NAME_TABLE_ENTITY, entityInfoCache.getTableName(), entityInfoCache); @@ -258,37 +258,37 @@ public class BindingCacheManager { } } } - } - else{ + } else { log.debug("未获取到任何有效@Service."); } // 初始化没有service的table-mapper缓存 SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class); - Collection> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers(); - if(V.notEmpty(mappers)){ - for(Class mapperClass : mappers){ - Type[] types = mapperClass.getGenericInterfaces(); - try{ - if(types != null && types.length > 0 && types[0] != null){ - ParameterizedType genericType = (ParameterizedType) types[0]; - Type[] superTypes = genericType.getActualTypeArguments(); - if(superTypes != null && superTypes.length > 0 && superTypes[0] != null){ - String entityClassName = superTypes[0].getTypeName(); - if(!uniqueEntitySet.contains(entityClassName) && entityClassName.length() > 1){ - Class entityClass = Class.forName(entityClassName); - EntityInfoCache entityInfoCache = new EntityInfoCache(entityClass, null); - entityInfoCache.setBaseMapper((Class) mapperClass); - cacheManager.putCacheObj(CACHE_NAME_CLASS_ENTITY, entityClass.getName(), entityInfoCache); - cacheManager.putCacheObj(CACHE_NAME_TABLE_ENTITY, entityInfoCache.getTableName(), entityInfoCache); - cacheManager.putCacheObj(CACHE_NAME_ENTITYNAME_CLASS, entityClass.getSimpleName(), entityClass); - uniqueEntitySet.add(entityClass.getName()); + if (sqlSessionFactory != null) { + Collection> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers(); + if (V.notEmpty(mappers)) { + for (Class mapperClass : mappers) { + Type[] types = mapperClass.getGenericInterfaces(); + try { + if (types.length > 0 && types[0] != null) { + ParameterizedType genericType = (ParameterizedType) types[0]; + Type[] superTypes = genericType.getActualTypeArguments(); + if (superTypes != null && superTypes.length > 0 && superTypes[0] != null) { + String entityClassName = superTypes[0].getTypeName(); + if (!uniqueEntitySet.contains(entityClassName) && entityClassName.length() > 1) { + Class entityClass = Class.forName(entityClassName); + EntityInfoCache entityInfoCache = new EntityInfoCache(entityClass, null); + entityInfoCache.setBaseMapper((Class) mapperClass); + cacheManager.putCacheObj(CACHE_NAME_CLASS_ENTITY, entityClass.getName(), entityInfoCache); + cacheManager.putCacheObj(CACHE_NAME_TABLE_ENTITY, entityInfoCache.getTableName(), entityInfoCache); + cacheManager.putCacheObj(CACHE_NAME_ENTITYNAME_CLASS, entityClass.getSimpleName(), entityClass); + uniqueEntitySet.add(entityClass.getName()); + } } } + } catch (Exception e) { + log.warn("解析mapper异常", e); } } - catch (Exception e){ - log.warn("解析mapper异常", e); - } } } uniqueEntitySet = null; @@ -305,40 +305,4 @@ public class BindingCacheManager { return propInfoCache; } - /** - * 初始化fields - * @param beanClazz - * @return - */ - private static List initClassFields(Class beanClazz, Class annotation){ - List fieldList = new ArrayList<>(); - Set fieldNameSet = new HashSet<>(); - loopFindFields(beanClazz, annotation, fieldList, fieldNameSet); - return fieldList; - } - - /** - * 循环向上查找fields - * @param beanClazz - * @param annotation - * @param fieldList - * @param fieldNameSet - */ - private static void loopFindFields(Class beanClazz, Class annotation, List fieldList, Set fieldNameSet){ - if(beanClazz == null) { - return; - } - Field[] fields = beanClazz.getDeclaredFields(); - if(V.notEmpty(fields)){ //被重写属性,以子类override的为准 - Arrays.stream(fields).forEach((field)->{ - if(!fieldNameSet.contains(field.getName()) && - (annotation == null || field.getAnnotation(annotation) != null)){ - fieldList.add(field); - fieldNameSet.add(field.getName()); - } - }); - } - loopFindFields(beanClazz.getSuperclass(), annotation, fieldList, fieldNameSet); - } - } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/helper/ResultAssembler.java b/diboot-core/src/main/java/com/diboot/core/binding/helper/ResultAssembler.java index 855dda70820b606b2713bcaad5b2538be3318e51..2ad7a3daa6fb908ab9fc0ae48a848f8264c79b6e 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/helper/ResultAssembler.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/helper/ResultAssembler.java @@ -20,6 +20,7 @@ import com.diboot.core.util.BeanUtils; import com.diboot.core.util.S; import com.diboot.core.util.V; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanWrapper; import java.util.*; @@ -47,6 +48,7 @@ public class ResultAssembler { StringBuilder sb = new StringBuilder(); try{ for(E object : fromList){ + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(object); sb.setLength(0); for(int i=0; i> bindFieldListGroupMap; + /** + * count计数关联注解 + */ + private List bindCountAnnotations; /** * 深度绑定实体 */ @@ -97,7 +101,11 @@ public class BindAnnotationGroup { } else if (annotation instanceof BindFieldList) { BindFieldList bindField = (BindFieldList) annotation; key = bindField.entity().getName() + ":" + bindField.condition() + ":" + bindField.orderBy(); - } else if (annotation instanceof BindEntity) { + } else if (annotation instanceof BindCount) { + BindCount bindCount = (BindCount) annotation; + key = bindCount.entity().getName() + ":" + bindCount.condition(); + } + else if (annotation instanceof BindEntity) { BindEntity bindEntity = (BindEntity) annotation; key = bindEntity.entity().getName(); } else if (annotation instanceof BindEntityList) { @@ -153,6 +161,12 @@ public class BindAnnotationGroup { List list = bindFieldListGroupMap.computeIfAbsent(key, k -> new ArrayList<>(4)); list.add(fieldAnnotation); } + else if(annotation instanceof BindCount){ + if(bindCountAnnotations == null){ + bindCountAnnotations = new ArrayList<>(4); + } + bindCountAnnotations.add(fieldAnnotation); + } } public List getBindDictAnnotations() { @@ -175,6 +189,10 @@ public class BindAnnotationGroup { return bindFieldListGroupMap; } + public List getBindCountAnnotations() { + return bindCountAnnotations; + } + public List getDeepBindEntityAnnotations() { return deepBindEntityAnnotations; } @@ -184,7 +202,7 @@ public class BindAnnotationGroup { } public boolean isEmpty() { - return V.isAllEmpty(bindDictAnnotations, bindFieldGroupMap, bindEntityAnnotations, bindEntityListAnnotations, bindFieldListGroupMap); + return V.isAllEmpty(bindDictAnnotations, bindFieldGroupMap, bindEntityAnnotations, bindEntityListAnnotations, bindFieldListGroupMap, bindCountAnnotations); } /** diff --git a/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionManager.java b/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionManager.java index 16e67b6b7ff8e991031a90ed76b2f7b3c9cabf34..a511651ad80d271f5a0a3ac483c59415eeb7d29e 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionManager.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionManager.java @@ -16,14 +16,19 @@ package com.diboot.core.binding.parser; import com.diboot.core.binding.binder.BaseBinder; +import com.diboot.core.binding.query.Comparison; +import com.diboot.core.exception.InvalidUsageException; import com.diboot.core.util.S; import com.diboot.core.util.V; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.*; import net.sf.jsqlparser.schema.Column; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -83,19 +88,33 @@ public class ConditionManager extends BaseConditionManager{ } } else{ - binder.andEQ(annoColumn, express.getRightExpression().toString()); - binder.additionalCondition(annoColumn + " = " + express.getRightExpression().toString()); + // this.xx = 'abc' + if(isCurrentObjColumn(express.getLeftExpression().toString())){ + Object consValue = extractConsValue(express.getRightExpression()); + binder.joinOnFieldComparison(annoColumn, Comparison.EQ, consValue); + } + else{ + binder.andEQ(annoColumn, express.getRightExpression().toString()); + binder.additionalCondition(annoColumn + " = " + express.getRightExpression().toString()); + } } continue; } - if(operator instanceof NotEqualsTo){ + else if(operator instanceof NotEqualsTo){ NotEqualsTo express = (NotEqualsTo)operator; String annoColumn = removeLeftAlias(express.getLeftExpression().toString()); if(express.getRightExpression() instanceof Column){ binder.andApply(annoColumn + " != " + express.getRightExpression().toString()); } else{ - binder.andNE(annoColumn, express.getRightExpression().toString()); + // this.xx != 'abc' + if(isCurrentObjColumn(express.getLeftExpression().toString())){ + Object consValue = extractConsValue(express.getRightExpression()); + binder.joinOnFieldComparison(annoColumn, Comparison.NOT_EQ, consValue); + } + else { + binder.andNE(annoColumn, express.getRightExpression().toString()); + } } } else if(operator instanceof GreaterThan){ @@ -152,10 +171,24 @@ public class ConditionManager extends BaseConditionManager{ InExpression express = (InExpression)operator; String annoColumn = removeLeftAlias(express.getLeftExpression().toString()); if(express.isNot() == false){ - binder.andApply(annoColumn + " IN " + express.getRightItemsList().toString()); + // this.xx in ('abc') + if(isCurrentObjColumn(express.getLeftExpression().toString())){ + List consValues = extractConsValues(express.getRightItemsList()); + binder.joinOnFieldComparison(annoColumn, Comparison.IN, consValues); + } + else { + binder.andApply(annoColumn + " IN " + express.getRightItemsList().toString()); + } } else{ - binder.andApply(annoColumn + " NOT IN " + express.getRightItemsList().toString()); + // this.xx not in ('abc') + if(isCurrentObjColumn(express.getLeftExpression().toString())){ + List consValues = extractConsValues(express.getRightItemsList()); + binder.joinOnFieldComparison(annoColumn, Comparison.NOT_IN, consValues); + } + else { + binder.andApply(annoColumn + " NOT IN " + express.getRightItemsList().toString()); + } } } else if(operator instanceof Between){ @@ -173,15 +206,24 @@ public class ConditionManager extends BaseConditionManager{ String annoColumn = removeLeftAlias(express.getLeftExpression().toString()); String value = express.getRightExpression().toString(); if(express.isNot() == false){ - binder.andLike(annoColumn, value); + // this.xx != 'abc' + if(isCurrentObjColumn(express.getLeftExpression().toString())){ + StringValue valueObj = (StringValue) express.getRightExpression(); + String consValue = S.replace(valueObj.getValue(), "%", ""); + binder.joinOnFieldComparison(annoColumn, Comparison.CONTAINS, consValue); + } + else { + binder.andLike(annoColumn, value); + } } else{ binder.andNotLike(annoColumn, value); } } else{ - log.warn("不支持的条件: "+operator.toString()); - continue; + String warnMsg = "不支持的条件: "+operator.toString(); + log.warn(warnMsg); + throw new InvalidUsageException(warnMsg); } binder.additionalCondition(operator.toString().replaceAll("^\\w+\\.", "")); } @@ -326,4 +368,43 @@ public class ConditionManager extends BaseConditionManager{ return annoColumn; } -} + /** + * 提取常量 + * @param expression + * @return + */ + private static Object extractConsValue(Expression expression) { + Object consValue = null; + if(expression instanceof StringValue) { + consValue = ((StringValue)expression).getValue(); + } + else if(expression instanceof LongValue) { + consValue = ((LongValue)expression).getValue(); + } + else { + String warnMsg = "不支持的附加条件类型: " + expression.toString(); + log.warn(warnMsg); + throw new InvalidUsageException(warnMsg); + } + return consValue; + } + + /** + * 提取常量值 + * @param itemsList + * @return + */ + private static List extractConsValues(ItemsList itemsList) { + if(itemsList instanceof ExpressionList) { + List expressions = ((ExpressionList)itemsList).getExpressions(); + List list = new ArrayList(); + for(Expression expression : expressions){ + list.add(extractConsValue(expression)); + } + return list; + } + log.warn("不支持的附加条件写法: {}", itemsList.toString()); + return Collections.emptyList(); + } + +} \ No newline at end of file diff --git a/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionParser.java b/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionParser.java index 9d56eb350660288029d19d69198b6fc68a85797a..51d9296b4c0b93d9b265acb4f515f13a1b9d148d 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionParser.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/parser/ConditionParser.java @@ -350,6 +350,14 @@ public class ConditionParser implements ExpressionVisitor,ItemsListVisitor { } + @Override + public void visit(IsDistinctExpression isDistinctExpression) { + } + + @Override + public void visit(GeometryDistance geometryDistance) { + } + @Override public void visit(BitwiseRightShift aThis) { } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/parser/FieldComparison.java b/diboot-core/src/main/java/com/diboot/core/binding/parser/FieldComparison.java new file mode 100644 index 0000000000000000000000000000000000000000..84405731714fdf02e0549672fa387844a6ecf86a --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/binding/parser/FieldComparison.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.binding.parser; + +import com.diboot.core.binding.query.Comparison; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 字段 + * @author JerryMa + * @version v2.6.0 + * @date 2022/6/2 + * Copyright © diboot.com + */ +@Getter @Setter @Accessors(chain = true) +public class FieldComparison implements Serializable { + private static final long serialVersionUID = -1080962768714815036L; + + private String fieldName; + + private Comparison comparison; + + private Object value; + + public FieldComparison(){} + + public FieldComparison(String fieldName, Comparison comparison, Object value) { + this.fieldName = fieldName; + this.comparison = comparison; + this.value = value; + } + +} \ No newline at end of file diff --git a/diboot-core/src/main/java/com/diboot/core/binding/parser/ParserCache.java b/diboot-core/src/main/java/com/diboot/core/binding/parser/ParserCache.java index 58f96d390143d7c269d51006c2b3ec36c190dba6..f5575ac2250f9b8e14f2844cffcd0b3f9fd9a2fe 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/parser/ParserCache.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/parser/ParserCache.java @@ -65,7 +65,7 @@ public class ParserCache { * 用于查询注解是否是Bind相关注解 */ private static final Set> BIND_ANNOTATION_SET = new HashSet<>(Arrays.asList( - BindDict.class, BindField.class, BindFieldList.class, BindEntity.class, BindEntityList.class + BindDict.class, BindField.class, BindFieldList.class, BindEntity.class, BindEntityList.class, BindCount.class )); /** diff --git a/diboot-core/src/main/java/com/diboot/core/binding/parser/PropInfo.java b/diboot-core/src/main/java/com/diboot/core/binding/parser/PropInfo.java index 96688e21c9ba3abd6461a9d73ca5d560cd6cf863..85db8efcbe35398dcfd9533d7b8f462aa393b8af 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/parser/PropInfo.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/parser/PropInfo.java @@ -50,6 +50,10 @@ public class PropInfo implements Serializable { * 列-字段的映射 */ private final Map columnToFieldMap = new HashMap<>(); + /** + * 列-字段类型的映射 + */ + private final Map columnToFieldTypeMap = new HashMap<>(); /** * 自动更新字段列表 */ @@ -110,6 +114,7 @@ public class PropInfo implements Serializable { this.fieldToColumnMap.put(fldName, columnName); if(V.notEmpty(columnName)){ this.columnToFieldMap.put(columnName, fldName); + this.columnToFieldTypeMap.put(columnName, fld.getType()); this.columns.add(columnName); } } @@ -151,4 +156,15 @@ public class PropInfo implements Serializable { return this.columnToFieldMap.get(idColumn); } + /** + * 根据列名获取字段类型 + * @return + */ + public Class getFieldTypeByColumn(String columnName){ + if(V.isEmpty(this.columnToFieldTypeMap)){ + return null; + } + return this.columnToFieldTypeMap.get(columnName); + } + } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/query/Comparison.java b/diboot-core/src/main/java/com/diboot/core/binding/query/Comparison.java index 5d8d8bd0401f0e1bdd27a3d82a843be7db9db2cc..293b6f755b299cbdc7eca42695f2a3a028070e5c 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/query/Comparison.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/query/Comparison.java @@ -40,4 +40,5 @@ public enum Comparison { BETWEEN_END, //介于之前 NOT_EQ, //不等于 + NOT_IN // 不在...内 } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/query/Strategy.java b/diboot-core/src/main/java/com/diboot/core/binding/query/Strategy.java index b976fc54bbb2a3da0497275104c1b3bf0a26e3db..770e047fb2d7b76d894ee09f0d896a8ce8db6d7e 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/query/Strategy.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/query/Strategy.java @@ -31,4 +31,8 @@ public enum Strategy { * 空字符串""参与查询 */ INCLUDE_EMPTY, + /** + * null参与构建isNull + */ + INCLUDE_NULL, } diff --git a/diboot-core/src/main/java/com/diboot/core/binding/query/dynamic/DynamicSqlProvider.java b/diboot-core/src/main/java/com/diboot/core/binding/query/dynamic/DynamicSqlProvider.java index c15d72ca98b373d7895048d58196ad5d8a5e368f..b3f396ada55410b0793d506d88d4c0ac2bad9417 100644 --- a/diboot-core/src/main/java/com/diboot/core/binding/query/dynamic/DynamicSqlProvider.java +++ b/diboot-core/src/main/java/com/diboot/core/binding/query/dynamic/DynamicSqlProvider.java @@ -17,6 +17,7 @@ package com.diboot.core.binding.query.dynamic; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments; +import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.diboot.core.binding.QueryBuilder; import com.diboot.core.binding.parser.ParserCache; @@ -72,7 +73,7 @@ public class DynamicSqlProvider { SELECT_DISTINCT("self.*"); } else{ - SELECT_DISTINCT(formatSqlSelect(ew.getSqlSelect())); + SELECT_DISTINCT(formatSqlSelect(ew.getSqlSelect(), page)); } FROM(wrapper.getEntityTable()+" self"); //提取字段,根据查询条件中涉及的表,动态join @@ -121,7 +122,7 @@ public class DynamicSqlProvider { if(isDeletedCol != null && QueryBuilder.checkHasColumn(segments.getNormal(), isDeletedSection) == false){ WHERE(isDeletedSection+ " = " +BaseConfig.getActiveFlagValue()); } - if(segments.getOrderBy() != null){ + if(segments.getOrderBy() != null && !segments.getOrderBy().isEmpty()){ String orderBySql = segments.getOrderBy().getSqlSegment(); int beginIndex = S.indexOfIgnoreCase(orderBySql,"ORDER BY "); if(beginIndex >= 0){ @@ -139,15 +140,24 @@ public class DynamicSqlProvider { * @param sqlSelect * @return */ - private String formatSqlSelect(String sqlSelect){ + private String formatSqlSelect(String sqlSelect, Page page){ String[] columns = S.split(sqlSelect); + Set columnSets = new HashSet<>(); StringBuilder sb = new StringBuilder(); for(int i=0; i0){ sb.append(Cons.SEPARATOR_COMMA); } - sb.append("self."+column); + sb.append("self.").append(column); + columnSets.add("self."+column); + } + if(page != null && page.getOrders() != null) { + for(OrderItem orderItem : page.getOrders()){ + if(!columnSets.contains(orderItem.getColumn())){ + sb.append(Cons.SEPARATOR_COMMA).append(orderItem.getColumn()).append(" AS _").append(S.replace(orderItem.getColumn(), ".", "_")); + } + } } return sb.toString(); } diff --git a/diboot-core/src/main/java/com/diboot/core/cache/BaseCacheManager.java b/diboot-core/src/main/java/com/diboot/core/cache/BaseCacheManager.java index 3fd9ad1896173db4bf15a00e82092ee75668ae5d..d2aeecf42c6d413748bbe4e43f1365ad1d03036a 100644 --- a/diboot-core/src/main/java/com/diboot/core/cache/BaseCacheManager.java +++ b/diboot-core/src/main/java/com/diboot/core/cache/BaseCacheManager.java @@ -1,9 +1,20 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.diboot.core.cache; -import org.springframework.cache.Cache; -import org.springframework.cache.concurrent.ConcurrentMapCache; -import org.springframework.cache.support.SimpleCacheManager; - /** * 缓存manager父类 * @author JerryMa @@ -11,7 +22,7 @@ import org.springframework.cache.support.SimpleCacheManager; * @date 2021/4/17 * Copyright © diboot.com */ -public abstract class BaseCacheManager extends SimpleCacheManager { +public interface BaseCacheManager { /** * 获取缓存对象 @@ -19,10 +30,14 @@ public abstract class BaseCacheManager extends SimpleCacheManager { * @param * @return */ - public T getCacheObj(String cacheName, Object objKey, Class tClass){ - Cache cache = getCache(cacheName); - return cache != null? cache.get(objKey, tClass) : null; - } + T getCacheObj(String cacheName, Object objKey, Class tClass); + + /** + * 获取缓存对象 + * @param objKey + * @return + */ + String getCacheString(String cacheName, Object objKey); /** * 缓存对象 @@ -30,19 +45,35 @@ public abstract class BaseCacheManager extends SimpleCacheManager { * @param objKey * @param obj */ - public void putCacheObj(String cacheName, Object objKey, Object obj){ - Cache cache = getCache(cacheName); - cache.put(objKey, obj); + void putCacheObj(String cacheName, Object objKey, Object obj); + + /** + * 缓存对象 - 支持过期时间 + * @param cacheName + * @param objKey + * @param obj + */ + default void putCacheObj(String cacheName, Object objKey, Object obj, int expiredMinutes){ + putCacheObj(cacheName, objKey, obj); } + /** + * 删除缓存对象 + * @param cacheName + * @param objKey + */ + void removeCacheObj(String cacheName, Object objKey); + /** * 尚未初始化的 * @param cacheName * @return */ - public boolean isUninitializedCache(String cacheName){ - ConcurrentMapCache cache = (ConcurrentMapCache)getCache(cacheName); - return cache.getNativeCache().isEmpty(); - } + boolean isUninitializedCache(String cacheName); + + /** + * 清理所有过期的数据:系统空闲时调用 + */ + void clearOutOfDateData(String cacheName); } diff --git a/diboot-core/src/main/java/com/diboot/core/cache/BaseMemoryCacheManager.java b/diboot-core/src/main/java/com/diboot/core/cache/BaseMemoryCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e0a544f09866c09bf40819fe448b40b2502c4e8e --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/cache/BaseMemoryCacheManager.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.cache.support.SimpleCacheManager; + +/** + * 缓存manager父类 + * @author JerryMa + * @version v2.2.1 + * @date 2021/4/17 + * Copyright © diboot.com + */ +@Slf4j +public abstract class BaseMemoryCacheManager extends SimpleCacheManager implements BaseCacheManager{ + + /** + * 获取缓存对象 + * @param objKey + * @param + * @return + */ + public T getCacheObj(String cacheName, Object objKey, Class tClass){ + Cache cache = getCache(cacheName); + T value = cache != null? cache.get(objKey, tClass) : null; + if(log.isTraceEnabled()){ + log.trace("从缓存读取: {}.{} = {}", cacheName, objKey, value); + } + return value; + } + + /** + * 获取缓存对象 + * @param objKey + * @return + */ + public String getCacheString(String cacheName, Object objKey){ + return getCacheObj(cacheName, objKey, String.class); + } + + /** + * 缓存对象 + * @param cacheName + * @param objKey + * @param obj + */ + public void putCacheObj(String cacheName, Object objKey, Object obj){ + Cache cache = getCache(cacheName); + cache.put(objKey, obj); + if(log.isDebugEnabled()){ + ConcurrentMapCache mapCache = (ConcurrentMapCache)cache; + log.debug("缓存: {} 新增-> {} , 当前size={}", cacheName, objKey, mapCache.getNativeCache().size()); + } + } + + /** + * 删除缓存对象 + * @param cacheName + * @param objKey + */ + public void removeCacheObj(String cacheName, Object objKey){ + Cache cache = getCache(cacheName); + cache.evict(objKey); + if(log.isDebugEnabled()){ + ConcurrentMapCache mapCache = (ConcurrentMapCache)cache; + log.debug("缓存删除: {}.{} , 当前size={}", cacheName, objKey, mapCache.getNativeCache().size()); + } + } + + /** + * 尚未初始化的 + * @param cacheName + * @return + */ + public boolean isUninitializedCache(String cacheName){ + ConcurrentMapCache cache = (ConcurrentMapCache)getCache(cacheName); + return cache.getNativeCache().isEmpty(); + } + +} diff --git a/diboot-core/src/main/java/com/diboot/core/cache/DynamicMemoryCacheManager.java b/diboot-core/src/main/java/com/diboot/core/cache/DynamicMemoryCacheManager.java index df851448099524e8ad06cc65b2777b78b2fa5e31..3bd69feb138fc426f5655d3b77bca5e3dd5687e3 100644 --- a/diboot-core/src/main/java/com/diboot/core/cache/DynamicMemoryCacheManager.java +++ b/diboot-core/src/main/java/com/diboot/core/cache/DynamicMemoryCacheManager.java @@ -1,10 +1,30 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.diboot.core.cache; +import com.diboot.core.util.V; +import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import org.springframework.cache.concurrent.ConcurrentMapCache; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -15,7 +35,8 @@ import java.util.concurrent.ConcurrentMap; * @date 2021/4/17 * Copyright © diboot.com */ -public class DynamicMemoryCacheManager extends BaseCacheManager{ +@Slf4j +public class DynamicMemoryCacheManager extends BaseMemoryCacheManager implements BaseCacheManager{ public DynamicMemoryCacheManager(String... cacheNames){ List caches = new ArrayList<>(cacheNames.length); @@ -27,30 +48,35 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ } /** - * 默认的过期时间(分钟) + * cache的清理时间缓存 */ - private long expiredMinutes = 60; + private final ConcurrentHashMap CACHE_CLEANDATE_CACHE = new ConcurrentHashMap<>(); + + /** + * cache的过期时间缓存 + */ + private final ConcurrentHashMap CACHE_EXPIREDMINUTES_CACHE = new ConcurrentHashMap<>(); + /** * cache的时间戳缓存 */ - private ConcurrentHashMap> CACHE_TIMESTAMP_CACHE = null; + private final ConcurrentHashMap> CACHE_TIMESTAMP_CACHE = new ConcurrentHashMap<>(); public DynamicMemoryCacheManager(){ - this.CACHE_TIMESTAMP_CACHE = new ConcurrentHashMap<>(); - super.afterPropertiesSet(); - } - public DynamicMemoryCacheManager(int expiredMinutes){ - this.expiredMinutes = expiredMinutes; - this.CACHE_TIMESTAMP_CACHE = new ConcurrentHashMap<>(); super.afterPropertiesSet(); } + /** + * 统一过期时间 + * @param expiredMinutes + * @param cacheNames + */ public DynamicMemoryCacheManager(int expiredMinutes, String... cacheNames){ - this.expiredMinutes = expiredMinutes; - this.CACHE_TIMESTAMP_CACHE = new ConcurrentHashMap<>(); List caches = new ArrayList<>(cacheNames.length); for(String cacheName : cacheNames){ caches.add(new ConcurrentMapCache(cacheName)); + this.CACHE_EXPIREDMINUTES_CACHE.put(cacheName, expiredMinutes); + this.CACHE_CLEANDATE_CACHE.put(cacheName, ""); } setCaches(caches); super.afterPropertiesSet(); @@ -69,7 +95,10 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ } // 已过期,则清空缓存返回null if(isExpired(cacheName, objKey)){ - cache.clear(); + cache.evict(objKey); + if(log.isDebugEnabled()){ + log.debug("缓存已过期被清理: {}.{}", cacheName, objKey); + } return null; } return cache.get(objKey, tClass); @@ -84,6 +113,43 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ public void putCacheObj(String cacheName, Object objKey, Object obj) { super.putCacheObj(cacheName, objKey, obj); refreshCacheTimestamp(cacheName, objKey); + clearOutOfDateDataIfNeeded(cacheName); + } + + /** + * 缓存对象 + * @param cacheName + * @param objKey + * @param obj + */ + public void putCacheObj(String cacheName, Object objKey, Object obj, int expireMinutes) { + if(!this.CACHE_EXPIREDMINUTES_CACHE.containsKey(cacheName) || (this.CACHE_EXPIREDMINUTES_CACHE.get(cacheName) != expireMinutes)){ + this.CACHE_EXPIREDMINUTES_CACHE.put(cacheName, expireMinutes); + if(log.isDebugEnabled()){ + log.debug("设置缓存过期时间: {}={}", cacheName, expireMinutes); + } + } + this.putCacheObj(cacheName, objKey, obj); + } + + @Override + public synchronized void clearOutOfDateData(String cacheName) { + Cache cache = getCache(cacheName); + ConcurrentMap cacheMap = (ConcurrentMap)cache.getNativeCache(); + if(V.isEmpty(cacheMap)){ + log.debug("暂无缓存数据: {}", cacheName); + return; + } + int count = 0; + for(Map.Entry entry : cacheMap.entrySet()){ + // 已过期,则清空缓存返回null + if(isExpired(cacheName, entry.getKey())){ + cache.evict(entry.getKey()); + count++; + log.debug("清理已过期的缓存: {}.{}", cacheName, entry.getKey()); + } + } + log.debug("清理完成已过期缓存数据: {} 共 {} 条", cacheName, count); } /** @@ -95,7 +161,7 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ */ public boolean isExpired(String cacheName, Object objKey) { ConcurrentMap timestampCache = CACHE_TIMESTAMP_CACHE.get(cacheName); - if(timestampCache == null){ + if(V.isEmpty(timestampCache)){ return false; } Long cacheTimestamp = timestampCache.get(objKey); @@ -103,22 +169,20 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ return false; } long currentTimestamp = System.currentTimeMillis(); + int expiredMinutes = CACHE_EXPIREDMINUTES_CACHE.get(cacheName); return (currentTimestamp - cacheTimestamp) > (expiredMinutes*60000); } - /** - * 清空缓存 - * @param cacheName - */ - public void clearCache(String cacheName) { - Cache cache = getCache(cacheName); - if(cache != null){ - cache.clear(); - // 清空时间戳 - ConcurrentMap timestampCache = CACHE_TIMESTAMP_CACHE.get(cacheName); - if(timestampCache != null){ - timestampCache.clear(); - } + private void clearOutOfDateDataIfNeeded(String cacheName){ + boolean needed = true; + String today = LocalDate.now().toString(); + if(CACHE_CLEANDATE_CACHE.containsKey(cacheName)){ + needed = V.notEquals(today, CACHE_CLEANDATE_CACHE.get(cacheName)); + } + if(needed){ + log.debug("新的执行周期清理过期的本地缓存: {}", cacheName); + clearOutOfDateData(cacheName); + CACHE_CLEANDATE_CACHE.put(cacheName, today); } } @@ -133,5 +197,4 @@ public class DynamicMemoryCacheManager extends BaseCacheManager{ } timestampCache.put(objKey, System.currentTimeMillis()); } - } diff --git a/diboot-core/src/main/java/com/diboot/core/cache/StaticMemoryCacheManager.java b/diboot-core/src/main/java/com/diboot/core/cache/StaticMemoryCacheManager.java index b8259f13bd605083e833ae490a7163ea5e32d78a..ce2fb6115e8281d2a0a08e394a265593b02f9e07 100644 --- a/diboot-core/src/main/java/com/diboot/core/cache/StaticMemoryCacheManager.java +++ b/diboot-core/src/main/java/com/diboot/core/cache/StaticMemoryCacheManager.java @@ -1,5 +1,21 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.diboot.core.cache; +import com.diboot.core.exception.InvalidUsageException; import org.springframework.cache.Cache; import org.springframework.cache.concurrent.ConcurrentMapCache; @@ -13,7 +29,7 @@ import java.util.List; * @date 2021/4/17 * Copyright © diboot.com */ -public class StaticMemoryCacheManager extends BaseCacheManager{ +public class StaticMemoryCacheManager extends BaseMemoryCacheManager implements BaseCacheManager{ public StaticMemoryCacheManager(String... cacheNames){ List caches = new ArrayList<>(); @@ -24,4 +40,8 @@ public class StaticMemoryCacheManager extends BaseCacheManager{ super.afterPropertiesSet(); } + @Override + public void clearOutOfDateData(String cacheName) { + throw new InvalidUsageException("StaticMemoryCacheManager 缓存不存在过期,不支持清理!"); + } } \ No newline at end of file diff --git a/diboot-core/src/main/java/com/diboot/core/config/BaseConfig.java b/diboot-core/src/main/java/com/diboot/core/config/BaseConfig.java index 64df54d44248ddf3601c92f6ee6361f98290aa00..585fdda449675b5135c586411d61e897ee8d76d1 100644 --- a/diboot-core/src/main/java/com/diboot/core/config/BaseConfig.java +++ b/diboot-core/src/main/java/com/diboot/core/config/BaseConfig.java @@ -121,14 +121,24 @@ public class BaseConfig { return batchSize; } - private static String ACTIVE_FLAG_VALUE = null; + private static Object ACTIVE_FLAG_VALUE = null; /** * 获取有效记录的标记值,如 0 * @return */ - public static String getActiveFlagValue(){ + public static Object getActiveFlagValue(){ if(ACTIVE_FLAG_VALUE == null){ - ACTIVE_FLAG_VALUE = getProperty("mybatis-plus.global-config.db-config.logic-not-delete-value", "0"); + String activeFlagValue = getProperty("mybatis-plus.global-config.db-config.logic-not-delete-value", "0"); + switch (activeFlagValue) { + case "false": + ACTIVE_FLAG_VALUE = false; + break; + case "0": + ACTIVE_FLAG_VALUE = 0; + break; + default: + ACTIVE_FLAG_VALUE = activeFlagValue; + } } return ACTIVE_FLAG_VALUE; } diff --git a/diboot-core/src/main/java/com/diboot/core/config/Cons.java b/diboot-core/src/main/java/com/diboot/core/config/Cons.java index 80b8bf6b7a07ee0f70b22ba0f07e338a705dd184..b5071378941ebb1b583d625ebdd8178952cde3eb 100644 --- a/diboot-core/src/main/java/com/diboot/core/config/Cons.java +++ b/diboot-core/src/main/java/com/diboot/core/config/Cons.java @@ -46,6 +46,10 @@ public class Cons { * 竖线分隔符,or */ public final static String SEPARATOR_OR = "|"; + /** + * 分号分隔符 + */ + public final static String SEPARATOR_SEMICOLON = ";"; /** * 排序 - 降序标记 */ diff --git a/diboot-core/src/main/java/com/diboot/core/controller/BaseController.java b/diboot-core/src/main/java/com/diboot/core/controller/BaseController.java index a7213218085b3ade7d7d27bb710df1f1f8a16845..99c6d45e7fc4d9393052d879eeaf4c4e207ccb69 100644 --- a/diboot-core/src/main/java/com/diboot/core/controller/BaseController.java +++ b/diboot-core/src/main/java/com/diboot/core/controller/BaseController.java @@ -61,17 +61,6 @@ public class BaseController { @Autowired(required = false) protected DictionaryService dictionaryService; - /*** - * 构建查询QueryWrapper (根据BindQuery注解构建相应的查询条件) - * @param entityOrDto Entity对象或者DTO对象 (属性若无BindQuery注解,默认构建为为EQ相等条件) - * @see #buildQueryWrapperByDTO #buildQueryWrapperByQueryParams - * @return - */ - @Deprecated - protected QueryWrapper buildQueryWrapper(DTO entityOrDto) throws Exception{ - return buildQueryWrapperByQueryParams(entityOrDto); - } - /*** * 根据DTO构建查询QueryWrapper (根据BindQuery注解构建相应的查询条件,DTO中的非空属性均参与构建) * @param entityOrDto Entity对象或者DTO对象 (属性若无BindQuery注解,默认构建为为EQ相等条件) diff --git a/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateConverter.java b/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..44f42d0e58a79c5dd7af77c3f0aeedb8129d00ce --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +/** + * Date - LocalDate 转换器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +public class Date2LocalDateConverter implements Converter { + + @Override + public LocalDate convert(Date source) { + if (source == null) { + return null; + } + ZonedDateTime zonedDateTime = source.toInstant().atZone(ZoneId.systemDefault()); + return zonedDateTime.toLocalDate(); + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateTimeConverter.java b/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateTimeConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..717b9cdde9c9e469b77e0202b59812b6ee3919ec --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/Date2LocalDateTimeConverter.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import org.springframework.core.convert.converter.Converter; + +import java.util.Date; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +/** + * Date - LocalDateTime 转换器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +public class Date2LocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(Date source) { + if (source == null) { + return null; + } + ZonedDateTime zonedDateTime = source.toInstant().atZone(ZoneId.systemDefault()); + return zonedDateTime.toLocalDateTime(); + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/converter/EnhancedConversionService.java b/diboot-core/src/main/java/com/diboot/core/converter/EnhancedConversionService.java new file mode 100644 index 0000000000000000000000000000000000000000..611197af9d2f7c773c806d753189e1402d726749 --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/EnhancedConversionService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.stereotype.Component; + +/** + * 扩展的转换service + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +@Component +public class EnhancedConversionService extends DefaultConversionService { + + public EnhancedConversionService(){ + super(); + addConverter(new Date2LocalDateConverter()); + addConverter(new Date2LocalDateTimeConverter()); + addConverter(new String2DateConverter()); + addConverter(new String2BooleanConverter()); + addConverter(new Timestamp2LocalDateTimeConverter()); + } + +} diff --git a/diboot-core/src/main/java/com/diboot/core/converter/String2BooleanConverter.java b/diboot-core/src/main/java/com/diboot/core/converter/String2BooleanConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..887e845a98430898d20be6d74bf965d4964514fb --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/String2BooleanConverter.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import com.diboot.core.util.V; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +/** + * String - boolean 转换器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +public class String2BooleanConverter implements Converter { + + @Override + public Boolean convert(String source) { + return V.isTrue(source); + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/converter/String2DateConverter.java b/diboot-core/src/main/java/com/diboot/core/converter/String2DateConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..8400f037b42ac510b53295b8f51dc495b3a8f563 --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/String2DateConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import com.diboot.core.util.D; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * String - date 转换器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +public class String2DateConverter implements Converter { + + @Override + public Date convert(String dateString) { + return D.fuzzyConvert(dateString); + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/converter/Timestamp2LocalDateTimeConverter.java b/diboot-core/src/main/java/com/diboot/core/converter/Timestamp2LocalDateTimeConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..f9d01adf074cbf0881ae6728751d41b7cdd002d1 --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/converter/Timestamp2LocalDateTimeConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.sql.Timestamp; +import java.time.LocalDateTime; + +/** + * Timestamp - LocalDateTime 转换器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/11 + * Copyright © diboot.com + */ +public class Timestamp2LocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(Timestamp source) { + if (source == null) { + return null; + } + return source.toLocalDateTime(); + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/data/access/DataAccessInterface.java b/diboot-core/src/main/java/com/diboot/core/data/access/DataAccessInterface.java index 3e2c7ab57a4a7e7338060193e598ad466e7b2644..da0d783394fb4481b121de5b7ade1cdf655a6f7a 100644 --- a/diboot-core/src/main/java/com/diboot/core/data/access/DataAccessInterface.java +++ b/diboot-core/src/main/java/com/diboot/core/data/access/DataAccessInterface.java @@ -57,6 +57,6 @@ public interface DataAccessInterface { * * */ - List getAccessibleIds(Class entityClass, String fieldName); + List getAccessibleIds(Class entityClass, String fieldName); } diff --git a/diboot-core/src/main/java/com/diboot/core/data/copy/AcceptAnnoCopier.java b/diboot-core/src/main/java/com/diboot/core/data/copy/AcceptAnnoCopier.java index f7d50883132f38c0b78c435b4b29aba7b1cf6c20..f8677dc156058f41ace2e4c034a50cb7459782dc 100644 --- a/diboot-core/src/main/java/com/diboot/core/data/copy/AcceptAnnoCopier.java +++ b/diboot-core/src/main/java/com/diboot/core/data/copy/AcceptAnnoCopier.java @@ -18,6 +18,7 @@ package com.diboot.core.data.copy; import com.diboot.core.util.BeanUtils; import com.diboot.core.util.V; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanWrapper; import java.lang.reflect.Field; import java.util.ArrayList; @@ -69,6 +70,7 @@ public class AcceptAnnoCopier { if(V.isEmpty(acceptAnnos)){ return; } + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(target); for(String[] annoDef : acceptAnnos){ boolean override = !"0".equals(annoDef[IDX_OVERRIDE]); if(!override){ @@ -82,7 +84,7 @@ public class AcceptAnnoCopier { if(sourceField != null){ Object sourceValue = BeanUtils.getProperty(source, annoDef[IDX_SOURCE_FIELD]); if(sourceValue != null){ - BeanUtils.setProperty(target, annoDef[IDX_TARGET_FIELD], sourceValue); + beanWrapper.setPropertyValue(annoDef[IDX_TARGET_FIELD], sourceValue); } } } diff --git a/diboot-core/src/main/java/com/diboot/core/handler/DataAccessControlInterceptor.java b/diboot-core/src/main/java/com/diboot/core/handler/DataAccessControlInterceptor.java index 9a3c29ef38c51de210255fbe0063b65f41e8bf30..1a6c3cb08c27c6b578fc3872d3bb9ff42258a505 100644 --- a/diboot-core/src/main/java/com/diboot/core/handler/DataAccessControlInterceptor.java +++ b/diboot-core/src/main/java/com/diboot/core/handler/DataAccessControlInterceptor.java @@ -26,6 +26,7 @@ import com.diboot.core.util.S; import com.diboot.core.util.V; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.relational.EqualsTo; @@ -87,7 +88,7 @@ public class DataAccessControlInterceptor implements InnerInterceptor { mpBoundSql.sql(newSql); // 打印修改后的SQL if (log.isTraceEnabled() && V.notEquals(originSql, newSql)) { - log.trace("DataAccess Inteceptor SQL : {}", newSql); + log.trace("DataAccess Interceptor SQL : {}", newSql); } } else { noCheckpointCache.add(ms.getId()); @@ -127,7 +128,7 @@ public class DataAccessControlInterceptor implements InnerInterceptor { if (checkImpl == null) { throw new InvalidUsageException("无法从上下文中获取数据权限的接口实现:DataAccessInterface"); } - List idValues = checkImpl.getAccessibleIds(entityClass, entry.getKey()); + List idValues = checkImpl.getAccessibleIds(entityClass, entry.getKey()); if (idValues == null) { return null; } @@ -140,10 +141,21 @@ public class DataAccessControlInterceptor implements InnerInterceptor { } else if (idValues.size() == 1) { EqualsTo equalsTo = new EqualsTo(); equalsTo.setLeftExpression(new Column(idCol)); - equalsTo.setRightExpression(new StringValue(S.defaultValueOf(idValues.get(0)))); + if(idValues.get(0) instanceof Long){ + equalsTo.setRightExpression(new LongValue((Long)idValues.get(0))); + } + else{ + equalsTo.setRightExpression(new StringValue(S.defaultValueOf(idValues.get(0)))); + } return equalsTo; } else { - String conditionExpr = idCol + " IN ('" + S.join(idValues, "', '") + "')"; + String conditionExpr = idCol + " IN "; + if(idValues.get(0) instanceof Long){ + conditionExpr += "(" + S.join(idValues, ", ") + ")"; + } + else{ + conditionExpr += "('" + S.join(idValues, "', '") + "')"; + } try { return CCJSqlParserUtil.parseCondExpression(conditionExpr); } catch (JSQLParserException e) { diff --git a/diboot-core/src/main/java/com/diboot/core/holder/AnnotationRestApiHolder.java b/diboot-core/src/main/java/com/diboot/core/holder/AnnotationRestApiHolder.java index 3caf4cc482d9cd38ffdb514b326e5790f0e16217..d4afb43771af5f9decd748c528d4a07cda0ce0b6 100644 --- a/diboot-core/src/main/java/com/diboot/core/holder/AnnotationRestApiHolder.java +++ b/diboot-core/src/main/java/com/diboot/core/holder/AnnotationRestApiHolder.java @@ -24,6 +24,7 @@ import com.diboot.core.util.AnnotationUtils; import com.diboot.core.util.BeanUtils; import com.diboot.core.util.ContextHelper; import com.diboot.core.util.V; +import com.diboot.core.vo.ApiUri; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,7 +32,9 @@ import org.springframework.web.bind.annotation.RestController; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * 注解 RestApi 信息缓存 @@ -78,7 +81,7 @@ public class AnnotationRestApiHolder { * @param category * @return */ - public static List getRestApiList(String category){ + public synchronized static List getRestApiList(String category){ List apiList = getCacheManager() .getCacheObj(CACHE_NAME_CATEGORY_TO_APILIST, category, List.class); if(apiList == null && getCacheManager().isUninitializedCache(CACHE_NAME_CATEGORY_TO_APILIST)){ @@ -94,7 +97,7 @@ public class AnnotationRestApiHolder { * @param className * @return */ - public static RestApiWrapper getRestApiWrapper(String className){ + public synchronized static RestApiWrapper getRestApiWrapper(String className){ RestApiWrapper apiWrapper = getCacheManager() .getCacheObj(CACHE_NAME_CLASS_TO_WRAPPER, className, RestApiWrapper.class); if(apiWrapper == null && getCacheManager().isUninitializedCache(CACHE_NAME_CLASS_TO_WRAPPER)){ @@ -108,7 +111,7 @@ public class AnnotationRestApiHolder { /** * 初始化 */ - private static void initRestApiCache() { + private synchronized static void initRestApiCache() { List controllerList = ContextHelper.getBeansByAnnotation(RestController.class); if(V.isEmpty(controllerList)) { return; @@ -125,16 +128,31 @@ public class AnnotationRestApiHolder { getCacheManager() .putCacheObj(CACHE_NAME_CLASS_TO_WRAPPER, wrapper.getClassName(), wrapper); if(V.notEmpty(wrapper.getChildren())){ + Map> categoryApisMap = new HashMap<>(8); for(RestApi api : wrapper.getChildren()){ - List categoryApis = getCacheManager() - .getCacheObj(CACHE_NAME_CATEGORY_TO_APILIST, api.getCategory(), List.class); + List categoryApis = categoryApisMap.get(api.getCategory()); if(categoryApis == null){ categoryApis = new ArrayList<>(); - getCacheManager() - .putCacheObj(CACHE_NAME_CATEGORY_TO_APILIST, api.getCategory(), categoryApis); + categoryApisMap.put(api.getCategory(), categoryApis); } categoryApis.add(api); } + for(Map.Entry> entry : categoryApisMap.entrySet()){ + List categoryApis = getCacheManager() + .getCacheObj(CACHE_NAME_CATEGORY_TO_APILIST, entry.getKey(), List.class); + if(categoryApis == null){ + categoryApis = entry.getValue(); + } + else{ + for(RestApi api : entry.getValue()){ + if(!categoryApis.contains(api)){ + categoryApis.add(api); + } + } + } + getCacheManager() + .putCacheObj(CACHE_NAME_CATEGORY_TO_APILIST, entry.getKey(), categoryApis); + } } } } @@ -161,12 +179,12 @@ public class AnnotationRestApiHolder { // 处理Annotation注解 CollectThisApi restApiAnno = AnnotationUtils.getAnnotation(method, CollectThisApi.class); // 提取方法上的注解url - String[] methodAndUrl = AnnotationUtils.extractRequestMethodAndMappingUrl(method); - if(methodAndUrl[0] == null || methodAndUrl[1] == null){ + ApiUri apiUri = AnnotationUtils.extractRequestMethodAndMappingUrl(method); + if(apiUri.isEmpty()){ continue; } // 提取请求url-注解的关系 - buildRestApi(wrapper, urlPrefix, methodAndUrl, restApiAnno); + buildRestApi(wrapper, urlPrefix, apiUri, restApiAnno); } } } @@ -175,10 +193,10 @@ public class AnnotationRestApiHolder { * 构建restApiList * @param wrapper * @param urlPrefix - * @param methodAndUrl + * @param apiUri */ - private static void buildRestApi(RestApiWrapper wrapper, String urlPrefix, String[] methodAndUrl, CollectThisApi annotation){ - String requestMethod = methodAndUrl[0], url = methodAndUrl[1]; + private static void buildRestApi(RestApiWrapper wrapper, String urlPrefix, ApiUri apiUri, CollectThisApi annotation){ + String requestMethod = apiUri.getMethod(), url = apiUri.getUri(); for(String m : requestMethod.split(Cons.SEPARATOR_COMMA)){ for(String u : url.split(Cons.SEPARATOR_COMMA)){ if(V.notEmpty(urlPrefix)){ diff --git a/diboot-core/src/main/java/com/diboot/core/service/impl/BaseServiceImpl.java b/diboot-core/src/main/java/com/diboot/core/service/impl/BaseServiceImpl.java index 11ae845bcc509e4e9398f1d9332f09cde25eaad2..560585ef348aec0bc42f441d7d9d4d10d365e144 100644 --- a/diboot-core/src/main/java/com/diboot/core/service/impl/BaseServiceImpl.java +++ b/diboot-core/src/main/java/com/diboot/core/service/impl/BaseServiceImpl.java @@ -51,6 +51,7 @@ import com.diboot.core.vo.Status; import org.apache.ibatis.reflection.property.PropertyNamer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanWrapper; import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; @@ -311,23 +312,23 @@ public class BaseServiceImpl, T> extends ServiceImpl } else { selectOld.select(followerColumnName); } - List> oldMap; IService iService = entityInfo.getService(); BaseMapper baseMapper = entityInfo.getBaseMapper(); - if (iService != null) { - oldMap = iService.listMaps(selectOld); - } else { - oldMap = baseMapper.selectMaps(selectOld); - } - + List oldEntityList = (iService != null)? iService.list(selectOld) : baseMapper.selectList(selectOld); // 删除失效关联 List delIds = new ArrayList<>(); - for (Map map : oldMap) { - if (V.notEmpty(followerIdList) && followerIdList.remove((Serializable) map.get(followerColumnName))) { + List removeFollowerIds = new ArrayList<>(); + for (R entity : oldEntityList) { + Serializable followerId = (Serializable)BeanUtils.getProperty(entity, followerFieldName); + if (V.notEmpty(followerIdList) && followerIdList.contains(followerId)) { + removeFollowerIds.add(followerId); continue; } - delIds.add((Serializable) map.get(isExistPk ? entityInfo.getIdColumn() : followerColumnName)); + Serializable id = isExistPk? (Serializable)BeanUtils.getProperty(entity, entityInfo.getPropInfo().getIdFieldName()) : followerId; + if(id != null) { + delIds.add(id); + } } if (!delIds.isEmpty()) { if (isExistPk) { @@ -349,15 +350,21 @@ public class BaseServiceImpl, T> extends ServiceImpl } } } - // 新增关联 if (V.notEmpty(followerIdList)) { - List n2nRelations = new ArrayList<>(followerIdList.size()); + List newFollowIds = new ArrayList(); + for(Serializable id : followerIdList) { + if(!removeFollowerIds.contains(id)) { + newFollowIds.add(id); + } + } + List n2nRelations = new ArrayList<>(newFollowIds.size()); try { - for (Serializable followerId : followerIdList) { + for (Serializable followerId : newFollowIds) { R relation = middleTableClass.newInstance(); - BeanUtils.setProperty(relation, driverFieldName, driverId); - BeanUtils.setProperty(relation, followerFieldName, followerId); + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(relation); + beanWrapper.setPropertyValue(driverFieldName, driverId); + beanWrapper.setPropertyValue(followerFieldName, followerId); if (setConsumer != null) { setConsumer.accept(relation); } @@ -703,8 +710,8 @@ public class BaseServiceImpl, T> extends ServiceImpl } Map idNameMap = new HashMap<>(mapList.size()); for(Map map : mapList){ - ID key = (ID)map.get(entityInfo.getIdColumn()); - String value = S.valueOf(map.get(columnName)); + ID key = (ID)MapUtils.getIgnoreCase(map, entityInfo.getIdColumn()); + String value = S.valueOf(MapUtils.getIgnoreCase(map, columnName)); idNameMap.put(key, value); } return idNameMap; diff --git a/diboot-core/src/main/java/com/diboot/core/util/AnnotationUtils.java b/diboot-core/src/main/java/com/diboot/core/util/AnnotationUtils.java index ea2ad538f7e4733a080e8711ea512b6869605887..74778bc0ec3afca5aa1a0c4d2ee9d14e59775e4c 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/AnnotationUtils.java +++ b/diboot-core/src/main/java/com/diboot/core/util/AnnotationUtils.java @@ -15,6 +15,7 @@ */ package com.diboot.core.util; +import com.diboot.core.vo.ApiUri; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -39,7 +40,7 @@ public class AnnotationUtils extends org.springframework.core.annotation.Annotat * @param method * @return */ - public static String[] extractRequestMethodAndMappingUrl(Method method){ + public static ApiUri extractRequestMethodAndMappingUrl(Method method){ String requestMethod = null, url = null; if(method.getAnnotation(GetMapping.class) != null){ GetMapping anno = AnnotationUtils.getAnnotation(method, GetMapping.class); @@ -73,14 +74,14 @@ public class AnnotationUtils extends org.springframework.core.annotation.Annotat requestMethod = S.join(methods); } else{ - requestMethod = "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE"; + requestMethod = "GET,POST"; } url = getNotEmptyStr(anno.value(), anno.path()); } else{ log.warn("无法识别到URL Mapping相关注解: "+method.getName()); } - return new String[]{requestMethod, url}; + return new ApiUri(requestMethod, url); } /** diff --git a/diboot-core/src/main/java/com/diboot/core/util/BeanUtils.java b/diboot-core/src/main/java/com/diboot/core/util/BeanUtils.java index b5a3066bf96c3618e5f237d3b5a82ae64bd500e0..94587945bf2a2fd44c528b5265440fe9823a2815 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/BeanUtils.java +++ b/diboot-core/src/main/java/com/diboot/core/util/BeanUtils.java @@ -16,8 +16,8 @@ package com.diboot.core.util; import com.baomidou.mybatisplus.annotation.TableField; -import com.diboot.core.binding.cache.BindingCacheManager; import com.diboot.core.config.Cons; +import com.diboot.core.converter.*; import com.diboot.core.data.copy.AcceptAnnoCopier; import com.diboot.core.entity.BaseEntity; import com.diboot.core.exception.BusinessException; @@ -31,6 +31,7 @@ import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; import org.springframework.util.ReflectionUtils; import java.io.Serializable; @@ -148,17 +149,13 @@ public class BeanUtils { if (V.isAnyEmpty(model, propMap)) { return; } - Map fieldNameMaps = BindingCacheManager.getFieldsMap(model.getClass()); + BeanWrapper beanWrapper = BeanUtils.getBeanWrapper(model); for(Map.Entry entry : propMap.entrySet()){ - Field field = fieldNameMaps.get(entry.getKey()); - if(field != null){ - try{ - Object value = convertValueToFieldType(entry.getValue(), field); - setProperty(model, entry.getKey(), value); - } - catch (Exception e){ - log.warn("复制属性{}.{}异常: {}", model.getClass().getSimpleName(), entry.getKey(), e.getMessage()); - } + try{ + beanWrapper.setPropertyValue(entry.getKey(), entry.getValue()); + } + catch (Exception e){ + log.warn("复制属性{}.{}异常: {}", model.getClass().getSimpleName(), entry.getKey(), e.getMessage()); } } } @@ -170,6 +167,10 @@ public class BeanUtils { * @return */ public static Object getProperty(Object obj, String field){ + if(obj instanceof Map){ + Map objMap = (Map)obj; + return objMap.get(field); + } try { BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj); return wrapper.getPropertyValue(field); @@ -187,6 +188,10 @@ public class BeanUtils { * @return */ public static String getStringProperty(Object obj, String field){ + if(obj instanceof Map){ + Map objMap = (Map)obj; + return objMap.containsKey(field)? S.valueOf(objMap.get(field)) : null; + } Object property = getProperty(obj, field); if(property == null){ return null; @@ -194,6 +199,19 @@ public class BeanUtils { return String.valueOf(property); } + /*** + * 设置属性值 + * @param obj + */ + public static BeanWrapper getBeanWrapper(Object obj) { + BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj); + if(wrapper.getConversionService() == null){ + ConversionService conversionService = ContextHelper.getBean(EnhancedConversionService.class); + wrapper.setConversionService(conversionService); + } + return wrapper; + } + /*** * 设置属性值 * @param obj @@ -201,7 +219,7 @@ public class BeanUtils { * @param value */ public static void setProperty(Object obj, String field, Object value) { - BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(obj); + BeanWrapper wrapper = getBeanWrapper(obj); wrapper.setPropertyValue(field, value); } @@ -392,26 +410,39 @@ public class BeanUtils { } /*** - * 构建指定根节点的上下级关联的树形结构(上级parentId、子节点children) + * 构建指定根节点的上下级关联的树形结构(主键id,上级属性parentId、子节点属性children) * @param allNodes 所有节点对象 * @param rootNodeId 跟节点ID * @param * @return */ public static List buildTree(List allNodes, Object rootNodeId){ - return buildTree(allNodes, rootNodeId, Cons.FieldName.parentId.name(), Cons.FieldName.children.name()); + return buildTree(allNodes, rootNodeId, Cons.FieldName.id.name(), Cons.FieldName.parentId.name(), Cons.FieldName.children.name()); } /*** - * 构建指定根节点的上下级关联的树形结构(上级parentId、子节点children) + * 构建指定根节点的上下级关联的树形结构(主键指定,上级属性parentId、子节点属性children) * @param allNodes 所有节点对象 * @param rootNodeId 根节点ID + * @param idFieldName 主键属性名 + * @param + * @return + */ + public static List buildTree(List allNodes, Object rootNodeId, String idFieldName){ + return buildTree(allNodes, rootNodeId, idFieldName, Cons.FieldName.parentId.name(), Cons.FieldName.children.name()); + } + + /*** + * 构建指定根节点的上下级关联的树形结构(指定主键属性,上级属性、子节点属性名) + * @param allNodes 所有节点对象 + * @param rootNodeId 根节点ID + * @param idFieldName 主键属性名 * @param parentIdFieldName 父节点属性名 * @param childrenFieldName 子节点集合属性名 * @param * @return */ - public static List buildTree(List allNodes, Object rootNodeId, String parentIdFieldName, String childrenFieldName){ + public static List buildTree(List allNodes, Object rootNodeId, String idFieldName, String parentIdFieldName, String childrenFieldName){ if(V.isEmpty(allNodes)){ return null; } @@ -422,7 +453,7 @@ public class BeanUtils { if(parentId == null || V.fuzzyEqual(parentId, rootNodeId)){ topLevelModels.add(node); } - Object nodeId = getProperty(node, Cons.FieldName.id.name()); + Object nodeId = getProperty(node, idFieldName); if(V.equals(nodeId, parentId)){ throw new BusinessException(Status.WARN_PERFORMANCE_ISSUE, "parentId关联自身,请检查!" + node.getClass().getSimpleName()+":"+nodeId); } @@ -432,8 +463,8 @@ public class BeanUtils { } // 遍历第一级节点,并挂载 children 子节点 for(T node : allNodes) { - Object nodeId = getProperty(node, Cons.FieldName.id.name()); - List children = buildTreeChildren(nodeId, allNodes, parentIdFieldName, childrenFieldName); + Object nodeId = getProperty(node, idFieldName); + List children = buildTreeChildren(nodeId, allNodes, idFieldName, parentIdFieldName, childrenFieldName); setProperty(node, childrenFieldName, children); } return topLevelModels; @@ -443,11 +474,12 @@ public class BeanUtils { * 递归构建树节点的子节点 * @param parentId * @param nodeList + * @param idFieldName * @param parentIdFieldName 父节点属性名 * @param childrenFieldName 子节点集合属性名 * @return */ - public static List buildTreeChildren(Object parentId, List nodeList, String parentIdFieldName, String childrenFieldName) { + public static List buildTreeChildren(Object parentId, List nodeList, String idFieldName, String parentIdFieldName, String childrenFieldName) { List children = null; for(T node : nodeList) { Object nodeParentId = getProperty(node, parentIdFieldName); @@ -460,12 +492,11 @@ public class BeanUtils { } if(children != null){ for(T child : children) { - Object nodeId = getProperty(child, Cons.FieldName.id.name()); - List childNodeChildren = buildTreeChildren(nodeId, nodeList, parentIdFieldName, childrenFieldName); - if(childNodeChildren == null) { - childNodeChildren = new ArrayList<>(); + Object nodeId = getProperty(child, idFieldName); + List childNodeChildren = buildTreeChildren(nodeId, nodeList, idFieldName, parentIdFieldName, childrenFieldName); + if(childNodeChildren != null) { + setProperty(child, childrenFieldName, childNodeChildren); } - setProperty(child, childrenFieldName, childNodeChildren); } } return children; @@ -730,7 +761,7 @@ public class BeanUtils { * @return */ public static List extractAllFields(Class clazz){ - return BindingCacheManager.getFields(clazz); + return extractClassFields(clazz, null); } /** @@ -739,7 +770,7 @@ public class BeanUtils { * @return */ public static List extractFields(Class clazz, Class annotation){ - return BindingCacheManager.getFields(clazz, annotation); + return extractClassFields(clazz, annotation); } /** @@ -784,7 +815,6 @@ public class BeanUtils { * @return */ public static Class getGenericityClass(Object instance, int index){ - //TODO 可缓存 Class hostClass = getTargetClass(instance); ResolvableType resolvableType = ResolvableType.forClass(hostClass).getSuperType(); ResolvableType[] types = resolvableType.getGenerics(); @@ -887,4 +917,82 @@ public class BeanUtils { wrapper.setPropertyValue(fieldName, null); } } + + /** + * 转换集合中的string类型id值为指定类型 + * @param values + * @param fieldType + * @return + */ + public static Collection convertIdValuesToType(Collection values, Class fieldType) { + if(V.isEmpty(values)) { + return values; + } + if(V.equals(values.iterator().next().getClass(), fieldType)) { + return values; + } + Collection formatValues = new ArrayList(values.size()); + for(Object value : values) { + formatValues.add(convertIdValueToType(value, fieldType)); + } + return formatValues; + } + + /** + * 转换string类型id值为指定类型 + * @param value + * @param fieldType + * @return + */ + public static Object convertIdValueToType(Object value, Class fieldType) { + if(V.isEmpty(value)) { + return null; + } + if(Long.class.equals(fieldType)) { + return Long.parseLong(S.valueOf(value)); + } + if(Integer.class.equals(fieldType)) { + return Integer.parseInt(S.valueOf(value)); + } + return value; + } + + /** + * 初始化fields + * @param beanClazz + * @return + */ + private static List extractClassFields(Class beanClazz, Class annotation){ + List fieldList = new ArrayList<>(); + Set fieldNameSet = new HashSet<>(); + loopFindFields(beanClazz, annotation, fieldList, fieldNameSet); + return fieldList; + } + + /** + * 循环向上查找fields + * @param beanClazz + * @param annotation + * @param fieldList + * @param fieldNameSet + */ + private static void loopFindFields(Class beanClazz, Class annotation, List fieldList, Set fieldNameSet){ + if(beanClazz == null) { + return; + } + Field[] fields = beanClazz.getDeclaredFields(); + if (V.notEmpty(fields)) { + for (Field field : fields) { + // 被重写属性,以子类的为准 + if (!fieldNameSet.add(field.getName())) { + continue; + } + if (annotation == null || field.getAnnotation(annotation) != null) { + fieldList.add(field); + } + } + } + loopFindFields(beanClazz.getSuperclass(), annotation, fieldList, fieldNameSet); + } + } diff --git a/diboot-core/src/main/java/com/diboot/core/util/ContextHelper.java b/diboot-core/src/main/java/com/diboot/core/util/ContextHelper.java index e88ece4a812ee288b5d3676ab9030b30e54c85cc..7a52eb92c15dd33134b6c6780ec0d35d1ac7e1c0 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/ContextHelper.java +++ b/diboot-core/src/main/java/com/diboot/core/util/ContextHelper.java @@ -24,20 +24,21 @@ import com.diboot.core.binding.parser.EntityInfoCache; import com.diboot.core.binding.parser.ParserCache; import com.diboot.core.binding.parser.PropInfo; import com.diboot.core.service.BaseService; -import org.apache.ibatis.session.SqlSessionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Lazy; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.web.context.ContextLoader; +import javax.sql.DataSource; import java.lang.annotation.Annotation; +import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -50,7 +51,7 @@ import java.util.Map; */ @Component @Lazy(false) -public class ContextHelper implements ApplicationContextAware, ApplicationListener { +public class ContextHelper implements ApplicationContextAware, ApplicationListener { private static final Logger log = LoggerFactory.getLogger(ContextHelper.class); /*** @@ -66,11 +67,13 @@ public class ContextHelper implements ApplicationContextAware, ApplicationListen @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { APPLICATION_CONTEXT = applicationContext; + log.debug("ApplicationContext已赋值: {}", APPLICATION_CONTEXT.getDisplayName()); } @Override - public void onApplicationEvent(ContextRefreshedEvent event) { + public void onApplicationEvent(ApplicationReadyEvent event) { APPLICATION_CONTEXT = event.getApplicationContext(); + log.debug("ApplicationContext已注入: {}", APPLICATION_CONTEXT.getDisplayName()); } /*** @@ -78,6 +81,7 @@ public class ContextHelper implements ApplicationContextAware, ApplicationListen */ public static ApplicationContext getApplicationContext() { if (APPLICATION_CONTEXT == null){ + log.debug("ApplicationContext未初始化,通过ContextLoader获取!"); APPLICATION_CONTEXT = ContextLoader.getCurrentWebApplicationContext(); } if(APPLICATION_CONTEXT == null){ @@ -179,6 +183,7 @@ public class ContextHelper implements ApplicationContextAware, ApplicationListen if(iService instanceof BaseService){ return (BaseService)iService; } + log.warn("Entity的service实现类: {} 非BaseService实现!", entityInfoCache.getService()); return null; } @@ -223,8 +228,24 @@ public class ContextHelper implements ApplicationContextAware, ApplicationListen * @return */ public static String getJdbcUrl() { - Environment environment = getApplicationContext().getEnvironment(); - String jdbcUrl = environment.getProperty("spring.datasource.url"); + ApplicationContext applicationContext = getApplicationContext(); + if (applicationContext == null) { + return null; + } + String jdbcUrl = null; + try{ + DataSource dataSource = applicationContext.getBean(DataSource.class); + Connection connection = dataSource.getConnection(); + jdbcUrl = connection.getMetaData().getURL(); + connection.close(); + return jdbcUrl; + } + catch (Exception e){ + log.warn("获取JDBC URL异常: {}", e.getMessage()); + } + // 候补识别方式,暂时保留 + Environment environment = applicationContext.getEnvironment(); + jdbcUrl = environment.getProperty("spring.datasource.url"); if(jdbcUrl == null){ jdbcUrl = environment.getProperty("spring.datasource.druid.url"); } @@ -265,12 +286,6 @@ public class ContextHelper implements ApplicationContextAware, ApplicationListen DATABASE_TYPE = DbType.SQL_SERVER.getDb(); } } - else{ - SqlSessionFactory sqlSessionFactory = getBean(SqlSessionFactory.class); - if(sqlSessionFactory != null){ - DATABASE_TYPE = sqlSessionFactory.getConfiguration().getDatabaseId(); - } - } if(DATABASE_TYPE == null){ log.warn("无法识别数据库类型,请检查数据源配置:spring.datasource.url等"); } diff --git a/diboot-core/src/main/java/com/diboot/core/util/DateConverter.java b/diboot-core/src/main/java/com/diboot/core/util/DateConverter.java index 6225eb45a115a54634880edda3b6d09173d8f01b..972746681625ce3c9a16bf17f5c7ed1faba310ab 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/DateConverter.java +++ b/diboot-core/src/main/java/com/diboot/core/util/DateConverter.java @@ -22,11 +22,13 @@ import org.springframework.core.convert.converter.Converter; import java.util.Date; /** - * Spring表单自动绑定到Java属性时的日期格式转换 + * Spring表单自动绑定到Java属性时的日期格式转换
+ * @see com.diboot.core.converter.String2DateConverter * @author mazc@dibo.ltd * @version v2.0 * @date 2019/01/01 */ +@Deprecated public class DateConverter implements Converter { private static final Logger log = LoggerFactory.getLogger(DateConverter.class); diff --git a/diboot-core/src/main/java/com/diboot/core/util/MapUtils.java b/diboot-core/src/main/java/com/diboot/core/util/MapUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..058e582a3b2aba201700c607b75ac654b2f262df --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/util/MapUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.core.util; + +import java.util.Map; + +/** + * Map相关工具类 + * @author JerryMa + * @version v2.6.0 + * @date 2022/6/15 + * Copyright © diboot.com + */ +public class MapUtils { + + /** + * 忽略key大小写,兼容多库等场景 + * @param map + * @param key + * @return + * @param + */ + public static T getIgnoreCase(Map map, String key) { + if(map.containsKey(key)) { + return map.get(key); + } + if(map.containsKey(key.toUpperCase())) { + return map.get(key.toUpperCase()); + } + return map.get(key.toLowerCase()); + } + +} diff --git a/diboot-core/src/main/java/com/diboot/core/util/SqlFileInitializer.java b/diboot-core/src/main/java/com/diboot/core/util/SqlFileInitializer.java index f6c09d1fb19d6d4dcd5127af2e7e4f0e58e49b68..772d6c6d7d1545c8ffa1bd7a9fe70a97d7995dc6 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/SqlFileInitializer.java +++ b/diboot-core/src/main/java/com/diboot/core/util/SqlFileInitializer.java @@ -22,13 +22,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; +import javax.sql.DataSource; import java.io.FileNotFoundException; import java.io.InputStream; import java.sql.Connection; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; /** @@ -40,8 +40,6 @@ import java.util.stream.Collectors; public class SqlFileInitializer { private static final Logger log = LoggerFactory.getLogger(SqlFileInitializer.class); - // 数据字典SQL - private static final String MYBATIS_PLUS_SCHEMA_CONFIG = "mybatis-plus.global-config.db-config.schema"; private static String CURRENT_SCHEMA = null; private static Environment environment; @@ -63,6 +61,9 @@ public class SqlFileInitializer { if (DbType.MARIADB.getDb().equalsIgnoreCase(dbType)) { dbType = "mysql"; } + else if(DbType.KINGBASE_ES.getDb().equalsIgnoreCase(dbType)) { + dbType = DbType.POSTGRE_SQL.getDb(); + } String sqlPath = "META-INF/sql/init-" + module + "-" + dbType + ".sql"; return sqlPath; } @@ -204,15 +205,13 @@ public class SqlFileInitializer { sqlStatement = clearComments(sqlStatement); // 替换sqlStatement中的变量,如{SCHEMA} if(sqlStatement.contains("${SCHEMA}")){ - if(getDbType().equals(DbType.SQL_SERVER.getDb())){ - sqlStatement = S.replace(sqlStatement, "${SCHEMA}", getSqlServerCurrentSchema()); - } - else if(getDbType().equals(DbType.ORACLE.getDb())){ - sqlStatement = S.replace(sqlStatement, "${SCHEMA}", getOracleCurrentSchema()); - } - else{ + String schema = getCurrentSchema(); + if (V.isEmpty(schema)) { sqlStatement = S.replace(sqlStatement, "${SCHEMA}.", ""); } + else { + sqlStatement = S.replace(sqlStatement, "${SCHEMA}", schema); + } } return sqlStatement; } @@ -315,7 +314,7 @@ public class SqlFileInitializer { * @param inputSql * @return */ - private static String clearComments(String inputSql){ + public static String clearComments(String inputSql){ String[] sqlRows = inputSql.split("\\n"); List cleanSql = new ArrayList(); for(String row : sqlRows){ @@ -354,75 +353,28 @@ public class SqlFileInitializer { return inputSql; } - //SQL Server查询当前schema - public static final String SQL_DEFAULT_SCHEMA = "SELECT DISTINCT default_schema_name FROM sys.database_principals where default_schema_name is not null AND name!='guest'"; - /** - * 查询SqlServer当前schema - * @return - */ - public static String getSqlServerCurrentSchema(){ - if(CURRENT_SCHEMA == null){ - Object firstValue = queryFirstValue(SQL_DEFAULT_SCHEMA, "default_schema_name"); - if(firstValue != null){ - CURRENT_SCHEMA = (String)firstValue; - } - if(CURRENT_SCHEMA == null){ - CURRENT_SCHEMA = environment.getProperty(MYBATIS_PLUS_SCHEMA_CONFIG); - } - // dbo schema兜底 - if(CURRENT_SCHEMA == null){ - CURRENT_SCHEMA = "dbo"; - } - } - return CURRENT_SCHEMA; - } /** - * 获取当前schema,oracle默认schema=当前user + * 获取当前schema * @return */ - public static String getOracleCurrentSchema(){ - if(CURRENT_SCHEMA == null){ - // 先查找配置中是否存在指定 - String alterSessionSql = environment.getProperty("spring.datasource.hikari.connection-init-sql"); - if(V.notEmpty(alterSessionSql) && S.containsIgnoreCase(alterSessionSql," current_schema=")){ - CURRENT_SCHEMA = S.substringAfterLast(alterSessionSql, "="); + public static String getCurrentSchema() { + if(CURRENT_SCHEMA == null) { + DataSource dataSource = ContextHelper.getBean(DataSource.class); + try{ + Connection connection = dataSource.getConnection(); + CURRENT_SCHEMA = connection.getSchema(); + connection.close(); } - if(CURRENT_SCHEMA == null){ - CURRENT_SCHEMA = environment.getProperty(MYBATIS_PLUS_SCHEMA_CONFIG); + catch (Exception e){ + log.warn("获取schema异常: {}", e.getMessage()); } - if(CURRENT_SCHEMA == null){ - // 然后默认为当前用户名大写 - String username = environment.getProperty("spring.datasource.username"); - if(username != null){ - CURRENT_SCHEMA = username.toUpperCase(); - } + if(CURRENT_SCHEMA == null) { + CURRENT_SCHEMA = ""; } } return CURRENT_SCHEMA; } - - /** - * 查询SQL返回第一项 - * @return - */ - public static Object queryFirstValue(String sql, String key){ - try{ - List> mapList = SqlExecutor.executeQuery(sql, null); - if(V.notEmpty(mapList)){ - for (Map mapElement : mapList){ - if(mapElement.get(key) != null){ - return mapElement.get(key); - } - } - } - } - catch(Exception e){ - log.error("获取SqlServer默认Schema异常: {}", e.getMessage()); - } - return null; - } - /** * 获取数据库类型 * @return diff --git a/diboot-core/src/main/java/com/diboot/core/util/V.java b/diboot-core/src/main/java/com/diboot/core/util/V.java index fa55b31a4fd944271e3629e6d188430c031d103e..b14a196b3d1d7ce8465fd04a52e77d9119aaacb4 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/V.java +++ b/diboot-core/src/main/java/com/diboot/core/util/V.java @@ -45,6 +45,9 @@ public class V { */ private static Validator VALIDATOR = null; + // region 判空方法 + /* ================================================== 判空方法 =================================================== */ + /** * 对象是否为空 */ @@ -59,6 +62,12 @@ public class V { return isEmpty((Map) obj); } else if (obj.getClass().isArray()) { return Array.getLength(obj) == 0; + } else if (obj instanceof Iterator) { + return isEmpty((Iterator) obj); + } else if (obj instanceof Iterable) { + return isEmpty((Iterable) obj); + } else if (obj instanceof Enumeration) { + return isEmpty((Enumeration) obj); } return false; } @@ -98,6 +107,18 @@ public class V { return obj == null || obj.isEmpty(); } + public static boolean isEmpty(Iterator iterator) { + return iterator == null || !iterator.hasNext(); + } + + public static boolean isEmpty(Iterable iterable) { + return iterable == null || isEmpty(iterable.iterator()); + } + + public static boolean isEmpty(Enumeration enumeration) { + return enumeration == null || !enumeration.hasMoreElements(); + } + public static boolean isEmpty(boolean[] array) { return array == null || array.length == 0; } @@ -134,42 +155,6 @@ public class V { return array == null || array.length == 0; } - public static boolean notEmpty(boolean[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(byte[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(char[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(double[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(float[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(int[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(long[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(short[] array) { - return array != null && array.length != 0; - } - - public static boolean notEmpty(Object[] array) { - return array != null && array.length != 0; - } - /** * 任意元素为空则返回true * @@ -188,14 +173,16 @@ public class V { return false; } - /** - * 全都不为空则返回true - * - * @param objs objs - * @return true/false - */ - public static boolean isNoneEmpty(Object... objs) { - return !isAnyEmpty(objs); + public static boolean isAnyEmpty(Collection collection) { + if (isEmpty(collection)) { + return true; + } + for (T obj : collection) { + if (isEmpty(obj)) { + return true; + } + } + return false; } /** @@ -216,6 +203,23 @@ public class V { return true; } + public static boolean isAllEmpty(Collection collection) { + if (isEmpty(collection)) { + return true; + } + for (T obj : collection) { + if (notEmpty(obj)) { + return false; + } + } + return true; + } + /* ================================================== end ======================================================= */ + // endregion + + // region 非空判断方法 + /* ================================================== 非空判断方法 ================================================ */ + /** * 对象是否为空 */ @@ -230,6 +234,12 @@ public class V { return notEmpty((Map) obj); } else if (obj.getClass().isArray()) { return Array.getLength(obj) != 0; + } else if (obj instanceof Iterator) { + return notEmpty((Iterator) obj); + } else if (obj instanceof Iterable) { + return notEmpty((Iterable) obj); + } else if (obj instanceof Enumeration) { + return notEmpty((Enumeration) obj); } return true; } @@ -269,6 +279,54 @@ public class V { return obj != null && !obj.isEmpty(); } + public static boolean notEmpty(Iterator iterator) { + return iterator != null && iterator.hasNext(); + } + + public static boolean notEmpty(Iterable iterable) { + return iterable != null && notEmpty(iterable.iterator()); + } + + public static boolean notEmpty(Enumeration enumeration) { + return enumeration != null && enumeration.hasMoreElements(); + } + + public static boolean notEmpty(boolean[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(byte[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(char[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(short[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(int[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(long[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(double[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(float[] array) { + return array != null && array.length != 0; + } + + public static boolean notEmpty(Object[] array) { + return array != null && array.length != 0; + } + /** * 对象不为空且不为0 */ @@ -283,6 +341,22 @@ public class V { return intObj != null && intObj != 0; } + /** + * 全都不为空则返回true + * + * @param objs objs + * @return true/false + */ + public static boolean isNoneEmpty(Object... objs) { + return !isAnyEmpty(objs); + } + + public static boolean isNoneEmpty(Collection collection) { + return !isAnyEmpty(collection); + } + /* ================================================== end ======================================================= */ + // endregion + /** * 集合中是否包含指定元素 * @@ -294,6 +368,17 @@ public class V { return collection != null && collection.contains(target); } + /** + * 集合中是否包含指定字符串或其大写字符串 + * + * @param collection 集合 + * @param target 查找字符串 + * @return 集合为空或者不包含元素,则返回false + */ + public static boolean containsIgnoreCase(Collection collection, String target) { + return collection != null && (collection.contains(target) || collection.contains(target.toLowerCase()) || collection.contains(target.toUpperCase())); + } + /** * 集合中是否不包含指定元素 * @@ -305,6 +390,17 @@ public class V { return collection != null && !collection.contains(target); } + /** + * 集合中是否不包含指定字符串或其大写字符串 + * + * @param collection 集合 + * @param target 查找字符串 + * @return 集合为空或者不包含元素,则返回false + */ + public static boolean notContainsIgnoreCase(Collection collection, String target) { + return !(containsIgnoreCase(collection, target)); + } + /** * 判断是否为数字(允许小数点) * @@ -402,6 +498,17 @@ public class V { return TRUE_SET.contains(value); } + /** + * 是否是false + *

不要再写"xxx == false"了

+ * + * @param expression bool表达式 + * @return 入参为false时返回true + */ + public static boolean isFalse(boolean expression) { + return !expression; + } + /** * 根据指定规则校验字符串的值是否合法 */ @@ -506,12 +613,22 @@ public class V { // size相等,且一个为空集合 return true; } - // 已经确定两个集合的数量相等,如果相同位置的值不相等,则必定不相等 - // 避免使用某些集合容器低效率的contains方法 - Iterator sourceIterator = sourceList.iterator(); - Iterator targetIterator = targetList.iterator(); - while (sourceIterator.hasNext()) { - if (!equals(sourceIterator.next(), targetIterator.next())) { + if (source instanceof Set) { + //noinspection SuspiciousMethodCalls + return ((Set) source).containsAll(targetList); + } + + // copy from org.apache.commons.collections4.CollectionUtils.isEqualCollection() + // 分别统计 Collection中 元素对应的数量 + CardinalityHelper helper = new CardinalityHelper<>(sourceList, targetList); + // 如果 两个Collection的统计结果 不一致 + if (helper.cardinalityA.size() != helper.cardinalityB.size()) { + return false; + } + // 遍历统计结果 + for (final Object obj : helper.cardinalityA.keySet()) { + // 相同元素 在两个Collection中的数量却不等 + if (helper.freqA(obj) != helper.freqB(obj)) { return false; } } @@ -523,13 +640,8 @@ public class V { } else if (sourceMap.isEmpty()) { return true; } - Iterator> sourceIterator = sourceMap.entrySet().iterator(); - Iterator> targetIterator = targetMap.entrySet().iterator(); - while (sourceIterator.hasNext()) { - Map.Entry sourceEntry = sourceIterator.next(); - Map.Entry targetEntry = targetIterator.next(); - if (!equals(sourceEntry.getKey(), targetEntry.getKey()) - || !equals(sourceEntry.getValue(), targetEntry.getValue())) { + for (Map.Entry entry : sourceMap.entrySet()) { + if (!equals(entry.getValue(), targetMap.get(entry.getKey()))) { return false; } } @@ -632,4 +744,104 @@ public class V { return S.join(allErrors); } + /** + * Helper class to easily access cardinality properties of two collections. + * + * @param the element type + */ + private static class CardinalityHelper { + + /** + * Contains the cardinality for each object in collection A. + */ + final Map cardinalityA; + + /** + * Contains the cardinality for each object in collection B. + */ + final Map cardinalityB; + + /** + * Create a new CardinalityHelper for two collections. + * + * @param a the first collection + * @param b the second collection + */ + CardinalityHelper(final Iterable a, final Iterable b) { + cardinalityA = getCardinalityMap(a); + cardinalityB = getCardinalityMap(b); + } + + /** + * Returns a {@link Map} mapping each unique element in the given + * {@link Collection} to an {@link Integer} representing the number + * of occurrences of that element in the {@link Collection}. + *

+ * Only those elements present in the collection will appear as + * keys in the map. + *

+ * + * @param the type of object in the returned {@link Map}. This is a super type of <I>. + * @param coll the collection to get the cardinality map for, must not be null + * @return the populated cardinality map + * @throws NullPointerException if coll is null + */ + public static Map getCardinalityMap(final Iterable coll) { + Objects.requireNonNull(coll, "coll"); + final Map count = new HashMap<>(); + for (final O obj : coll) { + count.merge(obj, 1, Integer::sum); + } + return count; + } + + /** + * Returns the maximum frequency of an object. + * + * @param obj the object + * @return the maximum frequency of the object + */ + public final int max(final Object obj) { + return Math.max(freqA(obj), freqB(obj)); + } + + /** + * Returns the minimum frequency of an object. + * + * @param obj the object + * @return the minimum frequency of the object + */ + public final int min(final Object obj) { + return Math.min(freqA(obj), freqB(obj)); + } + + /** + * Returns the frequency of this object in collection A. + * + * @param obj the object + * @return the frequency of the object in collection A + */ + public int freqA(final Object obj) { + return getFreq(obj, cardinalityA); + } + + /** + * Returns the frequency of this object in collection B. + * + * @param obj the object + * @return the frequency of the object in collection B + */ + public int freqB(final Object obj) { + return getFreq(obj, cardinalityB); + } + + private int getFreq(final Object obj, final Map freqMap) { + final Integer count = freqMap.get(obj); + if (count != null) { + return count; + } + return 0; + } + } + } diff --git a/diboot-core/src/main/java/com/diboot/core/vo/ApiUri.java b/diboot-core/src/main/java/com/diboot/core/vo/ApiUri.java new file mode 100644 index 0000000000000000000000000000000000000000..3f2715930a17d949b0ed8d9664ce9e7c010b5a5a --- /dev/null +++ b/diboot-core/src/main/java/com/diboot/core/vo/ApiUri.java @@ -0,0 +1,54 @@ +package com.diboot.core.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * API接口地址 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/21 + * Copyright © diboot.com + */ +@Getter +@Setter +public class ApiUri implements Serializable { + private static final long serialVersionUID = 662520281067250572L; + + public ApiUri() { + } + + public ApiUri(String method, String uri){ + this.method = method; + this.uri = uri; + } + + public ApiUri(String method, String uri, String label){ + this.method = method; + this.uri = uri; + this.label = label; + } + + /** + * 接口Method + */ + private String method; + + /** + * 接口URI + */ + private String uri; + + /** + * 接口备注 + */ + private String label; + + @JsonIgnore + public boolean isEmpty(){ + return method == null || uri == null; + } +} diff --git a/diboot-core/src/main/java/com/diboot/core/vo/Pagination.java b/diboot-core/src/main/java/com/diboot/core/vo/Pagination.java index 20c846219d3fc79178c94858da24ad17e8b33ac6..604df46999d6abeded39104d7cbfe8720e2bbde9 100644 --- a/diboot-core/src/main/java/com/diboot/core/vo/Pagination.java +++ b/diboot-core/src/main/java/com/diboot/core/vo/Pagination.java @@ -186,6 +186,18 @@ public class Pagination implements Serializable { return this.orderBy = Cons.FieldName.createTime.name() + ":" + Cons.ORDER_DESC; } + /** + * 是否为分页参数 + * @param paramName + * @return + */ + public static boolean isPaginationParam(String paramName){ + return "pageIndex".equals(paramName) + || "pageSize".equals(paramName) + || "orderBy".equals(paramName) + || "totalCount".equals(paramName); + } + private PropInfo getEntityPropInfo(){ if (this.entityClass != null) { return BindingCacheManager.getPropInfoByClass(this.entityClass); diff --git a/diboot-file-starter/pom.xml b/diboot-file-starter/pom.xml index c9835598372fafdbc9489f6bcc63ee803471ae15..085d524f6affb6d4e5c4240d5a418a63d99b4a08 100644 --- a/diboot-file-starter/pom.xml +++ b/diboot-file-starter/pom.xml @@ -7,11 +7,11 @@ diboot-root com.diboot - 2.5.0 + 2.6.0 diboot-file-spring-boot-starter - 2.5.0 + 2.6.0 jar diboot file component project @@ -45,7 +45,7 @@ com.alibaba easyexcel - 3.0.5 + ${easyexcel.version} org.apache.commons diff --git a/diboot-file-starter/src/main/java/com/diboot/file/excel/listener/ReadExcelListener.java b/diboot-file-starter/src/main/java/com/diboot/file/excel/listener/ReadExcelListener.java index 8c54f266f18557cbd43df2d10525b2c57b4a669d..8e898cd3e3eb9f84b09d8df30ed2c3efdc5b7a62 100644 --- a/diboot-file-starter/src/main/java/com/diboot/file/excel/listener/ReadExcelListener.java +++ b/diboot-file-starter/src/main/java/com/diboot/file/excel/listener/ReadExcelListener.java @@ -385,7 +385,7 @@ public abstract class ReadExcelListener implements Rea BeanUtils.setProperty(data, setFieldName, 0); } } else if (excelBindField.empty().equals(EmptyStrategy.WARN)) { - data.addComment(entry.getKey(), name + " 值不存在"); + data.addComment(entry.getKey(), "关联值不存在"); } else if (excelBindField.empty().equals(EmptyStrategy.IGNORE) && valueNotNull) { log.warn("非空字段 {} 不应设置 EmptyStrategy.IGNORE.", entry.getKey()); } @@ -396,7 +396,7 @@ public abstract class ReadExcelListener implements Rea } } else { if (excelBindField.duplicate().equals(DuplicateStrategy.WARN)) { - data.addComment(entry.getKey(), name + " 匹配到多个值"); + data.addComment(entry.getKey(), "匹配到多个关联值"); } else if (excelBindField.duplicate().equals(DuplicateStrategy.FIRST)) { // 非预览时 赋值 if (!preview) { @@ -417,7 +417,7 @@ public abstract class ReadExcelListener implements Rea } // 非空才报错 if (valueNotNull && V.isEmpty(valList)) { - data.addComment(entry.getKey(), name + " 无匹配字典"); + data.addComment(entry.getKey(), "无匹配字典"); continue; } } diff --git a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-dm.sql b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..9116049ec21c39ac5af88a0826a1833bc3e2cda4 --- /dev/null +++ b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-dm.sql @@ -0,0 +1,39 @@ +-- 上传文件表 +CREATE TABLE ${SCHEMA}.upload_file ( + uuid VARCHAR(32) NOT NULL, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(50), + rel_obj_type VARCHAR(150), + rel_obj_id VARCHAR(100), + rel_obj_field VARCHAR(150), + file_name VARCHAR(300) NOT NULL, + storage_path VARCHAR(500) NOT NULL, + access_url VARCHAR(500), + file_type VARCHAR(50), + data_count INT DEFAULT 0 not null, + description VARCHAR(300), + is_deleted BIT DEFAULT 0 not null, + create_by BIGINT default 0 , + create_time timestamp default CURRENT_TIMESTAMP not null, + constraint PK_upload_file primary key (uuid) +); +-- 添加备注, +comment on column ${SCHEMA}.upload_file.uuid is 'UUID'; +comment on column ${SCHEMA}.upload_file.tenant_id is '租户ID'; +comment on column ${SCHEMA}.upload_file.app_module is '应用模块'; +comment on column ${SCHEMA}.upload_file.rel_obj_type is '关联对象类'; +comment on column ${SCHEMA}.upload_file.rel_obj_id is '关联对象ID'; +comment on column ${SCHEMA}.upload_file.rel_obj_field is '关联对象属性名称'; +comment on column ${SCHEMA}.upload_file.file_name is '文件名'; +comment on column ${SCHEMA}.upload_file.storage_path is '存储路径'; +comment on column ${SCHEMA}.upload_file.access_url is '访问地址'; +comment on column ${SCHEMA}.upload_file.file_type is '文件类型'; +comment on column ${SCHEMA}.upload_file.data_count is '数据量'; +comment on column ${SCHEMA}.upload_file.description is '备注'; +comment on column ${SCHEMA}.upload_file.is_deleted is '删除标记'; +comment on column ${SCHEMA}.upload_file.create_by is '创建人'; +comment on column ${SCHEMA}.upload_file.create_time is '创建时间'; +comment on table ${SCHEMA}.upload_file is '上传文件'; +-- 索引 +create index idx_upload_file on ${SCHEMA}.upload_file (rel_obj_type, rel_obj_id, rel_obj_field); +create index idx_upload_file_tenant on ${SCHEMA}.upload_file (tenant_id); \ No newline at end of file diff --git a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-oracle.sql b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-oracle.sql index 4e1804ead397618a52b5392429267821d5c2d5e9..778c7126a57eb0d8e0682378c0ae8ad1dcfa4016 100644 --- a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-oracle.sql +++ b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-oracle.sql @@ -1,21 +1,21 @@ -- 上传文件表 CREATE TABLE ${SCHEMA}.upload_file ( - uuid VARCHAR2(32) NOT NULL, - tenant_id NUMBER(20) default 0 not null, - app_module VARCHAR2(50), - rel_obj_type VARCHAR2(50), - rel_obj_id VARCHAR2(32), - rel_obj_field VARCHAR2(50), - file_name VARCHAR2(100) NOT NULL, - storage_path VARCHAR2(200) NOT NULL, - access_url VARCHAR2(200), - file_type VARCHAR2(20), - data_count NUMBER(9) DEFAULT 0 not null, - description VARCHAR2(100), - is_deleted NUMBER(1) DEFAULT 0 not null, - create_by NUMBER(20) default 0 , - create_time timestamp default CURRENT_TIMESTAMP not null, - constraint PK_upload_file primary key (uuid) + uuid VARCHAR2(32) NOT NULL, + tenant_id NUMBER(20) default 0 not null, + app_module VARCHAR2(50), + rel_obj_type VARCHAR2(50), + rel_obj_id VARCHAR2(32), + rel_obj_field VARCHAR2(50), + file_name VARCHAR2(100) NOT NULL, + storage_path VARCHAR2(200) NOT NULL, + access_url VARCHAR2(200), + file_type VARCHAR2(20), + data_count NUMBER(9) DEFAULT 0 not null, + description VARCHAR2(100), + is_deleted NUMBER(1) DEFAULT 0 not null, + create_by NUMBER(20) default 0 , + create_time timestamp default CURRENT_TIMESTAMP not null, + constraint PK_upload_file primary key (uuid) ); -- 添加备注, comment on column ${SCHEMA}.upload_file.uuid is 'UUID'; @@ -36,4 +36,4 @@ comment on column ${SCHEMA}.upload_file.create_time is '创建时间'; comment on table ${SCHEMA}.upload_file is '上传文件'; -- 索引 create index idx_upload_file on upload_file (rel_obj_type, rel_obj_id, rel_obj_field); -create index idx_upload_file_tenant on upload_file (tenant_id); +create index idx_upload_file_tenant on upload_file (tenant_id); \ No newline at end of file diff --git a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-postgresql.sql b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-postgresql.sql index e05737e5daf673889aa92267c04da5214b15a74b..994da5efbfec04f7caea9aa04abba52821f5ae49 100644 --- a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-postgresql.sql +++ b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-postgresql.sql @@ -1,21 +1,21 @@ -- 上传文件表 CREATE TABLE upload_file ( - uuid varchar(32) NOT NULL, - tenant_id bigint not null default 0, - app_module varchar(50), - rel_obj_type varchar(50), - rel_obj_id varchar(32), - rel_obj_field varchar(50), - file_name varchar(100) NOT NULL, - storage_path varchar(200) NOT NULL, - access_url varchar(200), - file_type varchar(20), - data_count int not null DEFAULT 0, - description varchar(100), - is_deleted BOOLEAN not null DEFAULT FALSE, - create_by bigint default 0, - create_time timestamp not null default CURRENT_TIMESTAMP, - constraint PK_upload_file primary key (uuid) + uuid varchar(32) NOT NULL, + tenant_id bigint not null default 0, + app_module varchar(50), + rel_obj_type varchar(50), + rel_obj_id varchar(32), + rel_obj_field varchar(50), + file_name varchar(100) NOT NULL, + storage_path varchar(200) NOT NULL, + access_url varchar(200), + file_type varchar(20), + data_count int not null DEFAULT 0, + description varchar(100), + is_deleted BOOLEAN not null DEFAULT FALSE, + create_by bigint default 0, + create_time timestamp not null default CURRENT_TIMESTAMP, + constraint PK_upload_file primary key (uuid) ); -- 添加备注, comment on column upload_file.uuid is 'UUID'; diff --git a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-sqlserver.sql b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-sqlserver.sql index 80b507636bc60d466e2489b4ac528990dda22d90..9e675dad67e13b52d3c86f83fe58f406114d9f4e 100644 --- a/diboot-file-starter/src/main/resources/META-INF/sql/init-file-sqlserver.sql +++ b/diboot-file-starter/src/main/resources/META-INF/sql/init-file-sqlserver.sql @@ -1,21 +1,21 @@ -- 上传文件表 CREATE TABLE ${SCHEMA}.upload_file ( - uuid varchar(32) NOT NULL, - tenant_id bigint not null default 0, - app_module varchar(50), - rel_obj_type varchar(50), - rel_obj_id varchar(32), - rel_obj_field varchar(50), - file_name varchar(100) NOT NULL, - storage_path varchar(200) NOT NULL, - access_url varchar(200) NULL, - file_type varchar(20), - data_count int not null DEFAULT 0, - description varchar(100), - is_deleted tinyint not null DEFAULT 0, - create_by bigint default 0, - create_time datetime not null default CURRENT_TIMESTAMP, - constraint PK_upload_file primary key (uuid) + uuid varchar(32) NOT NULL, + tenant_id bigint not null default 0, + app_module varchar(50), + rel_obj_type varchar(50), + rel_obj_id varchar(32), + rel_obj_field varchar(50), + file_name varchar(100) NOT NULL, + storage_path varchar(200) NOT NULL, + access_url varchar(200) NULL, + file_type varchar(20), + data_count int not null DEFAULT 0, + description varchar(100), + is_deleted tinyint not null DEFAULT 0, + create_by bigint default 0, + create_time datetime not null default CURRENT_TIMESTAMP, + constraint PK_upload_file primary key (uuid) ); -- 添加备注, execute sp_addextendedproperty 'MS_Description', N'UUID', 'SCHEMA', '${SCHEMA}', 'table', upload_file, 'column', 'uuid'; diff --git a/diboot-iam-starter/pom.xml b/diboot-iam-starter/pom.xml index 5938c46549cea1d1974463873ae6ac3576c0ab34..73b94a5d1396a329caf3f2f2ac775b24da8920f9 100644 --- a/diboot-iam-starter/pom.xml +++ b/diboot-iam-starter/pom.xml @@ -7,11 +7,11 @@ com.diboot diboot-root - 2.5.0 + 2.6.0 diboot-iam-spring-boot-starter - 2.5.0 + 2.6.0 jar diboot IAM project @@ -22,16 +22,11 @@ diboot-core-spring-boot-starter ${diboot.version} - + org.apache.shiro shiro-spring-boot-web-starter - 1.8.0 - - - io.jsonwebtoken - jjwt - 0.9.1 + 1.9.0 com.squareup.okhttp3 @@ -87,4 +82,4 @@ - \ No newline at end of file + diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/OperationCons.java b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/OperationCons.java index f011c1cc35bc690d3620e9459e943d864769cd58..58a2c973af309550b7f0f77a8be46b67c909168e 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/OperationCons.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/OperationCons.java @@ -22,6 +22,15 @@ package com.diboot.iam.annotation; * @date 2019/12/23 */ public class OperationCons { + /** + * 操作权限类型 - 读数据 + */ + public static final String CODE_READ = "read"; + /** + * 操作权限类型 - 写数据 + */ + public static final String CODE_WRITE = "write"; + /** * 操作权限类型 - 首页 */ @@ -66,6 +75,16 @@ public class OperationCons { * 操作权限类型 - 导出 */ public static final String CODE_EXPORT = "export"; + + /** + * 操作权限类型描述 - 读数据 + */ + public static final String LABEL_READ = "读权限"; + /** + * 操作权限类型描述 - 写数据 + */ + public static final String LABEL_WRITE = "写权限"; + /** * 操作权限类型描述 - 查看首页 */ diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java index 3ccf812d1b6a1ed12d68a0c7662c1148a6d91ed0..f5d310faddc7642876b2b26757619f978a4d3a9c 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java @@ -15,50 +15,58 @@ */ package com.diboot.iam.annotation.process; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.S; +import com.diboot.core.util.V; +import com.diboot.core.vo.ApiUri; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * 权限 Entity定义 -* @author mazc@dibo.ltd -* @version 2.0 -* @date 2019-12-03 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/21 + * Copyright © diboot.com */ @Getter @Setter @Accessors(chain = true) public class ApiPermission implements Serializable { - private static final long serialVersionUID = -1234249053749049729L; - - // 类别 - @JsonIgnore - private String className; - - // 类别标题 - @JsonIgnore - private String classTitle; - - // 接口名称 - private String apiName; + private static final long serialVersionUID = -17224355139977969L; + public ApiPermission(String code){ + this.code = code; + } /** - * 接口Method + * 权限编码 */ - private String apiMethod; - - // 接口URI - private String apiUri; - - // ID标识 - private String value; + private String code; + /** + * 接口地址定义 + */ + private List apiUriList; - // 权限许可编码 - @JsonIgnore - private String permissionCode; + public List getApiUriList(){ + if(apiUriList == null){ + apiUriList = new ArrayList<>(); + } + return apiUriList; + } - public String buildUniqueKey(){ - return className + "," + apiMethod + "," + apiUri + "," + permissionCode; + /** + * 权限码的备注 + * @return + */ + public String getLabel(){ + if(V.isEmpty(apiUriList)){ + return null; + } + List labels = BeanUtils.collectToList(apiUriList, ApiUri::getLabel); + return S.join(labels); } + } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java index abef013b12397567c2b1de7097ce33e673e61cf2..82e058c2e39da732c1e07b2044010071c838de4d 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -15,9 +15,13 @@ */ package com.diboot.iam.annotation.process; -import com.diboot.core.util.*; +import com.diboot.core.util.AnnotationUtils; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.ContextHelper; +import com.diboot.core.util.V; +import com.diboot.core.vo.ApiUri; import com.diboot.iam.annotation.BindPermission; -import com.diboot.iam.auth.IamCustomize; +import com.diboot.iam.cache.IamCacheManager; import com.diboot.iam.config.Cons; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; @@ -26,16 +30,14 @@ import org.springframework.web.bind.annotation.RestController; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** - * 注解提取器 - * @author mazc@dibo.ltd - * @version v2.0 - * @date 2019/12/23 + * 权限码注解提取器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/21 + * Copyright © diboot.com */ @Slf4j public class ApiPermissionExtractor { @@ -78,31 +80,13 @@ public class ApiPermissionExtractor { } for (Object obj : controllerList) { Class controllerClass = BeanUtils.getTargetClass(obj); - String title = null; - // 提取类信息 - String codePrefix = null; - // 注解 - BindPermission bindPermission = AnnotationUtils.findAnnotation(controllerClass, BindPermission.class); - if(bindPermission != null){ - // 当前资源权限 - codePrefix = bindPermission.code(); - if(V.isEmpty(codePrefix)){ - Class entityClazz = BeanUtils.getGenericityClass(controllerClass, 0); - if(entityClazz != null){ - codePrefix = entityClazz.getSimpleName(); - } - else{ - log.warn("无法获取{}相关的Entity,请指定注解BindPermission.code参数!", controllerClass.getName()); - } - } - title = bindPermission.name(); + if(UNIQUE_KEY_SET.contains(controllerClass.getName())){ + continue; } - else{ - title = S.substringBeforeLast(controllerClass.getSimpleName(), "Controller"); - } - ApiPermissionWrapper wrapper = new ApiPermissionWrapper(controllerClass.getSimpleName(), title); - buildApiPermissionsInClass(wrapper, controllerClass, codePrefix); - if(V.notEmpty(wrapper.getChildren())){ + UNIQUE_KEY_SET.add(controllerClass.getName()); + ApiPermissionWrapper wrapper = IamCacheManager.getPermissionCodeWrapper(controllerClass); + buildApiPermissionsInClass(wrapper, controllerClass); + if(wrapper.notEmpty()){ API_PERMISSION_CACHE.add(wrapper); } } @@ -112,104 +96,77 @@ public class ApiPermissionExtractor { * 构建controller中的Api权限 * @param wrapper * @param controllerClass - * @param codePrefix */ - private static void buildApiPermissionsInClass(ApiPermissionWrapper wrapper, Class controllerClass, String codePrefix) { + private static void buildApiPermissionsInClass(ApiPermissionWrapper wrapper, Class controllerClass) { + List annoMethods = AnnotationUtils.extractAnnotationMethods(controllerClass, BindPermission.class); + if(V.isEmpty(annoMethods)){ + return; + } String urlPrefix = ""; RequestMapping requestMapping = AnnotationUtils.findAnnotation(controllerClass, RequestMapping.class); if(requestMapping != null){ urlPrefix = AnnotationUtils.getNotEmptyStr(requestMapping.value(), requestMapping.path()); } - List annoMethods = AnnotationUtils.extractAnnotationMethods(controllerClass, BindPermission.class); - if(V.notEmpty(annoMethods)){ - List apiPermissions = new ArrayList<>(); - for(Method method : annoMethods){ - // 忽略私有方法 - if(Modifier.isPrivate(method.getModifiers())){ - continue; - } - // 处理BindPermission注解 - BindPermission bindPermission = AnnotationUtils.getAnnotation(method, BindPermission.class); - String[] permissionCodes = getExtPermissionCodes(method); - if(bindPermission == null && permissionCodes == null){ - continue; - } - // 提取方法上的注解url - String[] methodAndUrl = AnnotationUtils.extractRequestMethodAndMappingUrl(method); - if(methodAndUrl[0] == null || methodAndUrl[1] == null){ + List apiPermissions = new ArrayList<>(); + Map tempCode2ObjMap = new HashMap<>(); + for(Method method : annoMethods){ + // 忽略私有方法 + if(Modifier.isPrivate(method.getModifiers())){ + continue; + } + // 处理BindPermission注解 + BindPermission bindPermission = AnnotationUtils.getAnnotation(method, BindPermission.class); + if(bindPermission == null){ + continue; + } + // 提取方法上的注解url + ApiUri apiUriCombo = AnnotationUtils.extractRequestMethodAndMappingUrl(method); + if(apiUriCombo.isEmpty()){ + continue; + } + if(bindPermission != null){ + if(V.isEmpty(bindPermission.code())){ + log.warn("忽略无效的权限配置(未指定code): {}.{}", controllerClass.getSimpleName(), method.getName()); continue; } - if(bindPermission != null){ - String permissionCode = (codePrefix != null)? codePrefix+":"+bindPermission.code() : bindPermission.code(); - if (":".equals(permissionCode)) { - continue; - } - // 提取请求url-permission code的关系 - buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, bindPermission.name()); + String name = bindPermission.name(); + String code = (wrapper.getCode() != null)? wrapper.getCode()+":"+bindPermission.code() : bindPermission.code(); + ApiPermission apiPermission = tempCode2ObjMap.get(code); + if(apiPermission == null){ + apiPermission = new ApiPermission(code); + tempCode2ObjMap.put(code, apiPermission); + apiPermissions.add(apiPermission); } - // 处理RequirePermissions注解 - else if(V.notEmpty(permissionCodes)){ - for(String permissionCode : permissionCodes){ - // 提取请求url-permission code的关系 - buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, null); - } - } - } - // 添加至wrapper - if(apiPermissions.size() > 0){ - wrapper.setChildren(apiPermissions); + apiUriCombo.setLabel(name); + // 提取请求url-permission code的关系 + buildApiPermission(apiPermission, urlPrefix, apiUriCombo); } } + // 添加至wrapper + if(apiPermissions.size() > 0){ + wrapper.setApiPermissionList(apiPermissions); + } } /** * 构建ApiPermission - * @param apiPermissions - * @param controllerClass + * @param apiPermission * @param urlPrefix - * @param title - * @param permissionCode - * @param methodAndUrl - * @param apiName + * @param apiUriCombo */ - private static void buildApiPermission(List apiPermissions, Class controllerClass, String urlPrefix, String title, - String permissionCode, String[] methodAndUrl, String apiName){ - String requestMethod = methodAndUrl[0], url = methodAndUrl[1]; + private static void buildApiPermission(ApiPermission apiPermission, String urlPrefix, ApiUri apiUriCombo){ + String requestMethod = apiUriCombo.getMethod(), url = apiUriCombo.getUri(); + List apiUriList = apiPermission.getApiUriList(); for(String m : requestMethod.split(Cons.SEPARATOR_COMMA)){ for(String u : url.split(Cons.SEPARATOR_COMMA)){ + String uri = u; if(V.notEmpty(urlPrefix)){ - for(String path : urlPrefix.split(Cons.SEPARATOR_COMMA)){ - ApiPermission apiPermission = new ApiPermission().setClassName(controllerClass.getName()).setClassTitle(title); - apiPermission.setApiMethod(m).setApiName(apiName).setApiUri(path + u).setPermissionCode(permissionCode).setValue(m + ":" + path + u); - if(!UNIQUE_KEY_SET.contains(apiPermission.buildUniqueKey())){ - apiPermissions.add(apiPermission); - UNIQUE_KEY_SET.add(apiPermission.buildUniqueKey()); - } - } - } - else{ - ApiPermission apiPermission = new ApiPermission().setClassName(controllerClass.getName()).setClassTitle(title); - apiPermission.setApiMethod(m).setApiName(apiName).setApiUri(u).setPermissionCode(permissionCode).setValue(m + ":" + u); - if(!UNIQUE_KEY_SET.contains(apiPermission.buildUniqueKey())){ - apiPermissions.add(apiPermission); - UNIQUE_KEY_SET.add(apiPermission.buildUniqueKey()); - } + uri = urlPrefix + u; } + ApiUri apiUri = new ApiUri(m, uri, apiUriCombo.getLabel()); + apiUriList.add(apiUri); } } } - /** - * 获取扩展的待检查的权限码 - * @param method - * @return - */ - private static String[] getExtPermissionCodes(Method method){ - IamCustomize iamCustomize = ContextHelper.getBean(IamCustomize.class); - if(iamCustomize != null){ - return iamCustomize.getOrignPermissionCodes(method); - } - return null; - } - } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionWrapper.java b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionWrapper.java index 6615c469d968c6d2356ea83e4926c1374e8ee131..bc6923922071b716e7325d8a04556d272c57fba2 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionWrapper.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -15,6 +15,8 @@ */ package com.diboot.iam.annotation.process; +import com.diboot.core.util.V; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; @@ -23,34 +25,39 @@ import java.util.List; /** * 接口权限wrapper - * @author Mazhicheng - * @version v2.0 - * @date 2020/02/28 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/21 + * Copyright © diboot.com */ @Getter @Setter public class ApiPermissionWrapper implements Serializable { - private static final long serialVersionUID = 544405101900928328L; + private static final long serialVersionUID = 7795636645748631729L; public ApiPermissionWrapper(){} - public ApiPermissionWrapper(String className, String classTitle){ - this.className = className; - this.classTitle = classTitle; + public ApiPermissionWrapper(String name, String code){ + this.name = name; + this.code = code; } /** * 类名 */ - private String className; + private String name; /** * 类别标题 */ - private String classTitle; + private String code; /** - * 子节点 + * 子节点权限码集合 */ - private List children; + private List apiPermissionList; + @JsonIgnore + public boolean notEmpty(){ + return V.notEmpty(code) && V.notEmpty(apiPermissionList); + } } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/BindPermissionAspect.java b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/BindPermissionAspect.java index 74513be07822e5b48546fc1a476450393de8c252..346381b8fbbcfa0a85b6e907e99bcbb67e5af2ad 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/BindPermissionAspect.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/process/BindPermissionAspect.java @@ -16,13 +16,16 @@ package com.diboot.iam.annotation.process; import com.diboot.core.util.AnnotationUtils; -import com.diboot.core.util.S; import com.diboot.core.util.V; +import com.diboot.core.vo.Status; import com.diboot.iam.annotation.BindPermission; -import com.diboot.iam.auth.IamCustomize; import com.diboot.iam.cache.IamCacheManager; import com.diboot.iam.config.Cons; +import com.diboot.iam.exception.PermissionException; +import com.diboot.iam.starter.IamProperties; +import com.diboot.iam.util.IamSecurityUtils; import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authz.UnauthenticatedException; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -30,14 +33,8 @@ import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.HandlerMapping; -import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; -import java.util.Map; /** * BindPermission注解的切面处理 @@ -49,8 +46,8 @@ import java.util.Map; @Component @Slf4j public class BindPermissionAspect { - @Autowired(required = false) - private IamCustomize iamCustomize; + @Autowired + private IamProperties iamProperties; /** * 注解切面 @@ -64,51 +61,33 @@ public class BindPermissionAspect { */ @Before("pointCut()") public void before(JoinPoint joinPoint) { - if(iamCustomize.isEnablePermissionCheck() == false){ - log.debug("BindPermission权限检查已停用,如需启用请删除配置项: diboot.iam.enable-permission-check"); + if(iamProperties.isEnablePermissionCheck() == false){ + log.debug("BindPermission权限检查已停用,如需启用请删除配置项: diboot.iam.enable-permission-check=false"); return; } // 超级管理员 权限放过 - if (iamCustomize.checkCurrentUserHasRole(Cons.ROLE_SUPER_ADMIN)) { - return; - } - // 获取当前uri - RequestAttributes ra = RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest(); - // 根据uri获取对应权限标识 - String uriMapping = formatUriMapping(request); - String permissionCode = IamCacheManager.getPermissionCode(request.getMethod(), uriMapping); - if (permissionCode == null){ + if (IamSecurityUtils.getSubject().hasRole(Cons.ROLE_SUPER_ADMIN)) { return; } // 需要验证 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); - BindPermission bindPermission = AnnotationUtils.getAnnotation(method, BindPermission.class); - if(permissionCode.endsWith(":"+bindPermission.code())){ - iamCustomize.checkPermission(permissionCode); + BindPermission methodAnno = AnnotationUtils.getAnnotation(method, BindPermission.class); + String permissionCode = methodAnno.code(); + Class controllerClass = joinPoint.getTarget().getClass(); + ApiPermissionWrapper classAnno = IamCacheManager.getPermissionCodeWrapper(controllerClass); + if(classAnno != null && V.notEmpty(classAnno.getCode())){ + permissionCode = classAnno.getCode() + ":" + permissionCode; } - } - - /** - * 格式化URL,将当前url转换为Mapping定义中参数化url - * @param request - * @return - */ - private String formatUriMapping(HttpServletRequest request){ - boolean hasContextPath = (V.notEmpty(request.getContextPath()) && !request.getContextPath().equals("/")); - String url = hasContextPath? S.substringAfter(request.getRequestURI(), request.getContextPath()) : request.getRequestURI(); - Map map = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); - if(V.notEmpty(map)){ - for(Map.Entry entry : map.entrySet()){ - if(url.endsWith("/"+entry.getValue())){ - url = S.substringBeforeLast(url,"/"+entry.getValue()) + "/{"+entry.getKey()+"}"; - } - else if(url.contains("/"+entry.getValue()+"/")){ - url = S.replace(url,"/"+entry.getValue()+"/","/{"+entry.getKey()+"}/"); - } - } + try{ + IamSecurityUtils.getSubject().checkPermission(permissionCode); + } + catch (UnauthenticatedException e){ + throw new PermissionException(Status.FAIL_INVALID_TOKEN, e); + } + catch (Exception e){ + throw new PermissionException(Status.FAIL_NO_PERMISSION, e); } - return url; } + } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/AuthService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/AuthService.java index b27892ca48ee782b40521876a7f7fbe02b9ba343..310654372bd0687e8f5c91eb3e3ac679910119ec 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/AuthService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/AuthService.java @@ -17,8 +17,8 @@ package com.diboot.iam.auth; import com.diboot.iam.dto.AuthCredential; import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.jwt.BaseJwtAuthToken; -import com.diboot.iam.util.JwtUtils; +import com.diboot.iam.shiro.IamAuthToken; +import com.diboot.iam.util.TokenUtils; import org.apache.shiro.authc.AuthenticationException; /** @@ -40,16 +40,16 @@ public interface AuthService { * @return */ default int getExpiresInMinutes(){ - return JwtUtils.EXPIRES_IN_MINUTES; + return TokenUtils.EXPIRES_IN_MINUTES; } /** * 获取用户 - * @param jwtToken + * @param iamAuthToken * @return * @throws AuthenticationException */ - IamAccount getAccount(BaseJwtAuthToken jwtToken) throws AuthenticationException; + IamAccount getAccount(IamAuthToken iamAuthToken) throws AuthenticationException; /** * 申请Token diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamCustomize.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamCustomize.java index b46c3e9d3035b235c496d5b4e89ae37448eeeeac..6d9618c7f486105715f6b643be52e62bd63a1ef8 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamCustomize.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamCustomize.java @@ -19,8 +19,6 @@ import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; import com.diboot.iam.exception.PermissionException; -import java.lang.reflect.Method; - /** * IAM自定义接口 * @author mazc@dibo.ltd @@ -69,20 +67,4 @@ public interface IamCustomize { */ void clearAuthorizationCache(String username); - /** - * 清空所有权限 - */ - void clearAllAuthorizationCache(); - - /** - * 是否启用权限检查 - * @return - */ - boolean isEnablePermissionCheck(); - - /** - * 获取原生的权限码 - * @return - */ - String[] getOrignPermissionCodes(Method method); } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamExtensible.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamExtensible.java index 061b526922040a01f328d6c8c8672d4c5fe92df0..e14dc6a4ae464a7f1125e189d99fef71bd9bd652 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamExtensible.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/IamExtensible.java @@ -36,15 +36,15 @@ public interface IamExtensible { * @param extObj 登录扩展信息 * @return */ - LabelValue getUserExtentionObj(String userType, Long userId, Map extObj); + LabelValue getUserExtensionObj(String userType, Long userId, Map extObj); /** * 获取可扩展的角色 * @param userType * @param userId - * @param extentionObjId 岗位等当前扩展对象id + * @param extensionObjId 岗位等当前扩展对象id * @return */ - List getExtentionRoles(String userType, Long userId, Long extentionObjId); + List getExtensionRoles(String userType, Long userId, Long extensionObjId); } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/BaseAuthServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/BaseAuthServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d531aad155dc122aa5442c927d819062be41c196 --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/BaseAuthServiceImpl.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.auth.impl; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.diboot.core.exception.BusinessException; +import com.diboot.core.vo.Status; +import com.diboot.iam.annotation.process.IamAsyncWorker; +import com.diboot.iam.auth.AuthService; +import com.diboot.iam.config.Cons; +import com.diboot.iam.dto.AuthCredential; +import com.diboot.iam.entity.BaseLoginUser; +import com.diboot.iam.entity.IamAccount; +import com.diboot.iam.entity.IamLoginTrace; +import com.diboot.iam.service.IamAccountService; +import com.diboot.iam.shiro.IamAuthToken; +import com.diboot.iam.util.HttpHelper; +import com.diboot.iam.util.IamSecurityUtils; +import com.diboot.iam.util.TokenUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.subject.Subject; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.http.HttpServletRequest; + +/** + * 用户名密码认证的service实现 + * @author mazc@dibo.ltd + * @version v2.0 + * @date 2019/12/25 + */ +@Slf4j +public abstract class BaseAuthServiceImpl implements AuthService { + @Autowired + private IamAccountService accountService; + @Autowired + private IamAsyncWorker iamAsyncWorker; + @Autowired + private HttpServletRequest request; + + @Override + public String getAuthType() { + return Cons.DICTCODE_AUTH_TYPE.PWD.name(); + } + + /** + * 构建查询条件 + * @return + */ + protected abstract Wrapper buildQueryWrapper(IamAuthToken iamAuthToken); + + @Override + public IamAccount getAccount(IamAuthToken iamAuthToken) throws AuthenticationException { + IamAccount latestAccount = accountService.getSingleEntity(buildQueryWrapper(iamAuthToken)); + if(latestAccount == null){ + return null; + } + if (Cons.DICTCODE_ACCOUNT_STATUS.I.name().equals(latestAccount.getStatus())) { + throw new AuthenticationException("用户账号已禁用! account="+iamAuthToken.getAuthAccount()); + } + if (Cons.DICTCODE_ACCOUNT_STATUS.L.name().equals(latestAccount.getStatus())) { + throw new AuthenticationException("用户账号已锁定! account="+iamAuthToken.getAuthAccount()); + } + return latestAccount; + } + + @Override + public String applyToken(AuthCredential credential) { + IamAuthToken authToken = initAuthToken(credential); + try { + Subject subject = SecurityUtils.getSubject(); + subject.login(authToken); + if (subject.isAuthenticated()) { + String accessToken = (String) authToken.getCredentials(); + // 缓存当前token与用户信息 + TokenUtils.cacheAccessToken(accessToken, authToken.buildUserInfoStr(), authToken.getExpiresInMinutes()); + log.debug("申请token成功!authtoken={}", authToken.getCredentials()); + saveLoginTrace(authToken, true); + // 返回 + return accessToken; + } + else { + log.error("认证失败"); + saveLoginTrace(authToken, false); + throw new BusinessException(Status.FAIL_OPERATION, "认证失败"); + } + } catch (Exception e) { + log.error("登录异常", e); + saveLoginTrace(authToken, false); + throw new BusinessException(Status.FAIL_OPERATION, e.getMessage()); + } + } + + /** + * 初始化JwtAuthToken实例 + * @param credential + * @return + */ + protected IamAuthToken initAuthToken(AuthCredential credential){ + IamAuthToken token = new IamAuthToken(getAuthType(), credential.getUserTypeClass()); + // 设置账号密码 + token.setAuthAccount(credential.getAuthAccount()); + token.setAuthSecret(credential.getAuthSecret()); + token.setRememberMe(credential.isRememberMe()); + token.setTenantId(credential.getTenantId()); + token.setExtObj(credential.getExtObj()); + token.setExpiresInMinutes(getExpiresInMinutes()); + // 生成token + return token.generateAuthtoken(); + } + + /** + * 保存登录日志 + * @param authToken + * @param isSuccess + */ + protected void saveLoginTrace(IamAuthToken authToken, boolean isSuccess){ + IamLoginTrace loginTrace = new IamLoginTrace(); + loginTrace.setAuthType(getAuthType()).setAuthAccount(authToken.getAuthAccount()).setUserType(authToken.getUserType()).setSuccess(isSuccess); + BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser(); + if(currentUser != null){ + loginTrace.setUserId(currentUser.getId()); + } + // 记录客户端信息 + String userAgent = HttpHelper.getUserAgent(request); + String ipAddress = HttpHelper.getRequestIp(request); + loginTrace.setUserAgent(userAgent).setIpAddress(ipAddress); + iamAsyncWorker.saveLoginTraceLog(loginTrace); + } + +} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamCustomizeImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamCustomizeImpl.java index e1135410453d8a36a202a8664ca6a6f8dc563947..24b6ff85b5b06c1408e4e811fc7f5f64c5ded7ac 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamCustomizeImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamCustomizeImpl.java @@ -15,21 +15,15 @@ */ package com.diboot.iam.auth.impl; -import com.diboot.core.util.AnnotationUtils; import com.diboot.core.vo.Status; import com.diboot.iam.auth.IamCustomize; import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; import com.diboot.iam.exception.PermissionException; -import com.diboot.iam.starter.IamProperties; import com.diboot.iam.util.IamSecurityUtils; import org.apache.shiro.authz.UnauthenticatedException; -import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.lang.reflect.Method; - /** * IAM自定义扩展 * @author JerryMa @@ -38,8 +32,6 @@ import java.lang.reflect.Method; */ @Service public class IamCustomizeImpl implements IamCustomize { - @Autowired - private IamProperties iamProperties; @Override public BaseLoginUser getCurrentUser() { @@ -83,23 +75,4 @@ public class IamCustomizeImpl implements IamCustomize { IamSecurityUtils.clearAuthorizationCache(username); } - @Override - public void clearAllAuthorizationCache() { - IamSecurityUtils.clearAllAuthorizationCache(); - } - - @Override - public boolean isEnablePermissionCheck() { - return iamProperties.isEnablePermissionCheck(); - } - - @Override - public String[] getOrignPermissionCodes(Method method) { - RequiresPermissions requiresPermissions = AnnotationUtils.getAnnotation(method, RequiresPermissions.class); - if(requiresPermissions != null){ - return requiresPermissions.value(); - } - return null; - } - } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamExtensibleImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamExtensibleImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a69907e633682e5a8cbb5c6881461817f5f7a418 --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/IamExtensibleImpl.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.auth.impl; + +import com.diboot.core.util.ContextHelper; +import com.diboot.core.util.V; +import com.diboot.core.vo.LabelValue; +import com.diboot.iam.auth.IamExtensible; +import com.diboot.iam.entity.IamPosition; +import com.diboot.iam.entity.IamRole; +import com.diboot.iam.entity.IamUser; +import com.diboot.iam.entity.IamUserPosition; +import com.diboot.iam.service.IamOrgService; +import com.diboot.iam.service.IamPositionService; +import com.diboot.iam.service.IamUserService; +import com.diboot.iam.vo.PositionDataScope; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * IAM扩展配置 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/9 + * Copyright © diboot.com + */ +@Slf4j +@Service +public class IamExtensibleImpl implements IamExtensible { + + @Override + public LabelValue getUserExtensionObj(String userType, Long userId, Map extObj) { + if(!IamUser.class.getSimpleName().equals(userType)){ + log.warn("扩展的用户类型: {} 需自行实现附加扩展对象逻辑", userType); + return null; + } + IamPositionService iamPositionService = ContextHelper.getBean(IamPositionService.class); + IamUserPosition userPosition = iamPositionService.getUserPrimaryPosition(userType, userId); + if(userPosition != null){ + Long orgId = userPosition.getOrgId(); + IamPosition position = iamPositionService.getEntity(userPosition.getPositionId()); + PositionDataScope positionDataScope = new PositionDataScope(userId, position.getDataPermissionType(), userId, orgId); + List accessibleUserIds = new ArrayList<>(), accessibleOrgIds = new ArrayList<>(); + // 本人及下属的用户ids + accessibleUserIds.add(userId); + List userIds = ContextHelper.getBean(IamUserService.class).getUserIdsByManagerId(userId); + if(V.notEmpty(userIds)){ + userIds.forEach(uid -> { + if(!accessibleUserIds.contains(uid)){ + accessibleUserIds.add(uid); + } + }); + } + positionDataScope.setAccessibleUserIds(accessibleUserIds); + // 本部门及下属部门的ids + accessibleOrgIds.add(orgId); + List childOrgIds = ContextHelper.getBean(IamOrgService.class).getChildOrgIds(orgId); + if(V.notEmpty(childOrgIds)){ + childOrgIds.forEach(oid -> { + if(!accessibleOrgIds.contains(oid)){ + accessibleOrgIds.add(oid); + } + }); + } + positionDataScope.setAccessibleOrgIds(accessibleOrgIds); + return new LabelValue(position.getName(), position.getCode()).setExt(positionDataScope); + } + return null; + } + + @Override + public List getExtensionRoles(String userType, Long userId, Long extensionObjId) { + return null; + } +} \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/OAuth2SSOServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/OAuth2SSOServiceImpl.java index 80ee9c45acb4ae481c01d5c56b55e38394dcc3f0..34e05512eed61ccd29ff77d67cc57dd752791439 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/OAuth2SSOServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/OAuth2SSOServiceImpl.java @@ -15,28 +15,20 @@ */ package com.diboot.iam.auth.impl; +import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.diboot.core.exception.BusinessException; import com.diboot.core.exception.InvalidUsageException; import com.diboot.core.util.S; import com.diboot.core.util.V; import com.diboot.core.vo.Status; -import com.diboot.iam.auth.AuthService; import com.diboot.iam.config.Cons; import com.diboot.iam.dto.AuthCredential; import com.diboot.iam.dto.OAuth2SSOCredential; -import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.entity.IamLoginTrace; -import com.diboot.iam.jwt.BaseJwtAuthToken; -import com.diboot.iam.service.IamAccountService; -import com.diboot.iam.service.IamLoginTraceService; +import com.diboot.iam.shiro.IamAuthToken; import com.diboot.iam.starter.IamProperties; -import com.diboot.iam.util.IamSecurityUtils; import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpEntity; @@ -48,7 +40,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; @@ -63,13 +54,7 @@ import java.util.Map; @Slf4j @Service @ConditionalOnProperty(prefix = "diboot.iam.oauth2-client", name = {"client-id", "client-secret", "redirect-uri", "access-token-uri"}) -public class OAuth2SSOServiceImpl implements AuthService { - @Autowired - private HttpServletRequest request; - @Autowired - private IamAccountService accountService; - @Autowired - private IamLoginTraceService iamLoginTraceService; +public class OAuth2SSOServiceImpl extends BaseAuthServiceImpl { @Autowired(required = false) private RestTemplate restTemplate; @Autowired @@ -80,27 +65,22 @@ public class OAuth2SSOServiceImpl implements AuthService { return Cons.DICTCODE_AUTH_TYPE.SSO.name(); } + /** + * 构建查询条件 + * @param iamAuthToken + * @return + */ @Override - public IamAccount getAccount(BaseJwtAuthToken jwtToken) throws AuthenticationException { + protected Wrapper buildQueryWrapper(IamAuthToken iamAuthToken) { // 查询最新的记录 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .select(IamAccount::getAuthAccount, IamAccount::getUserType, IamAccount::getUserId, IamAccount::getStatus) - .eq(IamAccount::getUserType, jwtToken.getUserType()) - .eq(IamAccount::getTenantId, jwtToken.getTenantId()) + .eq(IamAccount::getUserType, iamAuthToken.getUserType()) + .eq(IamAccount::getTenantId, iamAuthToken.getTenantId()) //.eq(IamAccount::getAuthType, jwtToken.getAuthType()) SSO只检查用户名,支持任意类型账号 - .eq(IamAccount::getAuthAccount, jwtToken.getAuthAccount()) + .eq(IamAccount::getAuthAccount, iamAuthToken.getAuthAccount()) .orderByDesc(IamAccount::getId); - IamAccount latestAccount = accountService.getSingleEntity(queryWrapper); - if (latestAccount == null) { - return null; - } - if (Cons.DICTCODE_ACCOUNT_STATUS.I.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已禁用! account=" + jwtToken.getAuthAccount()); - } - if (Cons.DICTCODE_ACCOUNT_STATUS.L.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已锁定! account=" + jwtToken.getAuthAccount()); - } - return latestAccount; + return queryWrapper; } @Override @@ -111,25 +91,7 @@ public class OAuth2SSOServiceImpl implements AuthService { if(V.isEmpty(ssoCredential.getAuthAccount())){ throw new BusinessException(Status.FAIL_OPERATION, "认证中心验证失败"); } - BaseJwtAuthToken authToken = initBaseJwtAuthToken(credential); - try { - Subject subject = SecurityUtils.getSubject(); - subject.login(authToken); - if (subject.isAuthenticated()) { - log.debug("申请token成功!authtoken={}", authToken.getCredentials()); - saveLoginTrace(authToken, true); - // 跳转到首页 - return (String) authToken.getCredentials(); - } else { - log.error("认证失败"); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, "认证失败"); - } - } catch (Exception e) { - log.error("登录异常", e); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, e.getMessage()); - } + return super.applyToken(credential); } /** @@ -138,14 +100,15 @@ public class OAuth2SSOServiceImpl implements AuthService { * @param credential * @return */ - private BaseJwtAuthToken initBaseJwtAuthToken(AuthCredential credential) { - BaseJwtAuthToken token = new BaseJwtAuthToken(getAuthType(), credential.getUserTypeClass()); + @Override + protected IamAuthToken initAuthToken(AuthCredential credential) { + IamAuthToken token = new IamAuthToken(getAuthType(), credential.getUserTypeClass()); // 设置账号密码 token.setAuthAccount(credential.getAuthAccount()); token.setTenantId(credential.getTenantId()); token.setRememberMe(credential.isRememberMe()); // 生成token - return token.generateAuthtoken(getExpiresInMinutes()); + return token.generateAuthtoken(); } /** @@ -177,29 +140,4 @@ public class OAuth2SSOServiceImpl implements AuthService { } } - /** - * 保存登录日志 - * - * @param authToken - * @param isSuccess - */ - protected void saveLoginTrace(BaseJwtAuthToken authToken, boolean isSuccess) { - IamLoginTrace loginTrace = new IamLoginTrace(); - loginTrace.setAuthType(getAuthType()).setAuthAccount(authToken.getAuthAccount()).setUserType(authToken.getUserType()).setSuccess(isSuccess); - BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser(); - if (currentUser != null) { - Long userId = currentUser.getId(); - loginTrace.setUserId(userId); - } - // 记录客户端信息 - String userAgent = request.getHeader("user-agent"); - String ipAddress = IamSecurityUtils.getRequestIp(request); - loginTrace.setUserAgent(userAgent).setIpAddress(ipAddress); - try { - iamLoginTraceService.createEntity(loginTrace); - } catch (Exception e) { - log.warn("保存登录日志异常", e); - } - } - } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/PwdAuthServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/PwdAuthServiceImpl.java index 0d9e55ba11c54677a917cc952a33d00ef9885700..4c7f31b7ab54c5b9d111c725c6c6777d225becde 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/PwdAuthServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/PwdAuthServiceImpl.java @@ -15,30 +15,17 @@ */ package com.diboot.iam.auth.impl; +import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.diboot.core.exception.BusinessException; import com.diboot.core.util.Encryptor; -import com.diboot.core.vo.Status; -import com.diboot.iam.annotation.process.IamAsyncWorker; -import com.diboot.iam.auth.AuthService; import com.diboot.iam.config.Cons; -import com.diboot.iam.dto.AuthCredential; -import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.entity.IamLoginTrace; -import com.diboot.iam.jwt.BaseJwtAuthToken; -import com.diboot.iam.service.IamAccountService; -import com.diboot.iam.util.HttpHelper; +import com.diboot.iam.shiro.IamAuthToken; import com.diboot.iam.util.IamSecurityUtils; import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.subject.Subject; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.servlet.http.HttpServletRequest; - /** * 用户名密码认证的service实现 * @author mazc@dibo.ltd @@ -47,68 +34,38 @@ import javax.servlet.http.HttpServletRequest; */ @Service @Slf4j -public class PwdAuthServiceImpl implements AuthService { - @Autowired - private IamAccountService accountService; - @Autowired - private IamAsyncWorker iamAsyncWorker; - @Autowired - private HttpServletRequest request; - +public class PwdAuthServiceImpl extends BaseAuthServiceImpl { @Override public String getAuthType() { return Cons.DICTCODE_AUTH_TYPE.PWD.name(); } + /** + * 构建查询条件 + * @param iamAuthToken + * @return + */ @Override - public IamAccount getAccount(BaseJwtAuthToken jwtToken) throws AuthenticationException { + protected Wrapper buildQueryWrapper(IamAuthToken iamAuthToken) { // 查询最新的记录 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .select(IamAccount::getAuthAccount, IamAccount::getAuthSecret, IamAccount::getSecretSalt, IamAccount::getUserType, IamAccount::getUserId, IamAccount::getStatus) - .eq(IamAccount::getUserType, jwtToken.getUserType()) - .eq(IamAccount::getAuthType, jwtToken.getAuthType()) - .eq(IamAccount::getAuthAccount, jwtToken.getAuthAccount()) - .eq(IamAccount::getTenantId, jwtToken.getTenantId()) + .eq(IamAccount::getUserType, iamAuthToken.getUserType()) + .eq(IamAccount::getAuthType, iamAuthToken.getAuthType()) + .eq(IamAccount::getAuthAccount, iamAuthToken.getAuthAccount()) + .eq(IamAccount::getTenantId, iamAuthToken.getTenantId()) .orderByDesc(IamAccount::getId); - IamAccount latestAccount = accountService.getSingleEntity(queryWrapper); - if(latestAccount == null){ - return null; - } - if (Cons.DICTCODE_ACCOUNT_STATUS.I.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已禁用! account="+jwtToken.getAuthAccount()); - } - if (Cons.DICTCODE_ACCOUNT_STATUS.L.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已锁定! account="+jwtToken.getAuthAccount()); - } - // 如果需要密码校验,那么无状态的时候不需要验证 - if (jwtToken.isValidPassword() && isPasswordMatched(latestAccount, jwtToken) == false){ - throw new AuthenticationException("用户名或密码错误! account="+jwtToken.getAuthAccount()); - } - return latestAccount; + return queryWrapper; } @Override - public String applyToken(AuthCredential credential) { - BaseJwtAuthToken authToken = initBaseJwtAuthToken(credential); - try { - Subject subject = SecurityUtils.getSubject(); - subject.login(authToken); - if (subject.isAuthenticated()) { - log.debug("申请token成功!authtoken={}", authToken.getCredentials()); - saveLoginTrace(authToken, true); - // 跳转到首页 - return (String) authToken.getCredentials(); - } - else { - log.error("认证失败"); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, "认证失败"); - } - } catch (Exception e) { - log.error("登录异常", e); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, e.getMessage()); + public IamAccount getAccount(IamAuthToken iamAuthToken) throws AuthenticationException { + IamAccount latestAccount = super.getAccount(iamAuthToken); + // 如果需要密码校验,那么无状态的时候不需要验证 + if (iamAuthToken.isValidPassword() && isPasswordMatched(latestAccount, iamAuthToken) == false){ + throw new AuthenticationException("用户名或密码错误! account="+iamAuthToken.getAuthAccount()); } + return latestAccount; } /** @@ -117,7 +74,7 @@ public class PwdAuthServiceImpl implements AuthService { * @param jwtToken * @return */ - private static boolean isPasswordMatched(IamAccount account, BaseJwtAuthToken jwtToken){ + private static boolean isPasswordMatched(IamAccount account, IamAuthToken jwtToken){ //加密后比较 String encryptedStr = IamSecurityUtils.encryptPwd(jwtToken.getAuthSecret(), account.getSecretSalt()); // 暂时兼容RC2版本,后期移除 @@ -125,40 +82,4 @@ public class PwdAuthServiceImpl implements AuthService { return encryptedStr.equals(account.getAuthSecret()) || oldEncryptedStr.equals(account.getAuthSecret()); } - /** - * 初始化JwtAuthToken实例 - * @param credential - * @return - */ - private BaseJwtAuthToken initBaseJwtAuthToken(AuthCredential credential){ - BaseJwtAuthToken token = new BaseJwtAuthToken(getAuthType(), credential.getUserTypeClass()); - // 设置账号密码 - token.setAuthAccount(credential.getAuthAccount()); - token.setAuthSecret(credential.getAuthSecret()); - token.setRememberMe(credential.isRememberMe()); - token.setTenantId(credential.getTenantId()); - token.setExtObj(credential.getExtObj()); - // 生成token - return token.generateAuthtoken(getExpiresInMinutes()); - } - - /** - * 保存登录日志 - * @param authToken - * @param isSuccess - */ - protected void saveLoginTrace(BaseJwtAuthToken authToken, boolean isSuccess){ - IamLoginTrace loginTrace = new IamLoginTrace(); - loginTrace.setAuthType(getAuthType()).setAuthAccount(authToken.getAuthAccount()).setUserType(authToken.getUserType()).setSuccess(isSuccess); - BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser(); - if(currentUser != null){ - loginTrace.setUserId(currentUser.getId()); - } - // 记录客户端信息 - String userAgent = HttpHelper.getUserAgent(request); - String ipAddress = HttpHelper.getRequestIp(request); - loginTrace.setUserAgent(userAgent).setIpAddress(ipAddress); - iamAsyncWorker.saveLoginTraceLog(loginTrace); - } - } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/SSOAuthServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/SSOAuthServiceImpl.java deleted file mode 100644 index 01df07bb394dde492ee03173bd515ca99622f973..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/auth/impl/SSOAuthServiceImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.auth.impl; - -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.diboot.core.config.BaseConfig; -import com.diboot.core.exception.BusinessException; -import com.diboot.core.exception.InvalidUsageException; -import com.diboot.core.util.S; -import com.diboot.core.util.V; -import com.diboot.core.vo.Status; -import com.diboot.iam.auth.AuthService; -import com.diboot.iam.config.Cons; -import com.diboot.iam.dto.AuthCredential; -import com.diboot.iam.dto.SSOCredential; -import com.diboot.iam.entity.BaseLoginUser; -import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.entity.IamLoginTrace; -import com.diboot.iam.jwt.BaseJwtAuthToken; -import com.diboot.iam.service.IamAccountService; -import com.diboot.iam.service.IamLoginTraceService; -import com.diboot.iam.util.HttpHelper; -import com.diboot.iam.util.IamSecurityUtils; -import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.subject.Subject; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.servlet.http.HttpServletRequest; - -/** - * 用户名SSO认证的service实现 - * @author mazc@dibo.ltd - * @version v2.0 - * @date 2019/12/25 - */ -@Slf4j -@Deprecated -public class SSOAuthServiceImpl implements AuthService { - @Autowired - private HttpServletRequest request; - @Autowired - private IamAccountService accountService; - @Autowired - private IamLoginTraceService iamLoginTraceService; - // cas server url - private String casUrlPrefix; - - private String getCasUrlPrefix(){ - if(this.casUrlPrefix == null){ - this.casUrlPrefix = BaseConfig.getProperty("cas.server-url-prefix"); - } - if(V.isEmpty(this.casUrlPrefix)){ - throw new InvalidUsageException("未配置cas参数: cas.server-url-prefix"); - } - if(!this.casUrlPrefix.endsWith("/")){ - this.casUrlPrefix += "/"; - } - return this.casUrlPrefix; - } - - @Override - public String getAuthType() { - return Cons.DICTCODE_AUTH_TYPE.SSO.name(); - } - - @Override - public IamAccount getAccount(BaseJwtAuthToken jwtToken) throws AuthenticationException { - // 查询最新的记录 - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() - .select(IamAccount::getAuthAccount, IamAccount::getUserType, IamAccount::getUserId, IamAccount::getStatus) - .eq(IamAccount::getUserType, jwtToken.getUserType()) - .eq(IamAccount::getTenantId, jwtToken.getTenantId()) - //.eq(IamAccount::getAuthType, jwtToken.getAuthType()) SSO只检查用户名,支持任意类型账号 - .eq(IamAccount::getAuthAccount, jwtToken.getAuthAccount()) - .orderByDesc(IamAccount::getId); - IamAccount latestAccount = accountService.getSingleEntity(queryWrapper); - if(latestAccount == null){ - return null; - } - if (Cons.DICTCODE_ACCOUNT_STATUS.I.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已禁用! account="+jwtToken.getAuthAccount()); - } - if (Cons.DICTCODE_ACCOUNT_STATUS.L.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已锁定! account="+jwtToken.getAuthAccount()); - } - return latestAccount; - } - - @Override - public String applyToken(AuthCredential credential) { - BaseJwtAuthToken authToken = initBaseJwtAuthToken(credential); - try { - Subject subject = SecurityUtils.getSubject(); - subject.login(authToken); - if (subject.isAuthenticated()) { - log.debug("申请token成功!authtoken={}", authToken.getCredentials()); - saveLoginTrace(authToken, true); - // 跳转到首页 - return (String) authToken.getCredentials(); - } - else { - log.error("认证失败"); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, "认证失败"); - } - } catch (Exception e) { - log.error("登录异常", e); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, e.getMessage()); - } - } - - /** - * 初始化JwtAuthToken实例 - * @param credential - * @return - */ - private BaseJwtAuthToken initBaseJwtAuthToken(AuthCredential credential){ - // 通过CAS得到账号 - SSOCredential ssoCredential = (SSOCredential)credential; - BaseJwtAuthToken token = new BaseJwtAuthToken(getAuthType(), ssoCredential.getUserTypeClass()); - String username = parseCasTicket(ssoCredential); - ssoCredential.setAuthAccount(username); - // 设置账号密码 - token.setAuthAccount(ssoCredential.getAuthAccount()); - token.setTenantId(credential.getTenantId()); - token.setRememberMe(ssoCredential.isRememberMe()); - // 生成token - return token.generateAuthtoken(getExpiresInMinutes()); - } - - /** - * 保存登录日志 - * @param authToken - * @param isSuccess - */ - protected void saveLoginTrace(BaseJwtAuthToken authToken, boolean isSuccess){ - IamLoginTrace loginTrace = new IamLoginTrace(); - loginTrace.setAuthType(getAuthType()).setAuthAccount(authToken.getAuthAccount()).setUserType(authToken.getUserType()).setSuccess(isSuccess); - BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser(); - if(currentUser != null){ - Long userId = currentUser.getId(); - loginTrace.setUserId(userId); - } - // 记录客户端信息 - String userAgent = request.getHeader("user-agent"); - String ipAddress = IamSecurityUtils.getRequestIp(request); - loginTrace.setUserAgent(userAgent).setIpAddress(ipAddress); - try{ - iamLoginTraceService.createEntity(loginTrace); - } - catch (Exception e){ - log.warn("保存登录日志异常", e); - } - } - - /** - * 解析CAS ticket,提取username - * @param ssoCredential - */ - protected String parseCasTicket(SSOCredential ssoCredential) { - String casServiceValidateUrl = this.getCasUrlPrefix() + "p3/serviceValidate?service="+ssoCredential.getServiceUrl()+"&ticket="+ssoCredential.getTicket(); - String responseBody = HttpHelper.callGet(casServiceValidateUrl, null); - // 检查结果 - String errorMsg = S.substringBetween(responseBody, ""); - if(V.notEmpty(errorMsg)){ - errorMsg = S.substringAfter(errorMsg, ">"); - throw new BusinessException("CAS登录失败:" + errorMsg); - } - // 提取用户名 - String username = S.substringBetween(responseBody, "", ""); - - log.debug("CAS ticket {}, user = {}", ssoCredential.getTicket(), username); - return username; - } - -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/cache/IamCacheManager.java b/diboot-iam-starter/src/main/java/com/diboot/iam/cache/IamCacheManager.java index bfe50a32f34e5104e9787343a88bb981d6fff532..d0800b3faa06a5474ac2900ab578589237fc10fc 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/cache/IamCacheManager.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/cache/IamCacheManager.java @@ -15,11 +15,14 @@ */ package com.diboot.iam.cache; -import com.diboot.core.cache.StaticMemoryCacheManager; +import com.diboot.core.util.AnnotationUtils; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.S; import com.diboot.core.util.V; -import com.diboot.iam.annotation.process.ApiPermission; +import com.diboot.iam.annotation.BindPermission; import com.diboot.iam.annotation.process.ApiPermissionExtractor; import com.diboot.iam.annotation.process.ApiPermissionWrapper; +import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.Map; @@ -32,99 +35,57 @@ import java.util.concurrent.ConcurrentHashMap; * @date 2021/4/22 * Copyright © diboot.com */ +@Slf4j public class IamCacheManager { /** - * 接口相关定义缓存管理器 + * controller-权限码 缓存 */ - private static StaticMemoryCacheManager iamMemoryCacheManager; - /** - * controller-API 权限 - */ - private static final String CACHE_NAME_CONTROLLER_API = "CONTROLLER_API"; - /** - * url-permission 缓存 - */ - private static Map URL_PERMISSIONCODE_CACHE = new ConcurrentHashMap<>(8); - - private static StaticMemoryCacheManager getCacheManager(){ - if(iamMemoryCacheManager == null){ - iamMemoryCacheManager = new StaticMemoryCacheManager( - CACHE_NAME_CONTROLLER_API); - URL_PERMISSIONCODE_CACHE.clear(); - } - return iamMemoryCacheManager; - } + private static Map CLASS_PERMISSIONCODE_CACHE = new ConcurrentHashMap<>(); /** - * 读取缓存permission - * @param requestMethodAndUrl - * @return - */ - public static String getPermissionCode(String requestMethodAndUrl){ - return getUrlPermissionCacheMap().get(requestMethodAndUrl); - } - - /** - * 读取缓存permission - * @param requestMethod - * @param url - * @return - */ - public static String getPermissionCode(String requestMethod, String url){ - return getUrlPermissionCacheMap().get(requestMethod.toUpperCase()+":"+url); - } - - /** - * 返回全部ApiPermissionVO + * 返回全部接口权限码ApiPermission * @return */ public static List getApiPermissionVoList(){ return ApiPermissionExtractor.extractAllApiPermissions(); } - /** - * 获取接口权限wrapper - * @param className - * @return - */ - public static ApiPermissionWrapper getApiPermissionWrapper(String className){ - initApiPermissionCache(); - return getCacheManager().getCacheObj(CACHE_NAME_CONTROLLER_API, className, ApiPermissionWrapper.class); - } - /** * 缓存全部permissions */ - private static Map getUrlPermissionCacheMap(){ - if(V.notEmpty(URL_PERMISSIONCODE_CACHE)){ - return URL_PERMISSIONCODE_CACHE; + public static ApiPermissionWrapper getPermissionCodeWrapper(Class controllerClass){ + // 优先从缓存中读取 + ApiPermissionWrapper wrapper = CLASS_PERMISSIONCODE_CACHE.get(controllerClass.getName()); + if(wrapper != null){ + return wrapper; } - initApiPermissionCache(); - return URL_PERMISSIONCODE_CACHE; - } - - /** - * 初始化 - */ - private static synchronized void initApiPermissionCache() { - StaticMemoryCacheManager cacheManager = getCacheManager(); - if (cacheManager.isUninitializedCache(CACHE_NAME_CONTROLLER_API) == false) { - return; - } - List permissions = ApiPermissionExtractor.extractAllApiPermissions(); - if(V.notEmpty(permissions)){ - for(ApiPermissionWrapper wrapper : permissions){ - if(wrapper.getChildren() != null){ - for(ApiPermission apiPermission : wrapper.getChildren()){ - // 缓存url-permission - URL_PERMISSIONCODE_CACHE.put(apiPermission.getApiMethod().toUpperCase()+":"+apiPermission.getApiUri(), apiPermission.getPermissionCode()); - // 缓存class-api - cacheManager.putCacheObj(CACHE_NAME_CONTROLLER_API, wrapper.getClassName(), wrapper); - } + // 从controller中解析 + String name = null; + // 提取类信息 + String codePrefix = null; + // 注解 + BindPermission bindPermission = AnnotationUtils.findAnnotation(controllerClass, BindPermission.class); + if(bindPermission != null){ + // 当前资源权限 + name = bindPermission.name(); + codePrefix = bindPermission.code(); + if(V.isEmpty(codePrefix)){ + Class entityClazz = BeanUtils.getGenericityClass(controllerClass, 0); + if(entityClazz != null){ + codePrefix = entityClazz.getSimpleName(); + } + else{ + log.warn("无法获取{}相关的Entity,请指定注解BindPermission.code参数!", controllerClass.getName()); } } } + else{ + name = S.substringBeforeLast(controllerClass.getSimpleName(), "Controller"); + } + wrapper = new ApiPermissionWrapper(name, codePrefix); + CLASS_PERMISSIONCODE_CACHE.put(controllerClass.getName(), wrapper); + return wrapper; } } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/config/Cons.java b/diboot-iam-starter/src/main/java/com/diboot/iam/config/Cons.java index 52af88a6b6980b6bf90cc2a6e11e103c487cc568..de504aa647612edc73cea2e49baf8ff5d02008f9 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/config/Cons.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/config/Cons.java @@ -16,6 +16,7 @@ package com.diboot.iam.config; import com.diboot.core.config.BaseConfig; +import com.diboot.iam.shiro.IamAuthorizingRealm; /** * IAM数据字典等常量定义 @@ -44,9 +45,10 @@ public class Cons extends com.diboot.core.config.Cons { * 字典编码 - 数据权限类型 */ public enum DICTCODE_DATA_PERMISSION_TYPE{ - INDIVIDUAL, + SELF, + SELF_AND_SUB, DEPT, - DEPT_MEMS, + DEPT_AND_SUB, ALL } @@ -115,7 +117,18 @@ public class Cons extends com.diboot.core.config.Cons { */ public static final String ROLE_SUPER_ADMIN = "SUPER_ADMIN"; - public static final String AUTHENTICATION_CAHCE_NAME = "com.diboot.iam.jwt.BaseJwtRealm.authenticationCache"; - public static final String AUTHORIZATION_CAHCE_NAME = "com.diboot.iam.jwt.BaseJwtRealm.authorizationCache"; + public static final String AUTHENTICATION_CAHCE_NAME = IamAuthorizingRealm.class.getName() + ".authenticationCache"; + + public static final String AUTHORIZATION_CAHCE_NAME = IamAuthorizingRealm.class.getName() + ".authorizationCache"; + + /** + * 验证码 + */ + public static final String CACHE_CAPTCHA = "CAPTCHA"; + + /** + * token-用户信息 缓存 + */ + public static final String CACHE_TOKEN_USERINFO = "TOKEN_USERINFO"; } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/config/SystemConfigType.java b/diboot-iam-starter/src/main/java/com/diboot/iam/config/SystemConfigType.java index 2eda511473ba84eb41282ce8138bad66a4b60804..dfb9503d2a2db1e2d1df7993a1c400c8cff293e1 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/config/SystemConfigType.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/config/SystemConfigType.java @@ -20,6 +20,7 @@ import com.diboot.core.util.PropertiesUtils; import com.diboot.core.util.S; import com.diboot.iam.service.SystemConfigService; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -159,7 +160,7 @@ public interface SystemConfigType { * @return 指定类型配置值映射 */ static > Values values(Class typeClass) { - return new Values(ContextHelper.getBean(SystemConfigService.class).getConfigMapByType(typeClass.getSimpleName())); + return new Values<>(typeClass); } /** @@ -168,13 +169,18 @@ public interface SystemConfigType { * 用于获取同类型获取配置值减少查询 */ class Values> { + /** + * 类型 Class + */ + private final String type; /** * 值映射 */ private final Map valueMap; - private Values(Map valueMap) { - this.valueMap = valueMap; + private Values(Class typeClass) { + this.type = S.substringBefore(S.substringAfterLast(typeClass.getName(), "."), "$"); + this.valueMap = ContextHelper.getBean(SystemConfigService.class).getConfigMapByType(type); } /** @@ -230,6 +236,17 @@ public interface SystemConfigType { return S.isBlank(value) ? null : Enum.valueOf(clazz, value.trim()); } + /** + * 获取配置 Map + * + * @return 属性值映射 + */ + public Map getMap() { + ContextHelper.getBean(SystemConfigService.class).getConfigItemsMap() + .getOrDefault(type, Collections.emptyList()).forEach(e -> get((E) e)); + return valueMap; + } + } } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/data/DataAccessPermissionUserOrgImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/data/DataAccessPermissionUserOrgImpl.java index 20fc748d66f5b3eb80f162efa6cc6f3f4c2f0265..f2e3d357784fd43d0a4716ea7725c67f6dd870cd 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/data/DataAccessPermissionUserOrgImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/data/DataAccessPermissionUserOrgImpl.java @@ -15,14 +15,13 @@ */ package com.diboot.iam.data; -import com.diboot.core.config.Cons; import com.diboot.core.data.access.DataAccessInterface; -import com.diboot.core.util.V; +import com.diboot.core.vo.LabelValue; +import com.diboot.iam.config.Cons; import com.diboot.iam.entity.IamUser; -import com.diboot.iam.service.IamOrgService; import com.diboot.iam.util.IamSecurityUtils; +import com.diboot.iam.vo.PositionDataScope; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import java.io.Serializable; import java.util.ArrayList; @@ -39,11 +38,8 @@ import java.util.List; @Slf4j public class DataAccessPermissionUserOrgImpl implements DataAccessInterface { - @Autowired - private IamOrgService iamOrgService; - @Override - public List getAccessibleIds(Class entityClass, String fieldName) { + public List getAccessibleIds(Class entityClass, String fieldName) { // 获取当前登录用户 IamUser currentUser = null; try { @@ -54,23 +50,116 @@ public class DataAccessPermissionUserOrgImpl implements DataAccessInterface { return Collections.emptyList(); } if (currentUser == null) { + log.warn("无法获取当前用户"); return Collections.emptyList(); } - // 提取其可访问ids - List accessibleIds = new ArrayList<>(); - if(Cons.FieldName.orgId.name().equals(fieldName)){ - //添加当前部门ID - Long currentOrgId = currentUser.getOrgId(); - accessibleIds.add(currentOrgId); - List childOrgIds = iamOrgService.getChildOrgIds(currentOrgId); - if(V.notEmpty(childOrgIds)){ - accessibleIds.addAll(childOrgIds); + // 获取用户岗位对应的数据权限 + LabelValue extensionObj = currentUser.getExtensionObj(); + if(extensionObj == null || extensionObj.getExt() == null){ + // 提取其可访问ids + if(isOrgFieldName(fieldName)){ + return buildOrgIdsScope(currentUser); + } + else if(isUserFieldName(fieldName)){ + return buildUserIdsScope(currentUser); + } + else{ + log.warn("数据权限未能识别该字段类型: {}", fieldName); + return Collections.emptyList(); } } - else if(Cons.FieldName.userId.name().equals(fieldName)){ - accessibleIds.add(currentUser.getId()); + // 处理岗位对应的数据范围权限 + PositionDataScope positionDataScope = (PositionDataScope)extensionObj.getExt(); + // 可看全部数据,不拦截 + if(Cons.DICTCODE_DATA_PERMISSION_TYPE.ALL.name().equalsIgnoreCase(positionDataScope.getDataPermissionType())){ + return null; } - // ... 其他类型字段 + // 本人数据 + else if(Cons.DICTCODE_DATA_PERMISSION_TYPE.SELF.name().equalsIgnoreCase(positionDataScope.getDataPermissionType())){ + if(isUserFieldName(fieldName)){ + return buildUserIdsScope(currentUser); + } + else{// 忽略无关字段 + return null; + } + } + // 按user过滤,本人及下属 + else if(Cons.DICTCODE_DATA_PERMISSION_TYPE.SELF_AND_SUB.name().equalsIgnoreCase(positionDataScope.getDataPermissionType())){ + if(isUserFieldName(fieldName)){ + return positionDataScope.getAccessibleUserIds(); + } + else{// 忽略无关字段 + return null; + } + } + // 按部门过滤,本部门 + else if(Cons.DICTCODE_DATA_PERMISSION_TYPE.DEPT.name().equalsIgnoreCase(positionDataScope.getDataPermissionType())){ + if(isOrgFieldName(fieldName)){ + List accessibleIds = new ArrayList<>(1); + accessibleIds.add(positionDataScope.getOrgId()); + return accessibleIds; + } + else{// 忽略无关字段 + return null; + } + } + // 按部门过滤,本部门及下属部门 + else if(Cons.DICTCODE_DATA_PERMISSION_TYPE.DEPT_AND_SUB.name().equalsIgnoreCase(positionDataScope.getDataPermissionType())){ + if(isOrgFieldName(fieldName)){ + return positionDataScope.getAccessibleOrgIds(); + } + else{// 忽略无关字段 + return null; + } + } + else{ + log.warn("未知的数据权限类型: {}", positionDataScope.getDataPermissionType()); + return Collections.emptyList(); + } + } + + /** + * 未配置数据权限时的默认可见自己的 + * @param currentUser + * @return + */ + protected List buildUserIdsScope(IamUser currentUser){ + List accessibleIds = new ArrayList<>(1); + accessibleIds.add(currentUser.getId()); + return accessibleIds; + } + + /** + * 未配置数据权限时的默认本部门 + * @param currentUser + * @return + */ + protected List buildOrgIdsScope(IamUser currentUser){ + List accessibleIds = new ArrayList<>(); + accessibleIds.add(currentUser.getOrgId()); + /*List childOrgIds = ContextHelper.getBean(IamOrgService.class).getChildOrgIds(currentUser.getOrgId()); + if(V.notEmpty(childOrgIds)){ + accessibleIds.addAll(childOrgIds); + }*/ return accessibleIds; } + + /** + * 是否为可支持的用户字段 + * @param fieldName + * @return + */ + protected boolean isUserFieldName(String fieldName){ + return (Cons.FieldName.userId.name().equals(fieldName) || Cons.FieldName.createBy.name().equals(fieldName)); + } + + /** + * 是否为可支持的部门字段 + * @param fieldName + * @return + */ + protected boolean isOrgFieldName(String fieldName){ + return Cons.FieldName.orgId.name().equals(fieldName); + } + } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/AuthCredential.java b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/AuthCredential.java index 7d374a174dec021a89e91eaa68a541a9867a2c78..8f2e3914570173f5e2ec79e8ae12036216bf8be8 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/AuthCredential.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/AuthCredential.java @@ -37,6 +37,11 @@ import java.util.Map; public abstract class AuthCredential implements Serializable { private static final long serialVersionUID = -4721950772621829194L; + /** + * 唯一标识 + */ + private String traceId; + /** * 用户类型的Class */ diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/IamUserAccountDTO.java b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/IamUserAccountDTO.java index 195baa24e3002be817c5431070b0dad08155f6a6..30dd83296fb2908e21492c38719373950dcb1b4a 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/IamUserAccountDTO.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/IamUserAccountDTO.java @@ -15,6 +15,7 @@ */ package com.diboot.iam.dto; +import com.diboot.iam.config.Cons; import com.diboot.iam.entity.IamUser; import lombok.Getter; import lombok.Setter; @@ -33,6 +34,9 @@ import java.util.List; @Accessors(chain = true) public class IamUserAccountDTO extends IamUser { + // 认证方式 + private String authType = Cons.DICTCODE_AUTH_TYPE.PWD.name(); + private String username; private String password; diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/ModifyIamResourcePermissionDTO.java b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/ModifyIamResourcePermissionDTO.java deleted file mode 100644 index 21187fdd8457ea9896f7f31e02c85d082a3d42cd..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/ModifyIamResourcePermissionDTO.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.dto; - -import com.diboot.core.util.S; -import com.diboot.core.util.V; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; - -import java.io.Serializable; -import java.util.List; - -/** - * 修改权限DTO - * - * @author : uu - * @version : v1.0 - * @Date 2021/5/8 12:49 - * @copyright www.diboot.com - */ -@Getter -@Setter -@Accessors(chain = true) -public class ModifyIamResourcePermissionDTO implements Serializable { - private static final long serialVersionUID = 211276433727981441L; - - private Long id; - - private String apiSet; - - private List apiSetList; - - /*** - * 设置接口列表 - * @param apiSetList - */ - public void setApiSetList(List apiSetList) { - if (V.isEmpty(apiSetList)){ - this.setApiSet(null); - } - this.setApiSet(S.join(apiSetList, ",")); - } -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/SSOCredential.java b/diboot-iam-starter/src/main/java/com/diboot/iam/dto/SSOCredential.java deleted file mode 100644 index 900cda748b6fedf15885a6e947978556540c09df..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/dto/SSOCredential.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.dto; - -import com.diboot.iam.config.Cons; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; - -/** - * 登录凭证 - * @author mazc@dibo.ltd - * @version v2.0 - * @date 2019/12/18 - */ -@Getter -@Setter -@Accessors(chain = true) -@Deprecated -public class SSOCredential extends AuthCredential { - private static final long serialVersionUID = -5020652642432896556L; - - public SSOCredential(){ - setAuthType(Cons.DICTCODE_AUTH_TYPE.SSO.name()); - } - - public SSOCredential(String serviceUrl, String ticket){ - this.serviceUrl = serviceUrl; - this.ticket = ticket; - setAuthType(Cons.DICTCODE_AUTH_TYPE.SSO.name()); - } - - // serviceUrl - private String serviceUrl; - - // CAS ticket - private String ticket; - - // auth account账号 - private String authAccount; - - @Override - public String getAuthAccount() { - return this.authAccount; - } - - @Override - public String getAuthSecret() { - return null; - } - -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/BaseLoginUser.java b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/BaseLoginUser.java index 398c491f0dd12a13778e588c6792cfbb910e432a..2c45e2c97c2385562ae43fd5c95cc8c3f00828b7 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/BaseLoginUser.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/BaseLoginUser.java @@ -52,13 +52,13 @@ public abstract class BaseLoginUser extends BaseEntity { * 附加对象,用于岗位等扩展 */ @TableField(exist = false) - private LabelValue extentionObj; + private LabelValue extensionObj; - public LabelValue getExtentionObj(){ - return this.extentionObj; + public LabelValue getExtensionObj(){ + return this.extensionObj; } - public void setExtentionObj(LabelValue extentionObj){ - this.extentionObj = extentionObj; + public void setExtensionObj(LabelValue extensionObj){ + this.extensionObj = extensionObj; } public String getAuthToken(){ diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamOperationLog.java b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamOperationLog.java index ef3749f957580a660ffa0fc388a5d183aeeec9c3..f604e09e7375bbd7f263127b1f4b6cd0b179efa3 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamOperationLog.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamOperationLog.java @@ -126,8 +126,8 @@ public class IamOperationLog extends BaseEntity { private String errorMsg; public IamOperationLog setRequestParams(String requestParams){ - if(V.notEmpty(requestParams) && requestParams.length() > 1000){ - requestParams = S.cut(requestParams, 1000); + if(V.notEmpty(requestParams) && requestParams.length() > 980){ + requestParams = S.cut(requestParams, 980); } this.requestParams = requestParams; return this; diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamResourcePermission.java b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamResourcePermission.java index d66b13d85895d0e34a42cb85679fcc32ce19e3a5..3bf9839edffcb54d0c3849657b8299e43387ec1f 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamResourcePermission.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/entity/IamResourcePermission.java @@ -80,10 +80,10 @@ public class IamResourcePermission extends BaseEntity { @TableField() private String resourceCode; - // 接口列表 - @Length(max=5000, message="接口列表长度应小于5000") + // 权限编码 + @Length(max=200, message="权限编码长度应小于200") @TableField() - private String apiSet; + private String permissionCode; // 排序号 @TableField @@ -93,27 +93,26 @@ public class IamResourcePermission extends BaseEntity { @TableField(fill = FieldFill.UPDATE) private Date updateTime; - /*** - * 获取接口列表 + * 获取权限码列表 * @return */ - public String[] getApiSetList() { - if (V.isEmpty(this.getApiSet())){ + public String[] getPermissionCodes() { + if (V.isEmpty(permissionCode)){ return null; } - return S.split(this.getApiSet(), ","); + return S.split(permissionCode); } /*** - * 设置接口列表 - * @param apiSetList + * 设置权限码列表 + * @param permissionCodes */ - public void setApiSetList(List apiSetList) { - if (V.isEmpty(apiSetList)){ - this.setApiSet(null); + public void setPermissionCodes(List permissionCodes) { + if (V.isEmpty(permissionCodes)){ + this.setPermissionCode(null); } - this.setApiSet(S.join(apiSetList, ",")); + this.setPermissionCode(S.join(permissionCodes)); } } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtAuthToken.java b/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtAuthToken.java index 0c71795c0cfbaf5569b1ca06d41a6e511837833d..15e72d8f2b5aaf9c95151cd78409d0e5667f87f7 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtAuthToken.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtAuthToken.java @@ -15,128 +15,19 @@ */ package com.diboot.iam.jwt; -import com.diboot.iam.config.Cons; -import com.diboot.iam.entity.IamUser; -import com.diboot.iam.util.JwtUtils; +import com.diboot.iam.shiro.IamAuthToken; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.authc.RememberMeAuthenticationToken; - -import java.util.Map; /** * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ +@Deprecated @Getter @Setter @Slf4j -public class BaseJwtAuthToken implements RememberMeAuthenticationToken { - private static final long serialVersionUID = -5518501153334708409L; - - /** - * 用户类型的Class - */ - private Class userTypeClass = IamUser.class; - /** - * 认证类型,密码/微信... - */ - private String authType; - /** - * 认证账号 - */ - private String authAccount; - /** - * 认证凭证 - */ - private String authSecret; - /** - * 记住我 - */ - private boolean rememberMe; - - /** - * 扩展属性 - */ - private Map extObj; - - /**authz token*/ - private String authtoken; - - /** - * 租户id - */ - private Long tenantId = 0L; - - /** - * 是否校验密码 - */ - private boolean validPassword = true; - - private Object principal; - - private Object credentials; - - public BaseJwtAuthToken(){ - } - - /*** - * 初始化认证token - * @param authType 认证方式 - * @param userTypeClass 用户类型Class - */ - public BaseJwtAuthToken(String authType, Class userTypeClass){ - this.authType = authType; - this.userTypeClass = userTypeClass; - } - - /*** - * 验证失败的时候清空token - */ - public void clearAuthtoken(){ - this.authtoken = null; - } - - /** - * 设置 - * @return - */ - @Override - public Object getPrincipal() { - return this.authtoken; - } - - @Override - public Object getCredentials() { - return this.authtoken; - } - - /** - * 获取用户类型 - * @return - */ - public String getUserType(){ - return userTypeClass.getSimpleName(); - } - - /** - * 生成token tenantId,account,userTypeClass,authType,60 - * @param expiresInMinutes - * @return - */ - public BaseJwtAuthToken generateAuthtoken(int expiresInMinutes){ - String tokenInput = this.tenantId + Cons.SEPARATOR_COMMA - + this.authAccount + Cons.SEPARATOR_COMMA - + this.userTypeClass.getTypeName() + Cons.SEPARATOR_COMMA - + this.authType + Cons.SEPARATOR_COMMA - + expiresInMinutes; - this.authtoken = JwtUtils.generateToken(tokenInput, expiresInMinutes); - return this; - } - - @Override - public boolean isRememberMe() { - return rememberMe; - } +public class BaseJwtAuthToken extends IamAuthToken { + private static final long serialVersionUID = -3455501467921544790L; } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/DefaultJwtAuthFilter.java b/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/DefaultJwtAuthFilter.java deleted file mode 100644 index 005b3735b046ccef3577c70af5c5c9af64e35909..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/DefaultJwtAuthFilter.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.jwt; - -import com.diboot.core.util.ContextHelper; -import com.diboot.core.util.JSON; -import com.diboot.core.util.V; -import com.diboot.core.vo.JsonResult; -import com.diboot.core.vo.Status; -import com.diboot.iam.config.Cons; -import com.diboot.iam.util.JwtUtils; -import io.jsonwebtoken.Claims; -import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.cache.CacheManager; -import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - -/** - * JWT 认证过滤器 - * @author Yangzhao - * @version v2.0 - * @date 2019/6/6 - */ -@Slf4j -public class DefaultJwtAuthFilter extends BasicHttpAuthenticationFilter { - - /** - * Shiro权限拦截核心方法 使用JWT进行认证 返回true允许访问 - */ - @Override - protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - // 从header获取Token - Claims claims = JwtUtils.getClaimsFromRequest(httpRequest); - if (V.isEmpty(claims)) { - return false; - } - //解密是否成功 - if(V.notEmpty(claims.getSubject())){ - log.debug("Token验证成功!account={}, url={}", claims.getSubject(), httpRequest.getRequestURI()); - // 如果临近过期,则生成新的token返回 - String refreshToken = JwtUtils.generateNewTokenIfRequired(claims); - if(refreshToken != null){ - // 写入response header中 - JwtUtils.addTokenToResponseHeader((HttpServletResponse) response, refreshToken); - CacheManager cacheManager = ContextHelper.getBean(CacheManager.class); - if(cacheManager != null){ - // 记录新的token - if(cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME) != null){ - String currentToken = JwtUtils.getRequestToken(httpRequest); - Object cacheVal = cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME).get(currentToken); - cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME).put(refreshToken, cacheVal); - } - } - log.debug("返回新的token: {}", refreshToken); - } - return true; - } - log.debug("Token验证失败!url=" + httpRequest.getRequestURI()); - return false; - } - - /** - * 当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理 - * @param - */ - @Override - protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { - HttpServletRequest httpRequest = (HttpServletRequest) request; - log.debug("Token认证失败: onAccessDenied。url={}", httpRequest.getRequestURI()); - JsonResult jsonResult = new JsonResult(Status.FAIL_INVALID_TOKEN); - this.responseJson((HttpServletResponse) response, jsonResult); - return false; - } - - /*** - * 返回json格式错误信息 - * @param response - * @param jsonResult - */ - protected void responseJson(HttpServletResponse response, JsonResult jsonResult){ - // 处理异步请求 - response.setStatus(HttpStatus.OK.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setCharacterEncoding(Cons.CHARSET_UTF8); - try (PrintWriter pw = response.getWriter()){ - pw.write(JSON.stringify(jsonResult)); - pw.flush(); - } - catch (IOException e) { - log.error("处理异步请求异常", e); - } - } - -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/redis/ShiroRedisCacheManager.java b/diboot-iam-starter/src/main/java/com/diboot/iam/redis/ShiroRedisCacheManager.java index 46ce201119fae9b0673074263bb2d45838ed58c7..ce90b7f50586e935ec221e1be565cdec0221578b 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/redis/ShiroRedisCacheManager.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/redis/ShiroRedisCacheManager.java @@ -46,4 +46,4 @@ public class ShiroRedisCacheManager extends AbstractCacheManager { return new ShiroRedisCache(cacheName, redisTemplate, tokenExpireMinutes); } -} +} \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamOrgService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamOrgService.java index d15b857ef37f603fe23bb2edeaf143b8888f5eee..4ab48e8307a5cc613c746bb22d5ab01233730961 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamOrgService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamOrgService.java @@ -63,4 +63,11 @@ public interface IamOrgService extends BaseIamService { */ List getParentOrgIds(Long orgId, boolean includeThis); + /** + * 获取某负责人负责的相关部门ids + * @param managerId 负责人id + * @return + */ + List getOrgIdsByManagerId(Long managerId); + } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamPositionService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamPositionService.java index b52bf68580a6212283d1adb50a13e55dd060090d..b84ca69badc0f5770b0f874e3d060e7d1eb6045c 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamPositionService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamPositionService.java @@ -36,6 +36,14 @@ public interface IamPositionService extends BaseIamService { */ List getUserPositionListByUser(String userType, Long userId); + /** + * 获取用户的第一主岗 + * @param userType + * @param userId + * @return + */ + IamUserPosition getUserPrimaryPosition(String userType, Long userId); + /*** * 通过用户ID获取用户的所有任职岗位集合 * @param userType diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamResourcePermissionService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamResourcePermissionService.java index b9f9ec3b493608277b94f3bb66e24cad32f0f8a2..21f43d4400d0c110049508de9e430fcd32f89be8 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamResourcePermissionService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamResourcePermissionService.java @@ -21,7 +21,6 @@ import com.diboot.iam.entity.IamResourcePermission; import com.diboot.iam.vo.IamResourcePermissionListVO; import java.util.List; -import java.util.Map; /** * 前端资源权限相关Service @@ -77,16 +76,4 @@ public interface IamResourcePermissionService extends BaseIamService permissionList); - /*** - * 提取代码中的权限和已经存在数据库的权限不同的数据 - * - * 1、获取DB存储的API
- * 2、获取代码中的API
- * 3、对比,提出返回结果
- * 结果返回:数据库中无效的API、数据库无效的记录id - * - * @param application - * @return - */ - Map extractCodeDiffDbPermissions(String application); } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamRoleResourceService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamRoleResourceService.java index 13bc871490832daa4e3934516790b5bb91cd3dc5..78d99e40e4afefc301671521ce5c27723b177efb 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamRoleResourceService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamRoleResourceService.java @@ -62,6 +62,14 @@ public interface IamRoleResourceService extends BaseIamService */ List getApiUrlList(String appModule, List roleIds); + /** + * 获取指定角色集合对应的权限码集合 + * @param appModule + * @param roleIds + * @return + */ + List getPermissionCodeList(String appModule, List roleIds); + /** * 获取资源角色VO集合 * @return diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/Operation.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserPositionService.java similarity index 64% rename from diboot-iam-starter/src/main/java/com/diboot/iam/annotation/Operation.java rename to diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserPositionService.java index c0a1a62954660f88a4f50dfb501b2df50c307784..948c38a48f9d10039ea521e6d163cad348e35552 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/annotation/Operation.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserPositionService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + * Copyright (c) 2015-2022, www.dibo.ltd (service@dibo.ltd). *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -13,15 +13,17 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.diboot.iam.annotation; +package com.diboot.iam.service; + +import com.diboot.iam.entity.IamUserPosition; /** - * 权限-操作 类型定义 - * @author mazc@dibo.ltd - * @version v2.0 - * @date 2019/12/23 + * 用户岗位关联相关Service + * + * @author wind + * @version v2.6.0 + * @date 2022-06-23 */ -@Deprecated -public class Operation extends OperationCons { +public interface IamUserPositionService extends BaseIamService { -} +} \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserRoleService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserRoleService.java index db2244a30315712473b46087e58aee96a290e485..2fc225ee773b2be116b7ab2857cb7293136e6a69 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserRoleService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserRoleService.java @@ -44,10 +44,10 @@ public interface IamUserRoleService extends BaseIamService { * 获取用户所有的全部角色 * @param userType * @param userId - * @param extentionObjId 岗位等扩展对象id + * @param extensionObjId 岗位等扩展对象id * @return */ - List getUserRoleList(String userType, Long userId, Long extentionObjId); + List getUserRoleList(String userType, Long userId, Long extensionObjId); /** * 批量创建用户-角色的关系 diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserService.java index 9605ccb9df2bef37aeb047764e87b250bacf7eca..3d466eb585532aa70deb3f9db3e3943e386dbe79 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/IamUserService.java @@ -15,8 +15,11 @@ */ package com.diboot.iam.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.diboot.core.vo.Pagination; import com.diboot.iam.dto.IamUserAccountDTO; import com.diboot.iam.entity.IamUser; +import com.diboot.iam.vo.IamUserVO; import java.util.List; @@ -65,4 +68,21 @@ public interface IamUserService extends BaseIamService { */ boolean isUserNumExists(Long id, String userNum); + /** + * 获取指定管理者的下属人员 + * @param managerId + * @return + */ + List getUserIdsByManagerId(Long managerId); + + /** + * 获取用户VO列表 + * + * @param queryWrapper + * @param pagination + * @param orgId + * @return + */ + List getUserViewList(LambdaQueryWrapper queryWrapper, Pagination pagination, Long orgId); + } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/SystemConfigService.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/SystemConfigService.java index 86ca16c84c58f91c1b8a62c36a1214e815d8b421..8a6ff9e5b6dded46b1e591625483e598b8bc60a3 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/SystemConfigService.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/SystemConfigService.java @@ -17,6 +17,7 @@ package com.diboot.iam.service; import com.diboot.core.service.BaseService; import com.diboot.core.vo.LabelValue; +import com.diboot.iam.config.SystemConfigType; import com.diboot.iam.entity.SystemConfig; import com.diboot.iam.vo.SystemConfigVO; @@ -39,6 +40,13 @@ public interface SystemConfigService extends BaseService { */ List getTypeList(); + /** + * 获取配置元素类型映射 + * + * @return 全部配置元素类型映射 + */ + Map>> getConfigItemsMap(); + /** * 根据类型获取配置 * diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamOrgServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamOrgServiceImpl.java index 6fb27b97e9e04240af19fa0d9d52285b4f44f515..96eca7a9a98d5c69645b4ebb0a1dddcf9667d9e9 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamOrgServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamOrgServiceImpl.java @@ -82,6 +82,9 @@ public class IamOrgServiceImpl extends BaseIamServiceImpl @Override public List getChildOrgIds(Long rootOrgId) { + if(rootOrgId == null){ + return Collections.emptyList(); + } List childOrgs = getOrgTree(rootOrgId); if(V.notEmpty(childOrgs)){ List childOrgIds = new ArrayList<>(); @@ -172,6 +175,14 @@ public class IamOrgServiceImpl extends BaseIamServiceImpl return scopeIds; } + @Override + public List getOrgIdsByManagerId(Long managerId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(IamOrg::getManagerId, managerId); + List orgIdList = getValuesOfField(queryWrapper, IamOrg::getId); + return orgIdList; + } + /** * 提取id * @param orgs diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamPositionServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamPositionServiceImpl.java index 4148a839182f876247792a16004fc4c596dfc1a4..57f1cb8b2dc8ce0816a7cedbe34f84d3f2b3802b 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamPositionServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamPositionServiceImpl.java @@ -57,6 +57,22 @@ public class IamPositionServiceImpl extends BaseIamServiceImpl queryWrapper = Wrappers.lambdaQuery() + .eq(IamUserPosition::getUserType, userType) + .eq(IamUserPosition::getUserId, userId) + .eq(IamUserPosition::getIsPrimaryPosition, true); + List userPositionList = iamUserPositionMapper.selectList(queryWrapper); + if(V.isEmpty(userPositionList)){ + return null; + } + if(userPositionList.size() > 1){ + log.warn("用户 {}:{} 主岗多于1个,当前以第一个为准", userType, userId); + } + return userPositionList.get(0); + } + @Override public List getPositionListByUser(String userType, Long userId) { // 根据user与position的关联获取positionId列表 diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamResourcePermissionServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamResourcePermissionServiceImpl.java index 15629b3bf8bbae7b9d102ea2718d472786f061e1..5e784b7244a113176fdadb00ffb451445efaa150 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamResourcePermissionServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamResourcePermissionServiceImpl.java @@ -17,25 +17,24 @@ package com.diboot.iam.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.diboot.core.binding.RelationsBinder; import com.diboot.core.exception.BusinessException; import com.diboot.core.util.BeanUtils; -import com.diboot.core.util.S; import com.diboot.core.util.V; import com.diboot.core.vo.Status; -import com.diboot.iam.cache.IamCacheManager; import com.diboot.iam.config.Cons; import com.diboot.iam.dto.IamResourcePermissionDTO; import com.diboot.iam.entity.IamResourcePermission; import com.diboot.iam.mapper.IamResourcePermissionMapper; import com.diboot.iam.service.IamResourcePermissionService; import com.diboot.iam.vo.IamResourcePermissionListVO; -import com.diboot.iam.vo.InvalidResourcePermissionVO; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -100,10 +99,6 @@ public class IamResourcePermissionServiceImpl extends BaseIamServiceImpl permissionList = iamResourcePermissionDTO.getPermissionList(); @@ -126,12 +121,12 @@ public class IamResourcePermissionServiceImpl extends BaseIamServiceImpl oldPermissionList = this.getEntityList( Wrappers.lambdaQuery() .eq(IamResourcePermission::getParentId, iamResourcePermissionDTO.getId()) - .eq(IamResourcePermission::getDisplayType, Cons.RESOURCE_PERMISSION_DISPLAY_TYPE.PERMISSION) + .eq(IamResourcePermission::getDisplayType, Cons.RESOURCE_PERMISSION_DISPLAY_TYPE.PERMISSION.name()) ); if (V.notEmpty(oldPermissionList)){ LambdaQueryWrapper deleteWrapper = Wrappers.lambdaQuery() .eq(IamResourcePermission::getParentId, iamResourcePermissionDTO.getId()) - .eq(IamResourcePermission::getDisplayType, Cons.RESOURCE_PERMISSION_DISPLAY_TYPE.PERMISSION); + .eq(IamResourcePermission::getDisplayType, Cons.RESOURCE_PERMISSION_DISPLAY_TYPE.PERMISSION.name()); if (V.notEmpty(updatePermissionIdList)) { deleteWrapper.notIn(IamResourcePermission::getId, updatePermissionIdList); } @@ -220,61 +215,6 @@ public class IamResourcePermissionServiceImpl extends BaseIamServiceImpl extractCodeDiffDbPermissions(String application) { - List allResourcePermissions = getEntityList(Wrappers.lambdaQuery() - .orderByDesc(IamResourcePermission::getSortId, IamResourcePermission::getId)); - if(V.isEmpty(allResourcePermissions)){ - return Collections.emptyMap(); - } - List voList = RelationsBinder.convertAndBind(allResourcePermissions, InvalidResourcePermissionVO.class); - Map resultMap = new HashMap<>(32); - for (InvalidResourcePermissionVO invalidIamResourcePermission : voList) { - if (V.isEmpty(invalidIamResourcePermission.getApiSet())) { - continue; - } - for(String uri : invalidIamResourcePermission.getApiSetList()){ - // 根据uri获取code - String permissionCode = IamCacheManager.getPermissionCode(uri); - // code存在,表示该URI有效,否则无效 - if(V.isEmpty(permissionCode)){ - invalidIamResourcePermission.addInvalidApiSet(uri); - resultMap.put(invalidIamResourcePermission.getId(), invalidIamResourcePermission); - } - - } - } - if (V.isEmpty(resultMap)) { - return Collections.emptyMap(); - } - List outerPermissionList = new ArrayList<>(); - for (Map.Entry invalidResourcePermissionVOEntry : resultMap.entrySet()) { - InvalidResourcePermissionVO invalidResourcePermissionVO = invalidResourcePermissionVOEntry.getValue(); - // 获取当前权限的上级元素 - Long parentId = invalidResourcePermissionVO.getParentId(); - InvalidResourcePermissionVO parentInvalidResourcePermissionVO = resultMap.get(parentId); - // 如果上级元素不存在,表示,当前对象为最外层元素 - if (V.isEmpty(parentInvalidResourcePermissionVO)) { - outerPermissionList.add(invalidResourcePermissionVO); - } - } - List result = new ArrayList<>(resultMap.values()); - List diffDataIdList = new ArrayList<>(); - for (InvalidResourcePermissionVO value : result) { - List childNodeChildren = BeanUtils.buildTreeChildren(value.getId(), result, Cons.FieldName.parentId.name(), Cons.FieldName.children.name()); - - if(childNodeChildren == null) { - childNodeChildren = new ArrayList<>(); - } - BeanUtils.setProperty(value, Cons.FieldName.children.name(), childNodeChildren); - diffDataIdList.add(value.getId()); - } - return new HashMap() {{ - put("diffDataList", outerPermissionList); - put("diffDataIdList", diffDataIdList); - }}; - } - @Override @Transactional(rollbackFor = Exception.class) public void deleteMenuAndPermissions(List idList) { diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamRoleResourceServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamRoleResourceServiceImpl.java index fb9cacab5b532cc80639a559bbb96040973b0587..3e1272b29f1884ec8629d97ef6b7ce037c9cf413 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamRoleResourceServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamRoleResourceServiceImpl.java @@ -97,22 +97,43 @@ public class IamRoleResourceServiceImpl extends BaseIamServiceImpl resourcePermissions = iamResourcePermissionService.getEntityList( Wrappers.lambdaQuery() - .select(IamResourcePermission::getApiSet) + .select(IamResourcePermission::getPermissionCodes) .in(IamResourcePermission::getId, permissionIds) - .isNotNull(IamResourcePermission::getApiSet) + .isNotNull(IamResourcePermission::getPermissionCode) ); if(resourcePermissions == null){ return Collections.emptyList(); } // 转换为string list - List list = BeanUtils.collectToList(resourcePermissions, IamResourcePermission::getApiSet); + List list = BeanUtils.collectToList(resourcePermissions, IamResourcePermission::getPermissionCode); return list; } + @Override + public List getPermissionCodeList(String appModule, List roleIds) { + if (V.isEmpty(roleIds)) { + return Collections.emptyList(); + } + List permissionIds = getPermissionIdsByRoleIds(appModule, roleIds); + if (V.isEmpty(permissionIds)) { + return Collections.emptyList(); + } + // 查询权限 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .select(IamResourcePermission::getPermissionCode) + .in(IamResourcePermission::getId, permissionIds) + .isNotNull(IamResourcePermission::getPermissionCode); + // 仅查询PermissionCode字段 + List resourcePermissions = iamResourcePermissionService.getValuesOfField( + queryWrapper, IamResourcePermission::getPermissionCode + ); + return resourcePermissions; + } + @Override public List getAllResourceRoleVOList() { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() - .isNotNull(IamResourcePermission::getApiSet); + .isNotNull(IamResourcePermission::getPermissionCode); List list = iamResourcePermissionService.getEntityList(wrapper); if(list == null){ list = Collections.emptyList(); diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserPositionServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserPositionServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..f0d4ff1aa638b654f0fdd03e36d6435eb63dccdf --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserPositionServiceImpl.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-2022, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.service.impl; + +import com.diboot.iam.entity.IamUserPosition; +import com.diboot.iam.mapper.IamUserPositionMapper; +import com.diboot.iam.service.IamUserPositionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 用户岗位关联相关Service实现 + * + * @author wind + * @version v2.6.0 + * @date 2022-06-23 + */ +@Slf4j +@Service +public class IamUserPositionServiceImpl extends BaseIamServiceImpl implements IamUserPositionService { + +} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserRoleServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserRoleServiceImpl.java index 8a3c28fd34f19b194f821c51baf4fb80d3b1a0f4..4561d99d0ae014375de7f32ad70c07dbb9e1f843 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserRoleServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserRoleServiceImpl.java @@ -80,7 +80,7 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl getUserRoleList(String userType, Long userId, Long extentionObjId) { + public List getUserRoleList(String userType, Long userId, Long extensionObjId) { List userRoleList = getEntityList(Wrappers.lambdaQuery() .select(IamUserRole::getRoleId) .eq(IamUserRole::getUserType, userType) @@ -96,7 +96,7 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl extRoles = getIamExtensible().getExtentionRoles(userType, userId, extentionObjId); + List extRoles = getIamExtensible().getExtensionRoles(userType, userId, extensionObjId); if(V.notEmpty(extRoles)){ roles.addAll(extRoles); roles = BeanUtils.distinctByKey(roles, IamRole::getId); diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserServiceImpl.java b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserServiceImpl.java index 526a9015cfbe3b14d0daa77b9431ffe3b03e5b22..435cb1e80102a8371d5638a8ed15f9e6eb9496ae 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserServiceImpl.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/service/impl/IamUserServiceImpl.java @@ -20,23 +20,24 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.diboot.core.config.BaseConfig; import com.diboot.core.exception.BusinessException; import com.diboot.core.util.V; +import com.diboot.core.vo.Pagination; import com.diboot.core.vo.Status; import com.diboot.iam.auth.IamCustomize; -import com.diboot.iam.config.Cons; import com.diboot.iam.dto.IamUserAccountDTO; import com.diboot.iam.entity.IamAccount; import com.diboot.iam.entity.IamUser; +import com.diboot.iam.entity.IamUserPosition; import com.diboot.iam.mapper.IamUserMapper; -import com.diboot.iam.service.IamAccountService; -import com.diboot.iam.service.IamUserRoleService; -import com.diboot.iam.service.IamUserService; +import com.diboot.iam.service.*; import com.diboot.iam.util.IamSecurityUtils; +import com.diboot.iam.vo.IamUserVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; +import java.util.stream.Collectors; /** * 系统用户相关Service实现 @@ -55,6 +56,12 @@ public class IamUserServiceImpl extends BaseIamServiceImpl getUserIdsByManagerId(Long managerId) { + if(managerId == null){ + return null; + } + List orgIds = iamOrgService.getOrgIdsByManagerId(managerId); + if(V.isEmpty(orgIds)){ + return Collections.emptyList(); + } + List iamUserIds = getValuesOfField( + Wrappers.lambdaQuery().in(IamUser::getOrgId, orgIds), + IamUser::getId + ); + return iamUserIds; + } + + @Override + public List getUserViewList(LambdaQueryWrapper queryWrapper, Pagination pagination, Long orgId) { + List orgIds = new ArrayList<>(); + // 获取当前部门及所有下属部门的人员列表 + if (V.notEmpty(orgId) && V.notEquals(orgId, 0L)) { + orgIds.add(orgId); + // 获取所有下级部门列表 + orgIds.addAll(iamOrgService.getChildOrgIds(orgId)); + queryWrapper.in(IamUser::getOrgId, orgIds); + // 相应部门下岗位相关用户 + LambdaQueryWrapper queryUserIds = Wrappers.lambdaQuery() + .eq(IamUserPosition::getUserType, IamUser.class.getSimpleName()) + .in(IamUserPosition::getOrgId, orgIds); + List userIds = iamUserPositionService.getValuesOfField(queryUserIds, IamUserPosition::getUserId); + queryWrapper.or().in(V.notEmpty(userIds), IamUser::getId, userIds); + } + // 查询指定页的数据 + List voList = getViewObjectList(queryWrapper, pagination, IamUserVO.class); + if (V.isEmpty(orgIds)) { + return voList; + } + for (IamUserVO user : voList) { + List userPositionList = user.getUserPositionList(); + if (V.notEmpty(userPositionList)) { + user.setUserPositionList(userPositionList.stream().filter(p -> orgIds.contains(p.getOrgId())).collect(Collectors.toList())); + } + } + return voList; + } + /*** * 检查重复用户编号 * @param userNumList @@ -178,15 +231,16 @@ public class IamUserServiceImpl extends BaseIamServiceImpl configInjections; - private final List configTypeList = new ArrayList<>(); - private final Map>> configItemListMap = new HashMap<>(); - private final Map> configTypeTestMap = new HashMap<>(); + @Getter + private final List typeList = new ArrayList<>(); + @Getter + private final Map>> configItemsMap = new HashMap<>(); + private final Map> configTestMap = new HashMap<>(); private final Map> configTestDataClassMap = new HashMap<>(); @PostConstruct @@ -79,13 +81,13 @@ public class SystemConfigServiceImpl extends BaseServiceImpl) enumConstants[0]); + configTestMap.put(type, (SystemConfigTest) enumConstants[0]); Class testDataClass = findTestDataClass(ResolvableType.forClass(configType)); configTestDataClassMap.put(type, testDataClass); labelValue.setExt(BeanUtils.extractAllFields(testDataClass).stream().map(Field::getName).collect(Collectors.toSet())); } - configTypeList.add(labelValue); - configItemListMap.put(type, Arrays.asList(enumConstants)); + typeList.add(labelValue); + configItemsMap.put(type, Arrays.asList(enumConstants)); } } } @@ -108,20 +110,15 @@ public class SystemConfigServiceImpl extends BaseServiceImpl getTypeList() { - return configTypeList; - } - @Override public List getConfigByType(String type) { - if (!configItemListMap.containsKey(type)) { + if (!configItemsMap.containsKey(type)) { return Collections.emptyList(); } LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(SystemConfig::getType, type); Map configInfoMap = this.getViewObjectList(queryWrapper, null, SystemConfigVO.class) .stream().collect(Collectors.toMap(SystemConfig::getProp, e -> e)); - return configItemListMap.get(type).stream().map(item -> { + return configItemsMap.get(type).stream().map(item -> { SystemConfigType configProp = (SystemConfigType) item; Object defaultValue = configProp.buildDefaultValue(); return configInfoMap.getOrDefault(item.name(), new SystemConfigVO() {{ @@ -140,7 +137,7 @@ public class SystemConfigServiceImpl extends BaseServiceImpl data) { - SystemConfigTest systemConfigTest = configTypeTestMap.get(type); + SystemConfigTest systemConfigTest = configTestMap.get(type); if (systemConfigTest == null) { throw new BusinessException("系统配置`" + type + "`未实现测试方法"); } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthToken.java b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthToken.java new file mode 100644 index 0000000000000000000000000000000000000000..50e46cb8f82bc121bf004a1fd3ba34b60395f2cc --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthToken.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.shiro; + +import com.diboot.core.util.S; +import com.diboot.iam.config.Cons; +import com.diboot.iam.entity.IamUser; +import com.diboot.iam.util.TokenUtils; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.RememberMeAuthenticationToken; + +import java.util.Map; + +/** + * IAM 认证token定义 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/26 + * Copyright © diboot.com + */ +@Getter @Setter +@Slf4j +public class IamAuthToken implements RememberMeAuthenticationToken { + private static final long serialVersionUID = -5518501153334708409L; + + /** + * 用户类型的Class + */ + private Class userTypeClass = IamUser.class; + /** + * 认证类型,密码/微信... + */ + private String authType; + /** + * 认证账号 + */ + private String authAccount; + /** + * 认证凭证 + */ + private String authSecret; + /** + * 记住我 + */ + private boolean rememberMe; + + /** + * 扩展属性 + */ + private Map extObj; + + /**authz token*/ + private String authtoken; + + /** + * 租户id + */ + private Long tenantId = 0L; + + /** + * 是否校验密码 + */ + private boolean validPassword = true; + + private Object principal; + + private Object credentials; + + private int expiresInMinutes; + + public IamAuthToken(){ + } + + /*** + * 初始化认证token + * @param authType 认证方式 + * @param userTypeClass 用户类型Class + */ + public IamAuthToken(String authType, Class userTypeClass){ + this.authType = authType; + this.userTypeClass = userTypeClass; + } + + public IamAuthToken(String userInfoStr){ + String[] fields = S.split(userInfoStr, Cons.SEPARATOR_COMMA); + this.tenantId = Long.parseLong(fields[0]); + this.authAccount = fields[1]; + if(IamUser.class.getSimpleName().equals(fields[2]) != true){ + try { + this.userTypeClass = Class.forName(fields[2]); + } + catch (ClassNotFoundException e) { + log.debug("Token验证失败!用户类型{}不存在", fields[2]); + } + } + this.authType = fields[3]; + this.expiresInMinutes = Integer.parseInt(fields[4]); + } + + /*** + * 验证失败的时候清空token + */ + public void clearAuthtoken(){ + this.authtoken = null; + } + + /** + * 设置 + * @return + */ + @Override + public Object getPrincipal() { + return this.authtoken; + } + + @Override + public Object getCredentials() { + return this.authtoken; + } + + /** + * 获取用户类型 + * @return + */ + public String getUserType(){ + return userTypeClass.getSimpleName(); + } + + /** + * 生成token UUID + * @return + */ + public IamAuthToken generateAuthtoken(){ + this.authtoken = TokenUtils.generateToken(); + return this; + } + + @Override + public boolean isRememberMe() { + return rememberMe; + } + + /** + * 构建唯一串用于缓存 + * @return + */ + public String buildUserInfoStr(){ + return S.joinWith(Cons.SEPARATOR_COMMA, + this.getTenantId(), this.getAuthAccount(), this.getUserTypeClass().getName(), this.getAuthType(), this.expiresInMinutes, System.currentTimeMillis()); + } +} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthorizingRealm.java similarity index 67% rename from diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java rename to diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthorizingRealm.java index 56e28c20193b515f427265a52b32eba4c65312ab..3f5a984b7c49d6e45ecb9abbbe626cdc670b9f33 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/IamAuthorizingRealm.java @@ -13,16 +13,16 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.diboot.iam.jwt; +package com.diboot.iam.shiro; import com.diboot.core.service.BaseService; import com.diboot.core.util.ContextHelper; +import com.diboot.core.util.S; import com.diboot.core.util.V; import com.diboot.core.vo.LabelValue; import com.diboot.iam.auth.AuthService; import com.diboot.iam.auth.AuthServiceFactory; import com.diboot.iam.auth.IamExtensible; -import com.diboot.iam.cache.IamCacheManager; import com.diboot.iam.config.Cons; import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; @@ -30,6 +30,7 @@ import com.diboot.iam.entity.IamRole; import com.diboot.iam.service.IamRoleResourceService; import com.diboot.iam.service.IamUserRoleService; import com.diboot.iam.util.IamSecurityUtils; +import com.diboot.iam.vo.PositionDataScope; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; @@ -46,24 +47,26 @@ import java.util.List; import java.util.Set; /** - * @author Yangzhao - * @version v2.0 - * @date 2019/6/6 + * IAM realm定义 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/26 + * Copyright © diboot.com */ @Slf4j -public class BaseJwtRealm extends AuthorizingRealm { +public class IamAuthorizingRealm extends AuthorizingRealm { private IamUserRoleService iamUserRoleService; private IamRoleResourceService iamRoleResourceService; @Override public boolean supports(AuthenticationToken token) { - return token != null && token instanceof BaseJwtAuthToken; + return token != null && token instanceof IamAuthToken; } @Override public Class getAuthenticationTokenClass() { - return BaseJwtRealm.class; + return IamAuthorizingRealm.class; } /*** @@ -74,47 +77,48 @@ public class BaseJwtRealm extends AuthorizingRealm { */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - BaseJwtAuthToken jwtToken = (BaseJwtAuthToken) token; - String authAccount = (String) jwtToken.getPrincipal(); + IamAuthToken iamAuthToken = (IamAuthToken) token; + String authAccount = (String) iamAuthToken.getPrincipal(); if (V.isEmpty(authAccount)){ throw new AuthenticationException("无效的用户标识"); } else { // 获取认证方式 - AuthService authService = AuthServiceFactory.getAuthService(jwtToken.getAuthType()); + AuthService authService = AuthServiceFactory.getAuthService(iamAuthToken.getAuthType()); if(authService == null){ - jwtToken.clearAuthtoken(); - throw new AuthenticationException("认证类型: "+jwtToken.getAuthType()+" 的AccountAuthService未实现!"); + iamAuthToken.clearAuthtoken(); + throw new AuthenticationException("认证类型: "+iamAuthToken.getAuthType()+" 的AccountAuthService未实现!"); } - IamAccount account = authService.getAccount(jwtToken); + IamAccount account = authService.getAccount(iamAuthToken); // 登录失败则抛出相关异常 if (account == null){ - jwtToken.clearAuthtoken(); + iamAuthToken.clearAuthtoken(); throw new AuthenticationException("用户账号或密码错误!"); } // 获取当前user对象并缓存 BaseLoginUser loginUser = null; - BaseService userService = ContextHelper.getBaseServiceByEntity(jwtToken.getUserTypeClass()); + BaseService userService = ContextHelper.getBaseServiceByEntity(iamAuthToken.getUserTypeClass()); if(userService != null){ loginUser = (BaseLoginUser)userService.getEntity(account.getUserId()); } else{ - throw new AuthenticationException("用户 "+jwtToken.getUserTypeClass().getName()+" 相关的Service未定义!"); + throw new AuthenticationException("用户 "+iamAuthToken.getUserTypeClass().getName()+" 相关的Service未定义!"); } if(loginUser == null){ throw new AuthenticationException("用户不存在"); } - loginUser.setAuthToken(jwtToken.getAuthtoken()); + loginUser.setAuthToken(iamAuthToken.getAuthtoken()); IamExtensible iamExtensible = getIamUserRoleService().getIamExtensible(); if(iamExtensible != null){ - LabelValue extentionObj = iamExtensible.getUserExtentionObj(jwtToken.getUserTypeClass().getSimpleName(), account.getUserId(), jwtToken.getExtObj()); - if(extentionObj != null){ - loginUser.setExtentionObj(extentionObj); + LabelValue extensionObj = iamExtensible.getUserExtensionObj(iamAuthToken.getUserTypeClass().getSimpleName(), account.getUserId(), iamAuthToken.getExtObj()); + if(extensionObj != null){ + loginUser.setExtensionObj(extensionObj); } } // 清空当前用户缓存 this.clearCachedAuthorizationInfo(IamSecurityUtils.getSubject().getPrincipals()); - return new SimpleAuthenticationInfo(loginUser, jwtToken.getCredentials(), this.getName()); + log.debug("获取用户认证信息完成 : {}", iamAuthToken.getCredentials()); + return new SimpleAuthenticationInfo(loginUser, iamAuthToken.getCredentials(), this.getName()); } } @@ -128,13 +132,15 @@ public class BaseJwtRealm extends AuthorizingRealm { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); BaseLoginUser currentUser = (BaseLoginUser) principals.getPrimaryPrincipal(); // 根据用户类型与用户id获取roleList - Long extentionObjId = null; - LabelValue extentionObj = currentUser.getExtentionObj(); - if(extentionObj != null){ - extentionObjId = (Long)extentionObj.getValue(); + Long extensionObjId = null; + LabelValue extensionObj = currentUser.getExtensionObj(); + if(extensionObj != null){ + if(extensionObj.getExt() != null && extensionObj.getExt() instanceof PositionDataScope){ + extensionObjId = ((PositionDataScope)extensionObj.getExt()).getPositionId(); + } } // 获取角色列表 - List roleList = getIamUserRoleService().getUserRoleList(currentUser.getClass().getSimpleName(), currentUser.getId(), extentionObjId); + List roleList = getIamUserRoleService().getUserRoleList(currentUser.getClass().getSimpleName(), currentUser.getId(), extensionObjId); // 如果没有任何角色,返回 if (V.isEmpty(roleList)){ return authorizationInfo; @@ -147,22 +153,25 @@ public class BaseJwtRealm extends AuthorizingRealm { allRoleCodes.add(role.getCode()); roleIds.add(role.getId()); }); + authorizationInfo.setRoles(allRoleCodes); // 整理所有权限许可列表,从缓存匹配 - Set allPermissionCodes = new HashSet<>(); - List apiUrlList = getIamRoleResourceService().getApiUrlList(Cons.APPLICATION, roleIds); - if(V.notEmpty(apiUrlList)){ - apiUrlList.stream().forEach(set->{ - for(String uri : set.split(Cons.SEPARATOR_COMMA)){ - String permissionCode = IamCacheManager.getPermissionCode(uri); - if(permissionCode != null){ - allPermissionCodes.add(permissionCode); + List allPermissionCodes = getIamRoleResourceService().getPermissionCodeList(Cons.APPLICATION, roleIds); + Set permissionCodesSet = new HashSet<>(); + if(V.notEmpty(allPermissionCodes)){ + allPermissionCodes.forEach(permCodeStr -> { + if(!permCodeStr.contains(Cons.SEPARATOR_COMMA)){ + permissionCodesSet.add(permCodeStr); + } + else{ + for(String permCode : S.split(permCodeStr)){ + permissionCodesSet.add(permCode); } } }); } // 将所有角色和权限许可授权给用户 - authorizationInfo.setRoles(allRoleCodes); - authorizationInfo.setStringPermissions(allPermissionCodes); + authorizationInfo.setStringPermissions(permissionCodesSet); + log.debug("获取用户授权信息完成 : {}", currentUser.getDisplayName()); return authorizationInfo; } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/StatelessJwtAuthFilter.java b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessAccessControlFilter.java similarity index 51% rename from diboot-iam-starter/src/main/java/com/diboot/iam/jwt/StatelessJwtAuthFilter.java rename to diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessAccessControlFilter.java index a9130b118898323d5963dbc362faa26e676dddd9..19040279b1e197bc21663881d140a6dc53962d8c 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/jwt/StatelessJwtAuthFilter.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessAccessControlFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -13,20 +13,15 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.diboot.iam.jwt; +package com.diboot.iam.shiro; import com.diboot.core.util.JSON; -import com.diboot.core.util.S; import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Status; import com.diboot.iam.config.Cons; -import com.diboot.iam.entity.IamUser; import com.diboot.iam.util.IamSecurityUtils; -import com.diboot.iam.util.JwtUtils; -import io.jsonwebtoken.Claims; -import lombok.Getter; -import lombok.Setter; +import com.diboot.iam.util.TokenUtils; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.springframework.http.HttpStatus; @@ -40,15 +35,14 @@ import java.io.IOException; import java.io.PrintWriter; /** - * 无状态JWT过滤器 - * - * @author : uu - * @version : v1.0 - * @Date 2020/11/19 10:46 + * 无状态的访问控制过滤器 + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/26 + * Copyright © diboot.com */ @Slf4j -@Getter @Setter -public class StatelessJwtAuthFilter extends BasicHttpAuthenticationFilter { +public class StatelessAccessControlFilter extends BasicHttpAuthenticationFilter { /** * 判断是否登录 @@ -63,47 +57,23 @@ public class StatelessJwtAuthFilter extends BasicHttpAuthenticationFilter { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest httpRequest = (HttpServletRequest) request; // 从header获取Token - Claims claims = JwtUtils.getClaimsFromRequest(httpRequest); - if (V.isEmpty(claims)) { + String currentToken = TokenUtils.getRequestToken(httpRequest); + if (V.isEmpty(currentToken)) { + log.debug("token: {} 验证失败, uri={}", currentToken, httpRequest.getRequestURI()); return false; } - String currentToken = JwtUtils.getRequestToken(httpRequest); - //解密是否成功 - if(V.notEmpty(claims.getSubject())){ - String subject = S.substringBeforeLast(claims.getSubject(), Cons.SEPARATOR_COMMA); - log.debug("Token验证成功!subject={}", subject); - // 如果临近过期,则生成新的token返回 - String refreshToken = JwtUtils.generateNewTokenIfRequired(claims); - if(refreshToken != null){ - currentToken = refreshToken; - // 写入response header中 - JwtUtils.addTokenToResponseHeader((HttpServletResponse) response, refreshToken); - } - // 构建登陆的token - if(IamSecurityUtils.getSubject().isAuthenticated() == false || refreshToken != null){ - // tenantId,account,userTypeClass,authType,60 - String[] subjectDetail = subject.split(Cons.SEPARATOR_COMMA); - BaseJwtAuthToken baseJwtAuthToken = new BaseJwtAuthToken(); - baseJwtAuthToken.setTenantId(Long.valueOf(subjectDetail[0])); - baseJwtAuthToken.setAuthAccount(subjectDetail[1]); - if(!IamUser.class.getName().equals(subjectDetail[2])){ - try { - baseJwtAuthToken.setUserTypeClass(Class.forName(subjectDetail[2])); - } catch (ClassNotFoundException e) { - log.debug("Token验证失败!用户类型{}不存在,url={}", subjectDetail[2], httpRequest.getRequestURL()); - return false; - } - } - baseJwtAuthToken.setAuthType(subjectDetail[3]); - baseJwtAuthToken.setAuthtoken(currentToken); - baseJwtAuthToken.setValidPassword(false); - IamSecurityUtils.getSubject().login(baseJwtAuthToken); - log.debug("无状态token自动登录完成"); - } - return true; + log.debug("token: {} 验证通过", currentToken); + String cachedUserInfo = TokenUtils.getCachedUserInfoStr(currentToken); + if(IamSecurityUtils.getSubject().isAuthenticated() == false && cachedUserInfo != null){ + IamAuthToken authToken = new IamAuthToken(cachedUserInfo); + authToken.setAuthtoken(currentToken); + authToken.setValidPassword(false); + IamSecurityUtils.getSubject().login(authToken); + log.debug("token: {} 保活完成, uri={}", currentToken, httpRequest.getRequestURI()); } - log.debug("Token验证失败!url=" + httpRequest.getRequestURL()); - return false; + // 如果临近过期,则生成新的token返回 + TokenUtils.responseNewTokenIfRequired(response, cachedUserInfo); + return true; } /** @@ -139,4 +109,5 @@ public class StatelessJwtAuthFilter extends BasicHttpAuthenticationFilter { log.error("处理异步请求异常", e); } } + } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessSubjectFactory.java b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessSubjectFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..3aa2a13d2aafc055e752d9a45b8cc8a42828cff0 --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/shiro/StatelessSubjectFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.shiro; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.SubjectContext; +import org.apache.shiro.web.mgt.DefaultWebSubjectFactory; + +/** + * 无状态的SubjectFactory + * @author JerryMa + * @version v2.6.0 + * @date 2022/4/26 + * Copyright © diboot.com + */ +@Slf4j +public class StatelessSubjectFactory extends DefaultWebSubjectFactory { + public Subject createSubject(SubjectContext context) { + //不创建session + context.setSessionCreationEnabled(false); + return super.createSubject(context); + } +} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamAutoConfig.java b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamAutoConfig.java index c5420763e9592790a62f0e7bef90efec96745570..28e48f13ffaf014df3468235f8c74af05217cc79 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamAutoConfig.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamAutoConfig.java @@ -15,26 +15,28 @@ */ package com.diboot.iam.starter; +import com.diboot.core.cache.BaseCacheManager; +import com.diboot.core.cache.DynamicMemoryCacheManager; +import com.diboot.core.data.access.DataAccessInterface; import com.diboot.core.util.V; -import com.diboot.iam.jwt.BaseJwtRealm; -import com.diboot.iam.jwt.DefaultJwtAuthFilter; -import com.diboot.iam.jwt.StatelessJwtAuthFilter; +import com.diboot.iam.config.Cons; +import com.diboot.iam.data.DataAccessPermissionUserOrgImpl; +import com.diboot.iam.shiro.IamAuthorizingRealm; +import com.diboot.iam.shiro.StatelessAccessControlFilter; +import com.diboot.iam.shiro.StatelessSubjectFactory; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.MemoryConstrainedCacheManager; -import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.mgt.SessionsSecurityManager; +import org.apache.shiro.mgt.*; import org.apache.shiro.realm.Realm; +import org.apache.shiro.session.mgt.DefaultSessionManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.subject.SubjectContext; -import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; -import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator; import org.apache.shiro.web.mgt.DefaultWebSubjectFactory; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; @@ -82,7 +84,7 @@ public class IamAutoConfig { @ConditionalOnMissingBean @DependsOn({"shiroCacheManager"}) public Realm realm() { - BaseJwtRealm realm = new BaseJwtRealm(); + IamAuthorizingRealm realm = new IamAuthorizingRealm(); CacheManager cacheManager = shiroCacheManager(); if (cacheManager != null) { realm.setCachingEnabled(true); @@ -99,13 +101,40 @@ public class IamAutoConfig { */ @Bean(name = "shiroSecurityManager") @ConditionalOnMissingBean - public SessionsSecurityManager shiroSecurityManager() { + public DefaultWebSecurityManager shiroSecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setSubjectFactory(subjectFactory()); + securityManager.setSessionManager(sessionManager()); securityManager.setRealm(realm()); securityManager.setCacheManager(shiroCacheManager()); + // subject禁止存储到session + ((DefaultSubjectDAO) securityManager.getSubjectDAO()).setSessionStorageEvaluator(sessionStorageEvaluator()); return securityManager; } + @Bean + @ConditionalOnMissingBean + protected SessionStorageEvaluator sessionStorageEvaluator() { + DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + sessionStorageEvaluator.setSessionStorageEnabled(false); + return sessionStorageEvaluator; + } + + @Bean + @ConditionalOnMissingBean + public DefaultWebSubjectFactory subjectFactory(){ + StatelessSubjectFactory subjectFactory = new StatelessSubjectFactory(); + return subjectFactory; + } + + @Bean + @ConditionalOnMissingBean + public DefaultSessionManager sessionManager(){ + DefaultSessionManager sessionManager = new DefaultSessionManager(); + sessionManager.setSessionValidationSchedulerEnabled(false); + return sessionManager; + } + /** * 配置ShiroFilter * @@ -113,11 +142,8 @@ public class IamAutoConfig { */ @Bean @ConditionalOnMissingBean - public BasicHttpAuthenticationFilter shiroFilter() { - if (iamProperties.isEnableStatelessSession()) { - return new StatelessJwtAuthFilter(); - } - return new DefaultJwtAuthFilter(); + public AccessControlFilter shiroFilter() { + return new StatelessAccessControlFilter(); } @Bean @@ -135,20 +161,6 @@ public class IamAutoConfig { // 设置过滤器 Map filters = new LinkedHashMap<>(); filters.put("jwt", shiroFilter()); - - // 设置无状态session - if (securityManager instanceof DefaultWebSecurityManager && - V.equals(shiroFilter().getClass().getTypeName(), StatelessJwtAuthFilter.class.getTypeName())) { - DefaultWebSecurityManager defaultWebSecurityManager = ((DefaultWebSecurityManager) securityManager); - // 设置不创建session - defaultWebSecurityManager.setSubjectFactory(new StatelessDefaultSubjectFactory()); - // subject禁止存储到session - //详情见org.apache.shiro.mgt.DefaultSubjectDAO#save - DefaultWebSessionStorageEvaluator webEvalutator = new DefaultWebSessionStorageEvaluator(); - webEvalutator.setSessionStorageEnabled(false); - ((DefaultSubjectDAO) defaultWebSecurityManager.getSubjectDAO()) - .setSessionStorageEvaluator(webEvalutator); - } shiroFilterFactoryBean.setFilters(filters); //Shiro securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); @@ -190,20 +202,26 @@ public class IamAutoConfig { } /** - * 禁用session - * - * @author : uu - * @version : v1.0 - * @date 2020/11/19 11:06 + * 用户token缓存管理器 + * @return */ - static class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory { + @Bean + @ConditionalOnMissingBean + public BaseCacheManager baseCacheManager(){ + log.info("初始化IAM本地缓存: DynamicMemoryCacheManager"); + return new DynamicMemoryCacheManager(iamProperties.getTokenExpiresMinutes(), + Cons.CACHE_TOKEN_USERINFO, + Cons.CACHE_CAPTCHA); + } - @Override - public Subject createSubject(SubjectContext context) { - //不创建session - context.setSessionCreationEnabled(false); - return super.createSubject(context); - } + /** + * 数据访问控制实现,默认基于用户和部门过滤 + * @return + */ + @Bean + @ConditionalOnMissingBean + public DataAccessInterface dataAccessInterface(){ + return new DataAccessPermissionUserOrgImpl(); } } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamPluginInitializer.java b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamPluginInitializer.java index 64b2a0c001fefecc353e6a0aabbdaf81fa661195..78d21bfdd233ddc7c39e022ad17609591960a364 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamPluginInitializer.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamPluginInitializer.java @@ -99,8 +99,8 @@ public class IamPluginInitializer implements ApplicationRunner { IamResourcePermissionService resourcePermissionService = ContextHelper.getBean(IamResourcePermissionService.class); if(resourcePermissionService != null && !resourcePermissionService.exists(IamResourcePermission::getResourceCode, "system")){ String[] RESOURCE_PERMISSION_DATA = { - "{\"displayType\":\"MENU\",\"displayName\":\"系统管理\",\"resourceCode\":\"system\",\"children\":[{\"displayType\":\"MENU\",\"displayName\":\"数据字典管理\",\"resourceCode\":\"Dictionary\",\"apiSet\":\"GET:/dictionary/list\",\"sortId\":\"10030\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/dictionary/{id}\",\"sortId\":\"6\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/dictionary/\",\"sortId\":\"5\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/dictionary/{id},GET:/dictionary/{id}\",\"sortId\":\"4\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/dictionary/{id}\",\"sortId\":\"3\"}]},{\"displayType\":\"MENU\",\"displayName\":\"系统用户管理\",\"resourceCode\":\"IamUser\",\"apiSet\":\"GET:/iam/user/list\",\"sortId\":\"10029\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"部门查看\",\"resourceCode\":\"orgTree\",\"apiSet\":\"GET:/iam/org/tree\",\"sortId\":\"12\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/user/{id}\",\"sortId\":\"11\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/user/\",\"sortId\":\"10\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/user/{id},GET:/iam/user/{id}\",\"sortId\":\"9\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/user/{id}\",\"sortId\":\"8\"}]},{\"displayType\":\"MENU\",\"displayName\":\"角色资源管理\",\"resourceCode\":\"IamRole\",\"apiSet\":\"GET:/iam/role/list\",\"sortId\":\"10023\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/role/{id}\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/role/\",\"sortId\":\"15\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/role/{id},GET:/iam/role/{id},GET:/iam/resourcePermission/list\",\"sortId\":\"14\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/role/{id}\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"资源权限管理\",\"resourceCode\":\"IamResourcePermission\",\"apiSet\":\"GET:/iam/resourcePermission/list\",\"sortId\":\"10017\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/resourcePermission/{id}\",\"sortId\":\"23\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/resourcePermission/\",\"sortId\":\"21\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/resourcePermission/{id},GET:/iam/resourcePermission/{id}\",\"sortId\":\"20\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/resourcePermission/{id}\",\"sortId\":\"19\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"排序\",\"resourceCode\":\"sort\",\"apiSet\":\"POST:/iam/resourcePermission/sortList\",\"sortId\":\"18\"}]},{\"displayType\":\"MENU\",\"displayName\":\"定时任务管理\",\"resourceCode\":\"ScheduleJob\",\"apiSet\":\"GET:/scheduleJob/list\",\"sortId\":\"10012\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/scheduleJob/{id}\",\"sortId\":\"7\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/scheduleJob/{id}/{action},GET:/scheduleJob/{id}\",\"sortId\":\"6\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/scheduleJob/\",\"sortId\":\"5\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/scheduleJob/{id},GET:/scheduleJob/log/list,GET:/scheduleJob/log/{id}\",\"sortId\":\"4\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"运行一次\",\"resourceCode\":\"executeOnce\",\"apiSet\":\"PUT:/scheduleJob/executeOnce/{id}\",\"sortId\":\"3\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"日志记录\",\"resourceCode\":\"logList\",\"apiSet\":\"GET:/scheduleJob/log/list,GET:/scheduleJob/log/{id}\",\"sortId\":\"2\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"日志删除\",\"resourceCode\":\"logDelete\",\"apiSet\":\"DELETE:/scheduleJob/log/{id}\",\"sortId\":\"1\"}]},{\"displayType\":\"MENU\",\"displayName\":\"消息模板管理\",\"resourceCode\":\"MessageTemplate\",\"apiSet\":\"GET:/messageTemplate/list\",\"sortId\":\"10010\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/messageTemplate/{id}\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/messageTemplate/\",\"sortId\":\"15\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/messageTemplate/{id},GET:/messageTemplate/{id}\",\"sortId\":\"14\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/messageTemplate/{id}\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"消息记录管理\",\"resourceCode\":\"Message\",\"apiSet\":\"GET:/message/list\",\"sortId\":\"10009\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/message/{id}\",\"sortId\":\"16\"}]},{\"displayType\":\"MENU\",\"displayName\":\"上传文件管理\",\"resourceCode\":\"UploadFile\",\"apiSet\":\"GET:/uploadFile/list\",\"sortId\":\"10008\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/uploadFile/{id}\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/uploadFile/{id},GET:/uploadFile/{id}\",\"sortId\":\"14\"}]},{\"displayType\":\"MENU\",\"displayName\":\"系统配置管理\",\"resourceCode\":\"SystemConfig\",\"apiSet\":\"GET:/systemConfig/typeList,GET:/systemConfig/{type}\",\"sortId\":\"10007\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"POST:/systemConfig,DELETE:/systemConfig/{type},DELETE:/systemConfig/{type}/{prop},POST:/systemConfig/{type}\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"操作日志查看\",\"resourceCode\":\"IamOperationLog\",\"apiSet\":\"GET:/iam/operationLog/list\",\"sortId\":\"10006\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/operationLog/{id}\",\"sortId\":\"16\"}]},{\"displayType\":\"MENU\",\"displayName\":\"登录日志查看\",\"resourceCode\":\"IamLoginTrace\",\"apiSet\":\"GET:/iam/loginTrace/list\",\"sortId\":\"10001\",\"children\":[]}]}", - "{\"displayType\":\"MENU\",\"displayName\":\"组织机构\",\"resourceCode\":\"orgStructure\",\"children\":[{\"displayType\":\"MENU\",\"displayName\":\"组织机构管理\",\"resourceCode\":\"IamOrg\",\"apiSet\":\"POST:/iam/org/sortList,GET:/iam/org/tree,GET:/iam/org/tree/{parentNodeId},GET:/iam/org/list\",\"sortId\":\"10044\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"排序\",\"resourceCode\":\"sort\",\"apiSet\":\"POST:/iam/org/sortList\",\"sortId\":\"106\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/org/{id}\",\"sortId\":\"105\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/org/{id},GET:/iam/org/{id}\",\"sortId\":\"104\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/org/\",\"sortId\":\"103\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/org/{id}\",\"sortId\":\"102\"}]},{\"displayType\":\"MENU\",\"displayName\":\"岗位管理\",\"resourceCode\":\"IamPosition\",\"apiSet\":\"GET:/iam/position/list\",\"sortId\":\"10038\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/position/{id}\",\"sortId\":\"112\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/position/{id}\",\"sortId\":\"111\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/position/{id},GET:/iam/position/{id}\",\"sortId\":\"110\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/position/\",\"sortId\":\"108\"}]},{\"displayType\":\"MENU\",\"displayName\":\"组织人员管理\",\"resourceCode\":\"IamOrgUser\",\"apiSet\":\"GET:/iam/org/tree,GET:/iam/user/list\",\"sortId\":\"10032\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"apiSet\":\"POST:/iam/user/\",\"sortId\":\"40\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"apiSet\":\"PUT:/iam/user/{id},GET:/iam/user/{id}\",\"sortId\":\"39\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"apiSet\":\"DELETE:/iam/user/{id}\",\"sortId\":\"38\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"apiSet\":\"GET:/iam/user/{id}\",\"sortId\":\"37\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"导入\",\"resourceCode\":\"import\",\"apiSet\":\"POST:/iam/user/excel/previewSave,POST:/iam/user/excel/upload,POST:/iam/user/excel/preview,GET:/iam/user/excel/downloadExample\",\"sortId\":\"36\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"导出\",\"resourceCode\":\"export\",\"apiSet\":\"GET:/iam/user/excel/export\",\"sortId\":\"35\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"人员岗位设置\",\"resourceCode\":\"position\",\"apiSet\":\"POST:/iam/position/batchUpdateUserPositionRelations,GET:/iam/position/listUserPositions/{userType}/{userId}\",\"sortId\":\"34\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"添加岗位\",\"resourceCode\":\"addPosition\",\"apiSet\":\"POST:/iam/position/\",\"sortId\":\"33\"}]}]}" + "{\"displayType\":\"MENU\",\"displayName\":\"系统管理\",\"resourceCode\":\"system\",\"children\":[{\"displayType\":\"MENU\",\"displayName\":\"数据字典管理\",\"resourceCode\":\"Dictionary\",\"permissionCode\":\"Dictionary:read\",\"sortId\":\"10030\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"Dictionary:read\",\"sortId\":\"6\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"Dictionary:write\",\"sortId\":\"5\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"Dictionary:write,Dictionary:read\",\"sortId\":\"4\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"Dictionary:write\",\"sortId\":\"3\"}]},{\"displayType\":\"MENU\",\"displayName\":\"系统用户管理\",\"resourceCode\":\"IamUser\",\"permissionCode\":\"IamUser:read\",\"sortId\":\"10029\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"部门查看\",\"resourceCode\":\"orgTree\",\"permissionCode\":\"IamUser:read\",\"sortId\":\"12\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamUser:read\",\"sortId\":\"11\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamUser:write\",\"sortId\":\"10\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamUser:write,IamUser:read\",\"sortId\":\"9\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamUser:write\",\"sortId\":\"8\"}]},{\"displayType\":\"MENU\",\"displayName\":\"角色资源管理\",\"resourceCode\":\"IamRole\",\"permissionCode\":\"IamRole:read\",\"sortId\":\"10023\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamRole:read\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamRole:write\",\"sortId\":\"15\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamRole:write,IamRole:read,IamResourcePermission:read\",\"sortId\":\"14\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamRole:write\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"资源权限管理\",\"resourceCode\":\"IamResourcePermission\",\"permissionCode\":\"IamResourcePermission:read\",\"sortId\":\"10017\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamResourcePermission:read\",\"sortId\":\"23\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamResourcePermission:write\",\"sortId\":\"21\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamResourcePermission:write,IamResourcePermission:read\",\"sortId\":\"20\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamResourcePermission:write\",\"sortId\":\"19\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"排序\",\"resourceCode\":\"sort\",\"permissionCode\":\"IamResourcePermission:write\",\"sortId\":\"18\"}]},{\"displayType\":\"MENU\",\"displayName\":\"定时任务管理\",\"resourceCode\":\"ScheduleJob\",\"permissionCode\":\"ScheduleJob:read\",\"sortId\":\"10012\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"ScheduleJob:write\",\"sortId\":\"7\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"ScheduleJob:write,ScheduleJob:read\",\"sortId\":\"6\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"ScheduleJob:write\",\"sortId\":\"5\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"ScheduleJob:read\",\"sortId\":\"4\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"运行一次\",\"resourceCode\":\"executeOnce\",\"permissionCode\":\"ScheduleJob:write\",\"sortId\":\"3\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"日志记录\",\"resourceCode\":\"logList\",\"permissionCode\":\"ScheduleJob:read\",\"sortId\":\"2\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"日志删除\",\"resourceCode\":\"logDelete\",\"permissionCode\":\"ScheduleJob:write\",\"sortId\":\"1\"}]},{\"displayType\":\"MENU\",\"displayName\":\"消息模板管理\",\"resourceCode\":\"MessageTemplate\",\"permissionCode\":\"MessageTemplate:read\",\"sortId\":\"10010\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"MessageTemplate:read\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"MessageTemplate:write\",\"sortId\":\"15\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"MessageTemplate:write,MessageTemplate:read\",\"sortId\":\"14\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"MessageTemplate:write\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"消息记录管理\",\"resourceCode\":\"Message\",\"permissionCode\":\"Message:read\",\"sortId\":\"10009\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"Message:read\",\"sortId\":\"16\"}]},{\"displayType\":\"MENU\",\"displayName\":\"上传文件管理\",\"resourceCode\":\"UploadFile\",\"permissionCode\":\"UploadFile:read\",\"sortId\":\"10008\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"UploadFile:read\",\"sortId\":\"16\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"UploadFile:write,UploadFile:read\",\"sortId\":\"14\"}]},{\"displayType\":\"MENU\",\"displayName\":\"系统配置管理\",\"resourceCode\":\"SystemConfig\",\"permissionCode\":\"SystemConfig:read\",\"sortId\":\"10007\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"SystemConfig:write\",\"sortId\":\"13\"}]},{\"displayType\":\"MENU\",\"displayName\":\"操作日志查看\",\"resourceCode\":\"IamOperationLog\",\"permissionCode\":\"IamOperationLog:read\",\"sortId\":\"10006\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamOperationLog:read\",\"sortId\":\"16\"}]},{\"displayType\":\"MENU\",\"displayName\":\"登录日志查看\",\"resourceCode\":\"IamLoginTrace\",\"permissionCode\":\"IamLoginTrace:read\",\"sortId\":\"10001\",\"children\":[]}]}", + "{\"displayType\":\"MENU\",\"displayName\":\"组织机构\",\"resourceCode\":\"orgStructure\",\"children\":[{\"displayType\":\"MENU\",\"displayName\":\"组织机构管理\",\"resourceCode\":\"IamOrg\",\"permissionCode\":\"IamOrg:read\",\"sortId\":\"10044\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"排序\",\"resourceCode\":\"sort\",\"permissionCode\":\"IamOrg:write\",\"sortId\":\"106\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamOrg:write\",\"sortId\":\"105\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamOrg:write,IamOrg:read\",\"sortId\":\"104\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamOrg:write\",\"sortId\":\"103\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamOrg:read\",\"sortId\":\"102\"}]},{\"displayType\":\"MENU\",\"displayName\":\"岗位管理\",\"resourceCode\":\"IamPosition\",\"permissionCode\":\"IamPosition:read\",\"sortId\":\"10038\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamPosition:write\",\"sortId\":\"112\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamPosition:read\",\"sortId\":\"111\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamPosition:write,IamPosition:read\",\"sortId\":\"110\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamPosition:write\",\"sortId\":\"108\"}]},{\"displayType\":\"MENU\",\"displayName\":\"组织人员管理\",\"resourceCode\":\"IamOrgUser\",\"permissionCode\":\"IamOrg:read,IamUser:read\",\"sortId\":\"10032\",\"children\":[{\"displayType\":\"PERMISSION\",\"displayName\":\"新建\",\"resourceCode\":\"create\",\"permissionCode\":\"IamUser:write\",\"sortId\":\"40\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"更新\",\"resourceCode\":\"update\",\"permissionCode\":\"IamUser:write,IamUser:read\",\"sortId\":\"39\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"删除\",\"resourceCode\":\"delete\",\"permissionCode\":\"IamUser:write\",\"sortId\":\"38\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"详情\",\"resourceCode\":\"detail\",\"permissionCode\":\"IamUser:read\",\"sortId\":\"37\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"导入\",\"resourceCode\":\"import\",\"permissionCode\":\"IamUserExcel:import\",\"sortId\":\"36\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"导出\",\"resourceCode\":\"export\",\"permissionCode\":\"IamUserExcel:export\",\"sortId\":\"35\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"人员岗位设置\",\"resourceCode\":\"position\",\"permissionCode\":\"IamPosition:write,IamPosition:read\",\"sortId\":\"34\"},{\"displayType\":\"PERMISSION\",\"displayName\":\"添加岗位\",\"resourceCode\":\"addPosition\",\"permissionCode\":\"IamPosition:write,IamPosition:read\",\"sortId\":\"33\"}]}]}" }; // 插入多层级资源权限初始数据 try { diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamProperties.java b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamProperties.java index bd522c2d10167e24a8496aa1ce3d5ac008415c06..d24dc50b4efa5c720cef7fe0243894ca70508322 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamProperties.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamProperties.java @@ -40,17 +40,18 @@ public class IamProperties { /** * jwt header key */ - private String jwtHeaderKey = "authtoken"; + private String tokenHeaderKey = "authtoken"; /** - * jwt 签名key + * jwt token过期分钟数 */ - private String jwtSignkey = "Diboot"; + @Deprecated + private int jwtTokenExpiresMinutes = 60; /** - * jwt token过期分钟数 + * token过期分钟数 */ - private int jwtTokenExpiresMinutes = 60; + private int tokenExpiresMinutes = 60; /** * 匿名的url,以,逗号分隔 @@ -64,10 +65,6 @@ public class IamProperties { * 是否开启权限检查(开发环境可关闭方便调试) */ private boolean enablePermissionCheck = true; - /** - * 是否开启无状态 Jwt 身份验证过滤器 - */ - private boolean enableStatelessSession = false; /** * oauth2 客户端配置 */ diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamRedisAutoConfig.java b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamRedisAutoConfig.java index 24b65feedc45902f570be55bbaad976120329589..2351fb0b399846c0c84e2755110d753a9a388752 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamRedisAutoConfig.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/starter/IamRedisAutoConfig.java @@ -15,7 +15,11 @@ */ package com.diboot.iam.starter; +import com.diboot.core.cache.BaseCacheManager; +import com.diboot.core.cache.DynamicRedisCacheManager; +import com.diboot.iam.config.Cons; import com.diboot.iam.redis.ShiroRedisCacheManager; +import lombok.extern.slf4j.Slf4j; import org.apache.shiro.cache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -26,8 +30,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; /** * Shiro の Redis 缓存自动配置 @@ -37,6 +48,7 @@ import org.springframework.data.redis.core.RedisTemplate; * @date 2021/7/20 * Copyright © diboot.com */ +@Slf4j @Order(921) @Configuration @ConditionalOnBean(RedisTemplate.class) @@ -47,6 +59,10 @@ public class IamRedisAutoConfig { @Autowired private IamProperties iamProperties; + @Autowired + private RedisTemplate redisTemplate; + + /** * 启用RedisCacheManager定义 * @return @@ -54,7 +70,38 @@ public class IamRedisAutoConfig { @Bean(name = "shiroCacheManager") @ConditionalOnMissingBean(CacheManager.class) public CacheManager shiroCacheManager(RedisTemplate redisTemplate) { + log.info("初始化shiro缓存: ShiroRedisCacheManager"); return new ShiroRedisCacheManager(redisTemplate, iamProperties.getJwtTokenExpiresMinutes()); } + /** + * 验证码的缓存管理 + * @return + */ + @Bean + @ConditionalOnMissingBean + public BaseCacheManager baseCacheManager(){ + // redis配置参数 + RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration + .defaultCacheConfig() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())) + .entryTtl(Duration.ofMinutes(iamProperties.getTokenExpiresMinutes())); + Set cacheNames = new HashSet(){{ + add(Cons.CACHE_TOKEN_USERINFO); + add(Cons.CACHE_CAPTCHA); + }}; + + // 初始化redisCacheManager + RedisCacheManager redisCacheManager = + RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisTemplate.getConnectionFactory()) + .cacheDefaults(defaultCacheConfiguration) + .initialCacheNames(cacheNames) + .transactionAware() + .build(); + log.info("初始化IAM缓存: DynamicRedisCacheManager"); + return new DynamicRedisCacheManager(redisCacheManager); + } + } \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/util/IamSecurityUtils.java b/diboot-iam-starter/src/main/java/com/diboot/iam/util/IamSecurityUtils.java index e09abce09d4b727d780de75742424ed25bc3aefb..2a8d0aef7885f1043e6d2a1d54274366f33c9fec 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/util/IamSecurityUtils.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/util/IamSecurityUtils.java @@ -20,7 +20,7 @@ import com.diboot.core.util.S; import com.diboot.iam.config.Cons; import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.jwt.BaseJwtRealm; +import com.diboot.iam.shiro.IamAuthorizingRealm; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.SimpleAuthenticationInfo; @@ -69,6 +69,8 @@ public class IamSecurityUtils extends SecurityUtils { public static void logout(){ Subject subject = getSubject(); if(subject.isAuthenticated() || subject.getPrincipals() != null){ + // 缓存当前token与用户信息 + //TokenCacheHelper.removeAccessToken(accessToken); subject.logout(); } } @@ -94,6 +96,19 @@ public class IamSecurityUtils extends SecurityUtils { } } + /** + * 基于 accessToken 退出 注销指定用户 + */ + public static void logoutByToken(String accessToken){ + IamSecurityUtils.logout(); + CacheManager cacheManager = ContextHelper.getBean(CacheManager.class); + if(cacheManager != null && cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME) != null){ + cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME).remove(accessToken); + } + TokenUtils.removeAccessTokens(accessToken); + log.debug("token 已过期注销: {}", accessToken); + } + /** * 获取用户 "ID" 的值 * @return @@ -120,9 +135,9 @@ public class IamSecurityUtils extends SecurityUtils { */ public static void clearAuthorizationCache(String username){ RealmSecurityManager rsm = (RealmSecurityManager) IamSecurityUtils.getSecurityManager(); - BaseJwtRealm baseJwtRealm = (BaseJwtRealm)rsm.getRealms().iterator().next(); - if(baseJwtRealm != null){ - Cache cache = baseJwtRealm.getAuthorizationCache(); + IamAuthorizingRealm authorizingRealm = (IamAuthorizingRealm)rsm.getRealms().iterator().next(); + if(authorizingRealm != null){ + Cache cache = authorizingRealm.getAuthorizationCache(); if(cache != null) { cache.remove(username); log.debug("已清空账号 {} 的权限缓存,以便新权限生效.", username); @@ -135,9 +150,9 @@ public class IamSecurityUtils extends SecurityUtils { */ public static void clearAllAuthorizationCache(){ RealmSecurityManager rsm = (RealmSecurityManager) IamSecurityUtils.getSecurityManager(); - BaseJwtRealm baseJwtRealm = (BaseJwtRealm)rsm.getRealms().iterator().next(); - if(baseJwtRealm != null){ - Cache cache = baseJwtRealm.getAuthorizationCache(); + IamAuthorizingRealm authorizingRealm = (IamAuthorizingRealm)rsm.getRealms().iterator().next(); + if(authorizingRealm != null){ + Cache cache = authorizingRealm.getAuthorizationCache(); if(cache != null) { cache.clear(); log.debug("已清空全部登录用户的权限缓存,以便新权限生效."); diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/util/JwtUtils.java b/diboot-iam-starter/src/main/java/com/diboot/iam/util/JwtUtils.java deleted file mode 100644 index 734821ecc3a7229e9d9d3cbecada70df57338892..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/util/JwtUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.util; - -import com.diboot.core.config.BaseConfig; -import com.diboot.core.util.ContextHelper; -import com.diboot.core.util.S; -import com.diboot.core.util.V; -import com.diboot.iam.config.Cons; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.shiro.cache.CacheManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -/** - * Token相关操作类 - * @author Yangzhao - * @version v2.0 - * @date 2019/6/6 - */ -public class JwtUtils { - private static final Logger log = LoggerFactory.getLogger(JwtUtils.class); - - private static final String TOKEN_PREFIX = "Bearer "; - private static final String AUTH_HEADER = getConfigValue("diboot.iam.jwt-header-key", "authtoken"); - public static final String SIGN_KEY = getConfigValue("diboot.iam.jwt-signkey", "Diboot"); - // 默认过期时间 2小时 - public static final int EXPIRES_IN_MINUTES = getConfigIntValue("diboot.iam.jwt-token-expires-minutes", 1*60); - - // 默认加密算法 - private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; - - /*** - * 从token中获取用户名 + 用户类型 - *
- * 返回格式: - * authAccount,expiresInMinutes - * @param request - * @return - */ - public static Claims getClaimsFromRequest(HttpServletRequest request){ - String authtoken = getRequestToken(request); - if (V.isEmpty(authtoken)) { - log.debug("Token为空!url={}", request.getRequestURL()); - return null; - } - CacheManager cacheManager = ContextHelper.getBean(CacheManager.class); - if(cacheManager != null){ - if(cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME) != null){ - Object cacheVal = cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME).get(authtoken); - if(cacheVal == null){ - log.warn("非系统颁发的token: {}", authtoken); - return null; - } - } - } - try { - return Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(authtoken).getBody(); - } - catch (ExpiredJwtException e) { - log.warn("token已过期:{}", authtoken); - if(cacheManager != null){ - if(cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME) != null){ - cacheManager.getCache(Cons.AUTHENTICATION_CAHCE_NAME).remove(authtoken); - } - if(cacheManager.getCache(Cons.AUTHORIZATION_CAHCE_NAME) != null){ - cacheManager.getCache(Cons.AUTHORIZATION_CAHCE_NAME).remove(authtoken); - } - } - } - catch (Exception e){ - log.warn("token解析异常\n{}", ExceptionUtils.getRootCauseMessage(e)); - } - return null; - } - - /*** - * 从请求头中获取客户端发来的token - * @param request - * @return - */ - public static String getRequestToken(HttpServletRequest request) { - String authHeader = request.getHeader(AUTH_HEADER); - if(authHeader != null){ - if(authHeader.startsWith(TOKEN_PREFIX)){ - return authHeader.substring(TOKEN_PREFIX.length()); - } - return authHeader.trim(); - } - return null; - } - - /*** - * 将刷新的token放入response header - * @param response - * @return - */ - public static void addTokenToResponseHeader(HttpServletResponse response, String newToken) { - response.setHeader(AUTH_HEADER, newToken); - } - - /*** - * 生成Token - * @param accountInfo - * @param expiresInMinutes - * @return - */ - public static String generateToken(String accountInfo, long expiresInMinutes){ - Date expiration = expirationDate(expiresInMinutes); - String jwsToken = Jwts.builder() - .setSubject(accountInfo) - .setIssuedAt(now()) - .setExpiration(expiration) - .signWith(SIGNATURE_ALGORITHM, SIGN_KEY) - .compact(); - return jwsToken; - } - - /** - * 临近过期时生成新的token - * @param claims - * @return - */ - public static String generateNewTokenIfRequired(Claims claims){ - // 当前token是否临近过期 - long current = System.currentTimeMillis(); - long remaining = claims.getExpiration().getTime() - current; - if(remaining > 0){ - long elapsed = current - claims.getIssuedAt().getTime(); - // 小于1/4 则更新 - if((elapsed / remaining) >= 3){ - // 更新token - int expiresInMinutes = Integer.parseInt(S.substringAfterLast(claims.getSubject(), Cons.SEPARATOR_COMMA)); - return JwtUtils.generateToken(claims.getSubject(), expiresInMinutes); - } - } - return null; - } - - /** - * 当前时间 - * @return - */ - private static Date now(){ - return new Date(System.currentTimeMillis()); - } - - /*** - * 生成过期时间戳 - * @return - */ - private static Date expirationDate(long expiresInMinutes) { - return new Date(System.currentTimeMillis() + (expiresInMinutes*60000)); - } - - /** - * 获取配置参数值 - * @return - */ - private static String getConfigValue(String key, String defaultValue){ - String value = BaseConfig.getProperty(key); - return value != null? value : defaultValue; - } - - - /** - * 获取配置参数值 - * @return - */ - private static int getConfigIntValue(String key, int defaultValue){ - String value = BaseConfig.getProperty(key); - return value != null? Integer.parseInt(value) : defaultValue; - } - -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/util/TokenUtils.java b/diboot-iam-starter/src/main/java/com/diboot/iam/util/TokenUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c9b684bdd09ea6f977f104564f1f806a93a0a2d0 --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/util/TokenUtils.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.util; + +import com.diboot.core.cache.BaseCacheManager; +import com.diboot.core.config.BaseConfig; +import com.diboot.core.util.ContextHelper; +import com.diboot.core.util.S; +import com.diboot.core.util.V; +import com.diboot.iam.config.Cons; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Token相关操作类 + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ +public class TokenUtils { + private static final Logger log = LoggerFactory.getLogger(TokenUtils.class); + + private static final String AUTH_HEADER = getConfigValue("diboot.iam.token-header-key", "authtoken"); + public static final int EXPIRES_IN_MINUTES = getConfigIntValue("diboot.iam.token-expires-minutes", 60); + + /*** + * 从请求头中获取客户端发来的token + * @param request + * @return + */ + public static String getRequestToken(HttpServletRequest request) { + String authtoken = request.getHeader(AUTH_HEADER); + if(authtoken != null){ + if(authtoken.startsWith(Cons.TOKEN_PREFIX_BEARER)){ + authtoken = authtoken.substring(Cons.TOKEN_PREFIX_BEARER.length()); + } + authtoken = authtoken.trim(); + } + if(V.isEmpty(authtoken)){ + log.warn("请求未指定token: {}", authtoken); + return null; + } + if(isActiveAccessToken(authtoken) == false){ + log.warn("已过期或非系统颁发的token: {}", authtoken); + return null; + } + return authtoken; + } + + /*** + * 生成Token + * @return + */ + public static String generateToken(){ + return S.newUuid(); + } + + /** + * 临近过期时生成新的token + * @param cachedUserInfo + * @return + */ + public static synchronized void responseNewTokenIfRequired(ServletResponse response, String cachedUserInfo) { + if(isCloseToExpired(cachedUserInfo)){ + //将刷新的token放入response header + String refreshToken = generateToken(); + cacheRefreshToken(refreshToken, cachedUserInfo); + ((HttpServletResponse)response).setHeader(AUTH_HEADER, refreshToken); + log.debug("写回刷新token :{}", refreshToken); + } + } + + /** + * 缓存新的token + * @param accessToken + * @param userInfoStr + */ + public static void cacheAccessToken(String accessToken, String userInfoStr, int expiresInMinutes) { + BaseCacheManager baseCacheManager = ContextHelper.getBean(BaseCacheManager.class); + baseCacheManager.putCacheObj(Cons.CACHE_TOKEN_USERINFO, accessToken, userInfoStr, expiresInMinutes); + } + + /** + * 退出时移除失效的全部token + * @param accessToken + */ + public static void removeAccessTokens(String accessToken) { + BaseCacheManager baseCacheManager = ContextHelper.getBean(BaseCacheManager.class); + baseCacheManager.removeCacheObj(Cons.CACHE_TOKEN_USERINFO, accessToken); + } + + /** + * 获取缓存的token信息 + * @param accessToken + * @return + */ + public static String getCachedUserInfoStr(String accessToken) { + BaseCacheManager baseCacheManager = ContextHelper.getBean(BaseCacheManager.class); + String userInfoStr = baseCacheManager.getCacheString(Cons.CACHE_TOKEN_USERINFO, accessToken); + if(userInfoStr == null){ + log.info("token {} 缓存信息不存在", accessToken); + } + return userInfoStr; + } + + /** + * token是否有效: 未过期/刷新token 均有效 + * @param accessToken + * @return + */ + public static boolean isActiveAccessToken(String accessToken) { + String userInfoStr = getCachedUserInfoStr(accessToken); + if(V.isEmpty(userInfoStr)){ + return false; + } + if(isExpired(userInfoStr)){ + IamSecurityUtils.logoutByToken(accessToken); + return false; + } + return true; + } + + /** + * 缓存刷新token + * @param refreshToken + */ + public synchronized static void cacheRefreshToken(String refreshToken, String userInfoStr) { + String prefixTemp = S.substringBeforeLast(userInfoStr, Cons.SEPARATOR_COMMA); + int expiresMinutes = Integer.parseInt(S.substringAfterLast(prefixTemp, Cons.SEPARATOR_COMMA)); + //如果是刷新token则更新颁发时间 + userInfoStr = prefixTemp + Cons.SEPARATOR_COMMA + System.currentTimeMillis(); + cacheAccessToken(refreshToken, userInfoStr, expiresMinutes + 1); //适当延长1m避免临界点问题 + } + + /** + * 是否已过期 + * @param userInfoStr + * @return + */ + private static final int EXPIRES_MINUTES_INDEX = 4, ISSUED_AT_INDEX = 5; + public static boolean isExpired(String userInfoStr){ + if(V.isEmpty(userInfoStr)){ + return false; + } + String[] userFields = S.split(userInfoStr); + int expiresInMinutes = Integer.parseInt(userFields[EXPIRES_MINUTES_INDEX]); + // 获取当前token的过期时间 + long issuedAt = Long.parseLong(userFields[ISSUED_AT_INDEX]); + // 获取当前token的过期时间 + long expiredBefore = issuedAt + expiresInMinutes * 60000; + return System.currentTimeMillis() > expiredBefore; + } + + /** + * 是否临近过期 + * @param userInfoStr + * @return + */ + public static boolean isCloseToExpired(String userInfoStr){ + if(V.isEmpty(userInfoStr)){ + return false; + } + String[] userFields = S.split(userInfoStr); + int expiresInMinutes = Integer.parseInt(userFields[EXPIRES_MINUTES_INDEX]); + // 当前token是否临近过期 + long current = System.currentTimeMillis(); + long issuedAt = Long.parseLong(userFields[ISSUED_AT_INDEX]); + long expiration = issuedAt + expiresInMinutes*60000; + long remaining = expiration - current; + if(remaining > 0){ + long elapsed = current - issuedAt; + // 小于1/4 则更新 + double past = (elapsed / remaining); + return past > 3.0; + } + return false; + } + + /** + * 获取配置参数值 + * @return + */ + private static String getConfigValue(String key, String defaultValue){ + String value = BaseConfig.getProperty(key); + return value != null? value : defaultValue; + } + + /** + * 获取配置参数值 + * @return + */ + private static int getConfigIntValue(String key, int defaultValue){ + String value = BaseConfig.getProperty(key); + return value != null? Integer.parseInt(value) : defaultValue; + } + +} \ No newline at end of file diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/vo/IamUserVO.java b/diboot-iam-starter/src/main/java/com/diboot/iam/vo/IamUserVO.java index acdd6dfa5331f27990cf344bc0148ffbb8df91bb..b8d48303506c7d1f9e9835b802b09ddb41636e17 100644 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/vo/IamUserVO.java +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/vo/IamUserVO.java @@ -21,6 +21,7 @@ import com.diboot.core.binding.annotation.BindField; import com.diboot.iam.entity.IamOrg; import com.diboot.iam.entity.IamRole; import com.diboot.iam.entity.IamUser; +import com.diboot.iam.entity.IamUserPosition; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -51,4 +52,7 @@ public class IamUserVO extends IamUser { // 字段关联:this.id=iam_user_role.user_id AND iam_user_role.role_id=id AND iam_user_role.user_type = 'IamUser' @BindEntityList(entity = IamRole.class, condition = "this.id=iam_user_role.user_id AND iam_user_role.role_id=id AND iam_user_role.user_type = 'IamUser'") private List roleList; + + @BindEntityList(entity = IamUserPosition.class, condition = "this.id = user_id AND user_type = 'IamUser'") + private List userPositionList; } diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/vo/InvalidResourcePermissionVO.java b/diboot-iam-starter/src/main/java/com/diboot/iam/vo/InvalidResourcePermissionVO.java deleted file mode 100644 index 4cadf72cf9513e022784cbf08892c38ffee6ad17..0000000000000000000000000000000000000000 --- a/diboot-iam-starter/src/main/java/com/diboot/iam/vo/InvalidResourcePermissionVO.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.iam.vo; - -import com.diboot.core.binding.annotation.BindDict; -import com.diboot.core.binding.annotation.BindField; -import com.diboot.core.util.V; -import com.diboot.iam.entity.IamResourcePermission; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * 无效的资源权限VO - * @author : uu - * @version : v1.0 - * @Date 2021/5/7 13:18 - * @copyright www.diboot.com - */ -@Getter -@Setter -@Accessors(chain = true) -public class InvalidResourcePermissionVO extends IamResourcePermission { - - private static final long serialVersionUID = 6643651522844488124L; - - // display_type字段的关联数据字典 - public static final String DICT_RESOURCE_PERMISSION_TYPE = "RESOURCE_PERMISSION_TYPE"; - - // 字段关联:this.parent_id=id - @BindField(entity = IamResourcePermission.class, field = "displayName", condition = "this.parent_id=id") - private String parentDisplayName; - - // 关联数据字典:RESOURCE_PERMISSION_TYPE - @BindDict(type = DICT_RESOURCE_PERMISSION_TYPE, field = "displayType") - private String displayTypeLabel; - - private List children; - - /** - * 当前权限无效的的uri集合 - */ - private Set invalidApiSetList; - - public void addInvalidApiSet(String invalidApiSet) { - if (V.isEmpty(invalidApiSetList)) { - invalidApiSetList = new HashSet<>(); - } - invalidApiSetList.add(invalidApiSet); - } -} diff --git a/diboot-iam-starter/src/main/java/com/diboot/iam/vo/PositionDataScope.java b/diboot-iam-starter/src/main/java/com/diboot/iam/vo/PositionDataScope.java new file mode 100644 index 0000000000000000000000000000000000000000..83ccfcc3aa388e7eee8920fead0524f085e62d7c --- /dev/null +++ b/diboot-iam-starter/src/main/java/com/diboot/iam/vo/PositionDataScope.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015-2029, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.diboot.iam.vo; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.List; + +/** + * 岗位相关的数据范围 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/9 + * Copyright © diboot.com + */ +@Getter @Setter +public class PositionDataScope implements Serializable { + private static final long serialVersionUID = 3374139242516436636L; + + public PositionDataScope(){} + + public PositionDataScope(Long positionId, String dataPermissionType, Long userId, Long orgId){ + this.positionId = positionId; + this.dataPermissionType = dataPermissionType; + this.userId = userId; + this.orgId = orgId; + } + + /** + * 岗位id + */ + private Long positionId; + + /** + * 数据权限范围 + */ + private String dataPermissionType; + + /** + * 当前部门id + */ + private Long orgId; + + /** + * 当前及子级别部门ids + */ + private List accessibleOrgIds; + + /** + * 当前用户id + */ + private Long userId; + + /** + * 当前及子级用户ids + */ + private List accessibleUserIds; +} diff --git a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-dm.sql b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..0f2cb3a3c8851354929699fce347a06a3b678b75 --- /dev/null +++ b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-dm.sql @@ -0,0 +1,370 @@ +-- 用户表 +create table ${SCHEMA}.iam_user +( + id BIGINT not null primary key, + tenant_id BIGINT default 0 not null, + org_id BIGINT default 0 not null, + user_num VARCHAR(50) not null, + realname VARCHAR(50) not null, + gender VARCHAR(20) not null, + birthdate date null, + mobile_phone VARCHAR(30) null, + email VARCHAR(100) null, + avatar_url VARCHAR(500) null, + status VARCHAR(10) default 'A' not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +-- 添加备注, +comment on column ${SCHEMA}.iam_user.id is 'ID'; +comment on column ${SCHEMA}.iam_user.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_user.org_id is '组织ID'; +comment on column ${SCHEMA}.iam_user.user_num is '用户编号'; +comment on column ${SCHEMA}.iam_user.realname is '真实姓名'; +comment on column ${SCHEMA}.iam_user.gender is '性别'; +comment on column ${SCHEMA}.iam_user.birthdate is '出生日期'; +comment on column ${SCHEMA}.iam_user.mobile_phone is '手机号'; +comment on column ${SCHEMA}.iam_user.email is 'Email'; +comment on column ${SCHEMA}.iam_user.avatar_url is '头像'; +comment on column ${SCHEMA}.iam_user.status is '状态'; +comment on column ${SCHEMA}.iam_user.is_deleted is '删除标记'; +comment on column ${SCHEMA}.iam_user.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_user.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_user is '系统用户'; +-- 索引 +create index idx_iam_user_1 on ${SCHEMA}.iam_user (org_id); +create index idx_iam_user_2 on ${SCHEMA}.iam_user (mobile_phone); +create index idx_iam_user_num on ${SCHEMA}.iam_user (user_num); +create index idx_iam_user_tenant on ${SCHEMA}.iam_user (tenant_id); + +-- 账号表 +create table ${SCHEMA}.iam_account +( + id BIGINT not null primary key, + tenant_id BIGINT default 0 not null, + user_type VARCHAR(100) default 'IamUser' not null, + user_id BIGINT not null, + auth_type VARCHAR(50) default 'PWD' not null, + auth_account VARCHAR(200) not null, + auth_secret VARCHAR(100) null, + secret_salt VARCHAR(50) null, + status VARCHAR(10) default 'A' not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_account.id is 'ID'; +comment on column ${SCHEMA}.iam_account.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_account.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_account.user_id is '用户ID'; +comment on column ${SCHEMA}.iam_account.auth_type is '认证方式'; +comment on column ${SCHEMA}.iam_account.auth_account is '用户名'; +comment on column ${SCHEMA}.iam_account.auth_secret is '密码'; +comment on column ${SCHEMA}.iam_account.secret_salt is '加密盐'; +comment on column ${SCHEMA}.iam_account.status is '用户状态'; +comment on column ${SCHEMA}.iam_account.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_account.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_account.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_account is '登录账号'; +-- 创建索引 +create index idx_iam_account on ${SCHEMA}.iam_account(auth_account, auth_type, user_type); +create index idx_iam_account_tenant on ${SCHEMA}.iam_account (tenant_id); + +-- 角色表 +create table ${SCHEMA}.iam_role +( + id BIGINT not null primary key, + tenant_id BIGINT default 0 not null, + name VARCHAR(100) not null, + code VARCHAR(100) not null, + description VARCHAR(300) null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_role.id is 'ID'; +comment on column ${SCHEMA}.iam_role.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_role.name is '名称'; +comment on column ${SCHEMA}.iam_role.code is '编码'; +comment on column ${SCHEMA}.iam_role.description is '备注'; +comment on column ${SCHEMA}.iam_role.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_role.create_time is '创建时间'; +comment on table ${SCHEMA}.iam_role is '角色'; +-- 创建索引 +create index idx_iam_role_tenant on ${SCHEMA}.iam_role (tenant_id); + +-- 用户角色表 +create table ${SCHEMA}.iam_user_role +( + id BIGINT identity ( 10000,1) primary key, + tenant_id BIGINT default 0 not null, + user_type VARCHAR(100) default 'IamUser' not null, + user_id BIGINT not null, + role_id BIGINT not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_user_role.id is 'ID'; +comment on column ${SCHEMA}.iam_user_role.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_user_role.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_user_role.user_id is '用户ID'; +comment on column ${SCHEMA}.iam_user_role.role_id is '角色ID'; +comment on column ${SCHEMA}.iam_user_role.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_user_role.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_user_role.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_user_role is '用户角色关联'; +-- 索引 +create index idx_iam_user_role on ${SCHEMA}.iam_user_role (user_type, user_id); +create index idx_iam_user_role_tenant on ${SCHEMA}.iam_user_role (tenant_id); + +-- 资源权限表 +create table ${SCHEMA}.iam_resource_permission +( + id BIGINT identity ( 10000,1) primary key, + app_module VARCHAR(50), + tenant_id BIGINT default 0 not null, + parent_id BIGINT default 0 not null, + display_type VARCHAR(60) not null, + display_name VARCHAR(100) not null, + resource_code VARCHAR(100) null, + permission_code VARCHAR(300) null, + sort_id BIGINT null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_resource_permission.id is 'ID'; +comment on column ${SCHEMA}.iam_resource_permission.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_resource_permission.app_module is '应用模块'; +comment on column ${SCHEMA}.iam_resource_permission.parent_id is '父资源ID'; +comment on column ${SCHEMA}.iam_resource_permission.display_type is '展现类型'; +comment on column ${SCHEMA}.iam_resource_permission.display_name is '显示名称'; +comment on column ${SCHEMA}.iam_resource_permission.resource_code is '前端编码'; +comment on column ${SCHEMA}.iam_resource_permission.permission_code is '权限码'; +comment on column ${SCHEMA}.iam_resource_permission.sort_id is '排序号'; +comment on column ${SCHEMA}.iam_resource_permission.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_resource_permission.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_resource_permission.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_resource_permission is '资源权限表'; + +-- 索引 +create index idx_iam_resource_permission on ${SCHEMA}.iam_resource_permission (parent_id); +create index idx_resource_permission_tenant on ${SCHEMA}.iam_resource_permission (tenant_id); + +-- 角色-权限 +create table ${SCHEMA}.iam_role_resource +( + id BIGINT identity ( 10000,1) primary key, + tenant_id BIGINT default 0 not null, + role_id int not null, + resource_id int not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null +); +comment on column ${SCHEMA}.iam_role_resource.id is 'ID'; +comment on column ${SCHEMA}.iam_role_resource.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_role_resource.role_id is '角色ID'; +comment on column ${SCHEMA}.iam_role_resource.resource_id is '权限ID'; +comment on column ${SCHEMA}.iam_role_resource.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_role_resource.create_time is '创建时间'; +comment on table ${SCHEMA}.iam_role_resource is '角色资源'; +-- 索引 +create index idx_iam_role_resource on ${SCHEMA}.iam_role_resource (role_id, resource_id); +create index idx_iam_role_resource_tenant on ${SCHEMA}.iam_role_resource (tenant_id); + +-- 登录日志表 +create table ${SCHEMA}.iam_login_trace +( + id BIGINT identity ( 10000,1) primary key, + tenant_id BIGINT default 0 not null, + user_type VARCHAR(100) default 'IamUser' not null, + user_id BIGINT not null, + auth_type VARCHAR(60) default 'PWD' not null, + auth_account VARCHAR(100) not null, + ip_address VARCHAR(100) null, + user_agent VARCHAR(500) null, + is_success BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null +); +comment on column ${SCHEMA}.iam_login_trace.id is 'ID'; +comment on column ${SCHEMA}.iam_login_trace.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_login_trace.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_login_trace.user_id is '用户ID'; +comment on column ${SCHEMA}.iam_login_trace.auth_type is '认证方式'; +comment on column ${SCHEMA}.iam_login_trace.auth_account is '用户名'; +comment on column ${SCHEMA}.iam_login_trace.ip_address is 'IP'; +comment on column ${SCHEMA}.iam_login_trace.user_agent is '客户端信息'; +comment on column ${SCHEMA}.iam_login_trace.is_success is '是否成功'; +comment on column ${SCHEMA}.iam_login_trace.create_time is '创建时间'; +comment on table ${SCHEMA}.iam_login_trace is '登录日志'; +-- 创建索引 +create index idx_iam_login_trace on ${SCHEMA}.iam_login_trace (user_type, user_id); +create index idx_iam_login_trace_2 on ${SCHEMA}.iam_login_trace (auth_account); +create index idx_iam_login_trace_tenant on ${SCHEMA}.iam_login_trace (tenant_id); + +-- 操作日志表 +create table ${SCHEMA}.iam_operation_log +( + id BIGINT identity ( 10000,1) primary key, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(50), + business_obj VARCHAR(100) not null, + operation VARCHAR(100) not null, + user_type VARCHAR(100) DEFAULT 'IamUser' not null, + user_id BIGINT not null, + user_realname VARCHAR(100) null, + request_uri VARCHAR(500) not null, + request_method VARCHAR(20) not null, + request_params VARCHAR(3000) null, + request_ip VARCHAR(100) null, + status_code NUMBER(6) default 0 not null, + error_msg VARCHAR(3000) null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null +); +comment on column ${SCHEMA}.iam_operation_log.id is 'ID'; +comment on column ${SCHEMA}.iam_operation_log.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_operation_log.app_module is '应用模块'; +comment on column ${SCHEMA}.iam_operation_log.business_obj is '业务对象'; +comment on column ${SCHEMA}.iam_operation_log.operation is '操作描述'; +comment on column ${SCHEMA}.iam_operation_log.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_operation_log.user_id is '用户ID'; +comment on column ${SCHEMA}.iam_operation_log.user_realname is '用户姓名'; +comment on column ${SCHEMA}.iam_operation_log.request_uri is '请求URI'; +comment on column ${SCHEMA}.iam_operation_log.request_method is '请求方式'; +comment on column ${SCHEMA}.iam_operation_log.request_params is '请求参数'; +comment on column ${SCHEMA}.iam_operation_log.request_ip is 'IP'; +comment on column ${SCHEMA}.iam_operation_log.status_code is '状态码'; +comment on column ${SCHEMA}.iam_operation_log.error_msg is '异常信息'; +comment on column ${SCHEMA}.iam_operation_log.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_operation_log.create_time is '创建时间'; +comment on table ${SCHEMA}.iam_operation_log is '操作日志'; +-- 创建索引 +create index idx_iam_operation_log on ${SCHEMA}.iam_operation_log (user_type, user_id); +create index idx_iam_operation_log_tenant on ${SCHEMA}.iam_operation_log (tenant_id); + +-- 部门表 +CREATE TABLE ${SCHEMA}.iam_org ( + id BIGINT not null primary key, + tenant_id BIGINT default 0 not null, + parent_id BIGINT DEFAULT 0 NOT NULL, + top_org_id BIGINT DEFAULT 0 NOT NULL, + name VARCHAR(100) NOT NULL, + short_name VARCHAR(100) NOT NULL, + type VARCHAR(100) DEFAULT 'DEPT' NOT NULL, + code VARCHAR(50) NOT NULL, + manager_id BIGINT DEFAULT 0 NOT NULL, + depth NUMBER(6) DEFAULT 1 NOT NULL, + sort_id BIGINT DEFAULT 1 NOT NULL, + status VARCHAR(10) DEFAULT 'A' NOT NULL, + org_comment VARCHAR(500) null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_org.id is 'ID'; +comment on column ${SCHEMA}.iam_org.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_org.parent_id is '上级ID'; +comment on column ${SCHEMA}.iam_org.top_org_id is '企业ID'; +comment on column ${SCHEMA}.iam_org.name is '名称'; +comment on column ${SCHEMA}.iam_org.short_name is '简称'; +comment on column ${SCHEMA}.iam_org.type is '类型'; +comment on column ${SCHEMA}.iam_org.code is '编码'; +comment on column ${SCHEMA}.iam_org.manager_id is '负责人'; +comment on column ${SCHEMA}.iam_org.depth is '层级'; +comment on column ${SCHEMA}.iam_org.sort_id is '排序号'; +comment on column ${SCHEMA}.iam_org.status is '状态'; +comment on column ${SCHEMA}.iam_org.org_comment is '备注'; +comment on column ${SCHEMA}.iam_org.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_org.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_org.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_org is '部门'; +create index idx_iam_org on ${SCHEMA}.iam_org (parent_id); +create index idx_iam_org_tenant on ${SCHEMA}.iam_org (tenant_id); + +-- 岗位 +create table ${SCHEMA}.iam_position +( + id BIGINT not null primary key, + tenant_id BIGINT default 0 not null, + name VARCHAR(100) not null, + code VARCHAR(50) not null, + is_virtual BIT default 0 not null, + grade_name VARCHAR(50) null, + grade_value VARCHAR(50) default '0' null, + data_permission_type VARCHAR(50) default 'SELF' null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_position.id is 'ID'; +comment on column ${SCHEMA}.iam_position.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_position.name is '名称'; +comment on column ${SCHEMA}.iam_position.code is '编码'; +comment on column ${SCHEMA}.iam_position.is_virtual is '是否虚拟岗'; +comment on column ${SCHEMA}.iam_position.grade_name is '职级头衔'; +comment on column ${SCHEMA}.iam_position.grade_value is '职级'; +comment on column ${SCHEMA}.iam_position.data_permission_type is '数据权限类型'; +comment on column ${SCHEMA}.iam_position.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_position.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_position.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_position is '岗位'; +create index idx_iam_position on ${SCHEMA}.iam_position (code); +create index idx_iam_position_tenant on ${SCHEMA}.iam_position (tenant_id); + +-- 用户岗位 +create table ${SCHEMA}.iam_user_position +( + id BIGINT identity ( 100000,1 ) primary key, + tenant_id BIGINT default 0 not null, + user_type VARCHAR(100) default 'IamUser' not null, + user_id BIGINT not null, + org_id BIGINT default 0 not null, + position_id BIGINT not null, + is_primary_position BIT default 1 not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_user_position.id is 'ID'; +comment on column ${SCHEMA}.iam_user_position.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_user_position.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_user_position.user_id is '用户ID'; +comment on column ${SCHEMA}.iam_user_position.org_id is '组织ID'; +comment on column ${SCHEMA}.iam_user_position.position_id is '岗位ID'; +comment on column ${SCHEMA}.iam_user_position.is_primary_position is '是否主岗'; +comment on column ${SCHEMA}.iam_user_position.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_user_position.create_time is '创建时间'; +comment on column ${SCHEMA}.iam_user_position.update_time is '更新时间'; +comment on table ${SCHEMA}.iam_user_position is '用户岗位关联'; +create index idx_iam_user_position on ${SCHEMA}.iam_user_position (user_type, user_id); +create index idx_iam_user_position_pos on ${SCHEMA}.iam_user_position (position_id); + +-- 系统配置表 +create table ${SCHEMA}.system_config +( + id BIGINT identity ( 100000,1 ) primary key, + tenant_id NUMBER (20) default 0 not null, + type VARCHAR (100) not null, + prop VARCHAR (100) not null, + value VARCHAR (500), + is_deleted NUMBER (1) default 0 not null, + create_time TIMESTAMP default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +-- 添加备注 +comment on column ${SCHEMA}.system_config.id is 'ID'; +comment on column ${SCHEMA}.system_config.tenant_id is '租户ID'; +comment on column ${SCHEMA}.system_config.type is '类型'; +comment on column ${SCHEMA}.system_config.prop is '属性'; +comment on column ${SCHEMA}.system_config.value is '属性值'; +comment on column ${SCHEMA}.system_config.is_deleted is '删除标记'; +comment on column ${SCHEMA}.system_config.create_time is '创建时间'; +comment on column ${SCHEMA}.system_config.update_time is '更新时间'; + +comment on table ${SCHEMA}.system_config is '系统配置'; +-- 创建索引 +create index idx_system_config on ${SCHEMA}.system_config (type, prop); +create index idx_system_config_tenant on ${SCHEMA}.system_config (tenant_id); diff --git a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-mysql.sql b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-mysql.sql index d5f22aa8c061ad72cddd5fe667f2d439f27f0cf1..c720caa148e548dcfb93b48e86b2ffcf010900c4 100644 --- a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-mysql.sql +++ b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-mysql.sql @@ -82,8 +82,8 @@ create table iam_resource_permission app_module varchar(50) null comment '应用模块', display_type varchar(20) not null comment '展现类型', display_name varchar(100) not null comment '显示名称', - resource_code varchar(100) not null comment '前端编码', - api_set varchar(3000) null comment '接口列表', + resource_code varchar(100) null comment '权限编码', + permission_code varchar(200) null comment '权限编码', sort_id bigint null comment '排序号', is_deleted tinyint(1) default 0 not null comment '是否删除', create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间', diff --git a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-oracle.sql b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-oracle.sql index 45de878b836e768c6e21c35887c1c7ac1e1ed3b5..bb6f8eb42b452bd3d142e246e5af88a14cb48b58 100644 --- a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-oracle.sql +++ b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-oracle.sql @@ -83,6 +83,7 @@ create table ${SCHEMA}.iam_role description VARCHAR2(100) null, is_deleted NUMBER(1) DEFAULT 0 not null, create_time timestamp default CURRENT_TIMESTAMP null, + update_time timestamp default CURRENT_TIMESTAMP null, constraint PK_iam_role primary key (id) ); comment on column ${SCHEMA}.iam_role.id is 'ID'; @@ -133,7 +134,7 @@ create table ${SCHEMA}.iam_resource_permission display_type VARCHAR2(20) not null, display_name VARCHAR2(100) not null, resource_code VARCHAR2(100) null, - api_set VARCHAR2(3000) null, + permission_code VARCHAR2(200) null, sort_id NUMBER(20) null, is_deleted NUMBER(1) DEFAULT 0 not null, create_time timestamp default CURRENT_TIMESTAMP not null, @@ -147,7 +148,7 @@ comment on column ${SCHEMA}.iam_resource_permission.parent_id is '父资源ID'; comment on column ${SCHEMA}.iam_resource_permission.display_type is '展现类型'; comment on column ${SCHEMA}.iam_resource_permission.display_name is '显示名称'; comment on column ${SCHEMA}.iam_resource_permission.resource_code is '前端编码'; -comment on column ${SCHEMA}.iam_resource_permission.api_set is '接口列表'; +comment on column ${SCHEMA}.iam_resource_permission.permission_code is '权限码'; comment on column ${SCHEMA}.iam_resource_permission.sort_id is '排序号'; comment on column ${SCHEMA}.iam_resource_permission.is_deleted is '是否删除'; comment on column ${SCHEMA}.iam_resource_permission.create_time is '创建时间'; diff --git a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-postgresql.sql b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-postgresql.sql index ca19b7cdf9f89847d4b66d27b5ff4beccb45554e..1362073da0498070341b4cc8508f179a8e10d707 100644 --- a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-postgresql.sql +++ b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-postgresql.sql @@ -130,7 +130,7 @@ create table iam_resource_permission display_type varchar(20) not null, display_name varchar(100) not null, resource_code varchar(100) null, - api_set varchar(3000) null, + permission_code varchar(200) null, sort_id bigint null, is_deleted BOOLEAN default FALSE not null, create_time timestamp default CURRENT_TIMESTAMP not null, @@ -144,7 +144,7 @@ comment on column iam_resource_permission.parent_id is '父资源ID'; comment on column iam_resource_permission.display_type is '展现类型'; comment on column iam_resource_permission.display_name is '显示名称'; comment on column iam_resource_permission.resource_code is '前端编码'; -comment on column iam_resource_permission.api_set is '接口列表'; +comment on column iam_resource_permission.permission_code is '权限码'; comment on column iam_resource_permission.sort_id is '排序号'; comment on column iam_resource_permission.is_deleted is '是否删除'; comment on column iam_resource_permission.create_time is '创建时间'; diff --git a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-sqlserver.sql b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-sqlserver.sql index 39e76d1774e5d4b9030dc83773bfee6e37246f09..aabbe4b4d7b7bfcd6b9700d72e8bf70213f26df8 100644 --- a/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-sqlserver.sql +++ b/diboot-iam-starter/src/main/resources/META-INF/sql/init-iam-sqlserver.sql @@ -134,7 +134,7 @@ create table ${SCHEMA}.iam_resource_permission display_type varchar(20) not null, display_name varchar(100) not null, resource_code varchar(100) null, - api_set varchar(3000) null, + permission_code varchar(200) null, sort_id bigint null, is_deleted tinyint default 0 not null, create_time datetime default CURRENT_TIMESTAMP not null, @@ -148,7 +148,7 @@ execute sp_addextendedproperty 'MS_Description', N'父资源ID', 'SCHEMA', '${SC execute sp_addextendedproperty 'MS_Description', N'展现类型', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'display_type'; execute sp_addextendedproperty 'MS_Description', N'显示名称', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'display_name'; execute sp_addextendedproperty 'MS_Description', N'前端编码', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'resource_code'; -execute sp_addextendedproperty 'MS_Description', N'接口列表', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'api_set'; +execute sp_addextendedproperty 'MS_Description', N'权限码', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'permission_code'; execute sp_addextendedproperty 'MS_Description', N'排序号', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'sort_id'; execute sp_addextendedproperty 'MS_Description', N'是否删除', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'is_deleted'; execute sp_addextendedproperty 'MS_Description', N'创建时间', 'SCHEMA', '${SCHEMA}', 'table', iam_resource_permission, 'column', 'create_time'; diff --git a/diboot-message-starter/pom.xml b/diboot-message-starter/pom.xml index f34c8f6bc9f74a0619df5ef03d1a10e7065378d5..b871c52c99d959ca78f04751efd31adbbc66d459 100644 --- a/diboot-message-starter/pom.xml +++ b/diboot-message-starter/pom.xml @@ -5,12 +5,12 @@ diboot-root com.diboot - 2.5.0 + 2.6.0 4.0.0 diboot-message-spring-boot-starter - 2.5.0 + 2.6.0 jar diboot消息组件 @@ -33,5 +33,18 @@ org.springframework.boot spring-boot-starter-mail + + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + diff --git a/diboot-message-starter/src/main/java/com/diboot/message/annotation/TemplateVariable.java b/diboot-message-starter/src/main/java/com/diboot/message/annotation/BindVariable.java similarity index 72% rename from diboot-message-starter/src/main/java/com/diboot/message/annotation/TemplateVariable.java rename to diboot-message-starter/src/main/java/com/diboot/message/annotation/BindVariable.java index 0737723ea1b6d97d127e89c41f4020da9c3322ce..a53b095caa596ac652554fec8a6b9f505fefa99d 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/annotation/TemplateVariable.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/annotation/BindVariable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). + * Copyright (c) 2015-2022, www.dibo.ltd (service@dibo.ltd). *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -18,22 +18,21 @@ package com.diboot.message.annotation; import java.lang.annotation.*; /** - * 绑定的变量 - * - * @author : uu - * @version v1.0 - * @Date 2021/2/18 19:39 - * @Copyright © diboot.com + * 绑定变量 + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/27 + * Copyright © diboot.com */ -@Target({ElementType.TYPE, ElementType.METHOD}) +@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented -public @interface TemplateVariable { +public @interface BindVariable { /** - * 绑定变量名称 + * 绑定变量名称,如 ${用户姓名} * * @return */ String name(); -} +} \ No newline at end of file diff --git a/diboot-message-starter/src/main/java/com/diboot/message/channel/ChannelStrategy.java b/diboot-message-starter/src/main/java/com/diboot/message/channel/MessageChannel.java similarity index 88% rename from diboot-message-starter/src/main/java/com/diboot/message/channel/ChannelStrategy.java rename to diboot-message-starter/src/main/java/com/diboot/message/channel/MessageChannel.java index f9f386baa076250198ed0ccef69d6d7a24fa2ac4..60bd98ca93141d521ce5a5869eedadc42606bc00 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/channel/ChannelStrategy.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/channel/MessageChannel.java @@ -19,7 +19,7 @@ package com.diboot.message.channel; import com.diboot.message.entity.Message; /** - * 通道策略接口 + * 消息通道接口 * *

* 所有发送通道实现该接口,并实现发送方法 @@ -30,7 +30,13 @@ import com.diboot.message.entity.Message; * @Date 2021/2/18 18:42 * @Copyright © diboot.com */ -public interface ChannelStrategy { +public interface MessageChannel { + + /** + * 消息通道类型,如Email + * @return + */ + String type(); /** * 发送消息, 并更新发送结果 diff --git a/diboot-message-starter/src/main/java/com/diboot/message/channel/SimpleEmailChannel.java b/diboot-message-starter/src/main/java/com/diboot/message/channel/SimpleEmailChannel.java index 120c3fa6f2ede826fad48aeb6f3a476e71f0a608..4abbdd914bf828615761fa3a7317645f0fff3f22 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/channel/SimpleEmailChannel.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/channel/SimpleEmailChannel.java @@ -43,7 +43,7 @@ import java.util.Date; * @Copyright © diboot.com */ @Slf4j -public class SimpleEmailChannel implements ChannelStrategy { +public class SimpleEmailChannel implements MessageChannel { @Autowired(required = false) private JavaMailSender javaMailSender; @@ -51,12 +51,17 @@ public class SimpleEmailChannel implements ChannelStrategy { @Autowired private MessageService messageService; + @Override + public String type() { + return Cons.MESSAGE_CHANNEL.EMAIL.name(); + } + @Override @Async public void send(Message message) { log.debug("[开始发送邮件]:邮件内容:{}", JSON.stringify(message)); String result = "success"; - String status = Cons.MESSAGE_STATUS.DELIVERY.getItemValue(); + String status = Cons.MESSAGE_STATUS.DELIVERY.name(); try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); //发送有附件邮件 @@ -91,7 +96,7 @@ public class SimpleEmailChannel implements ChannelStrategy { } catch (Exception e) { log.error("[发送邮件失败]:信息为: {} , 异常", message, e); result = e.getMessage(); - status = Cons.MESSAGE_STATUS.EXCEPTION.getItemValue(); + status = Cons.MESSAGE_STATUS.FAILED.name(); } // 更新结果 messageService.updateEntity( diff --git a/diboot-message-starter/src/main/java/com/diboot/message/config/Cons.java b/diboot-message-starter/src/main/java/com/diboot/message/config/Cons.java index 97af82080b4e2210bc292f447712b2658dd54345..0c9aba2d04f9ae287af5d3779c901241fdfa5564 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/config/Cons.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/config/Cons.java @@ -32,41 +32,28 @@ public class Cons extends com.diboot.core.config.Cons { /** * 发送中 */ - SENDING("发送中", "SENDING"), + PENDING("待发送"), /** * 异常 */ - EXCEPTION("发送异常", "EXCEPTION"), + FAILED("发送失败"), // 如果是短信、邮件则送达表示发送成功 /** * 签收 */ - DELIVERY("已送达", "DELIVERY"), - // 如果是站内信,那么状态是已读、未读 - /** - * 未读 - */ - UNREAD("未读", "UNREAD"), + DELIVERY("已送达"), /** * 已读 */ - READ("已读", "READ"); - - private String itemName; - - private String itemValue; - - MESSAGE_STATUS(String itemName, String itemValue) { - this.itemName = itemName; - this.itemValue = itemValue; - } + READ("已读"); - public String getItemName() { - return itemName; + private String label; + MESSAGE_STATUS(String label){ + this.label = label; } - public String getItemValue() { - return itemValue; + public String label(){ + return label; } } @@ -74,25 +61,18 @@ public class Cons extends com.diboot.core.config.Cons { * 消息发送通道 */ public enum MESSAGE_CHANNEL { - WEBSOCKET("站内信", "WEBSOCKET"), - TEXT_MESSAGE("短信", "TEXT_MESSAGE"), - EMAIL("邮件", "EMAIL"); - - private String itemName; - - private String itemValue; - - MESSAGE_CHANNEL(String itemName, String itemValue) { - this.itemName = itemName; - this.itemValue = itemValue; - } + SMS("短信"), + SYS_MSG("系统消息"), + WEBSOCKET("站内信"), + EMAIL("邮件"); - public String getItemName() { - return itemName; + private String label; + MESSAGE_CHANNEL(String label){ + this.label = label; } - public String getItemValue() { - return itemValue; + public String label(){ + return label; } } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/entity/BaseVariableData.java b/diboot-message-starter/src/main/java/com/diboot/message/entity/BaseUserVariables.java similarity index 69% rename from diboot-message-starter/src/main/java/com/diboot/message/entity/BaseVariableData.java rename to diboot-message-starter/src/main/java/com/diboot/message/entity/BaseUserVariables.java index 2918049439a5494a1c7df9f3d8de1b6964cb5e97..cfdd6300db1e42e6854d50c39193a03197a63fc5 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/entity/BaseVariableData.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/entity/BaseUserVariables.java @@ -15,6 +15,7 @@ */ package com.diboot.message.entity; +import com.diboot.message.annotation.BindVariable; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -32,15 +33,31 @@ import java.io.Serializable; @Getter @Setter @Accessors(chain = true) -public class BaseVariableData implements Serializable { +public class BaseUserVariables implements Serializable { + private static final long serialVersionUID = 6254327279941140819L; /** * 姓名 */ + @BindVariable(name = "${用户姓名}") private String realName; + /** + * 称呼 + */ + @BindVariable(name = "${称呼}") + private String title; + /** * 手机号 */ + @BindVariable(name = "${手机号}") private String phone; + + /** + * 验证码 + */ + @BindVariable(name = "${验证码}") + private String verificationCode; + } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/entity/Message.java b/diboot-message-starter/src/main/java/com/diboot/message/entity/Message.java index 1bd5200a170e709d84c466382898d36a1e3aa7a9..c6def0cd72f83c9ec2c305837f6de10b36ede37d 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/entity/Message.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/entity/Message.java @@ -21,6 +21,7 @@ import com.diboot.core.entity.BaseEntity; import com.diboot.core.util.D; import com.diboot.core.util.JSON; import com.diboot.core.util.V; +import com.diboot.message.config.Cons; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Getter; import lombok.Setter; @@ -271,4 +272,12 @@ public class Message extends BaseEntity { String attachments = (String) extDataMap.get(ATTACHMENTS); return attachments.split(","); } + + /** + * 是否有关联模板 + * @return + */ + public boolean hasTemplate() { + return V.notEmpty(this.templateId) || V.notEmpty(this.templateCode); + } } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/entity/MessageTemplate.java b/diboot-message-starter/src/main/java/com/diboot/message/entity/MessageTemplate.java index bfccfad65abf1404a5809abc338d3a53d4c25207..2480babe2671a85ce4241d0b3bd57ab574e7fab7 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/entity/MessageTemplate.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/entity/MessageTemplate.java @@ -83,13 +83,6 @@ public class MessageTemplate extends BaseEntity { @TableField() private String content; - /** - * 模版变量 - */ - @Length(max = 200, message = "模版变量长度应小于200") - @TableField() - private String variables; - /** * 扩展数据 */ diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/MessageService.java b/diboot-message-starter/src/main/java/com/diboot/message/service/MessageService.java index eb7c418b01ddf50452a6370bd6a3983332e83ab0..1dbc2bd500d080785049acd1af14e03b5910011d 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/MessageService.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/service/MessageService.java @@ -16,7 +16,6 @@ package com.diboot.message.service; import com.diboot.core.service.BaseService; -import com.diboot.message.entity.BaseVariableData; import com.diboot.message.entity.Message; /** @@ -28,12 +27,20 @@ import com.diboot.message.entity.Message; * @Copyright © diboot.com */ public interface MessageService extends BaseService { + /** + * 发送信息(消息模板无变量) + * + * @param message {@link Message} 待发送的消息 + * @return + */ + boolean send(Message message); + /** * 发送信息 * * @param message {@link Message} 待发送的消息 - * @param variableData {@link BaseVariableData} 变量替换值 + * @param variableData {@link Object} 变量替换值 * @return */ - boolean send(Message message, BaseVariableData variableData); + boolean send(Message message, Object variableData); } \ No newline at end of file diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/MessageTemplateService.java b/diboot-message-starter/src/main/java/com/diboot/message/service/MessageTemplateService.java index 113b20997940841870dea3e24a9a454e4c32b5b9..968cd93393b5849c1d8e5ee5066c558e30b33a3d 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/MessageTemplateService.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/service/MessageTemplateService.java @@ -36,7 +36,7 @@ public interface MessageTemplateService extends BaseService { * @return * @throws Exception */ - List getTemplateVariableList() throws Exception; + List getTemplateVariableList() ; /** * 检查是否有重复的code @@ -46,5 +46,5 @@ public interface MessageTemplateService extends BaseService { * @return * @throws Exception */ - boolean existCode(Long id, String code) throws Exception; + boolean existCode(Long id, String code) ; } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/TemplateVariableService.java b/diboot-message-starter/src/main/java/com/diboot/message/service/TemplateVariableService.java deleted file mode 100644 index 5412304adb89a3fbed8186328b7a55ccbf8b3d97..0000000000000000000000000000000000000000 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/TemplateVariableService.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.message.service; - -import com.diboot.core.util.S; -import com.diboot.core.util.V; -import com.diboot.message.annotation.TemplateVariable; -import com.diboot.message.entity.BaseVariableData; -import com.diboot.message.utils.TemplateUtils; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 模版变量 + 构建Message 解析 - * - * @author : uu - * @version : v1.0 - * @Date 2021/2/18 18:36 - * @Copyright © diboot.com - */ -public interface TemplateVariableService { - - /** - * 非贪婪 解析正则 ${} - */ - Pattern TEMPLATE_VARIABLE = Pattern.compile("\\$\\{.+?\\}"); - - /** - * 变量方法缓存<{@link TemplateVariable#name()}, Method> - */ - Map TEMPLATE_STRATEGY_CACHE = new HashMap<>(128); - - /** - * 将模版内容解析成具体内容 - * - * @param templateContent - * @param variableData - * @return - */ - String parseTemplate2Content(String templateContent, BaseVariableData variableData) throws Exception; - - - /** - * 获取模版内容中的变量 - * - * @param templateContent - * @return - * @throws Exception - */ - default String[] getMessageTemplateVariables(String templateContent) throws Exception { - // 提取变量之前校验方法缓存是否有值,无值需要重新加载 - if (V.isEmpty(TEMPLATE_STRATEGY_CACHE)) { - TemplateUtils.loadTemplateVariableList(); - } - // 提取模版变量并将值设置 - Matcher matcher = TEMPLATE_VARIABLE.matcher(templateContent); - return getMatcherVariable(matcher); - } - - /** - * 获取匹配器 匹配的内容 - * - * @param matcher - * @return - * @throws Exception - */ - default String[] getMatcherVariable(Matcher matcher) throws Exception { - // 提取模版中的所有变量 - List templateVariableList = new ArrayList<>(); - while (matcher.find()) { - templateVariableList.add(matcher.group()); - } - return S.toStringArray(templateVariableList); - } - -} diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageServiceImpl.java b/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageServiceImpl.java index c920d0dcaef47ca5aedf5f8b53870b41da654f4d..4bebc67eac839effae4891e88a400282751f1c0d 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageServiceImpl.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageServiceImpl.java @@ -22,19 +22,19 @@ import com.diboot.core.exception.InvalidUsageException; import com.diboot.core.service.impl.BaseServiceImpl; import com.diboot.core.util.V; import com.diboot.core.vo.Status; -import com.diboot.message.channel.ChannelStrategy; -import com.diboot.message.entity.BaseVariableData; +import com.diboot.message.channel.MessageChannel; +import com.diboot.message.config.Cons; import com.diboot.message.entity.Message; import com.diboot.message.entity.MessageTemplate; import com.diboot.message.mapper.MessageMapper; import com.diboot.message.service.MessageService; -import com.diboot.message.service.MessageTemplateService; -import com.diboot.message.service.TemplateVariableService; +import com.diboot.message.utils.TemplateUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -45,67 +45,82 @@ import java.util.Map; * @Date 2021/2/25 09:39 * @Copyright © diboot.com */ -@Service @Slf4j public class MessageServiceImpl extends BaseServiceImpl implements MessageService { - /** - * 模版策略 - */ + @Lazy @Autowired - private TemplateVariableService templateVariableService; + private MessageTemplateServiceImpl messageTemplateService; /** * 发送通道 */ - @Autowired - @Lazy - private Map channelStrategyMap; + private Map typeToChannelMap = new HashMap<>(); - @Autowired - private MessageTemplateService messageTemplateService; + public MessageServiceImpl(List messageChannels, List> variableObjectClasses) { + if(messageChannels != null) { + for(MessageChannel channel : messageChannels) { + typeToChannelMap.put(channel.type(), channel); + } + } + MessageTemplateServiceImpl.extractVariablesFrom(variableObjectClasses); + } @Override - public boolean send(Message message, BaseVariableData variableData) { + public boolean send(Message message) { + return send(message, null); + } + + @Override + public boolean send(Message message, Object variableData) { // 获取发送通道 - ChannelStrategy channelStrategy = channelStrategyMap.get(message.getChannel()); - if (V.isEmpty(channelStrategy)) { + MessageChannel channel = typeToChannelMap.get(message.getChannel()); + if (V.isEmpty(channel)) { log.error("[获取发送通道失败],当前发送通道为:{}", message.getChannel()); throw new InvalidUsageException("获取发送通道失败! " + message.getChannel()); } - // 是否根据模板构建邮件内容 - LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() - .eq(V.notEmpty(message.getTemplateId()), MessageTemplate::getId, message.getTemplateId()) - .eq(V.notEmpty(message.getTemplateCode()), MessageTemplate::getCode, message.getTemplateCode()); - if (queryWrapper.nonEmptyOfNormal()) { - MessageTemplate messageTemplate = messageTemplateService.getSingleEntity(queryWrapper); - if (V.isEmpty(messageTemplate)) { - if (V.isEmpty(message.getTemplateCode())) { - log.error("[获取模版失败] 模版id为:{}", message.getTemplateId()); - } else if (V.isEmpty(message.getTemplateId())) { - log.error("[获取模版失败] 模版code为:{}", message.getTemplateCode()); - } else { - log.error("[获取模版失败] 模版id为:{} ,模版code为:{}", message.getTemplateId(), message.getTemplateCode()); + String content = message.getContent(); + if(message.hasTemplate()) { + // 是否根据模板构建邮件内容 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .eq(V.notEmpty(message.getTemplateId()), MessageTemplate::getId, message.getTemplateId()) + .eq(V.notEmpty(message.getTemplateCode()), MessageTemplate::getCode, message.getTemplateCode()); + if (queryWrapper.nonEmptyOfNormal()) { + MessageTemplate messageTemplate = messageTemplateService.getSingleEntity(queryWrapper); + if (V.isEmpty(messageTemplate)) { + if (V.isEmpty(message.getTemplateCode())) { + log.error("[获取模版失败] 模版id为:{}", message.getTemplateId()); + } else if (V.isEmpty(message.getTemplateId())) { + log.error("[获取模版失败] 模版code为:{}", message.getTemplateCode()); + } else { + log.error("[获取模版失败] 模版id为:{} ,模版code为:{}", message.getTemplateId(), message.getTemplateCode()); + } + throw new BusinessException(Status.FAIL_OPERATION, "获取模版失败!"); } - throw new BusinessException(Status.FAIL_OPERATION, "获取模版失败!"); + message.setTemplateId(messageTemplate.getId()); + content = messageTemplate.getContent(); } - message.setTemplateId(messageTemplate.getId()); + } + if(V.notEmpty(content)){ try { // 设置模版内容 - String content = templateVariableService.parseTemplate2Content(messageTemplate.getContent(), variableData); + content = TemplateUtils.parseTemplateContent(content, variableData); message.setContent(content); } catch (Exception e) { log.error("[消息解析失败],消息体为:{}", message); throw new BusinessException(Status.FAIL_OPERATION, "消息解析失败!"); } } - if (V.isEmpty(message.getContent())) { + else { throw new BusinessException("消息内容不能为 null"); } // 设置定时发送,则等待定时任务发送 if (V.notEmpty(message.getScheduleTime())) { message.setStatus("SCHEDULE"); } + else if(V.isEmpty(message.getStatus())){ + message.setStatus(Cons.MESSAGE_STATUS.PENDING.name()); + } // 创建Message boolean success = createEntity(message); // 停止发送 @@ -117,7 +132,7 @@ public class MessageServiceImpl extends BaseServiceImpl throw new BusinessException(Status.FAIL_OPERATION, "消息发送失败!"); } // 异步发送消息 - channelStrategy.send(message); + channel.send(message); return true; } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageTemplateServiceImpl.java b/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageTemplateServiceImpl.java index f39ba35967843c612534f6cbe4d60d29045f6808..6036e92f5a17cafdab921adf7a417891781b92cb 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageTemplateServiceImpl.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/service/impl/MessageTemplateServiceImpl.java @@ -19,8 +19,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.diboot.core.exception.BusinessException; import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.core.util.BeanUtils; import com.diboot.core.util.V; import com.diboot.core.vo.Status; +import com.diboot.message.annotation.BindVariable; import com.diboot.message.entity.MessageTemplate; import com.diboot.message.mapper.MessageTemplateMapper; import com.diboot.message.service.MessageTemplateService; @@ -28,6 +30,8 @@ import com.diboot.message.utils.TemplateUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.List; /** @@ -41,13 +45,35 @@ import java.util.List; @Slf4j public class MessageTemplateServiceImpl extends BaseServiceImpl implements MessageTemplateService { + /** + * 模板变量列表 + */ + private static List templateVariableList = new ArrayList<>(); + + public static void extractVariablesFrom(List> variableObjectClasses) { + if(variableObjectClasses != null) { + for(Class objClass : variableObjectClasses) { + List fields = BeanUtils.extractFields(objClass, BindVariable.class); + if(V.isEmpty(fields)){ + continue; + } + fields.forEach( fld -> { + BindVariable bindVariable = fld.getAnnotation(BindVariable.class); + if(!templateVariableList.contains(bindVariable.name())) { + templateVariableList.add(bindVariable.name()); + } + }); + } + } + } + @Override - public List getTemplateVariableList() throws Exception{ - return TemplateUtils.loadTemplateVariableList(); + public List getTemplateVariableList() { + return templateVariableList; } @Override - public boolean existCode(Long id, String code) throws Exception { + public boolean existCode(Long id, String code) { if (V.isEmpty(code)) { return false; } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/SystemTemplateVariableServiceImpl.java b/diboot-message-starter/src/main/java/com/diboot/message/service/impl/SystemTemplateVariableServiceImpl.java deleted file mode 100644 index bf4a82d4166cfa5886b04900a0de5af7474589d3..0000000000000000000000000000000000000000 --- a/diboot-message-starter/src/main/java/com/diboot/message/service/impl/SystemTemplateVariableServiceImpl.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2015-2021, www.dibo.ltd (service@dibo.ltd). - *

- * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - *

- * https://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.diboot.message.service.impl; - -import com.diboot.core.util.S; -import com.diboot.core.util.V; -import com.diboot.message.annotation.TemplateVariable; -import com.diboot.message.entity.BaseVariableData; -import com.diboot.message.service.TemplateVariableService; -import lombok.extern.slf4j.Slf4j; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -/** - * 系统模版默认实现 - * - * @author : uu - * @version : v1.0 - * @Date 2021/2/18 19:25 - * @Copyright © diboot.com - */ -@Slf4j -public class SystemTemplateVariableServiceImpl implements TemplateVariableService { - - @Override - public String parseTemplate2Content(String templateContent, BaseVariableData baseVariableData) throws Exception { - // 提取模版中使用到的模版变量 - String[] messageTemplateVariables = getMessageTemplateVariables(templateContent); - // 从缓存中获取变量对应的方法 - List variableValueList = new ArrayList<>(); - for (String variable : messageTemplateVariables) { - Method method = TEMPLATE_STRATEGY_CACHE.get(variable); - // 方法不存在,则当前变量无法替换,那么使用空字符串 - if (method == null) { - log.warn("【提取变量方法失败】:当前变量为:{}", variable); - variableValueList.add(""); - continue; - } - // 执行方法,获取变量值 - Object value = method.invoke(this, baseVariableData); - if (value != null) { - variableValueList.add(String.valueOf(value)); - } else { - log.warn("【执行方法获取内容为空】:当前变量为:{}", variable); - variableValueList.add(""); - } - } - return S.replaceEach(templateContent, messageTemplateVariables, S.toStringArray(variableValueList)); - } - - /** - * 用户名变量 - * - * @param baseVariableData - * @return - */ - @TemplateVariable(name = "${用户姓名}") - private String getRealName(BaseVariableData baseVariableData) { - return V.notEmpty(baseVariableData.getRealName()) ? baseVariableData.getRealName() : ""; - } - - /** - * 手机号变量 - * - * @param baseVariableData - * @return - */ - @TemplateVariable(name = "${手机号}") - private String getMobilePhone(BaseVariableData baseVariableData) { - return V.notEmpty(baseVariableData.getPhone()) ? baseVariableData.getPhone() : ""; - } -} diff --git a/diboot-message-starter/src/main/java/com/diboot/message/starter/MessageAutoConfig.java b/diboot-message-starter/src/main/java/com/diboot/message/starter/MessageAutoConfig.java index 1988aa2a009211ed9bbc5e202742198641afa40a..112ae2060ae73d4836a569ed338c970c460feb62 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/starter/MessageAutoConfig.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/starter/MessageAutoConfig.java @@ -15,10 +15,10 @@ */ package com.diboot.message.starter; -import com.diboot.message.channel.ChannelStrategy; import com.diboot.message.channel.SimpleEmailChannel; -import com.diboot.message.service.TemplateVariableService; -import com.diboot.message.service.impl.SystemTemplateVariableServiceImpl; +import com.diboot.message.entity.BaseUserVariables; +import com.diboot.message.service.MessageService; +import com.diboot.message.service.impl.MessageServiceImpl; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -26,6 +26,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import java.util.Arrays; + /** * 组件初始化 * @@ -46,17 +48,11 @@ public class MessageAutoConfig { */ @Bean @ConditionalOnMissingBean - public TemplateVariableService templateVariableService() { - return new SystemTemplateVariableServiceImpl(); + public MessageService messageService() { + return new MessageServiceImpl( + Arrays.asList(new SimpleEmailChannel()), + Arrays.asList(BaseUserVariables.class) + ); } - /** - * 简单邮箱发送通道 - * @return - */ - @Bean("EMAIL") - @ConditionalOnMissingBean(name = "EMAIL") - public ChannelStrategy simpleEmailChannel() { - return new SimpleEmailChannel(); - } } diff --git a/diboot-message-starter/src/main/java/com/diboot/message/utils/TemplateUtils.java b/diboot-message-starter/src/main/java/com/diboot/message/utils/TemplateUtils.java index 61968abdf77f4427a2eed09a8915b632c1861194..859d082037af30bed622d3c923a8e983f18871df 100644 --- a/diboot-message-starter/src/main/java/com/diboot/message/utils/TemplateUtils.java +++ b/diboot-message-starter/src/main/java/com/diboot/message/utils/TemplateUtils.java @@ -16,14 +16,16 @@ package com.diboot.message.utils; import com.diboot.core.util.BeanUtils; -import com.diboot.core.util.ContextHelper; +import com.diboot.core.util.S; import com.diboot.core.util.V; -import com.diboot.message.annotation.TemplateVariable; -import com.diboot.message.service.TemplateVariableService; +import com.diboot.message.annotation.BindVariable; +import lombok.extern.slf4j.Slf4j; -import java.lang.reflect.Method; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * 模版工具方法 @@ -33,31 +35,69 @@ import java.util.List; * @Date 2021/2/19 11:05 * @Copyright © diboot.com */ +@Slf4j public class TemplateUtils { - public static final List TEMPLATE_STRATEGY_LIST = new ArrayList<>(); + /** + * 非贪婪 解析正则 ${} + */ + private static Pattern TEMPLATE_VARIABLE = Pattern.compile("\\$\\{.+?\\}"); + + /** + * 获取模版内容中的变量 + * + * @param templateContent + * @return + * @throws Exception + */ + private static List getMessageTemplateVariables(String templateContent) throws Exception { + // 提取模版变量并将值设置 + Matcher matcher = TEMPLATE_VARIABLE.matcher(templateContent); + // 提取模版中的所有变量 + List templateVariableList = new ArrayList<>(); + while (matcher.find()) { + templateVariableList.add(matcher.group()); + } + return templateVariableList; + } /** - * 加载所有TemplateVariableService实现类 + * 解析模板内容 + * @param templateContent + * @param variableData + * @return + * @throws Exception */ - public static List loadTemplateVariableList() { - if (V.notEmpty(TEMPLATE_STRATEGY_LIST)) { - return TEMPLATE_STRATEGY_LIST; + public static String parseTemplateContent(String templateContent, Object variableData) throws Exception { + // 提取模版中使用到的模版变量 + List messageTemplateVariables = getMessageTemplateVariables(templateContent); + if(V.isEmpty(messageTemplateVariables)){ + return templateContent; } - TemplateVariableService templateVariableService = ContextHelper.getBean(TemplateVariableService.class); - if (V.isEmpty(templateVariableService)) { - return TEMPLATE_STRATEGY_LIST; + // 从缓存中获取变量对应的方法 + List variableValueList = new ArrayList<>(); + List fields = BeanUtils.extractFields(variableData.getClass(), BindVariable.class); + if (V.isEmpty(fields)) { + log.warn("{} 类中无@BindVariable变量绑定注解,无法替换变量", variableData.getClass()); + return templateContent; } - Class targetClass = BeanUtils.getTargetClass(templateVariableService); - Method[] methods = targetClass.getDeclaredMethods(); - for (Method method : methods) { - TemplateVariable templateVariable = method.getAnnotation(TemplateVariable.class); - if (templateVariable == null) { - continue; + for (String variable : messageTemplateVariables) { + // 执行方法,获取变量值 + for(Field field : fields){ + BindVariable bindVariable = field.getAnnotation(BindVariable.class); + if(variable.equals(bindVariable.name())) { + Object value = BeanUtils.getProperty(variableData, field.getName()); + if (value != null) { + variableValueList.add(String.valueOf(value)); + } else { + log.warn("【执行方法获取内容为空】:当前变量为:{}", variable); + variableValueList.add(""); + } + break; + } } - TEMPLATE_STRATEGY_LIST.add(templateVariable.name()); - TemplateVariableService.TEMPLATE_STRATEGY_CACHE.put(templateVariable.name(), method); } - return TEMPLATE_STRATEGY_LIST; + return S.replaceEach(templateContent, S.toStringArray(messageTemplateVariables), S.toStringArray(variableValueList)); } + } diff --git a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-dm.sql b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..923d87df37eaeff2567089f8dc4cd7a4c078875b --- /dev/null +++ b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-dm.sql @@ -0,0 +1,74 @@ +-- 消息模版表 +CREATE TABLE ${SCHEMA}.message_template ( + id BIGINT identity ( 100000,1 ) primary key, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(50), + code VARCHAR(50) NOT NULL, + title VARCHAR(200) NOT NULL, + content VARCHAR(1000) NOT NULL, + ext_data VARCHAR(1000), + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + create_by BIGINT DEFAULT 0 NOT NULL, + update_time timestamp default CURRENT_TIMESTAMP null +); +-- 添加备注 +comment on column ${SCHEMA}.message_template.id is 'ID'; +comment on column ${SCHEMA}.message_template.tenant_id is '租户ID'; +comment on column ${SCHEMA}.message_template.app_module is '应用模块'; +comment on column ${SCHEMA}.message_template.code is '模版编码'; +comment on column ${SCHEMA}.message_template.title is '模版标题'; +comment on column ${SCHEMA}.message_template.content is '模版内容'; +comment on column ${SCHEMA}.message_template.ext_data is '扩展数据'; +comment on column ${SCHEMA}.message_template.create_by is '创建人'; +comment on column ${SCHEMA}.message_template.update_time is '更新时间'; +comment on column ${SCHEMA}.message_template.is_deleted is '是否删除'; +comment on column ${SCHEMA}.message_template.create_time is '创建时间'; +comment on table ${SCHEMA}.message_template is '消息模版'; +-- 创建索引 +create index idx_msg_tmpl_tenant on ${SCHEMA}.message_template (tenant_id); +create index idx_msg_tmpl_code ON ${SCHEMA}.message_template(code); + +-- 消息表 +CREATE TABLE ${SCHEMA}.message ( + id BIGINT identity ( 100000,1 ) primary key, + tenant_id BIGINT default 0 not null, + app_module VARCHAR(100), + template_id BIGINT, + business_type VARCHAR(100) not null, + business_code VARCHAR(100) default 0 not null, + sender VARCHAR(200) not null, + receiver VARCHAR(200) not null, + title VARCHAR(200) NOT NULL, + content VARCHAR(1500) NOT NULL, + channel VARCHAR(50) NOT NULL, + status VARCHAR(30) NOT NULL, + result VARCHAR(600), + schedule_time timestamp null, + ext_data VARCHAR(600), + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.message.id is 'ID'; +comment on column ${SCHEMA}.message.tenant_id is '租户ID'; +comment on column ${SCHEMA}.message.app_module is '应用模块'; +comment on column ${SCHEMA}.message.template_id is '模版id'; +comment on column ${SCHEMA}.message.business_type is '业务类型'; +comment on column ${SCHEMA}.message.business_code is '业务标识'; +comment on column ${SCHEMA}.message.sender is '发送方'; +comment on column ${SCHEMA}.message.receiver is '接收方'; +comment on column ${SCHEMA}.message.title is '标题'; +comment on column ${SCHEMA}.message.content is '内容'; +comment on column ${SCHEMA}.message.channel is '发送通道'; +comment on column ${SCHEMA}.message.status is '消息状态'; +comment on column ${SCHEMA}.message.result is '发送结果'; +comment on column ${SCHEMA}.message.schedule_time is '定时发送时间'; +comment on column ${SCHEMA}.message.ext_data is '扩展数据'; +comment on column ${SCHEMA}.message.is_deleted is '是否删除'; +comment on column ${SCHEMA}.message.update_time is '更新时间'; +comment on column ${SCHEMA}.message.create_time is '创建时间'; +comment on table ${SCHEMA}.message is '消息'; +create index idx_msg_tenant on ${SCHEMA}.message (tenant_id); +create index idx_msg_template on ${SCHEMA}.message (template_id); +create index idx_msg_receiver on ${SCHEMA}.message (receiver); diff --git a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-mysql.sql b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-mysql.sql index 6895260b49beed9e7d6e7c0b1aac3de898e142bd..06c2d417e4ffc3658d7611c1bec1be9339a97d2b 100644 --- a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-mysql.sql +++ b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-mysql.sql @@ -6,7 +6,6 @@ CREATE TABLE `message_template` ( `code` varchar(20) NOT NULL COMMENT '模版编码', `title` varchar(100) NOT NULL COMMENT '模版标题', `content` varchar(500) NOT NULL COMMENT '模版内容', - `variables` varchar(200) DEFAULT NULL COMMENT '模版变量', `ext_data` varchar(500) DEFAULT NULL COMMENT '扩展数据', `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标记', `create_by` bigint DEFAULT '0' COMMENT '创建人', diff --git a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-oracle.sql b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-oracle.sql index cbdd84eee31d9df3cd3c6f01773b884cbdd12039..6be79627ab8eb0dff92526e1409ac98262be9d05 100644 --- a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-oracle.sql +++ b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-oracle.sql @@ -6,7 +6,6 @@ CREATE TABLE ${SCHEMA}.message_template ( code VARCHAR2(20) NOT NULL, title VARCHAR2(100) NOT NULL, content VARCHAR2(500) NOT NULL, - variables varchar(200), ext_data VARCHAR2(500), is_deleted NUMBER(1) DEFAULT 0 not null, create_time timestamp default CURRENT_TIMESTAMP not null, @@ -21,7 +20,6 @@ comment on column ${SCHEMA}.message_template.app_module is '应用模块'; comment on column ${SCHEMA}.message_template.code is '模版编码'; comment on column ${SCHEMA}.message_template.title is '模版标题'; comment on column ${SCHEMA}.message_template.content is '模版内容'; -comment on column ${SCHEMA}.message_template.variables is '模版变量'; comment on column ${SCHEMA}.message_template.ext_data is '扩展数据'; comment on column ${SCHEMA}.message_template.create_by is '创建人'; comment on column ${SCHEMA}.message_template.update_time is '更新时间'; diff --git a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-postgresql.sql b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-postgresql.sql index 9c228ec876808bc9748e1e75929535c1e29210b9..8a721781e6a8709532bd46667f57d77ba99c9b02 100644 --- a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-postgresql.sql +++ b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-postgresql.sql @@ -6,7 +6,6 @@ CREATE TABLE message_template ( code VARCHAR(20) NOT NULL, title VARCHAR(100) NOT NULL, content VARCHAR(500) NOT NULL, - variables varchar(200), ext_data VARCHAR(500), is_deleted BOOLEAN default FALSE not null, create_by bigint DEFAULT 0 NOT NULL, @@ -20,7 +19,6 @@ comment on column message_template.app_module is '应用模块'; comment on column message_template.code is '模版编码'; comment on column message_template.title is '模版标题'; comment on column message_template.content is '模版内容'; -comment on column message_template.variables is '模版变量'; comment on column message_template.ext_data is '扩展数据'; comment on column message_template.is_deleted is '是否删除'; comment on column message_template.create_by is '创建人'; diff --git a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-sqlserver.sql b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-sqlserver.sql index 7f3156f7f90bd5faf444cdf18847df1d8e15e8f0..a543474a3f6ff3210a22f16a79ec1b67107f1891 100644 --- a/diboot-message-starter/src/main/resources/META-INF/sql/init-message-sqlserver.sql +++ b/diboot-message-starter/src/main/resources/META-INF/sql/init-message-sqlserver.sql @@ -6,7 +6,6 @@ CREATE TABLE message_template ( code VARCHAR(20) NOT NULL, title VARCHAR(100) NOT NULL, content VARCHAR(500) NOT NULL, - variables varchar(200), ext_data VARCHAR(500), is_deleted tinyint not null DEFAULT 0, create_by bigint DEFAULT 0 NOT NULL, @@ -21,7 +20,6 @@ execute sp_addextendedproperty 'MS_Description', N'应用模块', 'SCHEMA', '${S execute sp_addextendedproperty 'MS_Description', N'模版编码', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'code'; execute sp_addextendedproperty 'MS_Description', N'模版标题', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'title'; execute sp_addextendedproperty 'MS_Description', N'模版内容', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'content'; -execute sp_addextendedproperty 'MS_Description', N'模版变量', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'variables'; execute sp_addextendedproperty 'MS_Description', N'扩展数据', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'ext_data'; execute sp_addextendedproperty 'MS_Description', N'是否删除', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'is_deleted'; execute sp_addextendedproperty 'MS_Description', N'创建人', 'SCHEMA', '${SCHEMA}', 'table', message_template, 'column', 'create_by'; diff --git a/diboot-message-starter/src/test/java/diboot/message/test/MessageServiceTest.java b/diboot-message-starter/src/test/java/diboot/message/test/MessageServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5c6a0af8366b2bcfa594c7e9d37d5e95c87e8d8f --- /dev/null +++ b/diboot-message-starter/src/test/java/diboot/message/test/MessageServiceTest.java @@ -0,0 +1,49 @@ +package diboot.message.test; + +import com.diboot.message.config.Cons; +import com.diboot.message.entity.Message; +import com.diboot.message.service.MessageService; +import com.diboot.message.service.MessageTemplateService; +import diboot.message.test.config.SpringMvcConfig; +import diboot.message.test.variable.MyVariableObj; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/28 + * Copyright © diboot.com + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = {SpringMvcConfig.class}) +@SpringBootTest(classes = {StartupApplication.class}) +public class MessageServiceTest { + + @Autowired + private MessageService messageService; + @Autowired + private MessageTemplateService messageTemplateService; + + @Test + public void test() { + Assert.assertTrue(messageTemplateService.getTemplateVariableList().contains("${手机号}")); + Assert.assertTrue(messageTemplateService.getTemplateVariableList().contains("${验证码}")); + + MyVariableObj myVariableObj = new MyVariableObj(); + myVariableObj.setVcode("876622").setSn("UDYY-9JDF-MNF8-NBS7"); + Message message = new Message(); + message.setChannel(Cons.MESSAGE_CHANNEL.SMS.name()).setStatus(Cons.MESSAGE_STATUS.PENDING.name()); + message.setBusinessType("A").setBusinessCode("B").setSender("admin").setReceiver("123"); + message.setContent("您的验证码是: ${验证码},产品序列号是: ${序列号}"); + messageService.send(message, myVariableObj); + Assert.assertTrue(message.getContent().contains("876622")); + Assert.assertTrue(message.getContent().contains("UDYY-9JDF-MNF8-NBS7")); + } + +} diff --git a/diboot-message-starter/src/test/java/diboot/message/test/StartupApplication.java b/diboot-message-starter/src/test/java/diboot/message/test/StartupApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..8aa7ea4b49ee42d07c799ecc6ff4081efff6b5d7 --- /dev/null +++ b/diboot-message-starter/src/test/java/diboot/message/test/StartupApplication.java @@ -0,0 +1,31 @@ +package diboot.message.test;/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * @author Administrator + */ +@SpringBootApplication +public class StartupApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(StartupApplication.class, args); + } + +} \ No newline at end of file diff --git a/diboot-message-starter/src/test/java/diboot/message/test/channel/SMSChannel.java b/diboot-message-starter/src/test/java/diboot/message/test/channel/SMSChannel.java new file mode 100644 index 0000000000000000000000000000000000000000..c6d3403d35b241c724691ab6658eed085130051d --- /dev/null +++ b/diboot-message-starter/src/test/java/diboot/message/test/channel/SMSChannel.java @@ -0,0 +1,24 @@ +package diboot.message.test.channel; + +import com.diboot.message.channel.MessageChannel; +import com.diboot.message.config.Cons; +import com.diboot.message.entity.Message; + +/** + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/28 + * Copyright © diboot.com + */ +public class SMSChannel implements MessageChannel { + + @Override + public String type() { + return Cons.MESSAGE_CHANNEL.SMS.name(); + } + + @Override + public void send(Message message) { + System.out.println("SMSChannel 发送短信: "+ message.getContent()); + } +} diff --git a/diboot-message-starter/src/test/java/diboot/message/test/config/SpringMvcConfig.java b/diboot-message-starter/src/test/java/diboot/message/test/config/SpringMvcConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..a428413fc8c25eeb17141e99c01685ccb2f9866a --- /dev/null +++ b/diboot-message-starter/src/test/java/diboot/message/test/config/SpringMvcConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package diboot.message.test.config; + +import com.diboot.message.channel.SimpleEmailChannel; +import com.diboot.message.entity.BaseUserVariables; +import com.diboot.message.service.MessageService; +import com.diboot.message.service.impl.MessageServiceImpl; +import diboot.message.test.channel.SMSChannel; +import diboot.message.test.variable.MyVariableObj; +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Arrays; + +/*** + * Spring配置文件 + * @author mazc@dibo.ltd + * @version v2.0 + * @date 2019/6/10 + */ +@TestConfiguration +@ComponentScan(basePackages={"com.diboot.message"}) +@MapperScan({"com.diboot.message.mapper"}) +public class SpringMvcConfig implements WebMvcConfigurer { + private static final Logger log = LoggerFactory.getLogger(SpringMvcConfig.class); + + @Bean + public MessageService messageService() { + return new MessageServiceImpl( + Arrays.asList(new SimpleEmailChannel(), new SMSChannel()), + Arrays.asList(BaseUserVariables.class, MyVariableObj.class) + ); + } + +} \ No newline at end of file diff --git a/diboot-message-starter/src/test/java/diboot/message/test/variable/MyVariableObj.java b/diboot-message-starter/src/test/java/diboot/message/test/variable/MyVariableObj.java new file mode 100644 index 0000000000000000000000000000000000000000..4c54122bd92641e06a3fac7b616a36945d518929 --- /dev/null +++ b/diboot-message-starter/src/test/java/diboot/message/test/variable/MyVariableObj.java @@ -0,0 +1,27 @@ +package diboot.message.test.variable; + +import com.diboot.message.annotation.BindVariable; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * @author JerryMa + * @version v2.6.0 + * @date 2022/5/28 + * Copyright © diboot.com + */ +@Getter @Setter @Accessors(chain = true) +public class MyVariableObj implements Serializable { + + private static final long serialVersionUID = -1993000690817844748L; + + @BindVariable(name = "${验证码}") + private String vcode; + + @BindVariable(name = "${序列号}") + private String sn; + +} diff --git a/diboot-message-starter/src/test/resources/application.properties b/diboot-message-starter/src/test/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..a030edc62bf22ce836fdd1a28da8dbe7026ad0a9 --- /dev/null +++ b/diboot-message-starter/src/test/resources/application.properties @@ -0,0 +1,21 @@ +server.port=8080 +server.servlet.context-path=/api + +#datasource config +spring.datasource.url=jdbc:mysql://localhost:3306/playground?characterEncoding=utf8&serverTimezone=GMT%2B8 +spring.datasource.username=diboot +spring.datasource.password=123456 +spring.datasource.hikari.maximum-pool-size=5 +spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.main.allow-bean-definition-overriding=true + +mybatis-plus.global-config.mapper-locations=classpath*:mapper/*Mapper.xml +mybatis-plus.global-config.db-config.logic-delete-value=1 +mybatis-plus.global-config.db-config.logic-not-delete-value=0 + +logging.level.root=DEBUG +logging.level.org.apache=info +logging.level.org.hibernate.validator=info +logging.level.org.springframework=info +logging.level.com.zaxxer.hikari=info \ No newline at end of file diff --git a/diboot-mobile-starter/pom.xml b/diboot-mobile-starter/pom.xml index 8a4cf2d8e3f34cb346572e0482b95a8d965fda5d..d5c2d3557388833e655bfadbe0855af0e9c643d9 100644 --- a/diboot-mobile-starter/pom.xml +++ b/diboot-mobile-starter/pom.xml @@ -6,7 +6,7 @@ diboot-root com.diboot - 2.5.0 + 2.6.0 com.diboot @@ -15,7 +15,7 @@ diboot mobile starter project - 4.2.0 + 4.3.0 @@ -59,4 +59,4 @@ - \ No newline at end of file + diff --git a/diboot-mobile-starter/src/main/java/com/diboot/mobile/auth/impl/WxAuthServiceImpl.java b/diboot-mobile-starter/src/main/java/com/diboot/mobile/auth/impl/WxAuthServiceImpl.java index 64a0b3b82a95aa330830c634f6794b900571c534..11ddd32ea944f54977f5b42a1d9db6978b507741 100644 --- a/diboot-mobile-starter/src/main/java/com/diboot/mobile/auth/impl/WxAuthServiceImpl.java +++ b/diboot-mobile-starter/src/main/java/com/diboot/mobile/auth/impl/WxAuthServiceImpl.java @@ -15,30 +15,17 @@ */ package com.diboot.mobile.auth.impl; +import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.diboot.core.exception.BusinessException; -import com.diboot.core.vo.Status; -import com.diboot.iam.auth.AuthService; +import com.diboot.iam.auth.impl.BaseAuthServiceImpl; import com.diboot.iam.config.Cons; import com.diboot.iam.dto.AuthCredential; -import com.diboot.iam.entity.BaseLoginUser; import com.diboot.iam.entity.IamAccount; -import com.diboot.iam.entity.IamLoginTrace; -import com.diboot.iam.jwt.BaseJwtAuthToken; -import com.diboot.iam.service.IamAccountService; -import com.diboot.iam.service.IamLoginTraceService; -import com.diboot.iam.util.HttpHelper; -import com.diboot.iam.util.IamSecurityUtils; +import com.diboot.iam.shiro.IamAuthToken; import com.diboot.mobile.dto.MobileCredential; import lombok.extern.slf4j.Slf4j; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.subject.Subject; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.servlet.http.HttpServletRequest; - /** * 微信认证实现 * @@ -49,16 +36,7 @@ import javax.servlet.http.HttpServletRequest; */ @Slf4j @Service -public class WxAuthServiceImpl implements AuthService { - - @Autowired - private HttpServletRequest request; - - @Autowired - private IamAccountService accountService; - - @Autowired - private IamLoginTraceService iamLoginTraceService; +public class WxAuthServiceImpl extends BaseAuthServiceImpl { @Override public String getAuthType() { @@ -66,49 +44,15 @@ public class WxAuthServiceImpl implements AuthService { } @Override - public IamAccount getAccount(BaseJwtAuthToken jwtToken) throws AuthenticationException { + protected Wrapper buildQueryWrapper(IamAuthToken iamAuthToken) { // 查询最新的记录 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .select(IamAccount::getAuthAccount, IamAccount::getUserType, IamAccount::getUserId, IamAccount::getStatus) - .eq(IamAccount::getUserType, jwtToken.getUserType()) - .eq(IamAccount::getAuthType, jwtToken.getAuthType()) - .eq(IamAccount::getAuthAccount, jwtToken.getAuthAccount()) + .eq(IamAccount::getUserType, iamAuthToken.getUserType()) + .eq(IamAccount::getAuthType, iamAuthToken.getAuthType()) + .eq(IamAccount::getAuthAccount, iamAuthToken.getAuthAccount()) .orderByDesc(IamAccount::getId); - IamAccount latestAccount = accountService.getSingleEntity(queryWrapper); - if(latestAccount == null){ - return null; - } - if (Cons.DICTCODE_ACCOUNT_STATUS.I.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已禁用! account="+jwtToken.getAuthAccount()); - } - if (Cons.DICTCODE_ACCOUNT_STATUS.L.name().equals(latestAccount.getStatus())) { - throw new AuthenticationException("用户账号已锁定! account="+jwtToken.getAuthAccount()); - } - return latestAccount; - } - - @Override - public String applyToken(AuthCredential credential) { - BaseJwtAuthToken authToken = initBaseJwtAuthToken(credential); - try { - Subject subject = SecurityUtils.getSubject(); - subject.login(authToken); - if (subject.isAuthenticated()) { - log.debug("申请token成功!authtoken={}", authToken.getCredentials()); - saveLoginTrace(authToken, true); - // 跳转到首页 - return (String) authToken.getCredentials(); - } - else { - log.error("认证失败"); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, "认证失败"); - } - } catch (Exception e) { - log.error("登录异常", e); - saveLoginTrace(authToken, false); - throw new BusinessException(Status.FAIL_OPERATION, e.getMessage()); - } + return queryWrapper; } /** @@ -116,39 +60,14 @@ public class WxAuthServiceImpl implements AuthService { * @param credential * @return */ - private BaseJwtAuthToken initBaseJwtAuthToken(AuthCredential credential){ + protected IamAuthToken initAuthToken(AuthCredential credential){ MobileCredential wxMpCredential = (MobileCredential)credential; - BaseJwtAuthToken token = new BaseJwtAuthToken(getAuthType(), wxMpCredential.getUserTypeClass()); + IamAuthToken token = new IamAuthToken(getAuthType(), wxMpCredential.getUserTypeClass()); // 设置登陆的 token.setAuthAccount(wxMpCredential.getAuthAccount()); token.setRememberMe(wxMpCredential.isRememberMe()); // 生成token - return token.generateAuthtoken(getExpiresInMinutes()); + return token.generateAuthtoken(); } - - /** - * 保存登录日志 - * @param authToken - * @param isSuccess - */ - protected void saveLoginTrace(BaseJwtAuthToken authToken, boolean isSuccess){ - IamLoginTrace loginTrace = new IamLoginTrace(); - loginTrace.setAuthType(getAuthType()).setAuthAccount(authToken.getAuthAccount()).setUserType(authToken.getUserType()).setSuccess(isSuccess); - BaseLoginUser currentUser = IamSecurityUtils.getCurrentUser(); - if(currentUser != null){ - Long userId = currentUser.getId(); - loginTrace.setUserId(userId); - } - // 记录客户端信息 - String userAgent = HttpHelper.getUserAgent(request); - String ipAddress = HttpHelper.getRequestIp(request); - loginTrace.setUserAgent(userAgent).setIpAddress(ipAddress); - try{ - iamLoginTraceService.createEntity(loginTrace); - } - catch (Exception e){ - log.warn("保存登录日志异常", e); - } - } } diff --git a/diboot-mobile-starter/src/main/resources/META-INF/sql/init-mobile-dm.sql b/diboot-mobile-starter/src/main/resources/META-INF/sql/init-mobile-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..8bc37118992c991ae56dfe690ca9e07c6f10b375 --- /dev/null +++ b/diboot-mobile-starter/src/main/resources/META-INF/sql/init-mobile-dm.sql @@ -0,0 +1,47 @@ +-- 移动端用户表 +CREATE TABLE ${SCHEMA}.iam_member ( + id BIGINT identity ( 100000,1 ) primary key, + tenant_id BIGINT default 0 not null, + org_id BIGINT default 0 not null, + user_id BIGINT default 0 not null, + user_type VARCHAR(100) NOT NULL DEFAULT 'IamUser', + openid VARCHAR(100) NOT NULL, + nickname VARCHAR(100), + avatar_url VARCHAR(500), + country VARCHAR(50), + province VARCHAR(50), + city VARCHAR(100), + mobile_phone VARCHAR(20), + email VARCHAR(100), + gender VARCHAR(10), + status VARCHAR(20) NOT NULL DEFAULT 'NORMAL', + description VARCHAR(600), + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.iam_member.id is 'ID'; +comment on column ${SCHEMA}.iam_member.tenant_id is '租户ID'; +comment on column ${SCHEMA}.iam_member.org_id is '组织'; +comment on column ${SCHEMA}.iam_member.user_id is '用户id'; +comment on column ${SCHEMA}.iam_member.user_type is '用户类型'; +comment on column ${SCHEMA}.iam_member.openid is 'openid'; +comment on column ${SCHEMA}.iam_member.nickname is '昵称'; +comment on column ${SCHEMA}.iam_member.avatar_url is '头像'; +comment on column ${SCHEMA}.iam_member.country is '国家'; +comment on column ${SCHEMA}.iam_member.province is '省'; +comment on column ${SCHEMA}.iam_member.city is '市'; +comment on column ${SCHEMA}.iam_member.mobile_phone is '手机号'; +comment on column ${SCHEMA}.iam_member.gender is '性别'; +comment on column ${SCHEMA}.iam_member.status is '状态'; +comment on column ${SCHEMA}.iam_member.description is '备注'; +comment on column ${SCHEMA}.iam_member.is_deleted is '是否删除'; +comment on column ${SCHEMA}.iam_member.update_time is '更新时间'; +comment on column ${SCHEMA}.iam_member.create_time is '创建时间'; +comment on table ${SCHEMA}.iam_member is '移动端用户'; +-- 索引 +create index idx_member_tenant on ${SCHEMA}.iam_member (tenant_id); +create index idx_member_orgid on ${SCHEMA}.iam_member (org_id); +create index idx_member_openid on ${SCHEMA}.iam_member (openid); +create index idx_member_phone on ${SCHEMA}.iam_member (mobile_phone); +create index idx_member_user on ${SCHEMA}.iam_member(user_id, user_type); diff --git a/diboot-mobile-ui/mixins/form.js b/diboot-mobile-ui/mixins/form.js index e6bc6a62ee214a98d5de11c8d204d1fc7d83b59d..090f5b56939e60ac012e7da2bbf4c7c05f252b80 100644 --- a/diboot-mobile-ui/mixins/form.js +++ b/diboot-mobile-ui/mixins/form.js @@ -144,6 +144,7 @@ export default { * @returns {Promise} */ async onSubmit() { + this.confirmSubmit = true uni.showLoading({ title: '提交中...' }); @@ -170,6 +171,7 @@ export default { console.log(e) } finally { uni.hideLoading() + this.confirmSubmit = false } }, /** * diff --git a/diboot-mobile-ui/mixins/list.js b/diboot-mobile-ui/mixins/list.js index fdd9dadd5153825503027d3f49b51725d8797a1e..062a0995a423b63fae8a7a6d6648a91367ea36d9 100644 --- a/diboot-mobile-ui/mixins/list.js +++ b/diboot-mobile-ui/mixins/list.js @@ -17,7 +17,7 @@ export default { // 与查询条件绑定的参数(会被查询表单重置和改变的参数) queryParam: {}, // //下拉刷新的状态 - triggered: false, + triggered: false, // load状态 status: 'loadmore', loadText: { @@ -29,7 +29,8 @@ export default { page: { pageIndex: 1, pageSize: 20, - totalCount: 0 + totalCount: 0, + totalPage: 0 }, // 激活的Index activeIndex: -100, @@ -54,12 +55,14 @@ export default { // 是否允许访问详情 allowGoDetail: true, // 状态栏高度 - diStatusBarHeight: 0 + diStatusBarHeight: 0, + // 阻止重复发送 + keyMap: {} } }, onLoad() { this.diStatusBarHeight = uni.getSystemInfoSync().statusBarHeight - + }, onShow() { this.activeIndex = -100 @@ -85,7 +88,7 @@ export default { url:`./detail?id=${id}` }) }, - /* + /* * 编辑 */ handleUpdate(id) { @@ -113,10 +116,12 @@ export default { console.log(e) this.showToast('网络异常!') } finally { + this.page.pageIndex = 1 + this.setQueryParamPage() this.getList(true) this.handleCancelDel() } - + }, /** * 取消删除 @@ -146,14 +151,25 @@ export default { if (this.triggered) return this.triggered = true this.page.pageIndex = 1 + this.setQueryParamPage() this.getList(true) }, /** * 触底加载 */ handleOnreachBottom() { + // 将当前pageIndex制作成下标,并查看缓存中是否存在指定下标的值 + const key = `_${this.page.pageIndex}` + const value = this.keyMap[key] + // 如果value存在,表示缓存有值,那么阻止请求 + if(value) { + return + } + // value不存在,表示第一次请求,设置占位 + this.keyMap[key] = 'temp' this.status = 'nomore' - if (this.page.pageIndex < this.page.totalCount / this.page.pageSize) { + if (this.page.pageIndex <= this.page.totalPage) { + this.setQueryParamPage() this.getList() } }, @@ -168,6 +184,7 @@ export default { this.list = replace ? res.data : this.list.concat(res.data) this.page = res.page this.page.pageIndex++ + console.log(this.page) } else { this.showToast(res.msg) } @@ -177,7 +194,10 @@ export default { this.triggered = false this.status = (this.list || []).length == this.page.totalCount ? 'nomore' : 'loadmore' } - + + }, + setQueryParamPage() { + this.queryParam.pageIndex = this.page.pageIndex }, /** * 展示提示 @@ -186,7 +206,7 @@ export default { */ showToast(title, icon = 'error') { uni.showToast({ - title, + title, icon }); } @@ -196,4 +216,4 @@ export default { return `margin: ${this.list.length === 0 ? 0 : 20}rpx` } } -} +} \ No newline at end of file diff --git a/diboot-mobile-ui/pages/component-page/crud/list.vue b/diboot-mobile-ui/pages/component-page/crud/list.vue index cd1ad27ae5ca160f8a8c970c3af99d6423a7e689..29db3847799bdb1aba739809f5d3723696419651 100644 --- a/diboot-mobile-ui/pages/component-page/crud/list.vue +++ b/diboot-mobile-ui/pages/component-page/crud/list.vue @@ -17,8 +17,8 @@ @click="handleActionClick" @open="handleActiveSwipeAction"> - - + + @@ -51,7 +51,8 @@ for (let i = 1; i <= this.page.pageSize; i++) { list.push({ id: ++count, - title: '列表展示 ' + count, + title: '标题' + count, + value: 'Didoot', createTime: '2021-11-22 10:27' }) } diff --git a/diboot-scheduler-starter/pom.xml b/diboot-scheduler-starter/pom.xml index 88c1760f6392f142ffa99b21ee6b664c39a8eda1..c00bb9c2a6a7a46308b4edc3d6d39017fd2687e9 100644 --- a/diboot-scheduler-starter/pom.xml +++ b/diboot-scheduler-starter/pom.xml @@ -5,13 +5,13 @@ diboot-root com.diboot - 2.5.0 + 2.6.0 4.0.0 com.diboot diboot-scheduler-spring-boot-starter - 2.5.0 + 2.6.0 diff --git a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/service/impl/QuartzSchedulerServiceImpl.java b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/service/impl/QuartzSchedulerServiceImpl.java index f74327f356ff750eaed86888805167ef87f0415c..aca80d64159192a8fa59193ff3a11fd1e2150ca5 100644 --- a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/service/impl/QuartzSchedulerServiceImpl.java +++ b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/service/impl/QuartzSchedulerServiceImpl.java @@ -24,7 +24,6 @@ import com.diboot.core.vo.Status; import com.diboot.scheduler.annotation.CollectThisJob; import com.diboot.scheduler.entity.ScheduleJob; import com.diboot.scheduler.service.QuartzSchedulerService; -import com.diboot.scheduler.starter.SchedulerProperties; import lombok.extern.slf4j.Slf4j; import org.quartz.*; import org.quartz.impl.matchers.GroupMatcher; @@ -42,8 +41,6 @@ import java.util.*; */ @Slf4j public class QuartzSchedulerServiceImpl implements QuartzSchedulerService { - @Autowired - private SchedulerProperties schedulerProperties; /** * 任务初始化策略 @@ -65,10 +62,6 @@ public class QuartzSchedulerServiceImpl implements QuartzSchedulerService { @PostConstruct public void startScheduler() { - if (schedulerProperties.isEnable() == false) { - log.info("定时任务组件已暂停,如需启用设置:diboot.component.scheduler.enable=true"); - return; - } try { scheduler.start(); } catch (SchedulerException e) { diff --git a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerJobInitializer.java b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerJobInitializer.java index 7cda12f1434fe2d9e293470247f540f7be2164b0..e03ff0c750a10567681c3a87444aa010757ed4cc 100644 --- a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerJobInitializer.java +++ b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerJobInitializer.java @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; */ @Slf4j @Component -@ConditionalOnProperty(prefix = "diboot.component.scheduler", name = "enable", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "MEMORY", matchIfMissing = true) public class SchedulerJobInitializer implements ApplicationRunner { @Autowired diff --git a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerProperties.java b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerProperties.java index 2c78af889e8e339efa9eaf86a1bc980a3a07d79e..1e8cb3b95d283f3129ae9ceec2edeb04a7cf8f30 100644 --- a/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerProperties.java +++ b/diboot-scheduler-starter/src/main/java/com/diboot/scheduler/starter/SchedulerProperties.java @@ -34,9 +34,4 @@ public class SchedulerProperties { */ private boolean initSql = true; - /** - * 是否启用(开发环境可禁用) - */ - private boolean enable = true; - } diff --git a/diboot-scheduler-starter/src/main/resources/META-INF/sql/init-scheduler-dm.sql b/diboot-scheduler-starter/src/main/resources/META-INF/sql/init-scheduler-dm.sql new file mode 100644 index 0000000000000000000000000000000000000000..4b528fa4754addf5ccf9fdd62a55ea838494e02d --- /dev/null +++ b/diboot-scheduler-starter/src/main/resources/META-INF/sql/init-scheduler-dm.sql @@ -0,0 +1,69 @@ +-- 定时任务表 +CREATE TABLE ${SCHEMA}.schedule_job ( + id BIGINT identity ( 100000,1) primary key, + tenant_id BIGINT default 0 not null, + job_key VARCHAR(100) not null, + job_name VARCHAR(200) not null, + cron VARCHAR(50), + param_json VARCHAR(500), + init_strategy VARCHAR(50), + job_status VARCHAR(10) default 'A' not null, + save_log BIT DEFAULT 1 not null, + job_comment VARCHAR(500), + is_deleted BIT DEFAULT 0 not null, + create_by BIGINT DEFAULT 0 NOT NULL, + create_time timestamp default CURRENT_TIMESTAMP not null, + update_time timestamp default CURRENT_TIMESTAMP null +); +comment on column ${SCHEMA}.schedule_job.id is 'ID'; +comment on column ${SCHEMA}.schedule_job.tenant_id is '租户ID'; +comment on column ${SCHEMA}.schedule_job.job_key is 'job编码'; +comment on column ${SCHEMA}.schedule_job.job_name is 'job名称'; +comment on column ${SCHEMA}.schedule_job.cron is '定时表达式'; +comment on column ${SCHEMA}.schedule_job.param_json is '参数'; +comment on column ${SCHEMA}.schedule_job.init_strategy is '初始化策略'; +comment on column ${SCHEMA}.schedule_job.job_status is '状态'; +comment on column ${SCHEMA}.schedule_job.job_comment is '备注'; +comment on column ${SCHEMA}.schedule_job.save_log is '是否记录日志'; +comment on column ${SCHEMA}.schedule_job.is_deleted is '是否删除'; +comment on column ${SCHEMA}.schedule_job.create_by is '创建人'; +comment on column ${SCHEMA}.schedule_job.create_time is '创建时间'; +comment on column ${SCHEMA}.schedule_job.update_time is '更新时间'; +comment on table ${SCHEMA}.schedule_job is '定时任务'; +create index idx_schedule_job on ${SCHEMA}.schedule_job (job_key); +create index idx_schedule_job_tenant on ${SCHEMA}.schedule_job (tenant_id); + +-- 定时任务日志表 +CREATE TABLE ${SCHEMA}.schedule_job_log ( + id BIGINT identity ( 100000,1) primary key, + tenant_id BIGINT default 0 not null, + job_id VARCHAR(100) not null, + job_name VARCHAR(100) not null, + cron VARCHAR(50), + param_json VARCHAR(500), + start_time timestamp null, + end_time timestamp null, + elapsed_seconds NUMBER(9), + run_status VARCHAR(10) default 'A' not null, + data_count NUMBER(9), + execute_msg VARCHAR(1000) not null, + is_deleted BIT DEFAULT 0 not null, + create_time timestamp default CURRENT_TIMESTAMP not null +); +comment on column ${SCHEMA}.schedule_job_log.id is 'ID'; +comment on column ${SCHEMA}.schedule_job_log.tenant_id is '租户ID'; +comment on column ${SCHEMA}.schedule_job_log.job_id is 'job编码'; +comment on column ${SCHEMA}.schedule_job_log.job_name is 'job名称'; +comment on column ${SCHEMA}.schedule_job_log.cron is '定时表达式'; +comment on column ${SCHEMA}.schedule_job_log.param_json is '参数'; +comment on column ${SCHEMA}.schedule_job_log.start_time is '开始时间'; +comment on column ${SCHEMA}.schedule_job_log.end_time is '结束时间'; +comment on column ${SCHEMA}.schedule_job_log.elapsed_seconds is '耗时(s)'; +comment on column ${SCHEMA}.schedule_job_log.run_status is '运行状态'; +comment on column ${SCHEMA}.schedule_job_log.data_count is '数据计数'; +comment on column ${SCHEMA}.schedule_job_log.execute_msg is '执行结果信息'; +comment on column ${SCHEMA}.schedule_job_log.is_deleted is '是否删除'; +comment on column ${SCHEMA}.schedule_job_log.create_time is '创建时间'; +comment on table ${SCHEMA}.schedule_job_log is '定时任务日志'; +create index idx_sch_job_log on ${SCHEMA}.schedule_job_log (job_id); +create index idx_sch_job_log_tenant on ${SCHEMA}.schedule_job_log (tenant_id); diff --git a/pom.xml b/pom.xml index 2e3b7cdc2af2dc022a820469d5b337c09e2aae9f..6126e199b7aee102483280bb16a654456d966787 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ org.springframework.boot spring-boot-starter-parent - 2.6.5 + 2.7.0 com.diboot diboot-root - 2.5.0 + 2.6.0 pom @@ -28,11 +28,12 @@ 6.2.3.Final - 3.5.1 - 4.9.3 - 1.9.9 + 3.5.2 + 4.10.0 + 1.9.9.1 2.11.0 - 2.5.0 + 3.1.1 + 2.6.0 @@ -56,5 +57,4 @@ - - \ No newline at end of file +